add nutzaps (beta)

This commit is contained in:
Believethehype
2024-08-19 13:25:03 +02:00
parent af49ee97c5
commit c22d0e5c79
10 changed files with 1091 additions and 53 deletions

View File

@@ -33,11 +33,11 @@ class DVM:
job_list: list job_list: list
jobs_on_hold_list: list jobs_on_hold_list: list
def __init__(self, dvm_config, admin_config=None): #def __init__(self, dvm_config, admin_config=None):
# asyncio.run(self.run_dvm(dvm_config, admin_config))
asyncio.run(self.run_dvm(dvm_config, admin_config))
async def run_dvm(self, dvm_config, admin_config): async def run_dvm(self, dvm_config, admin_config):
self.dvm_config = dvm_config self.dvm_config = dvm_config
self.admin_config = admin_config self.admin_config = admin_config
self.keys = Keys.parse(dvm_config.PRIVATE_KEY) self.keys = Keys.parse(dvm_config.PRIVATE_KEY)
@@ -62,7 +62,7 @@ class DVM:
await self.client.add_relay(relay) await self.client.add_relay(relay)
await self.client.connect() await self.client.connect()
zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_ZAP]).since(Timestamp.now()) zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_ZAP, EventDefinitions.KIND_NIP61_NUT_ZAP]).since(Timestamp.now())
kinds = [EventDefinitions.KIND_NIP90_GENERIC] kinds = [EventDefinitions.KIND_NIP90_GENERIC]
for dvm in self.dvm_config.SUPPORTED_DVMS: for dvm in self.dvm_config.SUPPORTED_DVMS:
if dvm.KIND not in kinds: if dvm.KIND not in kinds:
@@ -80,10 +80,8 @@ class DVM:
await nutzap_wallet.create_new_nut_wallet(self.dvm_config.NUZAP_MINTS, self.dvm_config.NUTZAP_RELAYS, await nutzap_wallet.create_new_nut_wallet(self.dvm_config.NUZAP_MINTS, self.dvm_config.NUTZAP_RELAYS,
self.client, self.keys, "DVM", "DVM Nutsack") self.client, self.keys, "DVM", "DVM Nutsack")
nut_wallet = await nutzap_wallet.get_nut_wallet(self.client, self.keys) nut_wallet = await nutzap_wallet.get_nut_wallet(self.client, self.keys)
if nut_wallet is not None:
await nutzap_wallet.announce_nutzap_info_event(nut_wallet, self.client, self.keys) await nutzap_wallet.announce_nutzap_info_event(nut_wallet, self.client, self.keys)
else:
print("Couldn't fetch wallet, please restart and see if it is there")
class NotificationHandler(HandleNotification): class NotificationHandler(HandleNotification):
client = self.client client = self.client
@@ -314,24 +312,80 @@ class DVM:
self.client, self.keys) self.client, self.keys)
user = await get_or_add_user(db=self.dvm_config.DB, npub=sender, client=self.client, user = await get_or_add_user(db=self.dvm_config.DB, npub=sender, client=self.client,
config=self.dvm_config) config=self.dvm_config)
zapped_event = None
for tag in nut_zap_event.tags():
if tag.as_vec()[0] == 'e':
zapped_event = await get_event_by_id(tag.as_vec()[1], client=self.client,
config=self.dvm_config)
if zapped_event is not None:
if zapped_event.kind() == EventDefinitions.KIND_FEEDBACK:
amount = 0
job_event = None
p_tag_str = ""
status = ""
for tag in zapped_event.tags():
if tag.as_vec()[0] == 'amount':
amount = int(float(tag.as_vec()[1]) / 1000)
elif tag.as_vec()[0] == 'e':
job_event = await get_event_by_id(tag.as_vec()[1], client=self.client,
config=self.dvm_config)
if job_event is not None:
job_event = check_and_decrypt_tags(job_event, self.dvm_config)
if job_event is None:
return
else:
return
elif tag.as_vec()[0] == 'status':
status = tag.as_vec()[1]
print(status)
# if a reaction by us got zapped
print(status)
task_supported, task = await check_task_is_supported(job_event, client=self.client,
config=self.dvm_config)
if job_event is not None and task_supported:
print("NutZap received for NIP90 task: " + str(received_amount) + " Sats from " + str(
user.name))
if amount <= received_amount:
print("[" + self.dvm_config.NIP89.NAME + "] Payment-request fulfilled...")
await send_job_status_reaction(job_event, "processing", client=self.client,
content=self.dvm_config.CUSTOM_PROCESSING_MESSAGE,
dvm_config=self.dvm_config, user=user)
indices = [i for i, x in enumerate(self.job_list) if
x.event == job_event]
index = -1
if len(indices) > 0:
index = indices[0]
if index > -1:
if self.job_list[index].is_processed:
self.job_list[index].is_paid = True
await check_and_return_event(self.job_list[index].result, job_event)
elif not (self.job_list[index]).is_processed:
# If payment-required appears before processing
self.job_list.pop(index)
print("Starting work...")
await do_work(job_event, received_amount)
else:
print("Job not in List, but starting work...")
await do_work(job_event, received_amount)
else:
await send_job_status_reaction(job_event, "payment-rejected",
False, received_amount, client=self.client,
dvm_config=self.dvm_config)
print("[" + self.dvm_config.NIP89.NAME + "] Invoice was not paid sufficiently")
if self.dvm_config.ENABLE_AUTO_MELT: if self.dvm_config.ENABLE_AUTO_MELT:
balance = nut_wallet.balance + received_amount balance = nut_wallet.balance + received_amount
if balance > self.dvm_config.AUTO_MELT_AMOUNT: if balance > self.dvm_config.AUTO_MELT_AMOUNT:
lud16 = None lud16 = self.admin_config.LUD16
npub = None npub = self.dvm_config.PUBLIC_KEY
mint_index = 0 mint_index = 0
await nutzap_wallet.melt_cashu(nut_wallet, self.dvm_config.NUZAP_MINTS[mint_index], await nutzap_wallet.melt_cashu(nut_wallet, self.dvm_config.NUZAP_MINTS[mint_index],
self.dvm_config.AUTO_MELT_AMOUNT, self.client, self.keys, self.dvm_config.AUTO_MELT_AMOUNT, self.client, self.keys,
lud16, npub) lud16, npub)
nut_wallet = await nutzap_wallet.get_nut_wallet(self.client, self.keys)
else: else:
print("NutZaps not enabled for this DVM. ") print("NutZaps not enabled for this DVM. ")

View File

@@ -103,11 +103,13 @@ class DVMTaskInterface:
print("Installing global Module: " + module) print("Installing global Module: " + module)
subprocess.check_call([sys.executable, "-m", "pip", "install", package]) subprocess.check_call([sys.executable, "-m", "pip", "install", package])
def run(self, join=False): async def run_dvm(self, dvm_config, admin_config):
nostr_dvm_thread = Thread(target=self.DVM, args=[self.dvm_config, self.admin_config]) print("Implement the run dvm function")
nostr_dvm_thread.start() pass
if join:
nostr_dvm_thread.join() def run(self):
dvm = DVM()
asyncio.run(dvm.run_dvm(self.dvm_config, self.admin_config))
async def schedule(self, dvm_config): async def schedule(self, dvm_config):
"""schedule something, e.g. define some time to update or to post, does nothing by default""" """schedule something, e.g. define some time to update or to post, does nothing by default"""

View File

