mirror of
https://github.com/believethehype/nostrdvm.git
synced 2025-06-01 11:29:13 +02:00
private requests, feedback, results, zaps
This commit is contained in:
parent
f5c6a5a3bf
commit
508278d10b
160
bot.py
160
bot.py
@ -16,7 +16,6 @@ from utils.zap_utils import parse_zap_event_tags, pay_bolt11_ln_bits, zap
|
||||
|
||||
class Bot:
|
||||
job_list: list
|
||||
|
||||
# This is a simple list just to keep track which events we created and manage, so we don't pay for other requests
|
||||
def __init__(self, dvm_config, admin_config=None):
|
||||
self.NAME = "Bot"
|
||||
@ -61,7 +60,8 @@ class Bot:
|
||||
keys = self.keys
|
||||
|
||||
def handle(self, relay_url, nostr_event):
|
||||
if EventDefinitions.KIND_NIP90_EXTRACT_TEXT + 1000 <= nostr_event.kind() <= EventDefinitions.KIND_NIP90_GENERIC + 1000:
|
||||
if (EventDefinitions.KIND_NIP90_EXTRACT_TEXT + 1000 <= nostr_event.kind()
|
||||
<= EventDefinitions.KIND_NIP90_GENERIC + 1000):
|
||||
handle_nip90_response_event(nostr_event)
|
||||
elif nostr_event.kind() == EventDefinitions.KIND_FEEDBACK:
|
||||
handle_nip90_feedback(nostr_event)
|
||||
@ -81,7 +81,6 @@ class Bot:
|
||||
print(decrypted_text)
|
||||
user = get_or_add_user(db=self.dvm_config.DB, npub=sender, client=self.client, config=self.dvm_config)
|
||||
|
||||
# We do a selection of tasks now, maybe change this later, Idk.
|
||||
if decrypted_text[0].isdigit():
|
||||
index = int(decrypted_text.split(' ')[0]) - 1
|
||||
task = self.dvm_config.SUPPORTED_DVMS[index].TASK
|
||||
@ -99,65 +98,58 @@ class Bot:
|
||||
"services.", None).to_event(self.keys)
|
||||
send_event(evt, client=self.client, dvm_config=dvm_config)
|
||||
|
||||
elif user.iswhitelisted or user.balance >= required_amount or required_amount == 0:
|
||||
time.sleep(2.0)
|
||||
if user.iswhitelisted:
|
||||
evt = EventBuilder.new_encrypted_direct_msg(self.keys, nostr_event.pubkey(),
|
||||
"As you are "
|
||||
"whitelisted, your balance remains at"
|
||||
+ str(user.balance) + " Sats.\n",
|
||||
nostr_event.id()).to_event(self.keys)
|
||||
elif user.balance >= required_amount or required_amount == 0:
|
||||
command = decrypted_text.replace(decrypted_text.split(' ')[0] + " ", "")
|
||||
input = command.split("-")[0].rstrip()
|
||||
|
||||
else:
|
||||
balance = max(user.balance - required_amount, 0)
|
||||
update_sql_table(db=self.dvm_config.DB, npub=user.npub, balance=balance,
|
||||
iswhitelisted=user.iswhitelisted, isblacklisted=user.isblacklisted,
|
||||
nip05=user.nip05, lud16=user.lud16, name=user.name,
|
||||
lastactive=Timestamp.now().as_secs())
|
||||
evt = EventBuilder.new_encrypted_direct_msg(self.keys, nostr_event.pubkey(),
|
||||
"New balance is " +
|
||||
str(balance)
|
||||
+ " Sats.\n",
|
||||
nostr_event.id()).to_event(self.keys)
|
||||
|
||||
input = decrypted_text.replace(decrypted_text.split(' ')[0] + " ", "")
|
||||
|
||||
dvm_keys = Keys.from_sk_str(self.dvm_config.SUPPORTED_DVMS[index].PK)
|
||||
i_tag = Tag.parse(["i", input, "text"])
|
||||
|
||||
# we use the y tag to keep information about the original sender, in order to forward the
|
||||
# results later
|
||||
|
||||
# TODO more advanced logic, more parsing, params etc, just very basic test functions for now
|
||||
# outTag = Tag.parse(["output", "image/png;format=url"])
|
||||
# paramTag1 = Tag.parse(["param", "size", "1024x1024"])
|
||||
|
||||
bid = str(self.dvm_config.SUPPORTED_DVMS[index].COST * 1000)
|
||||
bid_tag = Tag.parse(['bid', bid, bid])
|
||||
relays_tag = Tag.parse(["relays", json.dumps(self.dvm_config.RELAY_LIST)])
|
||||
alt_tag = Tag.parse(["alt", self.dvm_config.SUPPORTED_DVMS[index].TASK])
|
||||
p_tag = Tag.parse(['p', dvm_keys.public_key().to_hex()])
|
||||
|
||||
encrypted_params_string = json.dumps([i_tag.as_vec(), bid_tag.as_vec(),
|
||||
relays_tag.as_vec(), alt_tag.as_vec(), p_tag.as_vec()])
|
||||
tags = [i_tag.as_vec(), bid_tag.as_vec(), relays_tag.as_vec(), alt_tag.as_vec()]
|
||||
|
||||
remaining_text = command.replace(input, "")
|
||||
params = remaining_text.rstrip().split("-")
|
||||
|
||||
for i in params:
|
||||
if i != " ":
|
||||
try:
|
||||
split = i.split(" ")
|
||||
param = str(split[0])
|
||||
print(param)
|
||||
value = str(split[1])
|
||||
print(value)
|
||||
tag = Tag.parse(["param", param, value])
|
||||
tags.append(tag.as_vec())
|
||||
print("Added params: " + tag.as_vec())
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("Couldn't add " + i)
|
||||
|
||||
encrypted_params_string = json.dumps(tags)
|
||||
|
||||
print(encrypted_params_string)
|
||||
|
||||
encrypted_params = nip04_encrypt(self.keys.secret_key(), dvm_keys.public_key(),
|
||||
encrypted_params = nip04_encrypt(self.keys.secret_key(),
|
||||
PublicKey.from_hex(
|
||||
self.dvm_config.SUPPORTED_DVMS[index].PUBLIC_KEY),
|
||||
encrypted_params_string)
|
||||
|
||||
encrypted_tag = Tag.parse(['encrypted'])
|
||||
nip90request = EventBuilder(self.dvm_config.SUPPORTED_DVMS[index].KIND, encrypted_params,
|
||||
[p_tag, encrypted_tag]).to_event(self.keys)
|
||||
p_tag = Tag.parse(['p', self.dvm_config.SUPPORTED_DVMS[index].PUBLIC_KEY])
|
||||
encrypted_nip90request = (EventBuilder(self.dvm_config.SUPPORTED_DVMS[index].KIND,
|
||||
encrypted_params, [p_tag, encrypted_tag]).
|
||||
to_event(self.keys))
|
||||
|
||||
entry = {"npub": user.npub, "event_id": nip90request.id().to_hex(),
|
||||
"dvm_key": dvm_keys.public_key().to_hex(), "is_paid": False}
|
||||
entry = {"npub": user.npub, "event_id": encrypted_nip90request.id().to_hex(),
|
||||
"dvm_key": self.dvm_config.SUPPORTED_DVMS[index].PUBLIC_KEY, "is_paid": False}
|
||||
self.job_list.append(entry)
|
||||
|
||||
send_event(nip90request, client=self.client, dvm_config=dvm_config)
|
||||
send_event(encrypted_nip90request, client=self.client, dvm_config=dvm_config)
|
||||
|
||||
|
||||
print("[" + self.NAME + "] Replying " + user.name + " with \"scheduled\" confirmation")
|
||||
send_event(evt, client=self.client, dvm_config=dvm_config)
|
||||
else:
|
||||
print("Bot payment-required")
|
||||
time.sleep(2.0)
|
||||
@ -190,7 +182,9 @@ class Bot:
|
||||
print("Error in bot " + str(e))
|
||||
|
||||
def handle_nip90_feedback(nostr_event):
|
||||
|
||||
try:
|
||||
is_encrypted = False
|
||||
status = ""
|
||||
etag = ""
|
||||
ptag = ""
|
||||
@ -202,6 +196,33 @@ class Bot:
|
||||
etag = tag.as_vec()[1]
|
||||
elif tag.as_vec()[0] == "p":
|
||||
ptag = tag.as_vec()[1]
|
||||
elif tag.as_vec()[0] == "encrypted":
|
||||
is_encrypted = True
|
||||
|
||||
content = nostr_event.content()
|
||||
if is_encrypted:
|
||||
if ptag == self.dvm_config.PUBLIC_KEY:
|
||||
tags_str = nip04_decrypt(Keys.from_sk_str(dvm_config.PRIVATE_KEY).secret_key(),
|
||||
nostr_event.pubkey(), nostr_event.content())
|
||||
params = json.loads(tags_str)
|
||||
params.append(Tag.parse(["p", ptag]).as_vec())
|
||||
params.append(Tag.parse(["encrypted"]).as_vec())
|
||||
print(params)
|
||||
event_as_json = json.loads(nostr_event.as_json())
|
||||
event_as_json['tags'] = params
|
||||
event_as_json['content'] = ""
|
||||
nostr_event = Event.from_json(json.dumps(event_as_json))
|
||||
|
||||
for tag in nostr_event.tags():
|
||||
if tag.as_vec()[0] == "status":
|
||||
status = tag.as_vec()[1]
|
||||
elif tag.as_vec()[0] == "e":
|
||||
etag = tag.as_vec()[1]
|
||||
elif tag.as_vec()[0] == "content":
|
||||
content = tag.as_vec()[1]
|
||||
|
||||
else:
|
||||
return
|
||||
|
||||
if status == "success" or status == "error" or status == "processing" or status == "partial":
|
||||
entry = next((x for x in self.job_list if x['event_id'] == etag), None)
|
||||
@ -211,34 +232,49 @@ class Bot:
|
||||
|
||||
reply_event = EventBuilder.new_encrypted_direct_msg(self.keys,
|
||||
PublicKey.from_hex(user.npub),
|
||||
nostr_event.content(),
|
||||
content,
|
||||
None).to_event(self.keys)
|
||||
print(status + ": " + nostr_event.content())
|
||||
print(status + ": " + content)
|
||||
print(
|
||||
"[" + self.NAME + "] Received reaction from " + nostr_event.pubkey().to_hex() + " message to orignal sender " + user.name)
|
||||
send_event(reply_event, client=self.client, dvm_config=dvm_config)
|
||||
|
||||
|
||||
elif status == "payment-required" or status == "partial":
|
||||
amount = 0
|
||||
|
||||
for tag in nostr_event.tags():
|
||||
if tag.as_vec()[0] == "amount":
|
||||
amount_msats = int(tag.as_vec()[1])
|
||||
amount = int(amount_msats / 1000)
|
||||
entry = next((x for x in self.job_list if x['event_id'] == etag), None)
|
||||
if entry is not None and entry['is_paid'] is False and entry['dvm_key'] == ptag:
|
||||
print("PAYMENT: " + nostr_event.as_json())
|
||||
#if we get a bolt11, we pay and move on
|
||||
if entry is not None and entry['is_paid'] is False and entry['dvm_key'] == nostr_event.pubkey().to_hex():
|
||||
# if we get a bolt11, we pay and move on
|
||||
user = get_or_add_user(db=self.dvm_config.DB, npub=entry["npub"],
|
||||
client=self.client, config=self.dvm_config)
|
||||
balance = max(user.balance - amount, 0)
|
||||
update_sql_table(db=self.dvm_config.DB, npub=user.npub, balance=balance,
|
||||
iswhitelisted=user.iswhitelisted, isblacklisted=user.isblacklisted,
|
||||
nip05=user.nip05, lud16=user.lud16, name=user.name,
|
||||
lastactive=Timestamp.now().as_secs())
|
||||
time.sleep(2.0)
|
||||
evt = EventBuilder.new_encrypted_direct_msg(self.keys,
|
||||
PublicKey.from_hex(entry["npub"]),
|
||||
"Paid " + str(
|
||||
amount) + " Sats from balance to DVM. New balance is " +
|
||||
str(balance)
|
||||
+ " Sats.\n",
|
||||
None).to_event(self.keys)
|
||||
|
||||
print("[" + self.NAME + "] Replying " + user.name + " with \"scheduled\" confirmation")
|
||||
send_event(evt, client=self.client, dvm_config=dvm_config)
|
||||
|
||||
if len(tag.as_vec()) > 2:
|
||||
bolt11 = tag.as_vec()[2]
|
||||
|
||||
# else we create a zap
|
||||
else:
|
||||
user = get_or_add_user(db=self.dvm_config.DB, npub=nostr_event.pubkey().to_hex(),
|
||||
client=self.client, config=self.dvm_config)
|
||||
print("PAYING: " + user.name)
|
||||
bolt11 = zap(user.lud16, amount, "Zap", nostr_event, self.keys, self.dvm_config, "private")
|
||||
print("Paying: " + user.name)
|
||||
bolt11 = zap(user.lud16, amount, "Zap", nostr_event, self.keys, self.dvm_config,
|
||||
"private")
|
||||
if bolt11 == None:
|
||||
print("Receiver has no Lightning address")
|
||||
return
|
||||
@ -256,6 +292,7 @@ class Bot:
|
||||
|
||||
def handle_nip90_response_event(nostr_event: Event):
|
||||
try:
|
||||
ptag = ""
|
||||
is_encrypted = False
|
||||
for tag in nostr_event.tags():
|
||||
if tag.as_vec()[0] == "e":
|
||||
@ -274,7 +311,10 @@ class Bot:
|
||||
self.job_list.remove(entry)
|
||||
content = nostr_event.content()
|
||||
if is_encrypted:
|
||||
content = nip04_decrypt(self.keys.secret_key(), nostr_event.pubkey(), content)
|
||||
if ptag == self.dvm_config.PUBLIC_KEY:
|
||||
content = nip04_decrypt(self.keys.secret_key(), nostr_event.pubkey(), content)
|
||||
else:
|
||||
return
|
||||
|
||||
print("[" + self.NAME + "] Received results, message to orignal sender " + user.name)
|
||||
time.sleep(1.0)
|
||||
@ -291,11 +331,11 @@ class Bot:
|
||||
print("[" + self.NAME + "] Zap received")
|
||||
try:
|
||||
invoice_amount, zapped_event, sender, message, anon = parse_zap_event_tags(zap_event,
|
||||
self.keys, self.NAME,
|
||||
self.client, self.dvm_config)
|
||||
self.keys, self.NAME,
|
||||
self.client, self.dvm_config)
|
||||
|
||||
user = get_or_add_user(self.dvm_config.DB, sender, client=self.client, config=self.dvm_config)
|
||||
|
||||
print("ZAPED EVENT: " + zapped_event.as_json())
|
||||
if zapped_event is not None:
|
||||
if not anon:
|
||||
print("[" + self.NAME + "] Note Zap received for Bot balance: " + str(
|
||||
|
65
dvm.py
65
dvm.py
@ -90,8 +90,6 @@ class DVM:
|
||||
|
||||
user = get_or_add_user(self.dvm_config.DB, nip90_event.pubkey().to_hex(), client=self.client,
|
||||
config=self.dvm_config)
|
||||
|
||||
|
||||
cashu = ""
|
||||
p_tag_str = ""
|
||||
for tag in nip90_event.tags():
|
||||
@ -120,8 +118,13 @@ class DVM:
|
||||
task_is_free = True
|
||||
|
||||
cashu_redeemed = False
|
||||
cashu_message = ""
|
||||
if cashu != "":
|
||||
cashu_redeemed = redeem_cashu(cashu, self.dvm_config, self.client)
|
||||
cashu_redeemed, cashu_message = redeem_cashu(cashu, amount, self.dvm_config, self.client)
|
||||
if cashu_message != "":
|
||||
send_job_status_reaction(nip90_event, "error", False, amount, self.client, cashu_message,
|
||||
self.dvm_config)
|
||||
return
|
||||
# if user is whitelisted or task is free, just do the job
|
||||
if user.iswhitelisted or task_is_free or cashu_redeemed:
|
||||
print(
|
||||
@ -135,7 +138,7 @@ class DVM:
|
||||
# if task is directed to us via p tag and user has balance, do the job and update balance
|
||||
|
||||
elif p_tag_str == Keys.from_sk_str(
|
||||
self.dvm_config.PRIVATE_KEY).public_key().to_hex() and user.balance >= amount:
|
||||
self.dvm_config.PUBLIC_KEY) and user.balance >= amount:
|
||||
balance = max(user.balance - amount, 0)
|
||||
update_sql_table(db=self.dvm_config.DB, npub=user.npub, balance=balance,
|
||||
iswhitelisted=user.iswhitelisted, isblacklisted=user.isblacklisted,
|
||||
@ -176,11 +179,10 @@ class DVM:
|
||||
send_job_status_reaction(nip90_event, "payment-required",
|
||||
False, amount, client=self.client, dvm_config=self.dvm_config)
|
||||
|
||||
else:
|
||||
print("[" + self.dvm_config.NIP89.name + "] Task " + task + " not supported on this DVM, skipping..")
|
||||
#else:
|
||||
#print("[" + self.dvm_config.NIP89.name + "] Task " + task + " not supported on this DVM, skipping..")
|
||||
|
||||
def handle_zap(zap_event):
|
||||
|
||||
try:
|
||||
invoice_amount, zapped_event, sender, message, anon = parse_zap_event_tags(zap_event,
|
||||
self.keys,
|
||||
@ -188,6 +190,7 @@ class DVM:
|
||||
self.client, self.dvm_config)
|
||||
user = get_or_add_user(db=self.dvm_config.DB, npub=sender, client=self.client, config=self.dvm_config)
|
||||
|
||||
|
||||
if zapped_event is not None:
|
||||
if zapped_event.kind() == EventDefinitions.KIND_FEEDBACK:
|
||||
|
||||
@ -201,12 +204,11 @@ class DVM:
|
||||
job_event = 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
|
||||
|
||||
if p_tag_str is None:
|
||||
return
|
||||
|
||||
# if a reaction by us got zapped
|
||||
|
||||
task_supported, task, duration = check_task_is_supported(job_event,
|
||||
@ -330,11 +332,11 @@ class DVM:
|
||||
original_event = Event.from_json(original_event_as_str)
|
||||
request_tag = Tag.parse(["request", original_event_as_str.replace("\\", "")])
|
||||
e_tag = Tag.parse(["e", original_event.id().to_hex()])
|
||||
# p_tag = Tag.parse(["p", original_event.pubkey().to_hex()])
|
||||
p_tag = Tag.parse(["p", original_event.pubkey().to_hex()])
|
||||
alt_tag = Tag.parse(["alt", "This is the result of a NIP90 DVM AI task with kind " + str(
|
||||
original_event.kind()) + ". The task was: " + original_event.content()])
|
||||
status_tag = Tag.parse(["status", "success"])
|
||||
reply_tags = [request_tag, e_tag, alt_tag, status_tag]
|
||||
reply_tags = [request_tag, e_tag, p_tag, alt_tag, status_tag]
|
||||
encrypted = False
|
||||
for tag in original_event.tags():
|
||||
if tag.as_vec()[0] == "encrypted":
|
||||
@ -347,9 +349,6 @@ class DVM:
|
||||
i_tag = tag
|
||||
if not encrypted:
|
||||
reply_tags.append(i_tag)
|
||||
elif tag.as_vec()[0] == "p":
|
||||
p_tag = tag
|
||||
reply_tags.append(p_tag)
|
||||
|
||||
if encrypted:
|
||||
content = nip04_encrypt(self.keys.secret_key(), PublicKey.from_hex(original_event.pubkey().to_hex()),
|
||||
@ -369,16 +368,23 @@ class DVM:
|
||||
alt_description, reaction = build_status_reaction(status, task, amount, content)
|
||||
|
||||
e_tag = Tag.parse(["e", original_event.id().to_hex()])
|
||||
# p_tag = Tag.parse(["p", original_event.pubkey().to_hex()])
|
||||
p_tag = Tag.parse(["p", original_event.pubkey().to_hex()])
|
||||
alt_tag = Tag.parse(["alt", alt_description])
|
||||
status_tag = Tag.parse(["status", status])
|
||||
reply_tags = [e_tag, alt_tag, status_tag]
|
||||
encryption_tags = []
|
||||
|
||||
tags = [e_tag, alt_tag, status_tag]
|
||||
encrypted = False
|
||||
for tag in original_event.tags():
|
||||
if tag.as_vec()[0] == "encrypted":
|
||||
encrypted = True
|
||||
encrypted_tag = Tag.parse(["encrypted"])
|
||||
encryption_tags.append(encrypted_tag)
|
||||
|
||||
if tag.as_vec()[0] == "p":
|
||||
p_tag = tag
|
||||
tags.append(p_tag)
|
||||
if encrypted:
|
||||
encryption_tags.append(p_tag)
|
||||
else:
|
||||
reply_tags.append(p_tag)
|
||||
|
||||
if status == "success" or status == "error": #
|
||||
for x in self.job_list:
|
||||
@ -415,10 +421,25 @@ class DVM:
|
||||
amount_tag = Tag.parse(["amount", str(amount * 1000), bolt11])
|
||||
else:
|
||||
amount_tag = Tag.parse(["amount", str(amount * 1000)]) # to millisats
|
||||
tags.append(amount_tag)
|
||||
reply_tags.append(amount_tag)
|
||||
|
||||
if encrypted:
|
||||
content_tag = Tag.parse(["content", reaction])
|
||||
reply_tags.append(content_tag)
|
||||
str_tags = []
|
||||
for element in reply_tags:
|
||||
str_tags.append(element.as_vec())
|
||||
|
||||
content = json.dumps(str_tags)
|
||||
content = nip04_encrypt(self.keys.secret_key(), PublicKey.from_hex(original_event.pubkey().to_hex()),
|
||||
content)
|
||||
reply_tags = encryption_tags
|
||||
|
||||
else:
|
||||
content = reaction
|
||||
|
||||
keys = Keys.from_sk_str(dvm_config.PRIVATE_KEY)
|
||||
reaction_event = EventBuilder(EventDefinitions.KIND_FEEDBACK, reaction, tags).to_event(keys)
|
||||
reaction_event = EventBuilder(EventDefinitions.KIND_FEEDBACK, str(content), reply_tags).to_event(keys)
|
||||
send_event(reaction_event, client=self.client, dvm_config=self.dvm_config)
|
||||
print("[" + self.dvm_config.NIP89.name + "]" + ": Sent Kind " + str(
|
||||
EventDefinitions.KIND_FEEDBACK) + " Reaction: " + status + " " + reaction_event.as_json())
|
||||
|
@ -12,23 +12,21 @@ class DVMTaskInterface:
|
||||
KIND: int
|
||||
TASK: str
|
||||
COST: int
|
||||
PK: str
|
||||
PRIVATE_KEY: str
|
||||
PUBLIC_KEY: str
|
||||
DVM = DVM
|
||||
dvm_config: DVMConfig
|
||||
admin_config: AdminConfig
|
||||
|
||||
def NIP89_announcement(self, nip89config: NIP89Config):
|
||||
nip89 = NIP89Announcement()
|
||||
nip89.name = self.NAME
|
||||
nip89.kind = self.KIND
|
||||
nip89.pk = self.PK
|
||||
nip89.dtag = nip89config.DTAG
|
||||
nip89.content = nip89config.CONTENT
|
||||
return nip89
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, admin_config: AdminConfig = None,
|
||||
options=None):
|
||||
self.init(name, dvm_config, admin_config, nip89config)
|
||||
self.options = options
|
||||
|
||||
def init(self, name, dvm_config, admin_config, nip89config):
|
||||
def init(self, name, dvm_config, admin_config=None, nip89config=None):
|
||||
self.NAME = name
|
||||
self.PK = dvm_config.PRIVATE_KEY
|
||||
self.PRIVATE_KEY = dvm_config.PRIVATE_KEY
|
||||
self.PUBLIC_KEY = dvm_config.PUBLIC_KEY
|
||||
if dvm_config.COST is not None:
|
||||
self.COST = dvm_config.COST
|
||||
|
||||
@ -43,6 +41,14 @@ class DVMTaskInterface:
|
||||
nostr_dvm_thread = Thread(target=self.DVM, args=[self.dvm_config, self.admin_config])
|
||||
nostr_dvm_thread.start()
|
||||
|
||||
def NIP89_announcement(self, nip89config: NIP89Config):
|
||||
nip89 = NIP89Announcement()
|
||||
nip89.name = self.NAME
|
||||
nip89.kind = self.KIND
|
||||
nip89.pk = self.PRIVATE_KEY
|
||||
nip89.dtag = nip89config.DTAG
|
||||
nip89.content = nip89config.CONTENT
|
||||
return nip89
|
||||
|
||||
def is_input_supported(self, input_type, input_content) -> bool:
|
||||
"""Check if input is supported for current Task."""
|
||||
|
1
main.py
1
main.py
@ -18,6 +18,7 @@ def run_nostr_dvm_with_local_config():
|
||||
# Note this is very basic for now and still under development
|
||||
bot_config = DVMConfig()
|
||||
bot_config.PRIVATE_KEY = os.getenv("BOT_PRIVATE_KEY")
|
||||
bot_config.PUBLIC_KEY = Keys.from_sk_str(bot_config.PRIVATE_KEY).public_key().to_hex()
|
||||
bot_config.LNBITS_INVOICE_KEY = os.getenv("LNBITS_INVOICE_KEY")
|
||||
bot_config.LNBITS_ADMIN_KEY = os.getenv("LNBITS_ADMIN_KEY") # The bot will forward zaps for us, use responsibly
|
||||
bot_config.LNBITS_URL = os.getenv("LNBITS_HOST")
|
||||
|
@ -1,6 +1,9 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from nostr_sdk import PublicKey, Keys
|
||||
|
||||
from interfaces.dvmtaskinterface import DVMTaskInterface
|
||||
from tasks.imagegeneration_openai_dalle import ImageGenerationDALLE
|
||||
from tasks.imagegeneration_sdxl import ImageGenerationSDXL
|
||||
from tasks.textextractionpdf import TextExtractionPDF
|
||||
@ -40,6 +43,7 @@ admin_config.REBROADCAST_NIP89 = False
|
||||
def build_pdf_extractor(name):
|
||||
dvm_config = DVMConfig()
|
||||
dvm_config.PRIVATE_KEY = os.getenv("NOSTR_PRIVATE_KEY")
|
||||
dvm_config.PUBLIC_KEY = Keys.from_sk_str(dvm_config.PRIVATE_KEY).public_key().to_hex()
|
||||
dvm_config.LNBITS_INVOICE_KEY = os.getenv("LNBITS_INVOICE_KEY")
|
||||
dvm_config.LNBITS_URL = os.getenv("LNBITS_HOST")
|
||||
# Add NIP89
|
||||
@ -61,6 +65,7 @@ def build_pdf_extractor(name):
|
||||
def build_translator(name):
|
||||
dvm_config = DVMConfig()
|
||||
dvm_config.PRIVATE_KEY = os.getenv("NOSTR_PRIVATE_KEY")
|
||||
dvm_config.PUBLIC_KEY = Keys.from_sk_str(dvm_config.PRIVATE_KEY).public_key().to_hex()
|
||||
dvm_config.LNBITS_INVOICE_KEY = os.getenv("LNBITS_INVOICE_KEY")
|
||||
dvm_config.LNBITS_URL = os.getenv("LNBITS_HOST")
|
||||
|
||||
@ -93,6 +98,7 @@ def build_translator(name):
|
||||
def build_unstable_diffusion(name):
|
||||
dvm_config = DVMConfig()
|
||||
dvm_config.PRIVATE_KEY = os.getenv("NOSTR_PRIVATE_KEY")
|
||||
dvm_config.PUBLIC_KEY = Keys.from_sk_str(dvm_config.PRIVATE_KEY).public_key().to_hex()
|
||||
dvm_config.LNBITS_INVOICE_KEY = "" #This one will not use Lnbits to create invoices, but rely on zaps
|
||||
dvm_config.LNBITS_URL = ""
|
||||
|
||||
@ -126,6 +132,7 @@ def build_unstable_diffusion(name):
|
||||
def build_sketcher(name):
|
||||
dvm_config = DVMConfig()
|
||||
dvm_config.PRIVATE_KEY = os.getenv("NOSTR_PRIVATE_KEY2")
|
||||
dvm_config.PUBLIC_KEY = Keys.from_sk_str(dvm_config.PRIVATE_KEY).public_key().to_hex()
|
||||
dvm_config.LNBITS_INVOICE_KEY = os.getenv("LNBITS_INVOICE_KEY")
|
||||
dvm_config.LNBITS_URL = os.getenv("LNBITS_HOST")
|
||||
|
||||
@ -161,6 +168,7 @@ def build_sketcher(name):
|
||||
def build_dalle(name):
|
||||
dvm_config = DVMConfig()
|
||||
dvm_config.PRIVATE_KEY = os.getenv("NOSTR_PRIVATE_KEY3")
|
||||
dvm_config.PUBLIC_KEY = Keys.from_sk_str(dvm_config.PRIVATE_KEY).public_key().to_hex()
|
||||
dvm_config.LNBITS_INVOICE_KEY = os.getenv("LNBITS_INVOICE_KEY")
|
||||
dvm_config.LNBITS_URL = os.getenv("LNBITS_HOST")
|
||||
profit_in_sats = 10
|
||||
@ -190,6 +198,17 @@ def build_dalle(name):
|
||||
admin_config=admin_config)
|
||||
|
||||
|
||||
def external_dvm(name, pubkey):
|
||||
dvm_config = DVMConfig()
|
||||
dvm_config.PUBLIC_KEY = Keys.from_public_key(pubkey).public_key().to_hex()
|
||||
nip89info = {
|
||||
"name": name,
|
||||
}
|
||||
nip89config = NIP89Config()
|
||||
nip89config.CONTENT = json.dumps(nip89info)
|
||||
|
||||
return DVMTaskInterface(name=name, dvm_config=dvm_config, nip89config=nip89config)
|
||||
|
||||
# Little Gimmick:
|
||||
# For Dalle where we have to pay 4cent per image, we fetch current sat price in fiat
|
||||
# and update cost at each start
|
||||
|
@ -27,11 +27,9 @@ class ImageGenerationDALLE(DVMTaskInterface):
|
||||
TASK: str = "text-to-image"
|
||||
COST: int = 120
|
||||
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, admin_config: AdminConfig = None,
|
||||
options=None):
|
||||
|
||||
self.init(name, dvm_config, admin_config, nip89config)
|
||||
self.options = options
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
|
||||
admin_config: AdminConfig = None, options=None):
|
||||
super().__init__(name, dvm_config, nip89config, admin_config, options)
|
||||
|
||||
def is_input_supported(self, input_type, input_content):
|
||||
if input_type != "text":
|
||||
|
@ -24,9 +24,9 @@ class ImageGenerationSDXL(DVMTaskInterface):
|
||||
TASK: str = "text-to-image"
|
||||
COST: int = 50
|
||||
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, admin_config: AdminConfig = None, options=None):
|
||||
self.init(name, dvm_config, admin_config, nip89config)
|
||||
self.options = options
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
|
||||
admin_config: AdminConfig = None, options=None):
|
||||
super().__init__(name, dvm_config, nip89config, admin_config, options)
|
||||
|
||||
def is_input_supported(self, input_type, input_content):
|
||||
if input_type != "text":
|
||||
|
@ -25,9 +25,9 @@ class TextExtractionPDF(DVMTaskInterface):
|
||||
TASK: str = "pdf-to-text"
|
||||
COST: int = 0
|
||||
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, admin_config: AdminConfig = None, options=None):
|
||||
self.init(name, dvm_config, admin_config, nip89config)
|
||||
self.options = options
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
|
||||
admin_config: AdminConfig = None, options=None):
|
||||
super().__init__(name, dvm_config, nip89config, admin_config, options)
|
||||
|
||||
|
||||
def is_input_supported(self, input_type, input_content):
|
||||
|
@ -23,10 +23,9 @@ class Translation(DVMTaskInterface):
|
||||
TASK: str = "translation"
|
||||
COST: int = 0
|
||||
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, admin_config: AdminConfig = None,
|
||||
options=None):
|
||||
self.init(name, dvm_config, admin_config, nip89config)
|
||||
self.options = options
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
|
||||
admin_config: AdminConfig = None, options=None):
|
||||
super().__init__(name, dvm_config, nip89config, admin_config, options)
|
||||
|
||||
def is_input_supported(self, input_type, input_content):
|
||||
if input_type != "event" and input_type != "job" and input_type != "text":
|
||||
|
@ -132,7 +132,7 @@ def nostr_client():
|
||||
|
||||
#nostr_client_test_image("a beautiful purple ostrich watching the sunset")
|
||||
|
||||
cashutoken = "cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6IlhXQzAvRXRhcVM4QyIsImFtb3VudCI6MSwiQyI6IjAzMjBjMjBkNWZkNTYwODlmYjZjYTllNDFkYjVlM2MzYTAwMTdjNTUzYmY5MzNkZTgwNTg3NDg1YTk5Yjk2Y2E3OSIsInNlY3JldCI6IktrcnVtakdSeDlHTExxZHBQU3J4WUxaZnJjWmFHekdmZ3Q4T2pZN0c4NHM9In0seyJpZCI6IlhXQzAvRXRhcVM4QyIsImFtb3VudCI6MiwiQyI6IjAyNjYyMjQzNWUxMzBmM2E0ZWE2NGUyMmI4NGQyYWRhNzM2MjE4MTE3YzZjOWIyMmFkYjAwZTFjMzhmZDBiOTNjNCIsInNlY3JldCI6Ikw4dU1BbnBsQm1pdDA4cDZjQk0vcXhpVDFmejlpbnA3V3RzZEJTV284aEk9In0seyJpZCI6IlhXQzAvRXRhcVM4QyIsImFtb3VudCI6NCwiQyI6IjAzMTAxNWM0ZmZhN2U1NzhkNjA0MjFhY2Q2OWEzMTY5NGI4YmRlYTI2YjIwZjgxOWYxOWZhOTNjN2QwZTBiMTdlOCIsInNlY3JldCI6ImRVZ2E2VFo2emRhclozN015NXg2MFdHMzMraitDZnEyOWkzWExjVStDMFE9In0seyJpZCI6IlhXQzAvRXRhcVM4QyIsImFtb3VudCI6MTYsIkMiOiIwMzU0YmYxODdjOTgxZjdmNDk5MGExMDVlMmI2MjIxZDNmYTQ2ZWNlMmNjNWE0ZmI2Mzc3NTdjZDJjM2VhZTkzMGMiLCJzZWNyZXQiOiIyeUJJeEo4dkNGVnUvK1VWSzdwSXFjekkrbkZISngyNXF2ZGhWNDByNzZnPSJ9LHsiaWQiOiJYV0MwL0V0YXFTOEMiLCJhbW91bnQiOjMyLCJDIjoiMDJlYTNmMmFhZGI5MTA4MzljZDA5YTlmMTQ1YTZkY2Q4OGZmZDFmM2M5MjZhMzM5MGFmZjczYjM4ZjY0YjQ5NTU2Iiwic2VjcmV0IjoiQU1mU2FxUWFTN0l5WVdEbUpUaVM4NW9ReFNva0p6SzVJL1R6OUJ5UlFLdz0ifV0sIm1pbnQiOiJodHRwczovL2xuYml0cy5iaXRjb2luZml4ZXN0aGlzLm9yZy9jYXNodS9hcGkvdjEvOXVDcDIyUllWVXE4WjI0bzVCMlZ2VyJ9XX0="
|
||||
cashutoken = "cashuAeyJ0b2tlbiI6W3sicHJvb2ZzIjpbeyJpZCI6InZxc1VRSVorb0sxOSIsImFtb3VudCI6MSwiQyI6IjAyNWU3ODZhOGFkMmExYTg0N2YxMzNiNGRhM2VhMGIyYWRhZGFkOTRiYzA4M2E2NWJjYjFlOTgwYTE1NGIyMDA2NCIsInNlY3JldCI6InQ1WnphMTZKMGY4UElQZ2FKTEg4V3pPck5rUjhESWhGa291LzVzZFd4S0U9In0seyJpZCI6InZxc1VRSVorb0sxOSIsImFtb3VudCI6NCwiQyI6IjAyOTQxNmZmMTY2MzU5ZWY5ZDc3MDc2MGNjZmY0YzliNTMzMzVmZTA2ZGI5YjBiZDg2Njg5Y2ZiZTIzMjVhYWUwYiIsInNlY3JldCI6IlRPNHB5WE43WlZqaFRQbnBkQ1BldWhncm44UHdUdE5WRUNYWk9MTzZtQXM9In0seyJpZCI6InZxc1VRSVorb0sxOSIsImFtb3VudCI6MTYsIkMiOiIwMmRiZTA3ZjgwYmMzNzE0N2YyMDJkNTZiMGI3ZTIzZTdiNWNkYTBhNmI3Yjg3NDExZWYyOGRiZDg2NjAzNzBlMWIiLCJzZWNyZXQiOiJHYUNIdHhzeG9HM3J2WWNCc0N3V0YxbU1NVXczK0dDN1RKRnVwOHg1cURzPSJ9XSwibWludCI6Imh0dHBzOi8vbG5iaXRzLmJpdGNvaW5maXhlc3RoaXMub3JnL2Nhc2h1L2FwaS92MS9ScDlXZGdKZjlxck51a3M1eVQ2SG5rIn1dfQ=="
|
||||
nostr_client_test_image_private("a beautiful ostrich watching the sunset", cashutoken )
|
||||
class NotificationHandler(HandleNotification):
|
||||
def handle(self, relay_url, event):
|
||||
|
@ -62,39 +62,36 @@ def check_task_is_supported(event: Event, client, get_duration=False, config=Non
|
||||
duration = 1
|
||||
task = get_task(event, client=client, dvmconfig=dvm_config)
|
||||
|
||||
try:
|
||||
for tag in event.tags():
|
||||
if tag.as_vec()[0] == 'i':
|
||||
if len(tag.as_vec()) < 3:
|
||||
print("Job Event missing/malformed i tag, skipping..")
|
||||
return False, "", 0
|
||||
else:
|
||||
input_value = tag.as_vec()[1]
|
||||
input_type = tag.as_vec()[2]
|
||||
if input_type == "event":
|
||||
evt = get_event_by_id(input_value, client=client, config=dvm_config)
|
||||
if evt is None:
|
||||
print("Event not found")
|
||||
return False, "", 0
|
||||
elif input_type == 'url' and check_url_is_readable(input_value) is None:
|
||||
print("Url not readable / supported")
|
||||
return False, task, duration #
|
||||
for tag in event.tags():
|
||||
if tag.as_vec()[0] == 'i':
|
||||
if len(tag.as_vec()) < 3:
|
||||
print("Job Event missing/malformed i tag, skipping..")
|
||||
return False, "", 0
|
||||
else:
|
||||
input_value = tag.as_vec()[1]
|
||||
input_type = tag.as_vec()[2]
|
||||
if input_type == "event":
|
||||
evt = get_event_by_id(input_value, client=client, config=dvm_config)
|
||||
if evt is None:
|
||||
print("Event not found")
|
||||
return False, "", 0
|
||||
elif input_type == 'url' and check_url_is_readable(input_value) is None:
|
||||
print("Url not readable / supported")
|
||||
return False, task, duration #
|
||||
|
||||
elif tag.as_vec()[0] == 'output':
|
||||
# TODO move this to individual modules
|
||||
output = tag.as_vec()[1]
|
||||
if not (output == "text/plain"
|
||||
or output == "text/json" or output == "json"
|
||||
or output == "image/png" or "image/jpg"
|
||||
or output == "image/png;format=url" or output == "image/jpg;format=url"
|
||||
or output == ""):
|
||||
print("Output format not supported, skipping..")
|
||||
return False, "", 0
|
||||
|
||||
elif tag.as_vec()[0] == 'output':
|
||||
# TODO move this to individual modules
|
||||
output = tag.as_vec()[1]
|
||||
if not (output == "text/plain"
|
||||
or output == "text/json" or output == "json"
|
||||
or output == "image/png" or "image/jpg"
|
||||
or output == "image/png;format=url" or output == "image/jpg;format=url"
|
||||
or output == ""):
|
||||
print("Output format not supported, skipping..")
|
||||
return False, "", 0
|
||||
except Exception as e:
|
||||
print("Check task 2: " + str(e))
|
||||
|
||||
for dvm in dvm_config.SUPPORTED_DVMS:
|
||||
print(dvm.TASK)
|
||||
if dvm.TASK == task:
|
||||
if not dvm.is_input_supported(input_type, event.content()):
|
||||
return False, task, duration
|
||||
|
@ -1,11 +1,14 @@
|
||||
import os
|
||||
|
||||
from nostr_sdk import Keys
|
||||
|
||||
from utils.nip89_utils import NIP89Announcement
|
||||
|
||||
|
||||
class DVMConfig:
|
||||
SUPPORTED_DVMS= []
|
||||
PRIVATE_KEY: str = os.getenv("NOSTR_PRIVATE_KEY")
|
||||
PRIVATE_KEY: str = ""
|
||||
PUBLIC_KEY: str = ""
|
||||
COST: int = None
|
||||
|
||||
RELAY_LIST = ["wss://relay.damus.io", "wss://nostr-pub.wellorder.net", "wss://nos.lol", "wss://nostr.wine",
|
||||
|
@ -74,23 +74,54 @@ def check_and_decrypt_tags(event, dvm_config):
|
||||
p = tag.as_vec()[1]
|
||||
|
||||
if is_encrypted:
|
||||
if p != Keys.from_sk_str(dvm_config.PRIVATE_KEY).public_key().to_hex():
|
||||
if p != dvm_config.PUBLIC_KEY:
|
||||
print("[" + dvm_config.NIP89.name + "] Task encrypted and not addressed to this DVM, "
|
||||
"skipping..")
|
||||
return None
|
||||
|
||||
elif p == Keys.from_sk_str(dvm_config.PRIVATE_KEY).public_key().to_hex():
|
||||
print("encrypted")
|
||||
elif p == dvm_config.PUBLIC_KEY:
|
||||
tags_str = nip04_decrypt(Keys.from_sk_str(dvm_config.PRIVATE_KEY).secret_key(),
|
||||
event.pubkey(), event.content())
|
||||
params = json.loads(tags_str)
|
||||
params.append(Tag.parse(["p", p]).as_vec())
|
||||
print(params)
|
||||
eventasjson = json.loads(event.as_json())
|
||||
eventasjson['tags'] = params
|
||||
eventasjson['content'] = ""
|
||||
event = Event.from_json(json.dumps(eventasjson))
|
||||
print(event.as_json())
|
||||
params.append(Tag.parse(["encrypted"]).as_vec())
|
||||
event_as_json = json.loads(event.as_json())
|
||||
event_as_json['tags'] = params
|
||||
event_as_json['content'] = ""
|
||||
event = Event.from_json(json.dumps(event_as_json))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
return event
|
||||
|
||||
def check_and_decrypt_own_tags(event, dvm_config):
|
||||
try:
|
||||
tags = []
|
||||
is_encrypted = False
|
||||
p = ""
|
||||
sender = event.pubkey()
|
||||
for tag in event.tags():
|
||||
if tag.as_vec()[0] == 'encrypted':
|
||||
is_encrypted = True
|
||||
elif tag.as_vec()[0] == 'p':
|
||||
p = tag.as_vec()[1]
|
||||
|
||||
if is_encrypted:
|
||||
if dvm_config.PUBLIC_KEY != event.pubkey().to_hex():
|
||||
print("[" + dvm_config.NIP89.name + "] Task encrypted and not addressed to this DVM, "
|
||||
"skipping..")
|
||||
return None
|
||||
|
||||
elif event.pubkey().to_hex() == dvm_config.PUBLIC_KEY:
|
||||
tags_str = nip04_decrypt(Keys.from_sk_str(dvm_config.PRIVATE_KEY).secret_key(),
|
||||
PublicKey.from_hex(p), event.content())
|
||||
params = json.loads(tags_str)
|
||||
params.append(Tag.parse(["p", p]).as_vec())
|
||||
params.append(Tag.parse(["encrypted"]).as_vec())
|
||||
event_as_json = json.loads(event.as_json())
|
||||
event_as_json['tags'] = params
|
||||
event_as_json['content'] = ""
|
||||
event = Event.from_json(json.dumps(event_as_json))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
@ -18,7 +18,6 @@ def post_process_result(anno, original_event):
|
||||
print("Post-processing...")
|
||||
if isinstance(anno, pandas.DataFrame): # if input is an anno we parse it to required output format
|
||||
for tag in original_event.tags:
|
||||
print(tag.as_vec()[0])
|
||||
if tag.as_vec()[0] == "output":
|
||||
output_format = tag.as_vec()[1]
|
||||
print("requested output is " + str(tag.as_vec()[1]) + "...")
|
||||
@ -166,7 +165,7 @@ def build_status_reaction(status, task, amount, content):
|
||||
if content is None:
|
||||
reaction = alt_description + emoji.emojize(":thumbs_down:")
|
||||
else:
|
||||
reaction = alt_description + emoji.emojize(":thumbs_down:") + content
|
||||
reaction = alt_description + emoji.emojize(":thumbs_down:") + " " + content
|
||||
|
||||
elif status == "payment-required":
|
||||
alt_description = "NIP90 DVM AI task " + task + " requires payment of min " + str(
|
||||
|
@ -11,7 +11,7 @@ from nostr_sdk import nostr_sdk, PublicKey, SecretKey, Event, EventBuilder, Tag,
|
||||
|
||||
from utils.database_utils import get_or_add_user
|
||||
from utils.dvmconfig import DVMConfig
|
||||
from utils.nostr_utils import get_event_by_id
|
||||
from utils.nostr_utils import get_event_by_id, check_and_decrypt_tags, check_and_decrypt_own_tags
|
||||
import lnurl
|
||||
from hashlib import sha256
|
||||
|
||||
@ -22,12 +22,14 @@ def parse_zap_event_tags(zap_event, keys, name, client, config):
|
||||
anon = False
|
||||
message = ""
|
||||
sender = zap_event.pubkey()
|
||||
|
||||
for tag in zap_event.tags():
|
||||
if tag.as_vec()[0] == 'bolt11':
|
||||
invoice_amount = parse_amount_from_bolt11_invoice(tag.as_vec()[1])
|
||||
elif tag.as_vec()[0] == 'e':
|
||||
zapped_event = get_event_by_id(tag.as_vec()[1], client=client, config=config)
|
||||
zapped_event = check_and_decrypt_own_tags(zapped_event, config)
|
||||
elif tag.as_vec()[0] == 'p':
|
||||
p_tag = tag.as_vec()[1]
|
||||
elif tag.as_vec()[0] == 'description':
|
||||
zap_request_event = Event.from_json(tag.as_vec()[1])
|
||||
sender = check_for_zapplepay(zap_request_event.pubkey().to_hex(),
|
||||
@ -249,32 +251,39 @@ def parse_cashu(cashu_token):
|
||||
print(e)
|
||||
|
||||
token = cashu["token"][0]
|
||||
print(token)
|
||||
proofs = token["proofs"]
|
||||
mint = token["mint"]
|
||||
total_amount = 0
|
||||
for proof in proofs:
|
||||
total_amount += proof["amount"]
|
||||
fees = max(int(total_amount * 0.02), 2)
|
||||
fees = max(int(total_amount * 0.02), 3)
|
||||
redeem_invoice_amount = total_amount - fees
|
||||
return proofs, mint, redeem_invoice_amount
|
||||
return proofs, mint, redeem_invoice_amount, total_amount
|
||||
|
||||
except Exception as e:
|
||||
print("Could not parse this cashu token")
|
||||
return None, None, None
|
||||
return None, None, None, None
|
||||
|
||||
|
||||
def redeem_cashu(cashu, config, client):
|
||||
proofs, mint, redeem_invoice_amount = parse_cashu(cashu)
|
||||
def redeem_cashu(cashu, required_amount, config, client) -> (bool, str):
|
||||
proofs, mint, redeem_invoice_amount, total_amount = parse_cashu(cashu)
|
||||
fees = total_amount - redeem_invoice_amount
|
||||
if redeem_invoice_amount <= required_amount:
|
||||
err = ("Token value (Payment: " + str(total_amount) + " Sats. Fees: " +
|
||||
str(fees) + " Sats) below required amount of " + str(required_amount)
|
||||
+ " Sats. Cashu token has not been claimed.")
|
||||
print("[" + config.NIP89.name + "] " + err)
|
||||
return False, err
|
||||
|
||||
if config.LNBITS_INVOICE_KEY != "":
|
||||
invoice = create_bolt11_ln_bits(redeem_invoice_amount, config)
|
||||
else:
|
||||
user = get_or_add_user(db=config.DB, npub=Keys.from_sk_str(config.PRIVATE_KEY).public_key().to_hex(),
|
||||
user = get_or_add_user(db=config.DB, npub=config.PUBLIC_KEY,
|
||||
client=client, config=config)
|
||||
invoice = create_bolt11_lud16(user.lud16, redeem_invoice_amount)
|
||||
print(invoice)
|
||||
if invoice is None:
|
||||
return False
|
||||
return False, "couldn't create invoice"
|
||||
try:
|
||||
url = mint + "/melt" # Melt cashu tokens at Mint
|
||||
json_object = {"proofs": proofs, "pr": invoice}
|
||||
@ -282,15 +291,17 @@ def redeem_cashu(cashu, config, client):
|
||||
request_body = json.dumps(json_object).encode('utf-8')
|
||||
request = requests.post(url, data=request_body, headers=headers)
|
||||
tree = json.loads(request.text)
|
||||
is_paid = (tree.get("paid") == "true") if tree.get("detail") else False
|
||||
if is_paid:
|
||||
print(request.text)
|
||||
is_paid = tree["paid"] if tree.get("paid") else "false"
|
||||
print(is_paid)
|
||||
if is_paid == "true":
|
||||
print("token redeemed")
|
||||
return True
|
||||
return True, "success"
|
||||
else:
|
||||
msg = tree.get("detail").split('.')[0].strip() if tree.get("detail") else None
|
||||
print(msg)
|
||||
return False
|
||||
return False, msg
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
return False
|
||||
return False, ""
|
||||
|
Loading…
x
Reference in New Issue
Block a user