mirror of
https://github.com/believethehype/nostrdvm.git
synced 2025-10-11 09:23:15 +02:00
add nutzaps (beta)
This commit is contained in:
@@ -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. ")
|
||||||
|
@@ -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"""
|
||||||
|
@@ -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):
|
||||||
|
773
nostr_dvm/utils/nut_wallet_utils.py
Normal file
773
nostr_dvm/utils/nut_wallet_utils.py
Normal 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)
|
2
setup.py
2
setup.py
@@ -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
81
tests/dalle.py
Normal 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()
|
@@ -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
103
tests/nutzap_send.py
Normal 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))
|
@@ -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 = {
|
||||||
|
@@ -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__':
|
||||||
|
Reference in New Issue
Block a user