diff --git a/CHANGELOG.md b/CHANGELOG.md index 91f2c5e..f0fd46e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ - holdinvoice methods: ``make_hold_invoice``, ``cancel_hold_invoice``, ``settle_hold_invoice`` - holdinvoice notification: ``hold_invoice_accepted`` +### Removed +- ``multi_pay_invoice`` and ``multi_pay_keysend``, they were removed from the spec + ## [0.1.9] 2026-04-23 ### Fixed diff --git a/README.md b/README.md index 02fbb10..eb2425b 100644 --- a/README.md +++ b/README.md @@ -132,9 +132,7 @@ For methods or notifications related to holdinvoices you need v0.3.2+ of [hold]( ## Supported NWC methods * ``pay_invoice`` -* ``multi_pay_invoice`` * ``pay_keysend`` (no ``preimage`` in request allowed since CLN only supports generating it itself) -* ``multi_pay_keysend`` (no ``preimage`` in request allowed since CLN only supports generating it itself) * ``make_invoice`` * ``lookup_invoice`` * ``list_transactions`` diff --git a/src/main.rs b/src/main.rs index bb172bf..dd70aad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,12 +64,8 @@ pub const WALLET_READ_METHODS: [nip47::Method; 5] = [ nip47::Method::GetBalance, nip47::Method::GetInfo, ]; -pub const WALLET_PAY_METHODS: [nip47::Method; 4] = [ - nip47::Method::PayInvoice, - nip47::Method::MultiPayInvoice, - nip47::Method::PayKeysend, - nip47::Method::MultiPayKeysend, -]; +pub const WALLET_PAY_METHODS: [nip47::Method; 2] = + [nip47::Method::PayInvoice, nip47::Method::PayKeysend]; pub const WALLET_HOLD_METHODS: [nip47::Method; 3] = [ nip47::Method::MakeHoldInvoice, nip47::Method::CancelHoldInvoice, diff --git a/src/nwc.rs b/src/nwc.rs index 423e0cf..16c00c8 100644 --- a/src/nwc.rs +++ b/src/nwc.rs @@ -37,9 +37,9 @@ use crate::{ }, nwc_info::get_info_response, nwc_invoice::make_invoice_response, - nwc_keysend::{multi_pay_keysend, pay_keysend_response}, + nwc_keysend::pay_keysend_response, nwc_lookups::{list_transactions_response, lookup_invoice_response}, - nwc_pay::{multi_pay_invoice, pay_invoice_response}, + nwc_pay::pay_invoice_response, structs::{NwcStore, PluginState}, tasks::budget_task, util::{build_capabilities, build_notifications_vec, is_read_only_nwc}, @@ -236,7 +236,7 @@ async fn nwc_request_handler( notification: ClientNotification, nostr_client: &client::Client, plugin: &Plugin, - label: &String, + label: &str, wallet_keys: &Keys, client_pubkey: PublicKey, ) -> Result<(), Box> { @@ -249,8 +249,8 @@ async fn nwc_request_handler( ClientNotification::Message { relay_url: _, message: _, - } => return Ok(()), - ClientNotification::Shutdown => return Ok(()), + } + | ClientNotification::Shutdown => return Ok(()), }; if let Some(expi) = event.tags.expiration() { @@ -261,20 +261,14 @@ async fn nwc_request_handler( log::debug!("relay_url:{relay_url} subscription_id:{subscription_id} {event:?}"); let mut use_nip44 = check_nip44_support(&event); - let request = decrypt_request(&event.content, &wallet_keys, &client_pubkey, &mut use_nip44)?; + let request = decrypt_request(&event.content, wallet_keys, &client_pubkey, &mut use_nip44)?; let responses = match request.params { nip47::RequestParams::PayInvoice(pay_invoice_request) => { - pay_invoice_response(plugin.clone(), pay_invoice_request, &label).await - } - nip47::RequestParams::MultiPayInvoice(multi_pay_invoice_request) => { - multi_pay_invoice(plugin.clone(), multi_pay_invoice_request, &label).await + pay_invoice_response(plugin.clone(), pay_invoice_request, label).await } nip47::RequestParams::PayKeysend(pay_keysend_request) => { - pay_keysend_response(plugin.clone(), pay_keysend_request, &label).await - } - nip47::RequestParams::MultiPayKeysend(multi_pay_keysend_request) => { - multi_pay_keysend(plugin.clone(), multi_pay_keysend_request, &label).await + pay_keysend_response(plugin.clone(), pay_keysend_request, label).await } nip47::RequestParams::MakeInvoice(make_invoice_request) => { make_invoice_response(plugin.clone(), make_invoice_request).await @@ -285,8 +279,8 @@ async fn nwc_request_handler( nip47::RequestParams::ListTransactions(list_transactions_request) => { list_transactions_response(plugin.clone(), list_transactions_request).await } - nip47::RequestParams::GetBalance => get_balance_response(plugin.clone(), &label).await, - nip47::RequestParams::GetInfo => get_info_response(plugin.clone(), &label).await, + nip47::RequestParams::GetBalance => get_balance_response(plugin.clone(), label).await, + nip47::RequestParams::GetInfo => get_info_response(plugin.clone(), label).await, nip47::RequestParams::MakeHoldInvoice(make_hold_invoice_request) => { make_hold_invoice_response(plugin.clone(), make_hold_invoice_request).await } @@ -299,7 +293,7 @@ async fn nwc_request_handler( }; for (response, id) in responses { let content = - match encrypt_response_content(&response, &wallet_keys, &client_pubkey, use_nip44) { + match encrypt_response_content(&response, wallet_keys, &client_pubkey, use_nip44) { Ok(o) => o, Err(e) => { log::warn!("{e}"); @@ -308,7 +302,7 @@ async fn nwc_request_handler( }; let response_event = - match build_response_event(event.id, content, &wallet_keys, client_pubkey, id) { + match build_response_event(event.id, content, wallet_keys, client_pubkey, id) { Ok(o) => o, Err(e) => { log::warn!("Error signing reponse event! {e}"); diff --git a/src/nwc_keysend.rs b/src/nwc_keysend.rs index fbbe1a0..bc49ec2 100644 --- a/src/nwc_keysend.rs +++ b/src/nwc_keysend.rs @@ -1,4 +1,4 @@ -use std::{str::FromStr, time::Duration}; +use std::str::FromStr; use cln_plugin::Plugin; use cln_rpc::{ @@ -6,7 +6,6 @@ use cln_rpc::{ primitives::{Amount, PublicKey, TlvEntry, TlvStream}, }; use nostr::nips::nip47; -use tokio::time; use crate::{ structs::PluginState, @@ -149,36 +148,3 @@ async fn pay_keysend( }, } } - -pub async fn multi_pay_keysend( - plugin: Plugin, - params: nip47::MultiPayKeysendRequest, - label: &str, -) -> Vec<(nip47::Response, Option)> { - let mut responses = Vec::new(); - for pay in params.keysends { - let result = pay_keysend(plugin.clone(), pay.clone(), label).await; - let id = if let Some(i) = pay.id { i } else { pay.pubkey }; - let response_res = match result { - Ok(resp) => ( - nip47::Response { - result_type: nip47::Method::MultiPayKeysend, - error: None, - result: Some(nip47::ResponseResult::MultiPayKeysend(resp)), - }, - Some(id), - ), - Err(e) => ( - nip47::Response { - result_type: nip47::Method::MultiPayKeysend, - error: Some(e), - result: None, - }, - Some(id), - ), - }; - responses.push(response_res); - time::sleep(Duration::from_millis(100)).await; - } - responses -} diff --git a/src/nwc_lookups.rs b/src/nwc_lookups.rs index cef844c..5b5090c 100644 --- a/src/nwc_lookups.rs +++ b/src/nwc_lookups.rs @@ -275,7 +275,7 @@ async fn lookup_invoice( let state = match hold_invoice.state() { hold::InvoiceState::Unpaid => nip47::TransactionState::Pending, - hold::InvoiceState::Accepted => nip47::TransactionState::Pending, // TODO ACCEPTED STATE + hold::InvoiceState::Accepted => nip47::TransactionState::Accepted, hold::InvoiceState::Paid => nip47::TransactionState::Settled, hold::InvoiceState::Cancelled => nip47::TransactionState::Expired, }; @@ -462,7 +462,7 @@ async fn list_transactions( let state = match hold_invoice.state() { hold::InvoiceState::Unpaid => nip47::TransactionState::Pending, - hold::InvoiceState::Accepted => nip47::TransactionState::Pending, // TODO: ACCEPTED STATE + hold::InvoiceState::Accepted => nip47::TransactionState::Accepted, hold::InvoiceState::Paid => nip47::TransactionState::Settled, hold::InvoiceState::Cancelled => nip47::TransactionState::Expired, }; diff --git a/src/nwc_pay.rs b/src/nwc_pay.rs index 87b2733..a313720 100644 --- a/src/nwc_pay.rs +++ b/src/nwc_pay.rs @@ -1,5 +1,3 @@ -use std::time::Duration; - use cln_plugin::Plugin; use cln_rpc::{ model::{ @@ -11,7 +9,6 @@ use cln_rpc::{ RpcError, }; use nostr::nips::nip47; -use tokio::time; use crate::{ structs::{NwcStore, PluginState, NOT_INV_ERR}, @@ -380,35 +377,3 @@ async fn pay_with_legacy_full( ) .await } - -pub async fn multi_pay_invoice( - plugin: Plugin, - params: nip47::MultiPayInvoiceRequest, - label: &str, -) -> Vec<(nip47::Response, Option)> { - let mut responses = Vec::new(); - for pay in params.invoices { - let result = pay_invoice(plugin.clone(), pay, label).await; - let response_res = match result { - Ok((resp, id)) => ( - nip47::Response { - result_type: nip47::Method::MultiPayInvoice, - error: None, - result: Some(nip47::ResponseResult::MultiPayInvoice(resp)), - }, - id, - ), - Err((e, id)) => ( - nip47::Response { - result_type: nip47::Method::MultiPayInvoice, - error: Some(e), - result: None, - }, - id, - ), - }; - responses.push(response_res); - time::sleep(Duration::from_millis(100)).await; - } - responses -} diff --git a/tests/test_cln-nip47.py b/tests/test_cln-nip47.py index 6caba46..03210a9 100755 --- a/tests/test_cln-nip47.py +++ b/tests/test_cln-nip47.py @@ -15,7 +15,6 @@ from pyln.testing.utils import RpcError, wait_for, TIMEOUT from util import generate_random_label, get_plugin, get_hold # noqa: F401 from nostr_sdk import ( - Alphabet, Client, RelayUrl, EventBuilder, @@ -34,7 +33,6 @@ from nostr_sdk import ( Nwc, PayInvoiceRequest, PayKeysendRequest, - SingleLetterTag, Tag, TagKind, Method, @@ -223,9 +221,7 @@ async def test_get_info(nostr_relay, node_factory, get_plugin): # noqa: F811 Method.GET_BALANCE, Method.GET_INFO, Method.PAY_INVOICE, - Method.MULTI_PAY_INVOICE, Method.PAY_KEYSEND, - Method.MULTI_PAY_KEYSEND, ] assert get_info.network == "regtest" assert get_info.notifications == ["payment_received", "payment_sent"] @@ -255,9 +251,7 @@ async def test_get_info(nostr_relay, node_factory, get_plugin): # noqa: F811 Method.GET_BALANCE, Method.GET_INFO, Method.PAY_INVOICE, - Method.MULTI_PAY_INVOICE, Method.PAY_KEYSEND, - Method.MULTI_PAY_KEYSEND, ] assert get_info.network == "regtest" assert get_info.notifications == [] @@ -265,7 +259,7 @@ async def test_get_info(nostr_relay, node_factory, get_plugin): # noqa: F811 assert ( info_event.content() - == "make_invoice lookup_invoice list_transactions get_balance get_info pay_invoice multi_pay_invoice pay_keysend multi_pay_keysend" + == "make_invoice lookup_invoice list_transactions get_balance get_info pay_invoice pay_keysend" ) assert ( info_event.tags().find(TagKind.UNKNOWN("encryption")).content() @@ -450,99 +444,6 @@ async def test_pay_keysend(nostr_relay, node_factory, get_plugin): # noqa: F811 ) -@pytest.mark.asyncio -async def test_multi_keysend(nostr_relay, node_factory, get_plugin): # noqa: F811 - url = nostr_relay - l1, l2, l3 = node_factory.line_graph( - 3, - wait_for_announce=True, - opts=[ - { - "log-level": "debug", - "plugin": get_plugin, - "nip47-relays": url, - "broken_log": r"Relay receiver exited with error|Connection failed", - }, - {"log-level": "debug"}, - {"log-level": "debug"}, - ], - ) - uri_res = l1.rpc.call("nip47-create", ["test1", 3010]) - uri_str = uri_res["uri"] - client_pubkey = PublicKey.parse(uri_res["clientkey_public"]) - LOGGER.info(uri_str) - uri = NostrWalletConnectUri.parse(uri_str) - content = { - "method": "multi_pay_keysend", - "params": { - "keysends": [ - {"id": "4da52c32a1", "pubkey": l2.info["id"], "amount": 1000}, - {"id": "3da52c32a1", "pubkey": l3.info["id"], "amount": 2000}, - ], - }, - } - content = json.dumps(content) - signer = NostrSigner.keys(Keys(uri.secret())) - encrypted_content = await signer.nip04_encrypt(uri.public_key(), content) - event = ( - await EventBuilder(Kind(23194), encrypted_content) - .tags([Tag.public_key(uri.public_key())]) - .sign(signer) - ) - client = Client(signer) - await client.add_relay(RelayUrl.parse(url)) - await client.connect() - (responses1, _res) = await fetch_event_responses( - client, client_pubkey, 23195, client.send_event(event), 2 - ) - - content = { - "method": "multi_pay_keysend", - "params": { - "keysends": [ - {"id": "5da52c32a1", "pubkey": l2.info["id"], "amount": 5}, - {"id": "2da52c32a1", "pubkey": l3.info["id"], "amount": 5}, - ], - }, - } - content = json.dumps(content) - encrypted_content = await signer.nip04_encrypt(uri.public_key(), content) - event = ( - await EventBuilder(Kind(23194), encrypted_content) - .tags([Tag.public_key(uri.public_key())]) - .sign(signer) - ) - (responses2, _res) = await fetch_event_responses( - client, client_pubkey, 23195, client.send_event(event), 2 - ) - - reponses = responses1 + responses2 - - error_events = [] - success_events = [] - for event in reponses: - LOGGER.info(event) - assert event.tags().find( - TagKind.SINGLE_LETTER(SingleLetterTag.lowercase(Alphabet.D)) - ) - content = await signer.nip04_decrypt(uri.public_key(), event.content()) - content = json.loads(content) - if "result" in content and content["result"] is not None: - success_events.append(content) - if "error" in content and content["error"] is not None: - error_events.append(content) - - assert len(success_events) == 3 - assert len(error_events) == 1 - for content in success_events: - assert content["result_type"] == "multi_pay_keysend" - assert content["result"]["preimage"] is not None - for content in error_events: - assert content["result_type"] == "multi_pay_keysend" - assert content["error"]["message"] == "Payment exceeds budget!" - assert content["error"]["code"] == "QUOTA_EXCEEDED" - - @pytest.mark.asyncio async def test_lookup_invoice(nostr_relay, node_factory, get_plugin): # noqa: F811 url = nostr_relay @@ -1104,91 +1005,6 @@ async def test_pay_invoice(nostr_relay, node_factory, get_plugin): # noqa: F811 ) -@pytest.mark.asyncio -async def test_multi_pay(nostr_relay, node_factory, get_plugin): # noqa: F811 - url = nostr_relay - l1, l2 = node_factory.line_graph( - 2, - wait_for_announce=True, - opts=[ - { - "log-level": "debug", - "plugin": get_plugin, - "nip47-relays": url, - "broken_log": r"Relay receiver exited with error|Connection failed", - }, - {"log-level": "debug"}, - ], - ) - uri_res = l1.rpc.call("nip47-create", ["test1", 30000]) - uri_str = uri_res["uri"] - client_pubkey = PublicKey.parse(uri_res["clientkey_public"]) - LOGGER.info(uri_str) - uri = NostrWalletConnectUri.parse(uri_str) - invoice1 = l2.rpc.call( - "invoice", - {"label": generate_random_label(), "description": "test1", "amount_msat": 3000}, - ) - invoice2 = l2.rpc.call( - "invoice", - {"label": generate_random_label(), "description": "test2", "amount_msat": 4000}, - ) - invoice3 = l2.rpc.call( - "invoice", - { - "label": generate_random_label(), - "description": "test3", - "amount_msat": 23001, - }, - ) - content = { - "method": "multi_pay_invoice", - "params": { - "invoices": [ - {"id": "4da52c32a1", "invoice": invoice1["bolt11"]}, - {"id": "3da52c32a1", "invoice": invoice2["bolt11"]}, - {"id": "af3g2k2o11", "invoice": invoice3["bolt11"]}, - ], - }, - } - content = json.dumps(content) - signer = NostrSigner.keys(Keys(uri.secret())) - encrypted_content = await signer.nip44_encrypt(uri.public_key(), content) - request_event = ( - await EventBuilder(Kind(23194), encrypted_content) - .tags([Tag.public_key(uri.public_key())]) - .sign(signer) - ) - client = Client(signer) - await client.add_relay(RelayUrl.parse(url)) - await client.connect() - - (responses, _res) = await fetch_event_responses( - client, client_pubkey, 23195, client.send_event(request_event), 3 - ) - - success_pays = [] - error_pays = [] - for response in responses: - d_tag = response.tags().find( - TagKind.SINGLE_LETTER(SingleLetterTag.lowercase(Alphabet.D)) - ) - content = await signer.nip44_decrypt(uri.public_key(), response.content()) - content = json.loads(content) - assert content["result_type"] == "multi_pay_invoice" - if "result" in content and content["result"] is not None: - assert d_tag is not None - assert content["result"]["preimage"] is not None - success_pays.append(content) - if "error" in content and content["error"] is not None: - assert d_tag.content() == "af3g2k2o11" - assert content["error"]["code"] == "QUOTA_EXCEEDED" - assert content["error"]["message"] == "Payment exceeds budget!" - error_pays.append(content) - assert len(success_pays) == 2 - assert len(error_pays) == 1 - - @pytest.mark.asyncio async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811 url = nostr_relay @@ -1408,16 +1224,14 @@ async def test_budget_command(nostr_relay, node_factory, get_plugin): # noqa: F Method.GET_BALANCE, Method.GET_INFO, Method.PAY_INVOICE, - Method.MULTI_PAY_INVOICE, Method.PAY_KEYSEND, - Method.MULTI_PAY_KEYSEND, ] info_event = await fetch_info_event(client, uri) assert ( info_event.content() - == "make_invoice lookup_invoice list_transactions get_balance get_info pay_invoice multi_pay_invoice pay_keysend multi_pay_keysend notifications" + == "make_invoice lookup_invoice list_transactions get_balance get_info pay_invoice pay_keysend notifications" ) assert ( info_event.tags().find(TagKind.UNKNOWN("encryption")).content() @@ -1974,7 +1788,7 @@ async def test_hold_invoice( info_event = await fetch_info_event(client, uri) assert ( info_event.content() - == "make_invoice lookup_invoice list_transactions get_balance get_info pay_invoice multi_pay_invoice pay_keysend multi_pay_keysend make_hold_invoice cancel_hold_invoice settle_hold_invoice notifications" + == "make_invoice lookup_invoice list_transactions get_balance get_info pay_invoice pay_keysend make_hold_invoice cancel_hold_invoice settle_hold_invoice notifications" ) assert ( info_event.tags().find(TagKind.UNKNOWN("encryption")).content()