diff --git a/.gitignore b/.gitignore index 73e8ba7..ff37a51 100644 --- a/.gitignore +++ b/.gitignore @@ -166,3 +166,4 @@ outputs app_deploy.py app.py app_deploy.py +db/Cashu/wallet.sqlite3 diff --git a/backends/nova_server.py b/backends/nova_server.py index d8ba5c2..ef34e7d 100644 --- a/backends/nova_server.py +++ b/backends/nova_server.py @@ -101,10 +101,7 @@ def check_nova_server_status(jobID, address) -> str | pd.DataFrame: return result elif content_type == 'text/plain; charset=utf-8': - result = response.content.decode('utf-8') - # TODO: This should not be necessary? - result = result.replace(" ", "#").replace(" ", "").replace("#", " ") - return result + return response.content.decode('utf-8') elif content_type == "application/x-zip-compressed": zf = zipfile.ZipFile(io.BytesIO(response.content), "r") diff --git a/bot/bot.py b/bot/bot.py index 964f2b2..ad4cf0d 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -13,7 +13,8 @@ from utils.definitions import EventDefinitions from utils.nip89_utils import nip89_fetch_events_pubkey, NIP89Config 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.zap_utils import parse_zap_event_tags, pay_bolt11_ln_bits, zap, redeem_cashu +from utils.zap_utils import parse_zap_event_tags, pay_bolt11_ln_bits, zap +from utils.cashu_utils import redeem_cashu class Bot: diff --git a/core/dvm.py b/core/dvm.py index fe9a8b3..7926ded 100644 --- a/core/dvm.py +++ b/core/dvm.py @@ -14,8 +14,9 @@ from utils.backend_utils import get_amount_per_task, check_task_is_supported, ge from utils.database_utils import create_sql_table, get_or_add_user, update_user_balance, update_sql_table from utils.mediasource_utils import input_data_file_duration from utils.nostr_utils import get_event_by_id, get_referenced_event_by_id, send_event, check_and_decrypt_tags -from utils.output_utils import post_process_result, build_status_reaction -from utils.zap_utils import check_bolt11_ln_bits_is_paid, create_bolt11_ln_bits, parse_zap_event_tags, redeem_cashu +from utils.output_utils import build_status_reaction +from utils.zap_utils import check_bolt11_ln_bits_is_paid, create_bolt11_ln_bits, parse_zap_event_tags +from utils.cashu_utils import redeem_cashu use_logger = False if use_logger: diff --git a/requirements.txt b/requirements.txt index 7d7663d..339974e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -52,3 +52,6 @@ typing_extensions==4.8.0 tzdata==2023.3 urllib3==2.1.0 wcwidth==0.2.10 +--use-pep517 +secp256k1 +cashu diff --git a/utils/cashu_utils.py b/utils/cashu_utils.py new file mode 100644 index 0000000..52876ea --- /dev/null +++ b/utils/cashu_utils.py @@ -0,0 +1,177 @@ +import asyncio +import base64 +import json +import requests +from utils.database_utils import get_or_add_user +from utils.zap_utils import create_bolt11_ln_bits, create_bolt11_lud16 + + +async def get_cashu_balance(url): + from cashu.wallet.wallet import Wallet + from cashu.core.settings import settings + + + settings.tor = False + wallet = await Wallet.with_db( + url=url, + db="db/Cashu", + ) + await wallet.load_mint() + await wallet.load_proofs() + print("Cashu Wallet balance " + str(wallet.available_balance) + " sats") + mint_balances = await wallet.balance_per_minturl() + print(mint_balances) + + +async def mint_cashu_test(url, amount): + from cashu.wallet.wallet import Wallet + from cashu.core.settings import settings + + + settings.tor = False + wallet = await Wallet.with_db( + url=url, + db="db/Cashu", + ) + await wallet.load_mint() + await wallet.load_proofs() + print("Wallet balance " + str(wallet.available_balance) + " sats") + mint_balances = await wallet.balance_per_minturl() + print(mint_balances) + # mint tokens into wallet, skip if wallet already has funds + + #if wallet.available_balance <= 10: + # invoice = await wallet.request_mint(amount) + # input(f"Pay this invoice and press any button: {invoice.bolt11}\n") + # await wallet.mint(amount, id=invoice.id) + + # create 10 sat token + proofs_to_send, _ = await wallet.split_to_send(wallet.proofs, amount, set_reserved=True) + token_str = await wallet.serialize_proofs(proofs_to_send) + print(token_str) + return token_str + +async def receive_cashu_test(token_str): + from cashu.wallet.wallet import Wallet + from cashu.core.settings import settings + from cashu.core.base import TokenV3 + + token = TokenV3.deserialize(token_str) + print(token.token[0]) + + settings.tor = False + wallet = await Wallet.with_db( + url=token.token[0].mint, + db="db/Cashu", + ) + + await wallet.load_mint() + await wallet.load_proofs() + + print(f"Wallet balance: {wallet.available_balance} sats") + + + try: + await wallet.redeem(token.token[0].proofs) + print(f"Wallet balance: {wallet.available_balance} sats") + except Exception as e: + print(e) + + + + + + +def parse_cashu(cashu_token: str): + try: + prefix = "cashuA" + assert cashu_token.startswith(prefix), Exception( + f"Token prefix not valid. Expected {prefix}." + ) + if not cashu_token.endswith("="): + cashu_token = str(cashu_token) + "==" + print(cashu_token) + token_base64 = cashu_token[len(prefix):].encode("utf-8") + cashu = json.loads(base64.urlsafe_b64decode(token_base64)) + token = cashu["token"][0] + proofs = token["proofs"] + mint = token["mint"] + total_amount = 0 + for proof in proofs: + total_amount += proof["amount"] + + + return proofs, mint, total_amount, None + + except Exception as e: + print(e) + return None, None, None, "Cashu Parser: " + str(e) + + +def redeem_cashu(cashu, config, client, required_amount=0, update_self=False) -> (bool, str, int, int): + proofs, mint, total_amount, message = parse_cashu(cashu) + if message is not None: + return False, message, 0, 0 + + estimated_fees = max(int(total_amount * 0.02), 3) + estimated_redeem_invoice_amount = total_amount - estimated_fees + + # 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(estimated_redeem_invoice_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, estimated_redeem_invoice_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: + 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, 0, 0 + + if config.LNBITS_INVOICE_KEY != "": + invoice, paymenthash = create_bolt11_ln_bits(redeem_invoice_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, redeem_invoice_amount) + print(invoice) + + try: + url = mint + "/melt" # Melt cashu tokens at Mint + json_object = {"proofs": proofs, "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) + print(request.text) + is_paid = tree["paid"] if tree.get("paid") else False + print(is_paid) + if is_paid: + print("cashu token redeemed") + return True, "success", redeem_invoice_amount, fees + else: + msg = tree.get("detail").split('.')[0].strip() if tree.get("detail") else None + print(msg) + return False, msg + except Exception as e: + print(e) + + return False, "", redeem_invoice_amount, fees diff --git a/utils/zap_utils.py b/utils/zap_utils.py index 7c24999..0a4f96e 100644 --- a/utils/zap_utils.py +++ b/utils/zap_utils.py @@ -8,8 +8,6 @@ from Crypto.Cipher import AES from Crypto.Util.Padding import pad from bech32 import bech32_decode, convertbits, bech32_encode from nostr_sdk import nostr_sdk, PublicKey, SecretKey, Event, EventBuilder, Tag, Keys - -from utils.database_utils import get_or_add_user from utils.dvmconfig import DVMConfig from utils.nostr_utils import get_event_by_id, check_and_decrypt_own_tags import lnurl @@ -249,96 +247,6 @@ def zap(lud16: str, amount: int, content, zapped_event: Event, keys, dvm_config, return None -def parse_cashu(cashu_token: str): - try: - prefix = "cashuA" - assert cashu_token.startswith(prefix), Exception( - f"Token prefix not valid. Expected {prefix}." - ) - if not cashu_token.endswith("="): - cashu_token = str(cashu_token) + "==" - print(cashu_token) - token_base64 = cashu_token[len(prefix):].encode("utf-8") - cashu = json.loads(base64.urlsafe_b64decode(token_base64)) - token = cashu["token"][0] - proofs = token["proofs"] - mint = token["mint"] - total_amount = 0 - for proof in proofs: - total_amount += proof["amount"] - - - return proofs, mint, total_amount, None - - except Exception as e: - print(e) - return None, None, None, "Cashu Parser: " + str(e) - - -def redeem_cashu(cashu, config, client, required_amount=0, update_self=False) -> (bool, str, int, int): - proofs, mint, total_amount, message = parse_cashu(cashu) - 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: - 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, 0, 0 - - if config.LNBITS_INVOICE_KEY != "": - invoice, paymenthash = create_bolt11_ln_bits(redeem_invoice_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, redeem_invoice_amount) - print(invoice) - - try: - url = mint + "/melt" # Melt cashu tokens at Mint - json_object = {"proofs": proofs, "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) - print(request.text) - is_paid = tree["paid"] if tree.get("paid") else False - print(is_paid) - if is_paid: - print("cashu token redeemed") - return True, "success", redeem_invoice_amount, fees - else: - msg = tree.get("detail").split('.')[0].strip() if tree.get("detail") else None - print(msg) - return False, msg - except Exception as e: - print(e) - - return False, "", redeem_invoice_amount, fees