|
46 | 46 | from .logging import Logger
|
47 | 47 | from .onion_message import create_blinded_path, send_onion_message_to
|
48 | 48 | from .util import (bfh, json_decode, json_normalize, is_hash256_str, is_hex_str, to_bytes,
|
49 |
| - parse_max_spend, to_decimal, UserFacingException, InvalidPassword) |
| 49 | + parse_max_spend, to_decimal, UserFacingException, InvalidPassword, make_aiohttp_session) |
50 | 50 |
|
51 | 51 | from . import bitcoin
|
52 | 52 | from .bitcoin import is_address, hash_160, COIN
|
|
55 | 55 | from .transaction import (Transaction, multisig_script, TxOutput, PartialTransaction, PartialTxOutput,
|
56 | 56 | tx_from_any, PartialTxInput, TxOutpoint)
|
57 | 57 | from . import transaction
|
58 |
| -from .invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED |
| 58 | +from .invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, pr_tooltips |
59 | 59 | from .synchronizer import Notifier
|
60 | 60 | from .wallet import Abstract_Wallet, create_new_wallet, restore_wallet_from_text, Deterministic_Wallet, BumpFeeStrategy, Imported_Wallet
|
61 | 61 | from .address_synchronizer import TX_HEIGHT_LOCAL
|
62 | 62 | from .mnemonic import Mnemonic
|
63 |
| -from .lnutil import SENT, RECEIVED |
64 |
| -from .lnutil import LnFeatures |
65 | 63 | from .lntransport import extract_nodeid
|
66 |
| -from .lnutil import channel_id_from_funding_tx |
| 64 | +from .lnutil import channel_id_from_funding_tx, LnFeatures, SENT, RECEIVED, MIN_FINAL_CLTV_DELTA_FOR_INVOICE |
67 | 65 | from .plugin import run_hook, DeviceMgr, Plugins
|
68 | 66 | from .version import ELECTRUM_VERSION
|
69 | 67 | from .simple_config import SimpleConfig
|
@@ -1346,6 +1344,119 @@ async def add_request(self, amount, memo='', expiry=3600, lightning=False, force
|
1346 | 1344 | req = wallet.get_request(key)
|
1347 | 1345 | return wallet.export_request(req)
|
1348 | 1346 |
|
| 1347 | + @command('wnl') |
| 1348 | + async def add_hold_invoice( |
| 1349 | + self, |
| 1350 | + payment_hash, |
| 1351 | + amount = None, |
| 1352 | + memo = "", |
| 1353 | + expiry = 3600, |
| 1354 | + callback_url = None, |
| 1355 | + wallet: Abstract_Wallet = None |
| 1356 | + ) -> str: |
| 1357 | + """ |
| 1358 | + Create a lightning hold invoice for the given payment hash. Hold invoices have to get settled manually later. |
| 1359 | + The invoice has a final cltv delta of 147 blocks. |
| 1360 | + HTLCs will get failed if local_height + 144 > htlc.cltv_abs. |
| 1361 | +
|
| 1362 | + arg:str:payment_hash:Hex encoded payment hash to be used for the invoice |
| 1363 | + arg:decimal:amount:Optional requested amount (in btc) |
| 1364 | + arg:str:memo:Optional description of the invoice |
| 1365 | + arg:int:expiry:Optional expiry in seconds (default: 3600s) |
| 1366 | + arg:str:callback_url:Optional callback URL for the invoice, if provided a POST request will be triggered once all htlcs arrived (doesn't use proxy) |
| 1367 | + """ |
| 1368 | + assert len(payment_hash) == 64, f"Invalid payment hash length: {len(payment_hash)} != 64" |
| 1369 | + assert payment_hash not in wallet.lnworker.payment_info, "Payment hash already in use!" |
| 1370 | + amount = amount if amount and satoshis(amount) > 0 else None # make amount either >0 or None |
| 1371 | + inbound_capacity = wallet.lnworker.num_sats_can_receive() |
| 1372 | + assert inbound_capacity > satoshis(amount or 0), \ |
| 1373 | + f"Not enough inbound capacity [{inbound_capacity} sat] to receive this payment" |
| 1374 | + if callback_url: |
| 1375 | + assert callback_url.startswith("http"), "Callback URL must be http(s)://" |
| 1376 | + lnaddr, invoice = wallet.lnworker.get_bolt11_invoice( |
| 1377 | + payment_hash=bfh(payment_hash), |
| 1378 | + amount_msat=satoshis(amount) * 1000 if amount else None, |
| 1379 | + message=memo, |
| 1380 | + expiry=expiry, |
| 1381 | + min_final_cltv_expiry_delta=MIN_FINAL_CLTV_DELTA_FOR_INVOICE, |
| 1382 | + fallback_address=None |
| 1383 | + ) |
| 1384 | + wallet.lnworker.add_payment_info_for_hold_invoice( |
| 1385 | + bfh(payment_hash), |
| 1386 | + satoshis(amount) if amount else None, |
| 1387 | + ) |
| 1388 | + wallet.lnworker.register_cli_hold_invoice(payment_hash, callback_url) |
| 1389 | + result = json.dumps({ |
| 1390 | + "invoice": invoice |
| 1391 | + }, indent=4, sort_keys=True) |
| 1392 | + return result |
| 1393 | + |
| 1394 | + @command('wnl') |
| 1395 | + async def settle_hold_invoice(self, preimage, wallet: Abstract_Wallet = None) -> str: |
| 1396 | + """ |
| 1397 | + Settles lightning hold invoice with 'preimage'. |
| 1398 | + Doesn't wait for actual settlement of the HTLCs. |
| 1399 | +
|
| 1400 | + arg:str:preimage:Hex encoded preimage of the payment hash |
| 1401 | + """ |
| 1402 | + assert len(preimage) == 64, f"Invalid preimage length: {len(preimage)} != 64" |
| 1403 | + payment_hash: str = crypto.sha256(bfh(preimage)).hex() |
| 1404 | + assert payment_hash in wallet.lnworker.payment_info, \ |
| 1405 | + f"Couldn't find lightning invoice for payment hash {payment_hash}" |
| 1406 | + assert wallet.lnworker.is_accepted_mpp(bfh(payment_hash)), \ |
| 1407 | + f"MPP incomplete, cannot settle hold invoice with preimage {preimage}" |
| 1408 | + wallet.lnworker.save_preimage(bfh(payment_hash), preimage=bfh(preimage)) |
| 1409 | + wallet.lnworker.unregister_hold_invoice(bfh(payment_hash)) |
| 1410 | + result: str = json.dumps({ |
| 1411 | + "settled": payment_hash |
| 1412 | + }, indent=4) |
| 1413 | + return result |
| 1414 | + |
| 1415 | + @command('wnl') |
| 1416 | + async def cancel_hold_invoice(self, payment_hash, wallet: Abstract_Wallet = None) -> str: |
| 1417 | + """ |
| 1418 | + Cancels lightning hold invoice 'payment_hash'. |
| 1419 | +
|
| 1420 | + arg:str:payment_hash:Payment hash in hex of the hold invoice |
| 1421 | + """ |
| 1422 | + assert payment_hash in wallet.lnworker.payment_info, \ |
| 1423 | + f"Couldn't find lightning invoice for payment hash {payment_hash}" |
| 1424 | + assert payment_hash not in wallet.lnworker.preimages, \ |
| 1425 | + f"Hold invoice already settled with preimage: {crypto.sha256(bfh(payment_hash)).hex()}" |
| 1426 | + # set to PR_UNPAID so it can get deleted |
| 1427 | + wallet.lnworker.set_payment_status(bfh(payment_hash), PR_UNPAID) |
| 1428 | + wallet.lnworker.delete_payment_info(payment_hash) |
| 1429 | + wallet.lnworker.unregister_hold_invoice(bfh(payment_hash)) |
| 1430 | + result: str = json.dumps({ |
| 1431 | + "cancelled": payment_hash |
| 1432 | + }, indent=4) |
| 1433 | + return result |
| 1434 | + |
| 1435 | + @command('wnl') |
| 1436 | + async def check_hold_invoice(self, payment_hash, wallet: Abstract_Wallet = None) -> str: |
| 1437 | + """ |
| 1438 | + Checks the status of a lightning hold invoice 'payment_hash'. |
| 1439 | + Possible states: unpaid, paid, settled, unknown (cancelled or not found) |
| 1440 | +
|
| 1441 | + arg:str:payment_hash:Payment hash in hex of the hold invoice |
| 1442 | + """ |
| 1443 | + info = wallet.lnworker.get_payment_info(bfh(payment_hash)) |
| 1444 | + amount_sat = (wallet.lnworker.get_payment_mpp_amount_msat(bfh(payment_hash)) or 0) // 1000 |
| 1445 | + status = "unknown" |
| 1446 | + if info is None: |
| 1447 | + pass |
| 1448 | + elif info.status == PR_UNPAID or not wallet.lnworker.is_accepted_mpp(bfh(payment_hash)): |
| 1449 | + status = "unpaid" |
| 1450 | + elif info.status == PR_PAID and payment_hash not in wallet.lnworker.preimages: |
| 1451 | + status = "paid" |
| 1452 | + elif payment_hash in wallet.lnworker.preimages: |
| 1453 | + status = "settled" |
| 1454 | + result: str = json.dumps({ |
| 1455 | + "status": status, |
| 1456 | + "amount_sat": amount_sat |
| 1457 | + }, indent=4) |
| 1458 | + return result |
| 1459 | + |
1349 | 1460 | @command('w')
|
1350 | 1461 | async def addtransaction(self, tx, wallet: Abstract_Wallet = None):
|
1351 | 1462 | """
|
|
0 commit comments