@@ -16,12 +16,12 @@ class DVMConfig:
PER_UNIT_COST: float = None PER_UNIT_COST: float = None
RELAY_LIST = ["wss://relay.primal.net", RELAY_LIST = ["wss://relay.primal.net",
"wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", "wss://nostr.mom", "wss://nostr.oxtr.dev",
"wss://relay.nostr.net" "wss://relay.nostr.net"
] ]
RECONCILE_DB_RELAY_LIST = ["wss://relay.damus.io", "wss://nostr21.com", RECONCILE_DB_RELAY_LIST = ["wss://relay.damus.io", "wss://nostr21.com",
"wss://nostr.oxtr.dev", "wss://relay.nostr.bg", "wss://nostr.oxtr.dev",
"wss://relay.nostr.net" , "wss://relay.primal.net"] #, "wss://relay.snort.social"] "wss://relay.nostr.net" , "wss://relay.primal.net"] #, "wss://relay.snort.social"]
AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST
@@ -58,8 +58,8 @@ class DVMConfig:
ENABLE_NUTZAP = False ENABLE_NUTZAP = False
NUTZAP_RELAYS = ["wss://relay.primal.net"] NUTZAP_RELAYS = ["wss://relay.primal.net"]
NUZAP_MINTS = ["https://mint.minibits.cash/Bitcoin", "https://mint.gwoq.com"] NUZAP_MINTS = ["https://mint.minibits.cash/Bitcoin", "https://mint.gwoq.com"]
ENABLE_AUTO_MELT = True ENABLE_AUTO_MELT = False
AUTO_MELT_AMOUNT = 100 AUTO_MELT_AMOUNT = 1000
def build_default_config(identifier): def build_default_config(identifier):

View File

@@ -0,0 +1,773 @@
import asyncio
import json
import os
from collections import namedtuple
from datetime import timedelta
import requests
from nostr_dvm.utils.database_utils import fetch_user_metadata
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig
from nostr_dvm.utils.nostr_utils import check_and_set_private_key
from nostr_dvm.utils.zap_utils import pay_bolt11_ln_bits, zaprequest
from nostr_sdk import Tag, Keys, nip44_encrypt, nip44_decrypt, Nip44Version, EventBuilder, Client, Filter, Kind, \
EventId, nip04_decrypt, nip04_encrypt, Options, NostrSigner, PublicKey, init_logger, LogLevel, Metadata
from nostr_dvm.utils.print import bcolors
class NutWallet(object):
def __init__(self):
self.name: str = "NutWallet"
self.description: str = ""
self.balance: int = 0
self.unit: str = "sat"
self.mints: list = []
self.relays: list = []
self.nutmints: list = []
self.privkey: str = ""
self.d: str = ""
self.a: str = ""
self.legacy_encryption: bool = False # Use Nip04 instead of Nip44, for reasons, turn to False ASAP.
self.trust_unknown_mints: bool = False
class NutMint(object):
def __init__(self):
self.previous_event_id = None
self.proofs: list = []
self.mint_url: str = ""
self.previous_event_id: EventId
self.a: str = ""
def available_balance(self):
balance = 0
for proof in self.proofs:
balance += proof.amount
return balance
class NutZapWallet:
async def client_connect(self, relay_list):
keys = Keys.parse(check_and_set_private_key("TEST_ACCOUNT_PK"))
wait_for_send = False
skip_disconnected_relays = True
opts = (Options().wait_for_send(wait_for_send).send_timeout(timedelta(seconds=5))
.skip_disconnected_relays(skip_disconnected_relays))
signer = NostrSigner.keys(keys)
client = Client.with_opts(signer, opts)
for relay in relay_list:
await client.add_relay(relay)
await client.connect()
return client, keys
async def create_new_nut_wallet(self, mint_urls, relays, client, keys, name, description):
new_nut_wallet = NutWallet()
new_nut_wallet.privkey = Keys.generate().secret_key().to_hex()
new_nut_wallet.balance = 0
new_nut_wallet.unit = "sats"
new_nut_wallet.name = name
new_nut_wallet.description = description
new_nut_wallet.mints = mint_urls
new_nut_wallet.relays = relays
new_nut_wallet.d = "wallet" # sha256(str(new_nut_wallet.name + new_nut_wallet.description).encode('utf-8')).hexdigest()[:16]
new_nut_wallet.a = str(Kind(7375).as_u64()) + ":" + keys.public_key().to_hex() + ":" + new_nut_wallet.d
print("Creating Wallet..")
send_response_id = await self.create_or_update_nut_wallet_event(new_nut_wallet, client, keys)
if send_response_id is None:
print("Warning: Not published")
print(new_nut_wallet.name + ": " + str(new_nut_wallet.balance) + " " + new_nut_wallet.unit + " Mints: " + str(
new_nut_wallet.mints) + " Key: " + new_nut_wallet.privkey)
async def create_or_update_nut_wallet_event(self, nut_wallet: NutWallet, client, keys):
innertags = [Tag.parse(["balance", str(nut_wallet.balance), nut_wallet.unit]).as_vec(),
Tag.parse(["privkey", nut_wallet.privkey]).as_vec()]
if nut_wallet.legacy_encryption:
content = nip04_encrypt(keys.secret_key(), keys.public_key(), json.dumps(innertags))
else:
content = nip44_encrypt(keys.secret_key(), keys.public_key(), json.dumps(innertags), Nip44Version.V2)
if nut_wallet.unit is None:
nut_wallet.unit = "sat"
tags = [Tag.parse(["name", nut_wallet.name]),
Tag.parse(["unit", nut_wallet.unit]),
Tag.parse(["description", nut_wallet.description]),
Tag.parse(["d", nut_wallet.d])]
for mint in nut_wallet.mints:
mint_tag = Tag.parse(["mint", mint])
tags.append(mint_tag)
for relay in nut_wallet.relays:
relay_tag = Tag.parse(["relay", relay])
tags.append(relay_tag)
event = EventBuilder(EventDefinitions.KIND_NUT_WALLET, content, tags).to_event(keys)
send_response = await client.send_event(event)
print(
bcolors.BLUE + "[" + nut_wallet.name + "] announced nut wallet (" + send_response.id.to_hex() + ")" + bcolors.ENDC)
return send_response.id
async def get_nut_wallet(self, client, keys) -> NutWallet:
from cashu.core.base import Proof
nut_wallet = None
wallet_filter = Filter().kind(EventDefinitions.KIND_NUT_WALLET).author(keys.public_key())
wallets = await client.get_events_of([wallet_filter], timedelta(10))
if len(wallets) > 0:
nut_wallet = NutWallet()
latest = 0
best_wallet = None
for wallet_event in wallets:
isdeleted = False
for tag in wallet_event.tags():
if tag.as_vec()[0] == "deleted":
isdeleted = True
break
if isdeleted:
continue
else:
if wallet_event.created_at().as_secs() > latest:
latest = wallet_event.created_at().as_secs()
best_wallet = wallet_event
try:
content = nip44_decrypt(keys.secret_key(), keys.public_key(), best_wallet.content())
print(content)
except:
content = nip04_decrypt(keys.secret_key(), keys.public_key(), best_wallet.content())
print(content)
print("Warning: This Wallet is using a NIP04 enconding.., it should use NIP44 encoding ")
nut_wallet.legacy_encryption = True
inner_tags = json.loads(content)
for tag in inner_tags:
# These tags must be encrypted instead of in the outer tags
if tag[0] == "balance":
nut_wallet.balance = int(tag[1])
elif tag[0] == "privkey":
nut_wallet.privkey = tag[1]
# These tags can be encrypted instead of in the outer tags
elif tag[0] == "name":
nut_wallet.name = tag[1]
elif tag[0] == "description":
nut_wallet.description = tag[1]
elif tag[0] == "unit":
nut_wallet.unit = tag[1]
elif tag[0] == "relay":
if tag[1] not in nut_wallet.relays:
nut_wallet.relays.append(tag[1])
elif tag[0] == "mint":
if tag[1] not in nut_wallet.mints:
nut_wallet.mints.append(tag[1])
for tag in best_wallet.tags():
if tag.as_vec()[0] == "d":
nut_wallet.d = tag.as_vec()[1]
# These tags can be in the outer tags (if not encrypted)
elif tag.as_vec()[0] == "name":
nut_wallet.name = tag.as_vec()[1]
elif tag.as_vec()[0] == "description":
nut_wallet.description = tag.as_vec()[1]
elif tag.as_vec()[0] == "unit":
nut_wallet.unit = tag.as_vec()[1]
elif tag.as_vec()[0] == "relay":
if tag.as_vec()[1] not in nut_wallet.relays:
nut_wallet.relays.append(tag.as_vec()[1])
elif tag.as_vec()[0] == "mint":
if tag.as_vec()[1] not in nut_wallet.mints:
nut_wallet.mints.append(tag.as_vec()[1])
nut_wallet.a = str("37375:" + best_wallet.author().to_hex() + ":" + nut_wallet.d)
# Now all proof events
proof_filter = Filter().kind(Kind(7375)).author(keys.public_key())
proof_events = await client.get_events_of([proof_filter], timedelta(5))
latest_proof_sec = 0
latest_proof_event_id = EventId
for proof_event in proof_events:
if proof_event.created_at().as_secs() > latest_proof_sec:
latest_proof_sec = proof_event.created_at().as_secs()
latest_proof_event_id = proof_event.id()
for proof_event in proof_events:
try:
content = nip44_decrypt(keys.secret_key(), keys.public_key(), proof_event.content())
except:
content = nip04_decrypt(keys.secret_key(), keys.public_key(), proof_event.content())
print("Warning: This Proofs event is using a NIP04 enconding.., it should use NIP44 encoding ")
proofs_json = json.loads(content)
mint_url = ""
a = ""
print("")
print("AVAILABLE MINT:")
try:
mint_url = proofs_json['mint']
print("mint: " + mint_url)
a = proofs_json['a']
print("a: " + a)
except Exception as e:
pass
for tag in proof_event.tags():
if tag.as_vec()[0] == "mint":
mint_url = tag.as_vec()[1]
print("mint: " + mint_url)
elif tag.as_vec()[0] == "a":
a = tag.as_vec()[1]
print("a: " + a)
nut_mint = NutMint()
nut_mint.mint_url = mint_url
nut_mint.a = a
nut_mint.previous_event_id = latest_proof_event_id
nut_mint.proofs = []
for proof in proofs_json['proofs']:
proofs = [x for x in nut_mint.proofs if x.secret == proof['secret']]
if len(proofs) == 0:
nut_proof = Proof()
nut_proof.id = proof['id']
nut_proof.secret = proof['secret']
nut_proof.amount = proof['amount']
nut_proof.C = proof['C']
nut_mint.proofs.append(nut_proof)
#print(proof)
mints = [x for x in nut_wallet.nutmints if x.mint_url == mint_url]
if len(mints) == 0:
nut_wallet.nutmints.append(nut_mint)
print("Mint Balance: " + str(nut_mint.available_balance()) + " Sats")
return nut_wallet
async def update_nut_wallet(self, nut_wallet, mints, client, keys):
for mint in mints:
if mint not in nut_wallet.mints:
nut_wallet.mints.append(mint)
balance = 0
for mint in nut_wallet.nutmints:
for proof in mint.proofs:
balance += proof.amount
nut_wallet.balance = balance
id = await self.create_or_update_nut_wallet_event(nut_wallet, client, keys)
if id is None:
print(bcolors.RED + str("Error publishing WalletEvent") + bcolors.ENDC)
print(nut_wallet.name + ": " + str(nut_wallet.balance) + " " + nut_wallet.unit + " Mints: " + str(
nut_wallet.mints) + " Key: " + nut_wallet.privkey)
return nut_wallet
def get_mint(self, nut_wallet, mint_url) -> NutMint:
mints = [x for x in nut_wallet.nutmints if x.mint_url == mint_url]
if len(mints) == 0:
mint = NutMint()
mint.proofs = []
mint.previous_event_id = None
mint.a = nut_wallet.a
mint.mint_url = mint_url
else:
mint = mints[0]
return mint
async def create_transaction_history_event(self, nut_wallet: NutWallet, amount: int, unit: str,
event_old: EventId | None,
event_new: EventId, direction: str, marker, sender_hex, event_hex,
client: Client, keys: Keys):
# direction
# in = received
# out = sent
# marker:
# created - A new token event was created.
# destroyed - A token event was destroyed.
# redeemed - A [[NIP-61]] nutzap was redeemed."
relays = await client.relays()
relay_hints = relays.keys()
relay_hint = list(relay_hints)[0]
inner_tags = []
inner_tags.append(["direction", direction])
inner_tags.append(["amount", str(amount), unit])
if event_old is not None:
inner_tags.append(["e", event_old.to_hex(), relay_hint, "destroyed"])
inner_tags.append(["e", event_new.to_hex(), relay_hint, "created"])
message = json.dumps(inner_tags)
if nut_wallet.legacy_encryption:
content = nip04_encrypt(keys.secret_key(), keys.public_key(), message)
else:
content = nip44_encrypt(keys.secret_key(), keys.public_key(), message, Nip44Version.V2)
tags = [Tag.parse(["a", nut_wallet.a])]
if marker == "redeemed" or marker == "zapped":
e_tag = Tag.parse(["e", event_hex, relay_hint, marker])
tags.append(e_tag)
p_tag = Tag.parse(["p", sender_hex])
tags.append(p_tag)
event = EventBuilder(Kind(7376), content, tags).to_event(keys)
eventid = await client.send_event(event)
async def create_unspent_proof_event(self, nut_wallet: NutWallet, mint_proofs, mint_url, amount, direction, marker,
sender_hex, event_hex,
client, keys):
new_proofs = []
mint = self.get_mint(nut_wallet, mint_url)
mint.proofs = mint_proofs
for proof in mint_proofs:
proofjson = {
"id": proof['id'],
"amount": proof['amount'],
"secret": proof['secret'],
"C": proof['C']
}
#print("Mint proofs:")
#print(proof)
new_proofs.append(proofjson)
old_event_id = mint.previous_event_id
if mint.previous_event_id is not None:
print(
bcolors.MAGENTA + "[" + nut_wallet.name + "] Deleted previous proofs event.. : (" + mint.previous_event_id.to_hex() + ")" + bcolors.ENDC)
evt = EventBuilder.delete([mint.previous_event_id], reason="deleted").to_event(
keys) # .to_pow_event(keys, 28)
response = await client.send_event(evt)
tags = []
# print(nut_wallet.a)
a_tag = Tag.parse(["a", nut_wallet.a])
tags.append(a_tag)
j = {
"mint": mint_url,
"proofs": new_proofs
}
message = json.dumps(j)
# print(message)
if nut_wallet.legacy_encryption:
content = nip04_encrypt(keys.secret_key(), keys.public_key(), message)
else:
content = nip44_encrypt(keys.secret_key(), keys.public_key(), message, Nip44Version.V2)
event = EventBuilder(Kind(7375), content, tags).to_event(keys)
eventid = await client.send_event(event)
await self.create_transaction_history_event(nut_wallet, amount, nut_wallet.unit, old_event_id, eventid.id,
direction, marker, sender_hex, event_hex, client, keys)
print(
bcolors.GREEN + "[" + nut_wallet.name + "] Published new proofs event.. : (" + eventid.id.to_hex() + ")" + bcolors.ENDC)
return eventid.id
async def mint_token(self, mint, amount):
from cashu.wallet.wallet import Wallet
# TODO probably there's a library function for this
url = mint + "/v1/mint/quote/bolt11"
json_object = {"unit": "sat", "amount": amount}
headers = {"Content-Type": "application/json; charset=utf-8"}
request_body = json.dumps(json_object).encode('utf-8')
request = requests.post(url, data=request_body, headers=headers)
tree = json.loads(request.text)
lnbits_config = {
"LNBITS_ADMIN_KEY": os.getenv("LNBITS_ADMIN_KEY"),
"LNBITS_URL": os.getenv("LNBITS_HOST")
}
lnbits_config_obj = namedtuple("LNBITSCONFIG", lnbits_config.keys())(*lnbits_config.values())
paymenthash = pay_bolt11_ln_bits(tree["request"], lnbits_config_obj)
print(paymenthash)
url = f"{mint}/v1/mint/quote/bolt11/{tree['quote']}"
response = requests.get(url, data=request_body, headers=headers)
tree2 = json.loads(response.text)
waitfor = 5
while not tree2["paid"]:
await asyncio.sleep(1)
response = requests.get(url, data=request_body, headers=headers)
tree2 = json.loads(response.text)
waitfor -= 1
if waitfor == 0:
break
if tree2["paid"]:
# print(response.text)
wallet = await Wallet.with_db(
url=mint,
db="db/Cashu",
)
await wallet.load_mint()
proofs = await wallet.mint(amount, tree['quote'], None)
return proofs
async def announce_nutzap_info_event(self, nut_wallet, client, keys):
tags = []
for relay in nut_wallet.relays:
tags.append(Tag.parse(["relay", relay]))
for mint in nut_wallet.mints:
tags.append(Tag.parse(["mint", mint]))
pubkey = Keys.parse(nut_wallet.privkey).public_key().to_hex()
tags.append(Tag.parse(["pubkey", pubkey]))
event = EventBuilder(Kind(10019), "", tags).to_event(keys)
eventid = await client.send_event(event)
print(
bcolors.CYAN + "[" + nut_wallet.name + "] Announced mint preferences info event (" + eventid.id.to_hex() + ")" + bcolors.ENDC)
async def fetch_mint_info_event(self, pubkey, client):
mint_info_filter = Filter().kind(Kind(10019)).author(PublicKey.parse(pubkey))
preferences = await client.get_events_of([mint_info_filter], timedelta(5))
mints = []
relays = []
pubkey = ""
if len(preferences) > 0:
preference = preferences[0]
for tag in preference.tags():
if tag.as_vec()[0] == "pubkey":
pubkey = tag.as_vec()[1]
elif tag.as_vec()[0] == "relay":
relays.append(tag.as_vec()[1])
elif tag.as_vec()[0] == "mint":
mints.append(tag.as_vec()[1])
return pubkey, mints, relays
async def update_spend_mint_proof_event(self, nut_wallet, send_proofs, mint_url, marker, sender_hex, event_hex,
client, keys):
mint = self.get_mint(nut_wallet, mint_url)
print(mint.mint_url)
print(send_proofs)
amount = 0
for send_proof in send_proofs:
entry = [x for x in mint.proofs if x.id == send_proof.id and x.secret == send_proof.secret]
if len(entry) > 0:
mint.proofs.remove(entry[0])
amount += send_proof.amount
# create new event
mint.previous_event_id = await self.create_unspent_proof_event(nut_wallet, mint.proofs, mint.mint_url, amount,
"out",
marker, sender_hex,
event_hex, client, keys)
nut_wallet.balance = nut_wallet.balance - amount
return await self.update_nut_wallet(nut_wallet, [mint.mint_url], client, keys)
async def mint_cashu(self, nut_wallet: NutWallet, mint_url, client, keys, amount):
print("Minting new tokens on: " + mint_url)
# Mint the Token at the selected mint
proofs = await self.mint_token(mint_url, amount)
print(proofs)
return await self.add_proofs_to_wallet(nut_wallet, mint_url, proofs, "created", None, None, client, keys)
async def add_proofs_to_wallet(self, nut_wallet, mint_url, new_proofs, marker, sender, event, client: Client,
keys: Keys):
mint = self.get_mint(nut_wallet, mint_url)
additional_amount = 0
# store the new proofs in proofs_temp
all_proofs = []
# check for other proofs from same mint, add them to the list of proofs
for nut_proof in mint.proofs:
all_proofs.append(nut_proof)
# add new proofs and calculate additional balance
for proof in new_proofs:
all_proofs.append(proof)
nut_wallet.balance += proof.amount
additional_amount += proof.amount
print("New amount: " + str(additional_amount))
mint.previous_event_id = await self.create_unspent_proof_event(nut_wallet, all_proofs, mint_url,
additional_amount, "in",
marker,
sender, event, client, keys)
return await self.update_nut_wallet(nut_wallet, [mint_url], client, keys)
async def handle_low_balance_on_mint(self, nut_wallet, mint_url, mint, amount, client, keys):
mint_amount = amount - mint.available_balance()
reserved_fees = 3
await self.mint_cashu(nut_wallet, mint_url, client, keys, mint_amount+reserved_fees)
async def send_nut_zap(self, amount, comment, nut_wallet: NutWallet, zapped_event, zapped_user, client: Client,
keys: Keys):
from cashu.wallet.wallet import Wallet
unit = "sats"
p2pk_pubkey, mints, relays = await self.fetch_mint_info_event(zapped_user, client)
if len(mints) == 0:
print("No preferred mint set, returning")
return
mint_success = False
index = 0
mint_url = ""
sufficent_budget = False
# Some logic.
# First look if we have balance on a mint the user has in their list of trusted mints and use it
for mint in nut_wallet.nutmints:
if mint.available_balance() >= amount and mint.mint_url in mints:
mint_url = mint.mint_url
sufficent_budget = True
break
# If that's not the case, lets look or mints we both trust, take the first one.
if not sufficent_budget:
mint_url = next(i for i in nut_wallet.mints if i in mints)
mint = self.get_mint(nut_wallet, mint_url)
if mint.available_balance() < amount:
await self.handle_low_balance_on_mint(nut_wallet, mint_url, mint, amount, client, keys)
# If that's not the case, iterate over the recipents mints and try to mint there. This might be a bit dangerous as not all mints might give cashu, so loss of ln is possible
if mint_url is None:
if nut_wallet.trust_unknown_mints:
# maybe we don't do this for now..
while not mint_success:
try:
mint_url = mints[index] #
# Maybe we introduce a list of known failing mints..
if mint_url == "https://stablenut.umint.cash":
raise Exception("stablemint bad")
mint = self.get_mint(nut_wallet, mint_url)
if mint.available_balance() < amount:
await self.handle_low_balance_on_mint(nut_wallet, mint_url, mint, amount, client, keys)
mint_success = True
except:
mint_success = False
index += 1
else:
print("No trusted mints founds, enable trust_unknown_mints if you still want to proceed...")
return
tags = [Tag.parse(["amount", str(amount)]),
Tag.parse(["unit", unit]),
Tag.parse(["u", mint_url]),
Tag.parse(["p", zapped_user])]
if zapped_event != "" and zapped_event is not None:
tags.append(Tag.parse(["e", zapped_event]))
mint = self.get_mint(nut_wallet, mint_url)
cashu_wallet = await Wallet.with_db(
url=mint_url,
db="db/Cashu",
name="wallet_mint_api",
)
await cashu_wallet.load_mint()
secret_lock = await cashu_wallet.create_p2pk_lock("02" + p2pk_pubkey) # sender side
try:
proofs, fees = await cashu_wallet.select_to_send(mint.proofs, amount)
_, send_proofs = await cashu_wallet.swap_to_send(
proofs, amount, secret_lock=secret_lock, set_reserved=True
)
for proof in send_proofs:
nut_proof = {
'id': proof.id,
'C': proof.C,
'amount': proof.amount,
'secret': proof.secret,
}
tags.append(Tag.parse(["proof", json.dumps(nut_proof)]))
event = EventBuilder(Kind(9321), comment, tags).to_event(keys)
response = await client.send_event(event)
await self.update_spend_mint_proof_event(nut_wallet, proofs, mint_url, "zapped", keys.public_key().to_hex(),
response.id.to_hex(), client, keys)
print(bcolors.YELLOW + "[" + nut_wallet.name + "] Sent NutZap 🥜️⚡ with " + str(
amount) + " " + nut_wallet.unit + " to "
+ PublicKey.parse(zapped_user).to_bech32() +
"(" + response.id.to_hex() + ")" + bcolors.ENDC)
except Exception as e:
print(e)
def print_transaction_history(self, transactions, keys):
for transaction in transactions:
try:
content = nip44_decrypt(keys.secret_key(), keys.public_key(), transaction.content())
except:
content = nip04_decrypt(keys.secret_key(), keys.public_key(), transaction.content())
innertags = json.loads(content)
direction = ""
amount = ""
unit = "sats"
for tag in innertags:
if tag[0] == "direction":
direction = tag[1]
elif tag[0] == "amount":
amount = tag[1]
unit = tag[2]
if amount == "1" and unit == "sats":
unit = "sat"
sender = ""
event = ""
for tag in transaction.tags():
if tag.as_vec()[0] == "p":
sender = tag.as_vec()[1]
elif tag.as_vec()[0] == "e":
event = tag.as_vec()[1]
# marker = tag.as_vec()[2]
if direction == "in":
color = bcolors.GREEN
action = "minted"
dir = "from"
else:
color = bcolors.RED
action = "spent"
dir = "to"
if sender != "" and event != "":
print(
color + f"{direction:3}" + " " + f"{amount:6}" + " " + unit + " at " + transaction.created_at().to_human_datetime().replace(
"T", " ").replace("Z",
" ") + "GMT" + bcolors.ENDC + " " + bcolors.YELLOW + " (Nutzap 🥜⚡️ " + dir + ": " + PublicKey.parse(
sender).to_bech32() + "(" + event + "))" + bcolors.ENDC)
else:
print(
color + f"{direction:3}" + " " + f"{amount:6}" + " " + unit + " at " + transaction.created_at().to_human_datetime().replace(
"T", " ").replace("Z", " ") + "GMT" + " " + " (" + action + ")" + bcolors.ENDC)
async def reedeem_nutzap(self, event, nut_wallet: NutWallet, client: Client, keys: Keys):
from cashu.wallet.wallet import Wallet
from cashu.core.base import Proof
from cashu.core.crypto.keys import PrivateKey
proofs = []
mint_url = ""
amount = 0
unit = "sat"
zapped_user = ""
zapped_event = ""
sender = event.author().to_hex()
message = event.content()
for tag in event.tags():
if tag.as_vec()[0] == "proof":
proof_json = json.loads(tag.as_vec()[1])
proof = Proof().from_dict(proof_json)
proofs.append(proof)
elif tag.as_vec()[0] == "u":
mint_url = tag.as_vec()[1]
elif tag.as_vec()[0] == "amount":
amount = int(tag.as_vec()[1])
elif tag.as_vec()[0] == "unit":
unit = tag.as_vec()[1]
elif tag.as_vec()[0] == "p":
zapped_user = tag.as_vec()[1]
elif tag.as_vec()[0] == "e":
zapped_event = tag.as_vec()[1]
cashu_wallet = await Wallet.with_db(
url=mint_url,
db="db/Receiver",
name="receiver",
)
cashu_wallet.private_key = PrivateKey(bytes.fromhex(nut_wallet.privkey), raw=True)
await cashu_wallet.load_mint()
try:
new_proofs, _ = await cashu_wallet.redeem(proofs)
mint = self.get_mint(nut_wallet, mint_url)
print(mint.proofs)
print(new_proofs)
count_amount = 0
for proof in new_proofs:
count_amount += proof.amount
await self.add_proofs_to_wallet(nut_wallet, mint_url, new_proofs, "redeemed", event.author().to_hex(),
event.id().to_hex(), client, keys)
return count_amount, message, sender
except Exception as e:
print(bcolors.RED + str(e) + bcolors.ENDC)
return None, message, sender
async def melt_cashu(self, nut_wallet, mint_url, total_amount, client, keys, lud16=None, npub=None):
from cashu.wallet.wallet import Wallet
mint = self.get_mint(nut_wallet, mint_url)
cashu_wallet = await Wallet.with_db(
url=mint_url,
db="db/Cashu",
name="wallet_mint_api",
)
await cashu_wallet.load_mint()
cashu_wallet.proofs = mint.proofs
estimated_fees = max(int(total_amount * 0.02), 3)
estimated_redeem_invoice_amount = total_amount - estimated_fees
if npub is None:
# if we don't pass the npub, we default to our pubkey
npub = Keys.parse(check_and_set_private_key("TEST_ACCOUNT_PK")).public_key().to_hex()
if lud16 is None:
# if we don't pass a lud16, we try to fetch one from our profile (make sure it's set)
name, nip05, lud16 = await fetch_user_metadata(npub, client)
invoice = zaprequest(lud16, estimated_redeem_invoice_amount, "Melting from your nutsack", None,
PublicKey.parse(npub), keys, DVMConfig().RELAY_LIST, zaptype="private")
# else:
# invoice = create_bolt11_lud16(lud16, estimated_redeem_invoice_amount)
quote = await cashu_wallet.melt_quote(invoice)
send_proofs, _ = await cashu_wallet.select_to_send(cashu_wallet.proofs, total_amount)
await cashu_wallet.melt(send_proofs, invoice, estimated_fees, quote.quote)
await self.update_spend_mint_proof_event(nut_wallet, send_proofs, mint_url, "", None,
None, client, keys)
print(bcolors.YELLOW + "[" + nut_wallet.name + "] Redeemed on Lightning ⚡ " + str(
total_amount - estimated_fees) + " (Fees: " + str(estimated_fees) + ") " + nut_wallet.unit
+ bcolors.ENDC)
async def set_profile(self, name, about, lud16, image, client, keys):
metadata = Metadata() \
.set_name(name) \
.set_display_name(name) \
.set_about(about) \
.set_picture(image) \
.set_lud16(lud16) \
.set_nip05("")
print("[" + name + "] Setting profile metadata for " + keys.public_key().to_bech32() + "...")
print(metadata.as_json())
await client.set_metadata(metadata)

View File

@@ -1,6 +1,6 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
VERSION = '0.7.16' VERSION = '0.8.0'
DESCRIPTION = 'A framework to build and run Nostr NIP90 Data Vending Machines' DESCRIPTION = 'A framework to build and run Nostr NIP90 Data Vending Machines'
LONG_DESCRIPTION = ('A framework to build and run Nostr NIP90 Data Vending Machines. See the github repository for more information') LONG_DESCRIPTION = ('A framework to build and run Nostr NIP90 Data Vending Machines. See the github repository for more information')

81
tests/dalle.py Normal file
View File

@@ -0,0 +1,81 @@
import json
import os
from pathlib import Path
import dotenv
from nostr_sdk import Keys, LogLevel, init_logger
from nostr_dvm.tasks import search_users, advanced_search
from nostr_dvm.tasks.advanced_search import AdvancedSearch
from nostr_dvm.tasks.advanced_search_wine import AdvancedSearchWine
from nostr_dvm.tasks.imagegeneration_openai_dalle import ImageGenerationDALLE
from nostr_dvm.tasks.search_users import SearchUser
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.nostr_utils import check_and_set_private_key
from nostr_dvm.utils.zap_utils import check_and_set_ln_bits_keys, get_price_per_sat
rebroadcast_NIP89 = False # Announce NIP89 on startup Only do this if you know what you're doing.
rebroadcast_NIP65_Relay_List = False
update_profile = False
#use_logger = True
log_level = LogLevel.ERROR
#if use_logger:
# init_logger(log_level)
def build_dalle(name, identifier):
dvm_config = build_default_config(identifier)
dvm_config.NEW_USER_BALANCE = 0
dvm_config.USE_OWN_VENV = False
dvm_config.ENABLE_NUTZAP = True
profit_in_sats = 10
dvm_config.FIX_COST = int(((4.0 / (get_price_per_sat("USD") * 100)) + profit_in_sats))
nip89info = {
"name": name,
"image": "https://image.nostr.build/22f2267ca9d4ee9d5e8a0c7818a9fa325bbbcdac5573a60a2d163e699bb69923.jpg",
"about": "I create Images bridging OpenAI's DALL·E 3",
"encryptionSupported": True,
"cashuAccepted": True,
"nip90Params": {
"size": {
"required": False,
"values": ["1024:1024", "1024x1792", "1792x1024"]
}
}
}
nip89config = NIP89Config()
nip89config.DTAG = check_and_set_d_tag(identifier, name, dvm_config.PRIVATE_KEY,
nip89info["image"])
nip89config.CONTENT = json.dumps(nip89info)
aconfig = AdminConfig()
aconfig.REBROADCAST_NIP89 = False # We add an optional AdminConfig for this one, and tell the dvm to rebroadcast its NIP89
aconfig.LUD16 = dvm_config.LN_ADDRESS
return ImageGenerationDALLE(name=name, dvm_config=dvm_config, nip89config=nip89config, admin_config=aconfig)
def playground():
if os.getenv("OPENAI_API_KEY") is not None and os.getenv("OPENAI_API_KEY") != "":
dalle = build_dalle("Dall-E 3", "dalle3")
dalle.run()
if __name__ == '__main__':
env_path = Path('.env')
if not env_path.is_file():
with open('.env', 'w') as f:
print("Writing new .env file")
f.write('')
if env_path.is_file():
print(f'loading environment from {env_path.resolve()}')
dotenv.load_dotenv(env_path, verbose=True, override=True)
else:
raise FileNotFoundError(f'.env file not found at {env_path} ')
playground()

View File

@@ -36,7 +36,7 @@ update_profile = False
global_update_rate = 120 # set this high on first sync so db can fully sync before another process trys to. global_update_rate = 120 # set this high on first sync so db can fully sync before another process trys to.
use_logger = True use_logger = True
log_level = LogLevel.ERROR log_level = LogLevel.INFO

103
tests/nutzap_send.py Normal file
View File

@@ -0,0 +1,103 @@
from datetime import timedelta
from pathlib import Path
import dotenv
from nostr_sdk import PublicKey, Timestamp, Event, HandleNotification, Alphabet, Filter, SingleLetterTag, Kind
import asyncio
import argparse
from nostr_dvm.utils import dvmconfig
from nostr_dvm.utils.dvmconfig import DVMConfig
from nostr_dvm.utils.nut_wallet_utils import NutZapWallet
from nostr_dvm.utils.print import bcolors
# Run with params for test functions or set the default here
parser = argparse.ArgumentParser(description='Nutzaps')
parser.add_argument("--mint", type=bool, default=False)
parser.add_argument("--zap", type=bool, default=True)
parser.add_argument("--melt", type=bool, default=False)
args = parser.parse_args()
async def test(relays, mints):
nutzap_wallet = NutZapWallet()
update_wallet_info = False # leave this on false except when you manually changed relays/mints/keys
client, keys = await nutzap_wallet.client_connect(relays)
set_profile = False # Attention, this overwrites your current profile if on True, do not set if you use an non-test account
if set_profile:
lud16 = "hype@bitcoinfixesthis.org" #overwrite with your ln address
await nutzap_wallet.set_profile("Test", "I'm a nutsack test account", lud16, "https://i.nostr.build/V4FwExrV5aXHNm70.jpg", client, keys)
# Test 1 Config: Mint Tokens
mint_to_wallet = args.mint # Test function to mint 5 sats on the mint in your list with given index below
mint_index = 0 # Index of mint in mints list to mint a token
mint_amount = 10 # Amount to mint
# Test 2 Config: Send Nutzap
send_test = args.zap # Send a Nutzap
send_zap_amount = 3
send_zap_message = "From my nutsack"
send_reveiver = "npub139nfkqamy53j7vce9lw6w7uwxm3a8zrwnd2m836tj5y3aytv37vqygz42j" # keys.public_key().to_bech32() # This is ourself, for testing purposes, some other people to nutzap: #npub1nxa4tywfz9nqp7z9zp7nr7d4nchhclsf58lcqt5y782rmf2hefjquaa6q8 # dbth #npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft # pablof7z
send_zapped_event = None # None, or zap an event like this: Nip19Event.from_nostr_uri("nostr:nevent1qqsxq59mhz8s6aj9jzltcmqmmv3eutsfcpkeny2x755vdu5dtq44ldqpz3mhxw309ucnydewxqhrqt338g6rsd3e9upzp75cf0tahv5z7plpdeaws7ex52nmnwgtwfr2g3m37r844evqrr6jqvzqqqqqqyqtxyr6").event_id().to_hex()
# Test 3 Config: Melt to ln address
melt = args.melt
melt_amount = 6
print("PrivateKey: " + keys.secret_key().to_bech32() + " PublicKey: " + keys.public_key().to_bech32())
# See if we already have a wallet and fetch it
nut_wallet = await nutzap_wallet.get_nut_wallet(client, keys)
# If we have a wallet but want to maually update the info..
if nut_wallet is not None and update_wallet_info:
await nutzap_wallet.update_nut_wallet(nut_wallet, mints, client, keys)
await nutzap_wallet.announce_nutzap_info_event(nut_wallet, client, keys)
# If we don't have a wallet, we create one, fetch it and announce our info
if nut_wallet is None:
await nutzap_wallet.create_new_nut_wallet(mints, relays, client, keys, "Test", "My Nutsack")
nut_wallet = await nutzap_wallet.get_nut_wallet(client, keys)
if nut_wallet is not None:
await nutzap_wallet.announce_nutzap_info_event(nut_wallet, client, keys)
else:
print("Couldn't fetch wallet, please restart and see if it is there")
# Test 1: We mint to our own wallet
if mint_to_wallet:
await nutzap_wallet.mint_cashu(nut_wallet, mints[mint_index], client, keys, mint_amount)
nut_wallet = await nutzap_wallet.get_nut_wallet(client, keys)
# Test 2: We send a nutzap to someone (can be ourselves)
if send_test:
zapped_event_id_hex = send_zapped_event
zapped_user_hex = PublicKey.parse(send_reveiver).to_hex()
await nutzap_wallet.send_nut_zap(send_zap_amount, send_zap_message, nut_wallet, zapped_event_id_hex, zapped_user_hex, client,
keys)
#Test 3: Melt back to lightning:
if melt:
# you can overwrite the lu16 and/or npub, otherwise it's fetched from the profile (set it once by setting set_profile to True)
lud16 = None
npub = None
await nutzap_wallet.melt_cashu(nut_wallet, mints[mint_index], melt_amount, client, keys, lud16, npub)
await nutzap_wallet.get_nut_wallet(client, keys)
if __name__ == '__main__':
env_path = Path('.env')
if env_path.is_file():
print(f'loading environment from {env_path.resolve()}')
dotenv.load_dotenv(env_path, verbose=True, override=True)
else:
raise FileNotFoundError(f'.env file not found at {env_path} ')
show_history = True
asyncio.run(test(DVMConfig().NUTZAP_RELAYS, DVMConfig().NUZAP_MINTS))

View File

@@ -19,12 +19,12 @@ rebroadcast_NIP89 = False # Announce NIP89 on startup Only do this if you know
rebroadcast_NIP65_Relay_List = False rebroadcast_NIP65_Relay_List = False
update_profile = False update_profile = False
use_logger = True #use_logger = True
log_level = LogLevel.ERROR log_level = LogLevel.ERROR
if use_logger: #if use_logger:
init_logger(log_level) # init_logger(log_level)
RELAY_LIST = ["wss://relay.primal.net", RELAY_LIST = ["wss://relay.primal.net",
@@ -37,16 +37,17 @@ def build_advanced_search(name, identifier):
dvm_config.PRIVATE_KEY = check_and_set_private_key(identifier) dvm_config.PRIVATE_KEY = check_and_set_private_key(identifier)
npub = Keys.parse(dvm_config.PRIVATE_KEY).public_key().to_bech32() npub = Keys.parse(dvm_config.PRIVATE_KEY).public_key().to_bech32()
dvm_config.RELAY_LIST = RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST
invoice_key, admin_key, wallet_id, user_id, lnaddress = check_and_set_ln_bits_keys(identifier, npub) dvm_config = build_default_config(identifier)
dvm_config.LNBITS_INVOICE_KEY = invoice_key
dvm_config.LNBITS_ADMIN_KEY = admin_key # The dvm might pay failed jobs back
# dvm_config.LNBITS_URL = os.getenv("LNBITS_HOST") # dvm_config.LNBITS_URL = os.getenv("LNBITS_HOST")
dvm_config.ENABLE_NUTZAP = True
dvm_config.FIX_COST = 5
admin_config = AdminConfig() admin_config = AdminConfig()
admin_config.REBROADCAST_NIP89 = rebroadcast_NIP89 admin_config.REBROADCAST_NIP89 = rebroadcast_NIP89
admin_config.REBROADCAST_NIP65_RELAY_LIST = rebroadcast_NIP65_Relay_List admin_config.REBROADCAST_NIP65_RELAY_LIST = rebroadcast_NIP65_Relay_List
admin_config.UPDATE_PROFILE = update_profile admin_config.UPDATE_PROFILE = update_profile
admin_config.LUD16 = lnaddress admin_config.LUD16 = dvm_config.LN_ADDRESS
# Add NIP89 # Add NIP89
nip89info = { nip89info = {

View File

@@ -4,6 +4,9 @@ import time
from pathlib import Path from pathlib import Path
from threading import Thread from threading import Thread
from nostr_dvm.utils.nut_wallet_utils import NutZapWallet
from nostr_dvm.utils.print import bcolors
import dotenv import dotenv
from nostr_sdk import Keys, Client, Tag, EventBuilder, Filter, HandleNotification, Timestamp, nip04_decrypt, \ from nostr_sdk import Keys, Client, Tag, EventBuilder, Filter, HandleNotification, Timestamp, nip04_decrypt, \
nip04_encrypt, NostrSigner, PublicKey, Event, Kind, RelayOptions nip04_encrypt, NostrSigner, PublicKey, Event, Kind, RelayOptions
@@ -47,12 +50,9 @@ async def nostr_client_test_search_profile(input):
keys = Keys.parse(check_and_set_private_key("test_client")) keys = Keys.parse(check_and_set_private_key("test_client"))
iTag = Tag.parse(["i", input, "text"]) iTag = Tag.parse(["i", input, "text"])
relaysTag = Tag.parse(['relays', "wss://relay.damus.io", "wss://blastr.f7z.xyz", "wss://relayable.org",
"wss://nostr-pub.wellorder.net"])
alttag = Tag.parse(["alt", "This is a NIP90 DVM AI task to translate a given Input"]) alttag = Tag.parse(["alt", "This is a NIP90 DVM AI task to translate a given Input"])
event = EventBuilder(EventDefinitions.KIND_NIP90_USER_SEARCH, str("Search for user"), event = EventBuilder(EventDefinitions.KIND_NIP90_USER_SEARCH, str("Search for user"),
[iTag, relaysTag, alttag]).to_event(keys) [iTag, alttag]).to_event(keys)
relay_list = ["wss://relay.damus.io", "wss://blastr.f7z.xyz", "wss://relayable.org", relay_list = ["wss://relay.damus.io", "wss://blastr.f7z.xyz", "wss://relayable.org",
"wss://nostr-pub.wellorder.net"] "wss://nostr-pub.wellorder.net"]
@@ -83,12 +83,9 @@ async def nostr_client_test_image(prompt):
event = EventBuilder(EventDefinitions.KIND_NIP90_GENERATE_IMAGE, str("Generate an Image."), event = EventBuilder(EventDefinitions.KIND_NIP90_GENERATE_IMAGE, str("Generate an Image."),
[iTag, outTag, tTag, paramTag1, bidTag, relaysTag, alttag]).to_event(keys) [iTag, outTag, tTag, paramTag1, bidTag, relaysTag, alttag]).to_event(keys)
relay_list = ["wss://relay.damus.io", "wss://blastr.f7z.xyz", "wss://relayable.org",
"wss://nostr-pub.wellorder.net"]
signer = NostrSigner.keys(keys) signer = NostrSigner.keys(keys)
client = Client(signer) client = Client(signer)
for relay in relay_list: for relay in DVMConfig().RELAY_LIST:
await client.add_relay(relay) await client.add_relay(relay)
await client.connect() await client.connect()
config = DVMConfig config = DVMConfig
@@ -230,6 +227,7 @@ async def nostr_client_test_discovery_user(user, ptag):
eventid = await send_event(event, client=client, dvm_config=config) eventid = await send_event(event, client=client, dvm_config=config)
return event.as_json() return event.as_json()
async def nostr_client_test_discovery_gallery(user, ptag): async def nostr_client_test_discovery_gallery(user, ptag):
keys = Keys.parse(check_and_set_private_key("test_client")) keys = Keys.parse(check_and_set_private_key("test_client"))
@@ -311,25 +309,26 @@ async def nostr_client():
EventDefinitions.KIND_ZAP]).since( EventDefinitions.KIND_ZAP]).since(
Timestamp.now()) # events to us specific Timestamp.now()) # events to us specific
kinds = [EventDefinitions.KIND_NIP90_GENERIC] kinds = [EventDefinitions.KIND_NIP90_GENERIC]
SUPPORTED_KINDS = [Kind(6301)] SUPPORTED_KINDS = [Kind(6100), Kind(7000)]
for kind in SUPPORTED_KINDS: for kind in SUPPORTED_KINDS:
if kind not in kinds: if kind not in kinds:
kinds.append(kind) kinds.append(kind)
dvm_filter = (Filter().kinds(kinds).since(Timestamp.now())) dvm_filter = (Filter().kinds(kinds).since(Timestamp.now()).pubkey(pk))
await client.subscribe([dm_zap_filter, dvm_filter], None) await client.subscribe([dm_zap_filter, dvm_filter], None)
# await nostr_client_test_translation("This is the result of the DVM in spanish", "text", "es", 20, 20) # await nostr_client_test_translation("This is the result of the DVM in spanish", "text", "es", 20, 20)
# await nostr_client_test_translation("note1p8cx2dz5ss5gnk7c59zjydcncx6a754c0hsyakjvnw8xwlm5hymsnc23rs", "event", "es", 20,20) # await nostr_client_test_translation("note1p8cx2dz5ss5gnk7c59zjydcncx6a754c0hsyakjvnw8xwlm5hymsnc23rs", "event", "es", 20,20)
# await nostr_client_test_translation("44a0a8b395ade39d46b9d20038b3f0c8a11168e67c442e3ece95e4a1703e2beb", "event", "zh", 20, 20) # await nostr_client_test_translation("44a0a8b395ade39d46b9d20038b3f0c8a11168e67c442e3ece95e4a1703e2beb", "event", "zh", 20, 20)
# await nostr_client_test_image("a beautiful purple ostrich watching the sunset")
await nostr_client_test_image("a beautiful purple ostrich watching the sunset, eating a cashew nut")
# await nostr_client_test_search_profile("dontbelieve") # await nostr_client_test_search_profile("dontbelieve")
wot = ["99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64"] #wot = ["99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64"]
# await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "ab6cdf12ca3ae5109416295b8cd8a53fdec3a9d54beb7a9aee0ebfb67cb4edf7") # await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "ab6cdf12ca3ae5109416295b8cd8a53fdec3a9d54beb7a9aee0ebfb67cb4edf7")
# await nostr_client_test_discovery_gallery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "4add3944eb596a27a650f9b954f5ed8dfefeec6ca50473605b0fbb058dd11306") # await nostr_client_test_discovery_gallery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "4add3944eb596a27a650f9b954f5ed8dfefeec6ca50473605b0fbb058dd11306")
await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", # await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64",
"2cf10ff849d2769b2b021bd93a0270d03eecfd14126d07f94c6ca2269cb3f3b1") # "2cf10ff849d2769b2b021bd93a0270d03eecfd14126d07f94c6ca2269cb3f3b1")
# await nostr_client_test_censor_filter(wot) # await nostr_client_test_censor_filter(wot)
# await nostr_client_test_inactive_filter("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64") # await nostr_client_test_inactive_filter("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64")
@@ -338,11 +337,35 @@ async def nostr_client():
# cashutoken = "cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6InZxc1VRSVorb0sxOSIsImFtb3VudCI6MSwiQyI6IjAyNWU3ODZhOGFkMmExYTg0N2YxMzNiNGRhM2VhMGIyYWRhZGFkOTRiYzA4M2E2NWJjYjFlOTgwYTE1NGIyMDA2NCIsInNlY3JldCI6InQ1WnphMTZKMGY4UElQZ2FKTEg4V3pPck5rUjhESWhGa291LzVzZFd4S0U9In0seyJpZCI6InZxc1VRSVorb0sxOSIsImFtb3VudCI6NCwiQyI6IjAyOTQxNmZmMTY2MzU5ZWY5ZDc3MDc2MGNjZmY0YzliNTMzMzVmZTA2ZGI5YjBiZDg2Njg5Y2ZiZTIzMjVhYWUwYiIsInNlY3JldCI6IlRPNHB5WE43WlZqaFRQbnBkQ1BldWhncm44UHdUdE5WRUNYWk9MTzZtQXM9In0seyJpZCI6InZxc1VRSVorb0sxOSIsImFtb3VudCI6MTYsIkMiOiIwMmRiZTA3ZjgwYmMzNzE0N2YyMDJkNTZiMGI3ZTIzZTdiNWNkYTBhNmI3Yjg3NDExZWYyOGRiZDg2NjAzNzBlMWIiLCJzZWNyZXQiOiJHYUNIdHhzeG9HM3J2WWNCc0N3V0YxbU1NVXczK0dDN1RKRnVwOHg1cURzPSJ9XSwibWludCI6Imh0dHBzOi8vbG5iaXRzLmJpdGNvaW5maXhlc3RoaXMub3JnL2Nhc2h1L2FwaS92MS9ScDlXZGdKZjlxck51a3M1eVQ2SG5rIn1dfQ==" # cashutoken = "cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6InZxc1VRSVorb0sxOSIsImFtb3VudCI6MSwiQyI6IjAyNWU3ODZhOGFkMmExYTg0N2YxMzNiNGRhM2VhMGIyYWRhZGFkOTRiYzA4M2E2NWJjYjFlOTgwYTE1NGIyMDA2NCIsInNlY3JldCI6InQ1WnphMTZKMGY4UElQZ2FKTEg4V3pPck5rUjhESWhGa291LzVzZFd4S0U9In0seyJpZCI6InZxc1VRSVorb0sxOSIsImFtb3VudCI6NCwiQyI6IjAyOTQxNmZmMTY2MzU5ZWY5ZDc3MDc2MGNjZmY0YzliNTMzMzVmZTA2ZGI5YjBiZDg2Njg5Y2ZiZTIzMjVhYWUwYiIsInNlY3JldCI6IlRPNHB5WE43WlZqaFRQbnBkQ1BldWhncm44UHdUdE5WRUNYWk9MTzZtQXM9In0seyJpZCI6InZxc1VRSVorb0sxOSIsImFtb3VudCI6MTYsIkMiOiIwMmRiZTA3ZjgwYmMzNzE0N2YyMDJkNTZiMGI3ZTIzZTdiNWNkYTBhNmI3Yjg3NDExZWYyOGRiZDg2NjAzNzBlMWIiLCJzZWNyZXQiOiJHYUNIdHhzeG9HM3J2WWNCc0N3V0YxbU1NVXczK0dDN1RKRnVwOHg1cURzPSJ9XSwibWludCI6Imh0dHBzOi8vbG5iaXRzLmJpdGNvaW5maXhlc3RoaXMub3JnL2Nhc2h1L2FwaS92MS9ScDlXZGdKZjlxck51a3M1eVQ2SG5rIn1dfQ=="
# await nostr_client_test_image_private("a beautiful ostrich watching the sunset") # await nostr_client_test_image_private("a beautiful ostrich watching the sunset")
nutzap_wallet = NutZapWallet()
nut_wallet = await nutzap_wallet.get_nut_wallet(client, keys)
class NotificationHandler(HandleNotification): class NotificationHandler(HandleNotification):
async def handle(self, relay_url, subscription_id, event: Event): async def handle(self, relay_url, subscription_id, event: Event):
print(f"Received new event from {relay_url}: {event.as_json()}") print(
bcolors.BLUE + f"Received new event from {relay_url}: {event.as_json()}" + bcolors.ENDC)
if event.kind().as_u64() == 7000: if event.kind().as_u64() == 7000:
print("[Nostr Client]: " + event.as_json()) print("[Nostr Client]: " + event.as_json())
amount_sats = 0
for tag in event.tags():
if tag.as_vec()[0] == "amount":
amount_sats = int(int(tag.as_vec()[1]) / 1000) # millisats
# THIS IS FO TESTING
if event.author().to_hex() == "89669b03bb25232f33192fdda77b8e36e3d3886e9b55b3c74b95091e916c8f98":
nut_wallet = await nutzap_wallet.get_nut_wallet(client, keys)
if nut_wallet is None:
await nutzap_wallet.create_new_nut_wallet(dvmconfig.NUZAP_MINTS, dvmconfig.NUTZAP_RELAYS, client, keys, "Test", "My Nutsack")
nut_wallet = await nutzap_wallet.get_nut_wallet(client, keys)
if nut_wallet is not None:
await nutzap_wallet.announce_nutzap_info_event(nut_wallet, client, keys)
else:
print("Couldn't fetch wallet, please restart and see if it is there")
await nutzap_wallet.send_nut_zap(amount_sats, "From my nutsack lol", nut_wallet, event.id().to_hex(),
event.author().to_hex(), client,
keys)
elif 6000 < event.kind().as_u64() < 6999: elif 6000 < event.kind().as_u64() < 6999:
print("[Nostr Client]: " + event.as_json()) print("[Nostr Client]: " + event.as_json())
print("[Nostr Client]: " + event.content()) print("[Nostr Client]: " + event.content())
@@ -358,9 +381,10 @@ async def nostr_client():
async def handle_msg(self, relay_url, msg): async def handle_msg(self, relay_url, msg):
return return
await client.handle_notifications(NotificationHandler()) asyncio.create_task(client.handle_notifications(NotificationHandler()))
# await client.handle_notifications(NotificationHandler())
while True: while True:
await asyncio.sleep(5.0) await asyncio.sleep(2)
if __name__ == '__main__': if __name__ == '__main__':