mirror of
https://github.com/believethehype/nostrdvm.git
synced 2025-11-20 06:26:26 +01:00
check fees at mint, accept cashu with the bot
This commit is contained in:
23
bot/bot.py
23
bot/bot.py
@@ -10,10 +10,10 @@ from nostr_sdk import (Keys, Client, Timestamp, Filter, nip04_decrypt, HandleNot
|
|||||||
from utils.admin_utils import admin_make_database_updates
|
from utils.admin_utils import admin_make_database_updates
|
||||||
from utils.database_utils import get_or_add_user, update_user_balance, create_sql_table, update_sql_table, User
|
from utils.database_utils import get_or_add_user, update_user_balance, create_sql_table, update_sql_table, User
|
||||||
from utils.definitions import EventDefinitions
|
from utils.definitions import EventDefinitions
|
||||||
from utils.nip89_utils import nip89_fetch_events_pubkey
|
from utils.nip89_utils import nip89_fetch_events_pubkey, NIP89Config
|
||||||
from utils.nostr_utils import send_event
|
from utils.nostr_utils import send_event
|
||||||
from utils.output_utils import PostProcessFunctionType, post_process_list_to_users, post_process_list_to_events
|
from utils.output_utils import PostProcessFunctionType, post_process_list_to_users, post_process_list_to_events
|
||||||
from utils.zap_utils import parse_zap_event_tags, pay_bolt11_ln_bits, zap
|
from utils.zap_utils import parse_zap_event_tags, pay_bolt11_ln_bits, zap, redeem_cashu
|
||||||
|
|
||||||
|
|
||||||
class Bot:
|
class Bot:
|
||||||
@@ -24,6 +24,9 @@ class Bot:
|
|||||||
self.NAME = "Bot"
|
self.NAME = "Bot"
|
||||||
dvm_config.DB = "db/" + self.NAME + ".db"
|
dvm_config.DB = "db/" + self.NAME + ".db"
|
||||||
self.dvm_config = dvm_config
|
self.dvm_config = dvm_config
|
||||||
|
nip89config = NIP89Config()
|
||||||
|
nip89config.NAME = self.NAME
|
||||||
|
self.dvm_config.NIP89 = nip89config
|
||||||
self.admin_config = admin_config
|
self.admin_config = admin_config
|
||||||
self.keys = Keys.from_sk_str(dvm_config.PRIVATE_KEY)
|
self.keys = Keys.from_sk_str(dvm_config.PRIVATE_KEY)
|
||||||
wait_for_send = True
|
wait_for_send = True
|
||||||
@@ -153,9 +156,19 @@ class Bot:
|
|||||||
, None).to_event(self.keys)
|
, None).to_event(self.keys)
|
||||||
send_event(evt, client=self.client, dvm_config=dvm_config)
|
send_event(evt, client=self.client, dvm_config=dvm_config)
|
||||||
|
|
||||||
|
elif decrypted_text.startswith("cashuA"):
|
||||||
|
print("Received Cashu token:" + decrypted_text)
|
||||||
|
cashu_redeemed, cashu_message, total_amount, fees = redeem_cashu(decrypted_text, self.dvm_config, self.client)
|
||||||
|
print(cashu_message)
|
||||||
|
if cashu_message == "success":
|
||||||
|
update_user_balance(self.dvm_config.DB, sender, total_amount, client=self.client,
|
||||||
|
config=self.dvm_config)
|
||||||
|
else:
|
||||||
|
time.sleep(2.0)
|
||||||
|
message = "Error: " + cashu_message + ". Token has not been redeemed."
|
||||||
|
evt = EventBuilder.new_encrypted_direct_msg(self.keys, PublicKey.from_hex(sender), message,
|
||||||
|
None).to_event(self.keys)
|
||||||
|
send_event(evt, client=self.client, dvm_config=self.dvm_config)
|
||||||
else:
|
else:
|
||||||
# Build an overview of known DVMs and send it to the user
|
# Build an overview of known DVMs and send it to the user
|
||||||
answer_overview(nostr_event)
|
answer_overview(nostr_event)
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class DVM:
|
|||||||
cashu_redeemed = False
|
cashu_redeemed = False
|
||||||
if cashu != "":
|
if cashu != "":
|
||||||
print(cashu)
|
print(cashu)
|
||||||
cashu_redeemed, cashu_message = redeem_cashu(cashu, int(amount), self.dvm_config, self.client)
|
cashu_redeemed, cashu_message, redeem_amount, fees = redeem_cashu(cashu, self.dvm_config, self.client, int(amount))
|
||||||
print(cashu_message)
|
print(cashu_message)
|
||||||
if cashu_message != "success":
|
if cashu_message != "success":
|
||||||
send_job_status_reaction(nip90_event, "error", False, amount, self.client, cashu_message,
|
send_job_status_reaction(nip90_event, "error", False, amount, self.client, cashu_message,
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ def create_bolt11_ln_bits(sats: int, config: DVMConfig) -> (str, str):
|
|||||||
res = requests.post(url, json=data, headers=headers)
|
res = requests.post(url, json=data, headers=headers)
|
||||||
obj = json.loads(res.text)
|
obj = json.loads(res.text)
|
||||||
if obj.get("payment_request") and obj.get("payment_hash"):
|
if obj.get("payment_request") and obj.get("payment_hash"):
|
||||||
return obj["payment_request"], obj["payment_hash"]#
|
return obj["payment_request"], obj["payment_hash"] #
|
||||||
else:
|
else:
|
||||||
print(res.text)
|
print(res.text)
|
||||||
return None, None
|
return None, None
|
||||||
@@ -102,7 +102,6 @@ def create_bolt11_ln_bits(sats: int, config: DVMConfig) -> (str, str):
|
|||||||
|
|
||||||
|
|
||||||
def create_bolt11_lud16(lud16, amount):
|
def create_bolt11_lud16(lud16, amount):
|
||||||
|
|
||||||
if lud16.startswith("LNURL") or lud16.startswith("lnurl"):
|
if lud16.startswith("LNURL") or lud16.startswith("lnurl"):
|
||||||
url = lnurl.decode(lud16)
|
url = lnurl.decode(lud16)
|
||||||
elif '@' in lud16: # LNaddress
|
elif '@' in lud16: # LNaddress
|
||||||
@@ -252,43 +251,63 @@ def zap(lud16: str, amount: int, content, zapped_event: Event, keys, dvm_config,
|
|||||||
|
|
||||||
def parse_cashu(cashu_token: str):
|
def parse_cashu(cashu_token: str):
|
||||||
try:
|
try:
|
||||||
try:
|
prefix = "cashuA"
|
||||||
prefix = "cashuA"
|
assert cashu_token.startswith(prefix), Exception(
|
||||||
assert cashu_token.startswith(prefix), Exception(
|
f"Token prefix not valid. Expected {prefix}."
|
||||||
f"Token prefix not valid. Expected {prefix}."
|
)
|
||||||
)
|
if not cashu_token.endswith("="):
|
||||||
if not cashu_token.endswith("="):
|
cashu_token = str(cashu_token) + "=="
|
||||||
cashu_token = cashu_token + "="
|
print(cashu_token)
|
||||||
|
token_base64 = cashu_token[len(prefix):].encode("utf-8")
|
||||||
token_base64 = cashu_token[len(prefix):]
|
cashu = json.loads(base64.urlsafe_b64decode(token_base64))
|
||||||
cashu = json.loads(base64.urlsafe_b64decode(token_base64))
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
token = cashu["token"][0]
|
token = cashu["token"][0]
|
||||||
proofs = token["proofs"]
|
proofs = token["proofs"]
|
||||||
mint = token["mint"]
|
mint = token["mint"]
|
||||||
total_amount = 0
|
total_amount = 0
|
||||||
for proof in proofs:
|
for proof in proofs:
|
||||||
total_amount += proof["amount"]
|
total_amount += proof["amount"]
|
||||||
fees = max(int(total_amount * 0.02), 3)
|
|
||||||
redeem_invoice_amount = total_amount - fees
|
|
||||||
return proofs, mint, redeem_invoice_amount, total_amount
|
return proofs, mint, total_amount, None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Could not parse this cashu token")
|
print(e)
|
||||||
return None, None, None, None
|
return None, None, None, "Cashu Parser: " + str(e)
|
||||||
|
|
||||||
|
|
||||||
def redeem_cashu(cashu, required_amount, config, client, update_self=False) -> (bool, str):
|
def redeem_cashu(cashu, config, client, required_amount=0, update_self=False) -> (bool, str, int, int):
|
||||||
proofs, mint, redeem_invoice_amount, total_amount = parse_cashu(cashu)
|
proofs, mint, total_amount, message = parse_cashu(cashu)
|
||||||
fees = total_amount - redeem_invoice_amount
|
if message is not None:
|
||||||
|
return False, message, 0, 0
|
||||||
|
|
||||||
|
# Not sure if this the best way to go, we first create an invoice that we send to the mint, we catch the fees
|
||||||
|
# for that invoice, and create another invoice with the amount without fees to melt.
|
||||||
|
if config.LNBITS_INVOICE_KEY != "":
|
||||||
|
invoice, paymenthash = create_bolt11_ln_bits(total_amount, config)
|
||||||
|
else:
|
||||||
|
|
||||||
|
user = get_or_add_user(db=config.DB, npub=config.PUBLIC_KEY,
|
||||||
|
client=client, config=config, update=update_self)
|
||||||
|
invoice = create_bolt11_lud16(user.lud16, total_amount)
|
||||||
|
print(invoice)
|
||||||
|
if invoice is None:
|
||||||
|
return False, "couldn't create invoice", 0, 0
|
||||||
|
|
||||||
|
url = mint + "/checkfees" # Melt cashu tokens at Mint
|
||||||
|
json_object = {"pr": invoice}
|
||||||
|
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)
|
||||||
|
fees = tree["fee"]
|
||||||
|
print("Fees on this mint are " + str(fees) + " Sats")
|
||||||
|
redeem_invoice_amount = total_amount -fees
|
||||||
if redeem_invoice_amount < required_amount:
|
if redeem_invoice_amount < required_amount:
|
||||||
err = ("Token value (Payment: " + str(total_amount) + " Sats. Fees: " +
|
err = ("Token value (Payment: " + str(total_amount) + " Sats. Fees: " +
|
||||||
str(fees) + " Sats) below required amount of " + str(required_amount)
|
str(fees) + " Sats) below required amount of " + str(required_amount)
|
||||||
+ " Sats. Cashu token has not been claimed.")
|
+ " Sats. Cashu token has not been claimed.")
|
||||||
print("[" + config.NIP89.NAME + "] " + err)
|
print("[" + config.NIP89.NAME + "] " + err)
|
||||||
return False, err
|
return False, err, 0, 0
|
||||||
|
|
||||||
if config.LNBITS_INVOICE_KEY != "":
|
if config.LNBITS_INVOICE_KEY != "":
|
||||||
invoice, paymenthash = create_bolt11_ln_bits(redeem_invoice_amount, config)
|
invoice, paymenthash = create_bolt11_ln_bits(redeem_invoice_amount, config)
|
||||||
@@ -298,8 +317,7 @@ def redeem_cashu(cashu, required_amount, config, client, update_self=False) -> (
|
|||||||
client=client, config=config, update=update_self)
|
client=client, config=config, update=update_self)
|
||||||
invoice = create_bolt11_lud16(user.lud16, redeem_invoice_amount)
|
invoice = create_bolt11_lud16(user.lud16, redeem_invoice_amount)
|
||||||
print(invoice)
|
print(invoice)
|
||||||
if invoice is None:
|
|
||||||
return False, "couldn't create invoice"
|
|
||||||
try:
|
try:
|
||||||
url = mint + "/melt" # Melt cashu tokens at Mint
|
url = mint + "/melt" # Melt cashu tokens at Mint
|
||||||
json_object = {"proofs": proofs, "pr": invoice}
|
json_object = {"proofs": proofs, "pr": invoice}
|
||||||
@@ -312,7 +330,7 @@ def redeem_cashu(cashu, required_amount, config, client, update_self=False) -> (
|
|||||||
print(is_paid)
|
print(is_paid)
|
||||||
if is_paid:
|
if is_paid:
|
||||||
print("cashu token redeemed")
|
print("cashu token redeemed")
|
||||||
return True, "success"
|
return True, "success", redeem_invoice_amount, fees
|
||||||
else:
|
else:
|
||||||
msg = tree.get("detail").split('.')[0].strip() if tree.get("detail") else None
|
msg = tree.get("detail").split('.')[0].strip() if tree.get("detail") else None
|
||||||
print(msg)
|
print(msg)
|
||||||
@@ -320,7 +338,9 @@ def redeem_cashu(cashu, required_amount, config, client, update_self=False) -> (
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
return False, ""
|
return False, "", redeem_invoice_amount, fees
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_price_per_sat(currency):
|
def get_price_per_sat(currency):
|
||||||
import requests
|
import requests
|
||||||
@@ -337,4 +357,3 @@ def get_price_per_sat(currency):
|
|||||||
price_currency_per_sat = 0.0004
|
price_currency_per_sat = 0.0004
|
||||||
|
|
||||||
return price_currency_per_sat
|
return price_currency_per_sat
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user