diff --git a/nostr_dvm/dvm.py b/nostr_dvm/dvm.py index 9a469f4..65f5146 100644 --- a/nostr_dvm/dvm.py +++ b/nostr_dvm/dvm.py @@ -33,11 +33,11 @@ class DVM: job_list: list jobs_on_hold_list: list - def __init__(self, dvm_config, admin_config=None): - - asyncio.run(self.run_dvm(dvm_config, admin_config)) + #def __init__(self, dvm_config, admin_config=None): + # asyncio.run(self.run_dvm(dvm_config, admin_config)) async def run_dvm(self, dvm_config, admin_config): + self.dvm_config = dvm_config self.admin_config = admin_config self.keys = Keys.parse(dvm_config.PRIVATE_KEY) @@ -62,7 +62,7 @@ class DVM: await self.client.add_relay(relay) 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] for dvm in self.dvm_config.SUPPORTED_DVMS: 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, self.client, self.keys, "DVM", "DVM Nutsack") 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) - else: - print("Couldn't fetch wallet, please restart and see if it is there") + + await nutzap_wallet.announce_nutzap_info_event(nut_wallet, self.client, self.keys) class NotificationHandler(HandleNotification): client = self.client @@ -314,24 +312,80 @@ class DVM: self.client, self.keys) user = await get_or_add_user(db=self.dvm_config.DB, npub=sender, client=self.client, 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: balance = nut_wallet.balance + received_amount if balance > self.dvm_config.AUTO_MELT_AMOUNT: - lud16 = None - npub = None + lud16 = self.admin_config.LUD16 + npub = self.dvm_config.PUBLIC_KEY mint_index = 0 await nutzap_wallet.melt_cashu(nut_wallet, self.dvm_config.NUZAP_MINTS[mint_index], self.dvm_config.AUTO_MELT_AMOUNT, self.client, self.keys, lud16, npub) - nut_wallet = await nutzap_wallet.get_nut_wallet(self.client, self.keys) - - - else: print("NutZaps not enabled for this DVM. ") diff --git a/nostr_dvm/interfaces/dvmtaskinterface.py b/nostr_dvm/interfaces/dvmtaskinterface.py index 36e01e2..3820a9d 100644 --- a/nostr_dvm/interfaces/dvmtaskinterface.py +++ b/nostr_dvm/interfaces/dvmtaskinterface.py @@ -103,11 +103,13 @@ class DVMTaskInterface: print("Installing global Module: " + module) subprocess.check_call([sys.executable, "-m", "pip", "install", package]) - def run(self, join=False): - nostr_dvm_thread = Thread(target=self.DVM, args=[self.dvm_config, self.admin_config]) - nostr_dvm_thread.start() - if join: - nostr_dvm_thread.join() + async def run_dvm(self, dvm_config, admin_config): + print("Implement the run dvm function") + pass + + def run(self): + dvm = DVM() + asyncio.run(dvm.run_dvm(self.dvm_config, self.admin_config)) async def schedule(self, dvm_config): """schedule something, e.g. define some time to update or to post, does nothing by default""" diff --git a/nostr_dvm/utils/dvmconfig.py b/nostr_dvm/utils/dvmconfig.py index bf40fa5..9cd0caf 100644 --- a/nostr_dvm/utils/dvmconfig.py +++ b/nostr_dvm/utils/dvmconfig.py @@ -16,12 +16,12 @@ class DVMConfig: PER_UNIT_COST: float = None 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" ] 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"] AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST @@ -58,8 +58,8 @@ class DVMConfig: ENABLE_NUTZAP = False NUTZAP_RELAYS = ["wss://relay.primal.net"] NUZAP_MINTS = ["https://mint.minibits.cash/Bitcoin", "https://mint.gwoq.com"] - ENABLE_AUTO_MELT = True - AUTO_MELT_AMOUNT = 100 + ENABLE_AUTO_MELT = False + AUTO_MELT_AMOUNT = 1000 def build_default_config(identifier): diff --git a/nostr_dvm/utils/nut_wallet_utils.py b/nostr_dvm/utils/nut_wallet_utils.py new file mode 100644 index 0000000..870d2ba --- /dev/null +++ b/nostr_dvm/utils/nut_wallet_utils.py @@ -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) diff --git a/setup.py b/setup.py index d065085..29b621d 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ 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' LONG_DESCRIPTION = ('A framework to build and run Nostr NIP90 Data Vending Machines. See the github repository for more information') diff --git a/tests/dalle.py b/tests/dalle.py new file mode 100644 index 0000000..0399e1c --- /dev/null +++ b/tests/dalle.py @@ -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() diff --git a/tests/discovery.py b/tests/discovery.py index 2c566f3..8b11a2d 100644 --- a/tests/discovery.py +++ b/tests/discovery.py @@ -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. use_logger = True -log_level = LogLevel.ERROR +log_level = LogLevel.INFO diff --git a/tests/nutzap_send.py b/tests/nutzap_send.py new file mode 100644 index 0000000..164dbe2 --- /dev/null +++ b/tests/nutzap_send.py @@ -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)) diff --git a/tests/search.py b/tests/search.py index 92d6b5f..5bdf794 100644 --- a/tests/search.py +++ b/tests/search.py @@ -19,12 +19,12 @@ rebroadcast_NIP89 = False # Announce NIP89 on startup Only do this if you know rebroadcast_NIP65_Relay_List = False update_profile = False -use_logger = True +#use_logger = True log_level = LogLevel.ERROR -if use_logger: - init_logger(log_level) +#if use_logger: +# init_logger(log_level) 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) npub = Keys.parse(dvm_config.PRIVATE_KEY).public_key().to_bech32() 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.LNBITS_INVOICE_KEY = invoice_key - dvm_config.LNBITS_ADMIN_KEY = admin_key # The dvm might pay failed jobs back + dvm_config = build_default_config(identifier) # dvm_config.LNBITS_URL = os.getenv("LNBITS_HOST") + dvm_config.ENABLE_NUTZAP = True + dvm_config.FIX_COST = 5 + admin_config = AdminConfig() admin_config.REBROADCAST_NIP89 = rebroadcast_NIP89 admin_config.REBROADCAST_NIP65_RELAY_LIST = rebroadcast_NIP65_Relay_List admin_config.UPDATE_PROFILE = update_profile - admin_config.LUD16 = lnaddress + admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 nip89info = { diff --git a/tests/test_dvm_client.py b/tests/test_dvm_client.py index e49b6f0..0f8fda7 100644 --- a/tests/test_dvm_client.py +++ b/tests/test_dvm_client.py @@ -4,6 +4,9 @@ import time from pathlib import Path from threading import Thread +from nostr_dvm.utils.nut_wallet_utils import NutZapWallet +from nostr_dvm.utils.print import bcolors + import dotenv from nostr_sdk import Keys, Client, Tag, EventBuilder, Filter, HandleNotification, Timestamp, nip04_decrypt, \ 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")) 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"]) 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", "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."), [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) client = Client(signer) - for relay in relay_list: + for relay in DVMConfig().RELAY_LIST: await client.add_relay(relay) await client.connect() config = DVMConfig @@ -154,7 +151,7 @@ async def nostr_client_test_inactive_filter(user): async def nostr_client_test_tts(prompt): keys = Keys.parse(check_and_set_private_key("test_client")) iTag = Tag.parse(["i", "9d867cd3e868111a31c8acfa41ab7523b9940fc46c804d7db89d7f373c007fa6", "event"]) - #iTag = Tag.parse(["i", prompt, "text"]) + # iTag = Tag.parse(["i", prompt, "text"]) paramTag1 = Tag.parse(["param", "language", "en"]) bidTag = Tag.parse(['bid', str(1000 * 1000), str(1000 * 1000)]) @@ -230,6 +227,7 @@ async def nostr_client_test_discovery_user(user, ptag): eventid = await send_event(event, client=client, dvm_config=config) return event.as_json() + async def nostr_client_test_discovery_gallery(user, ptag): keys = Keys.parse(check_and_set_private_key("test_client")) @@ -311,25 +309,26 @@ async def nostr_client(): EventDefinitions.KIND_ZAP]).since( Timestamp.now()) # events to us specific kinds = [EventDefinitions.KIND_NIP90_GENERIC] - SUPPORTED_KINDS = [Kind(6301)] + SUPPORTED_KINDS = [Kind(6100), Kind(7000)] for kind in SUPPORTED_KINDS: if kind not in kinds: 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 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("44a0a8b395ade39d46b9d20038b3f0c8a11168e67c442e3ece95e4a1703e2beb", "event", "zh", 20, 20) - # await nostr_client_test_image("a beautiful purple ostrich watching the sunset") - # await nostr_client_test_search_profile("dontbelieve") - wot = ["99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64"] - #await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "ab6cdf12ca3ae5109416295b8cd8a53fdec3a9d54beb7a9aee0ebfb67cb4edf7") - #await nostr_client_test_discovery_gallery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "4add3944eb596a27a650f9b954f5ed8dfefeec6ca50473605b0fbb058dd11306") - await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", - "2cf10ff849d2769b2b021bd93a0270d03eecfd14126d07f94c6ca2269cb3f3b1") + await nostr_client_test_image("a beautiful purple ostrich watching the sunset, eating a cashew nut") + # await nostr_client_test_search_profile("dontbelieve") + #wot = ["99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64"] + # await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "ab6cdf12ca3ae5109416295b8cd8a53fdec3a9d54beb7a9aee0ebfb67cb4edf7") + # await nostr_client_test_discovery_gallery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "4add3944eb596a27a650f9b954f5ed8dfefeec6ca50473605b0fbb058dd11306") + + # await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", + # "2cf10ff849d2769b2b021bd93a0270d03eecfd14126d07f94c6ca2269cb3f3b1") # await nostr_client_test_censor_filter(wot) # await nostr_client_test_inactive_filter("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64") @@ -338,11 +337,35 @@ async def nostr_client(): # cashutoken = "cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6InZxc1VRSVorb0sxOSIsImFtb3VudCI6MSwiQyI6IjAyNWU3ODZhOGFkMmExYTg0N2YxMzNiNGRhM2VhMGIyYWRhZGFkOTRiYzA4M2E2NWJjYjFlOTgwYTE1NGIyMDA2NCIsInNlY3JldCI6InQ1WnphMTZKMGY4UElQZ2FKTEg4V3pPck5rUjhESWhGa291LzVzZFd4S0U9In0seyJpZCI6InZxc1VRSVorb0sxOSIsImFtb3VudCI6NCwiQyI6IjAyOTQxNmZmMTY2MzU5ZWY5ZDc3MDc2MGNjZmY0YzliNTMzMzVmZTA2ZGI5YjBiZDg2Njg5Y2ZiZTIzMjVhYWUwYiIsInNlY3JldCI6IlRPNHB5WE43WlZqaFRQbnBkQ1BldWhncm44UHdUdE5WRUNYWk9MTzZtQXM9In0seyJpZCI6InZxc1VRSVorb0sxOSIsImFtb3VudCI6MTYsIkMiOiIwMmRiZTA3ZjgwYmMzNzE0N2YyMDJkNTZiMGI3ZTIzZTdiNWNkYTBhNmI3Yjg3NDExZWYyOGRiZDg2NjAzNzBlMWIiLCJzZWNyZXQiOiJHYUNIdHhzeG9HM3J2WWNCc0N3V0YxbU1NVXczK0dDN1RKRnVwOHg1cURzPSJ9XSwibWludCI6Imh0dHBzOi8vbG5iaXRzLmJpdGNvaW5maXhlc3RoaXMub3JnL2Nhc2h1L2FwaS92MS9ScDlXZGdKZjlxck51a3M1eVQ2SG5rIn1dfQ==" # 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): 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: 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: print("[Nostr Client]: " + event.as_json()) print("[Nostr Client]: " + event.content()) @@ -358,9 +381,10 @@ async def nostr_client(): async def handle_msg(self, relay_url, msg): return - await client.handle_notifications(NotificationHandler()) + asyncio.create_task(client.handle_notifications(NotificationHandler())) + # await client.handle_notifications(NotificationHandler()) while True: - await asyncio.sleep(5.0) + await asyncio.sleep(2) if __name__ == '__main__':