pay: deprecate pay and paystatus.
What changed, and why it matters
This commit is a routine API deprecation notice. It marks the old 'pay' and 'paystatus' JSON-RPC commands as deprecated and tells users to switch to the newer 'xpay' and 'listpays' commands. The commands still work if a node operator explicitly allows deprecated APIs. There is no security bug being fixed here.
No security action required. Node operators and integrators should plan migration from 'pay'/'paystatus' to 'xpay'/'listpays' before v27.03, when 'pay' is scheduled to be replaced by 'xpay'.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change adds deprecation metadata to the ‘pay’ and ‘paystatus’ plugin commands in plugins/pay.c and updates documentation/schema cross-references to point to xpay/listpays. Tests that specifically exercise the old commands are updated to enable ‘allow-deprecated-apis’. No vulnerability, crash, memory-safety issue, or authentication bypass is present in the diff.
Changed components
plugins/pay.cdoc/schemas/pay.jsondoc/schemas/xpay.jsondoc/developers-guide/deprecated-features.mdtests/test_pay.pytests/test_plugin.pytests/test_xpay.pyInspect captured patch +67 / −55
diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json
index 7841b7cb..270dfa88 100644
--- a/contrib/msggen/msggen/schema.json
+++ b/contrib/msggen/msggen/schema.json
@@ -34,7 +34,7 @@
"Rusty Russell [rusty@rustcorp.com.au](mailto:rusty@rustcorp.com.au) is mainly responsible."
],
"see_also": [
- "lightning-pay(7)"
+ "lightning-xpay(7)"
],
"resources": [
"Main web site: [https://github.com/ElementsProject/lightning](https://github.com/ElementsProject/lightning)"
@@ -11084,7 +11084,7 @@
"Rusty Russell [rusty@rustcorp.com.au](mailto:rusty@rustcorp.com.au) is mainly responsible."
],
"see_also": [
- "lightning-pay(7)",
+ "lightning-xpay(7)",
"lightning-offer(7)",
"lightning-fetchinvoice(7)",
"lightning-sendinvoice(7)",
@@ -11966,7 +11966,7 @@
"see_also": [
"lightning-listpays(7)",
"lightning-listsendpays(7)",
- "lightning-paystatus(7)"
+ "lightning-xpay(7)"
],
"resources": [
"Main web site: [https://github.com/ElementsProject/lightning](https://github.com/ElementsProject/lightning)"
@@ -14120,7 +14120,7 @@
],
"see_also": [
"lightning-sendinvoice(7)",
- "lightning-pay(7)"
+ "lightning-xpay(7)"
],
"resources": [
"Main web site: [https://github.com/ElementsProject/lightning](https://github.com/ElementsProject/lightning)"
@@ -17395,7 +17395,7 @@
"see_also": [
"lightning-listinvoices(7)",
"lightning-delinvoice(7)",
- "lightning-pay(7)"
+ "lightning-xpay(7)"
],
"resources": [
"Main web site: [https://github.com/ElementsProject/lightning](https://github.com/ElementsProject/lightning)"
@@ -24897,7 +24897,7 @@
"Rusty Russell [rusty@rustcorp.com.au](mailto:rusty@rustcorp.com.au) is mainly responsible."
],
"see_also": [
- "lightning-pay(7)",
+ "lightning-xpay(7)",
"lightning-paystatus(7)",
"lightning-listsendpays(7)"
],
@@ -30434,7 +30434,7 @@
"description": [
"NOTE: As of v26.06, xpay transparantly handles most **pay** invocations, and **pay** is deprecated.",
"",
- "The **pay** RPC command attempts to find a route to the given destination, and send the funds it asks for. .",
+ "The **pay** RPC command attempts to find a route to the given destination, and send the funds it asks for.",
"",
"The response will occur when the payment fails or succeeds. Once a payment has succeeded, calls to **pay** with the same *bolt11* will succeed immediately.",
"",
@@ -30660,6 +30660,7 @@
"Rusty Russell [rusty@rustcorp.com.au](mailto:rusty@rustcorp.com.au) is mainly responsible."
],
"see_also": [
+ "lightning-xpay(7)",
"lightning-listpays(7)",
"lightning-decode(7)",
"lightning-listinvoices(7)",
@@ -31309,7 +31310,7 @@
"Ken Sedgwick [ken@bonsai.com](mailto:ken@bonsai.com) is mainly responsible."
],
"see_also": [
- "lightning-pay(7)"
+ "lightning-xpay(7)"
],
"resources": [
"Main web site: [https://github.com/ElementsProject/lightning](https://github.com/ElementsProject/lightning)"
@@ -33422,7 +33423,7 @@
"lightning-delinvoice(7)",
"lightning-getroute(7)",
"lightning-invoice(7)",
- "lightning-pay(7)",
+ "lightning-xpay(7)",
"lightning-waitsendpay(7)"
],
"resources": [
@@ -38512,7 +38513,7 @@
],
"see_also": [
"lightning-sendpay(7)",
- "lightning-pay(7)"
+ "lightning-xpay(7)"
],
"resources": [
"Main web site: [https://github.com/ElementsProject/lightning](https://github.com/ElementsProject/lightning)"
@@ -38824,11 +38825,11 @@
"added": "v24.11",
"additionalProperties": false,
"rpc": "xpay",
- "title": "Command for sending a payment for an invoice",
+ "title": "Command for sending a payment",
"description": [
- "The **xpay** RPC command attempts to find routes to the given destination, and send the funds it asks for.",
+ "The **xpay** RPC command attempts to make the specified payment: it can look up BIP353 names (such as \u20bfrusty@blockstream.com), it can resolve simple offers (lno1...), and pay bolt11 (lnbc...) and bolt12 invoices (lni1...).",
"",
- "This plugin is simpler and more sophisticated than the older 'pay' plugin, but does not have all the same features."
+ "This plugin is both simpler and more sophisticated than the older 'pay' plugin."
],
"request": {
"required": [
@@ -38857,7 +38858,7 @@
"layers": {
"type": "array",
"description": [
- "These are askrene layers to apply in addition to xpay's own: these can alter the topology or provide additional information on the lightning network. See askrene-create-layer."
+ "These are askrene layers to apply in addition to xpay's own: these can alter the topology or provide additional information on the lightning network. This lets you exclude particular nodes or channels, or bias against them: see askrene-create-layer."
],
"items": {
"type": "string",
@@ -38968,7 +38969,7 @@
"Rusty Russell [rusty@rustcorp.com.au](mailto:rusty@rustcorp.com.au) is mainly responsible."
],
"see_also": [
- "lightning-pay(7)",
+ "lightning-listpays(7)",
"lightning-decode(7)"
],
"resources": [
diff --git a/doc/developers-guide/deprecated-features.md b/doc/developers-guide/deprecated-features.md
index b6214c27..298e2bb4 100644
--- a/doc/developers-guide/deprecated-features.md
+++ b/doc/developers-guide/deprecated-features.md
@@ -27,6 +27,8 @@ privacy:
| getroutes.next_node_id | Field | v26.06 | v27.06 | Use `node_id_out` instead (since v26.06) |
| getroutes.amount_msat | Field | v26.06 | v27.06 | Use `amount_in_msat` instead (since v26.06) |
| getroutes.delay | Field | v26.06 | v27.06 | Use `cltv_in` instead (since v26.06) |
+| pay | Command | v26.06 | v27.03 | Use the more sophisticated and effective "xpay" command instead (since v24.11). |
+| paystatus | Command | v26.06 | v27.03 | Uses internal pay structures, doesn't work with xpay, doesn't work across restarts. |
Inevitably there are features which need to change: either to be generalized, or removed when they can no longer be supported.
diff --git a/doc/schemas/addgossip.json b/doc/schemas/addgossip.json
index 232f3d7e..087f06c6 100644
--- a/doc/schemas/addgossip.json
+++ b/doc/schemas/addgossip.json
@@ -32,7 +32,7 @@
"Rusty Russell [rusty@rustcorp.com.au](mailto:rusty@rustcorp.com.au) is mainly responsible."
],
"see_also": [
- "lightning-pay(7)"
+ "lightning-xpay(7)"
],
"resources": [
"Main web site: [https://github.com/ElementsProject/lightning](https://github.com/ElementsProject/lightning)"
diff --git a/doc/schemas/decode.json b/doc/schemas/decode.json
index 022d5f02..edc6a167 100644
--- a/doc/schemas/decode.json
+++ b/doc/schemas/decode.json
@@ -3097,7 +3097,7 @@
"Rusty Russell [rusty@rustcorp.com.au](mailto:rusty@rustcorp.com.au) is mainly responsible."
],
"see_also": [
- "lightning-pay(7)",
+ "lightning-xpay(7)",
"lightning-offer(7)",
"lightning-fetchinvoice(7)",
"lightning-sendinvoice(7)",
diff --git a/doc/schemas/delpay.json b/doc/schemas/delpay.json
index 8cef968e..723e73dd 100644
--- a/doc/schemas/delpay.json
+++ b/doc/schemas/delpay.json
@@ -202,7 +202,7 @@
"see_also": [
"lightning-listpays(7)",
"lightning-listsendpays(7)",
- "lightning-paystatus(7)"
+ "lightning-xpay(7)"
],
"resources": [
"Main web site: [https://github.com/ElementsProject/lightning](https://github.com/ElementsProject/lightning)"
diff --git a/doc/schemas/fetchinvoice.json b/doc/schemas/fetchinvoice.json
index 909d7d83..a69442a3 100644
--- a/doc/schemas/fetchinvoice.json
+++ b/doc/schemas/fetchinvoice.json
@@ -199,7 +199,7 @@
],
"see_also": [
"lightning-sendinvoice(7)",
- "lightning-pay(7)"
+ "lightning-xpay(7)"
],
"resources": [
"Main web site: [https://github.com/ElementsProject/lightning](https://github.com/ElementsProject/lightning)"
diff --git a/doc/schemas/invoice.json b/doc/schemas/invoice.json
index 2462f039..396d1a86 100644
--- a/doc/schemas/invoice.json
+++ b/doc/schemas/invoice.json
@@ -195,7 +195,7 @@
"see_also": [
"lightning-listinvoices(7)",
"lightning-delinvoice(7)",
- "lightning-pay(7)"
+ "lightning-xpay(7)"
],
"resources": [
"Main web site: [https://github.com/ElementsProject/lightning](https://github.com/ElementsProject/lightning)"
diff --git a/doc/schemas/listpays.json b/doc/schemas/listpays.json
index e2917ed8..201c48f2 100644
--- a/doc/schemas/listpays.json
+++ b/doc/schemas/listpays.json
@@ -266,7 +266,7 @@
"Rusty Russell [rusty@rustcorp.com.au](mailto:rusty@rustcorp.com.au) is mainly responsible."
],
"see_also": [
- "lightning-pay(7)",
+ "lightning-xpay(7)",
"lightning-paystatus(7)",
"lightning-listsendpays(7)"
],
diff --git a/doc/schemas/pay.json b/doc/schemas/pay.json
index 8d023c5b..c8f8b45b 100644
--- a/doc/schemas/pay.json
+++ b/doc/schemas/pay.json
@@ -6,7 +6,7 @@
"description": [
"NOTE: As of v26.06, xpay transparantly handles most **pay** invocations, and **pay** is deprecated.",
"",
- "The **pay** RPC command attempts to find a route to the given destination, and send the funds it asks for. .",
+ "The **pay** RPC command attempts to find a route to the given destination, and send the funds it asks for.",
"",
"The response will occur when the payment fails or succeeds. Once a payment has succeeded, calls to **pay** with the same *bolt11* will succeed immediately.",
"",
@@ -232,6 +232,7 @@
"Rusty Russell [rusty@rustcorp.com.au](mailto:rusty@rustcorp.com.au) is mainly responsible."
],
"see_also": [
+ "lightning-xpay(7)",
"lightning-listpays(7)",
"lightning-decode(7)",
"lightning-listinvoices(7)",
diff --git a/doc/schemas/preapproveinvoice.json b/doc/schemas/preapproveinvoice.json
index a74a8895..5cc3542d 100644
--- a/doc/schemas/preapproveinvoice.json
+++ b/doc/schemas/preapproveinvoice.json
@@ -38,7 +38,7 @@
"Ken Sedgwick [ken@bonsai.com](mailto:ken@bonsai.com) is mainly responsible."
],
"see_also": [
- "lightning-pay(7)"
+ "lightning-xpay(7)"
],
"resources": [
"Main web site: [https://github.com/ElementsProject/lightning](https://github.com/ElementsProject/lightning)"
diff --git a/doc/schemas/sendpay.json b/doc/schemas/sendpay.json
index ddde2995..fc1a98ae 100644
--- a/doc/schemas/sendpay.json
+++ b/doc/schemas/sendpay.json
@@ -375,7 +375,7 @@
"lightning-delinvoice(7)",
"lightning-getroute(7)",
"lightning-invoice(7)",
- "lightning-pay(7)",
+ "lightning-xpay(7)",
"lightning-waitsendpay(7)"
],
"resources": [
diff --git a/doc/schemas/waitsendpay.json b/doc/schemas/waitsendpay.json
index ebd07167..d252b0ad 100644
--- a/doc/schemas/waitsendpay.json
+++ b/doc/schemas/waitsendpay.json
@@ -227,7 +227,7 @@
],
"see_also": [
"lightning-sendpay(7)",
- "lightning-pay(7)"
+ "lightning-xpay(7)"
],
"resources": [
"Main web site: [https://github.com/ElementsProject/lightning](https://github.com/ElementsProject/lightning)"
diff --git a/doc/schemas/xpay.json b/doc/schemas/xpay.json
index e4316c2f..474752fd 100644
--- a/doc/schemas/xpay.json
+++ b/doc/schemas/xpay.json
@@ -4,11 +4,11 @@
"added": "v24.11",
"additionalProperties": false,
"rpc": "xpay",
- "title": "Command for sending a payment for an invoice",
+ "title": "Command for sending a payment",
"description": [
- "The **xpay** RPC command attempts to find routes to the given destination, and send the funds it asks for.",
+ "The **xpay** RPC command attempts to make the specified payment: it can look up BIP353 names (such as ₿rusty@blockstream.com), it can resolve simple offers (lno1...), and pay bolt11 (lnbc...) and bolt12 invoices (lni1...).",
"",
- "This plugin is simpler and more sophisticated than the older 'pay' plugin, but does not have all the same features."
+ "This plugin is both simpler and more sophisticated than the older 'pay' plugin."
],
"request": {
"required": [
@@ -37,7 +37,7 @@
"layers": {
"type": "array",
"description": [
- "These are askrene layers to apply in addition to xpay's own: these can alter the topology or provide additional information on the lightning network. See askrene-create-layer."
+ "These are askrene layers to apply in addition to xpay's own: these can alter the topology or provide additional information on the lightning network. This lets you exclude particular nodes or channels, or bias against them: see askrene-create-layer."
],
"items": {
"type": "string",
@@ -148,7 +148,7 @@
"Rusty Russell [rusty@rustcorp.com.au](mailto:rusty@rustcorp.com.au) is mainly responsible."
],
"see_also": [
- "lightning-pay(7)",
+ "lightning-listpays(7)",
"lightning-decode(7)"
],
"resources": [
diff --git a/plugins/pay.c b/plugins/pay.c
index 5f338ed7..7e230933 100644
--- a/plugins/pay.c
+++ b/plugins/pay.c
@@ -1309,6 +1309,12 @@ static struct command_result *json_pay(struct command *cmd,
NULL))
return command_param_failed();
+ /* We can't just mark this command deprecated, as that prevents
+ * xpay from overriding (lightningd stops it first!) */
+ if (!command_deprecated_in_ok(cmd, NULL, "v26.06", "v27.03"))
+ return command_fail(cmd, JSONRPC2_METHOD_NOT_FOUND,
+ "Command \"pay\" is deprecated");
+
p = payment_new(cmd, cmd, NULL /* No parent */, global_hints, paymod_mods);
p->invstring = tal_steal(p, b11str);
p->description = tal_steal(p, description);
@@ -1544,7 +1550,8 @@ static struct command_result *handle_channel_hint_update(struct command *cmd,
static const struct plugin_command commands[] = {
{
"paystatus",
- json_paystatus
+ json_paystatus,
+ "v26.06", "v27.03",
}, {
"listpays",
json_listpays
diff --git a/tests/benchmark.py b/tests/benchmark.py
index de8ea439..4259d12c 100644
--- a/tests/benchmark.py
+++ b/tests/benchmark.py
@@ -58,7 +58,7 @@ def test_single_hop(node_factory, executor):
inv = l2.rpc.invoice(1000, 'invoice-%d' % (i), 'desc')
invoices.append((inv['payment_hash'], inv['payment_secret']))
- route = l1.rpc.getroute(l2.rpc.getinfo()['id'], 1000, 1)['route']
+ route = l1.single_route(l2.rpc.getinfo()['id'], 1000)
print("Sending payments")
start_time = time()
diff --git a/tests/test_invoices.py b/tests/test_invoices.py
index 6743d8d4..92077b65 100644
--- a/tests/test_invoices.py
+++ b/tests/test_invoices.py
@@ -777,7 +777,8 @@ def test_wait_invoices(node_factory, executor):
def test_invoice_deschash(node_factory, chainparams):
# xpay ignores description, so use real pay.
- l1, l2 = node_factory.line_graph(2, opts={'xpay-handle-pay': False})
+ l1, l2 = node_factory.line_graph(2, opts={'xpay-handle-pay': False,
+ 'allow-deprecated-apis': True})
# BOLT #11:
# * `h`: tagged field: hash of description
diff --git a/tests/test_misc.py b/tests/test_misc.py
index 3547d9bd..ab04418f 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -4878,7 +4878,7 @@ def test_preapprove_use(node_factory, bitcoind, xkeysend):
# This will fail the same way
with pytest.raises(RpcError, match='invoice was declined'):
- l2.rpc.check('pay', bolt11=inv)
+ l2.rpc.check('xpay', invstring=inv)
# Now keysend.
with pytest.raises(RpcError, match='keysend was declined'):
diff --git a/tests/test_pay.py b/tests/test_pay.py
index e33df239..c8a557e0 100644
--- a/tests/test_pay.py
+++ b/tests/test_pay.py
@@ -85,7 +85,7 @@ def test_pay(node_factory):
def test_pay_invstring(node_factory):
- l1, l2 = node_factory.line_graph(2)
+ l1, l2 = node_factory.line_graph(2, opts={'allow-deprecated-apis': True})
l1.rpc.check_request_schemas = False
inv = l2.rpc.invoice(123000, 'test_pay_invstring', 'description')['bolt11']
@@ -3866,7 +3866,7 @@ def test_mpp_adaptive(node_factory, bitcoind):
}
"""
amt = 10**7 - 1
- l1, l2, l3, l4 = node_factory.get_nodes(4)
+ l1, l2, l3, l4 = node_factory.get_nodes(4, opts={'allow-deprecated-apis': True})
l1.connect(l2)
l2.connect(l4)
@@ -5055,7 +5055,8 @@ def test_self_pay(node_factory):
"""
l1, l2 = node_factory.line_graph(2, wait_for_announce=True,
- opts={'xpay-handle-pay': False})
+ opts={'xpay-handle-pay': False,
+ 'allow-deprecated-apis': True})
inv = l1.rpc.invoice(10000, 'test', 'test')['bolt11']
l1.rpc.pay(inv)
@@ -5284,7 +5285,8 @@ def test_sendpay_grouping(node_factory, bitcoind):
@pytest.mark.flaky(reruns=2)
def test_pay_manual_exclude(node_factory, bitcoind):
l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True,
- opts={'xpay-handle-pay': False})
+ opts={'xpay-handle-pay': False,
+ 'allow-deprecated-apis': True})
l1_id = l1.rpc.getinfo()['id']
l2_id = l2.rpc.getinfo()['id']
l3_id = l3.rpc.getinfo()['id']
@@ -5744,7 +5746,8 @@ def test_pay_routehint_minhtlc(node_factory, bitcoind):
@pytest.mark.openchannel('v2')
def test_pay_partial_msat(node_factory, executor):
l1, l2, l3 = node_factory.line_graph(3,
- opts={'xpay-handle-pay': False})
+ opts={'xpay-handle-pay': False,
+ 'allow-deprecated-apis': True})
inv = l3.rpc.invoice(100000000, "inv", "inv")
@@ -6156,7 +6159,8 @@ def diamond_network(node_factory):
"""
opts = [
{'fee-per-satoshi': 0, 'fee-base': 0, # Sender
- 'xpay-handle-pay': False},
+ 'xpay-handle-pay': False,
+ 'allow-deprecated-apis': True},
{'fee-per-satoshi': 0, 'fee-base': 0}, # Low fee, but exhausted channel
{'fee-per-satoshi': 5000, 'fee-base': 0}, # Disincentivize using fw2
{'fee-per-satoshi': 0, 'fee-base': 0}, # Recipient
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index b75df23b..367b5bd0 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -4900,13 +4900,11 @@ def test_peer_storage(node_factory, bitcoind):
assert not l2.daemon.is_in_log(r'PeerStorageFailed')
-@pytest.mark.parametrize("deprecated", [False, True])
-def test_pay_plugin_notifications(node_factory, bitcoind, chainparams, deprecated):
+def test_pay_plugin_notifications(node_factory, bitcoind, chainparams):
plugin = os.path.join(os.getcwd(), 'tests/plugins/all_notifications.py')
opts = {"plugin": plugin,
- "xpay-handle-pay": False}
- if deprecated:
- opts['allow-deprecated-apis'] = True
+ "xpay-handle-pay": False,
+ "allow-deprecated-apis": True}
l1, l2, l3 = node_factory.line_graph(3, opts=[opts, {}, {}],
wait_for_announce=True)
@@ -4939,9 +4937,8 @@ def test_pay_plugin_notifications(node_factory, bitcoind, chainparams, deprecate
'enabled': True}
channel_hint_update = {'origin': 'pay',
'channel_hint_update': channel_hint_update_core}
- if deprecated:
- # pyln-client's plugin.py duplicated payload into same name as update.
- channel_hint_update['payload'] = {'channel_hint': channel_hint_update_core}
+ # pyln-client's plugin.py duplicated payload into same name as update.
+ channel_hint_update['payload'] = {'channel_hint': channel_hint_update_core}
assert data == channel_hint_update
@@ -4953,9 +4950,8 @@ def test_pay_plugin_notifications(node_factory, bitcoind, chainparams, deprecate
'bolt11': inv1['bolt11']}
success = {'origin': 'pay',
'pay_success': success_core}
- if deprecated:
- # pyln-client's plugin.py duplicated payload into same name as update.
- success['payload'] = success_core
+ # pyln-client's plugin.py duplicated payload into same name as update.
+ success['payload'] = success_core
assert data == success
inv2 = l3.rpc.invoice(10000, "second", "desc")
@@ -4969,9 +4965,8 @@ def test_pay_plugin_notifications(node_factory, bitcoind, chainparams, deprecate
failure_core = {'payment_hash': inv2['payment_hash'], 'bolt11': inv2['bolt11'], 'error': {'message': 'failed: WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS (reply from remote)'}}
failure = {'origin': 'pay',
'pay_failure': failure_core}
- if deprecated:
- # pyln-client's plugin.py duplicated payload into same name as update.
- failure['payload'] = failure_core
+ # pyln-client's plugin.py duplicated payload into same name as update.
+ failure['payload'] = failure_core
assert data == failure
diff --git a/tests/test_xpay.py b/tests/test_xpay.py
index 97938947..9373cc2f 100644
--- a/tests/test_xpay.py
+++ b/tests/test_xpay.py
@@ -400,7 +400,8 @@ def test_xpay_partial_msat(node_factory, executor):
def test_xpay_takeover(node_factory, executor):
l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True,
- opts={'xpay-handle-pay': True})
+ opts={'xpay-handle-pay': True,
+ 'allow-deprecated-apis': True})
# Simple bolt11/bolt12 payment.
inv = l3.rpc.invoice(100000, "test_xpay_takeover1", "test_xpay_takeover1")['bolt11']
Why this scored 20/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.