removed multi_* methods

This commit is contained in:
daywalker90 2026-02-24 16:18:46 +01:00 committed by daywalker90
parent 78ac7cac05
commit 6690c9d49d
8 changed files with 23 additions and 287 deletions

View file

@ -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

View file

@ -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``

View file

@ -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,

View file

@ -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<PluginState>,
label: &String,
label: &str,
wallet_keys: &Keys,
client_pubkey: PublicKey,
) -> Result<(), Box<dyn std::error::Error>> {
@ -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}");

View file

@ -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<PluginState>,
params: nip47::MultiPayKeysendRequest,
label: &str,
) -> Vec<(nip47::Response, Option<String>)> {
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
}

View file

@ -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,
};

View file

@ -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<PluginState>,
params: nip47::MultiPayInvoiceRequest,
label: &str,
) -> Vec<(nip47::Response, Option<String>)> {
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
}

View file

@ -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()