Skip to content

Commit ce2a618

Browse files
committed
recvpsbt: New onchain command for PSBT output
ChangeLog-Added: New `addoutput` command for creating a PSBT that can receive funds to the on-chain wallet.
1 parent 9c95761 commit ce2a618

File tree

8 files changed

+239
-0
lines changed

8 files changed

+239
-0
lines changed

contrib/pyln-client/pyln/client/lightning.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,17 @@ def fundpsbt(self, satoshi, feerate, startweight, minconf=None, reserve=None, lo
15231523
}
15241524
return self.call("fundpsbt", payload)
15251525

1526+
def newoutput(self, satoshi, initialpsbt=None, locktime=None):
1527+
"""
1528+
Create a PSBT with an output of amount satoshi leading to the on-chain wallet
1529+
"""
1530+
payload = {
1531+
"satoshi": satoshi,
1532+
"initialpsbt": initialpsbt,
1533+
"locktime": locktime,
1534+
}
1535+
return self.call("newoutput", payload)
1536+
15261537
def utxopsbt(self, satoshi, feerate, startweight, utxos, reserve=None, reservedok=False, locktime=None, min_witness_weight=None, excess_as_change=False):
15271538
"""
15281539
Create a PSBT with given inputs, to give an output of satoshi.

doc/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ MANPAGES := doc/lightning-cli.1 \
5050
doc/lightning-fundchannel_complete.7 \
5151
doc/lightning-fundchannel_cancel.7 \
5252
doc/lightning-funderupdate.7 \
53+
doc/lightning-newoutput.7 \
5354
doc/lightning-fundpsbt.7 \
5455
doc/lightning-getroute.7 \
5556
doc/lightning-hsmtool.8 \

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Core Lightning Documentation
8585
lightning-multifundchannel <lightning-multifundchannel.7.md>
8686
lightning-multiwithdraw <lightning-multiwithdraw.7.md>
8787
lightning-newaddr <lightning-newaddr.7.md>
88+
lightning-newoutput <lightning-newoutput.7.md>
8889
lightning-notifications <lightning-notifications.7.md>
8990
lightning-offer <lightning-offer.7.md>
9091
lightning-openchannel_abort <lightning-openchannel_abort.7.md>

doc/lightning-newoutput.7.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
lightning-newoutput -- Command to populate PSBT outputs from the wallet
2+
================================================================
3+
4+
SYNOPSIS
5+
--------
6+
7+
**newoutput** *satoshi* [*initialpsbt*] [*locktime*]
8+
9+
DESCRIPTION
10+
-----------
11+
12+
`newoutput` is a low-level RPC command which creates or modifies a PSBT
13+
by adding a single output of amount *satoshi*.
14+
15+
This is used to receive funds into the on-chain wallet interactively
16+
using PSBTs.
17+
18+
*satoshi* is the satoshi value of the output. It can
19+
be a whole number, a whole number ending in *sat*, a whole number
20+
ending in *000msat*, or a number with 1 to 8 decimal places ending in
21+
*btc*.
22+
23+
*initialpsbt* is a PSBT to add the output to. If not speciifed, a PSBT
24+
will be created automatically.
25+
26+
*locktime* is an optional locktime: if not set, it is set to a recent
27+
block height (if no initial psbt is specified).
28+
29+
EXAMPLE USAGE
30+
-------------
31+
32+
Here is a command to make a PSBT with a 100,000 sat output that leads
33+
to the on-chain wallet.
34+
```shell
35+
lightning-cli newoutput 100000sat
36+
```
37+
38+
RETURN VALUE
39+
------------
40+
41+
[comment]: # (GENERATE-FROM-SCHEMA-START)
42+
On success, an object is returned, containing:
43+
44+
- **psbt** (string): Unsigned PSBT which fulfills the parameters given
45+
- **estimated\_added\_weight** (u32): The estimated weight of the added output
46+
- **outnum** (u32): The 0-based number where the output was placed
47+
48+
[comment]: # (GENERATE-FROM-SCHEMA-END)
49+
50+
AUTHOR
51+
------
52+
53+
@dusty\_daemon
54+
55+
SEE ALSO
56+
--------
57+
58+
lightning-fundpsbt(7), lightning-utxopsbt(7)
59+
60+
RESOURCES
61+
---------
62+
63+
Main web site: <https://github.com/ElementsProject/lightning>
64+
65+
[comment]: # ( SHA256STAMP:fad04aa277b18392570cc1ba6302506dadc71d1e3844720cb5c1b74812d731f2)

doc/schemas/newoutput.request.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"type": "object",
4+
"additionalProperties": false,
5+
"required": [
6+
"satoshi"
7+
],
8+
"added": "v23.08.1",
9+
"properties": {
10+
"satoshi": {
11+
"type": "msat"
12+
},
13+
"locktime": {
14+
"type": "u32"
15+
},
16+
"initialpsbt": {
17+
"type": "string",
18+
"description": "the (optional) base 64 encoded PSBT to begin with. If not specified, one will be generated automatically"
19+
}
20+
}
21+
}

doc/schemas/newoutput.schema.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"type": "object",
4+
"additionalProperties": false,
5+
"required": [
6+
"psbt",
7+
"estimated_added_weight",
8+
"outnum"
9+
],
10+
"added": "v23.08.1",
11+
"properties": {
12+
"psbt": {
13+
"type": "string",
14+
"description": "Unsigned PSBT which fulfills the parameters given"
15+
},
16+
"estimated_added_weight": {
17+
"type": "u32",
18+
"description": "The estimated weight of the added output"
19+
},
20+
"outnum": {
21+
"type": "u32",
22+
"description": "The 0-based number where the output was placed"
23+
}
24+
}
25+
}

tests/test_wallet.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,33 @@ def test_fundpsbt(node_factory, bitcoind, chainparams):
607607
l1.rpc.fundpsbt(amount // 2, feerate, 0)
608608

609609

610+
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
611+
def test_newoutput(node_factory, bitcoind, chainparams):
612+
amount1 = 1000000
613+
amount2 = 3333333
614+
locktime = 111
615+
l1 = node_factory.get_node()
616+
617+
result = l1.rpc.newoutput(amount1, locktime=locktime)
618+
assert result['outnum'] == 0
619+
620+
psbt_info = bitcoind.rpc.decodepsbt(l1.rpc.setpsbtversion(result['psbt'], 0)['psbt'])
621+
622+
assert len(psbt_info['tx']['vout']) == 1
623+
assert psbt_info['tx']['vout'][0]['n'] == result['outnum']
624+
assert psbt_info['tx']['vout'][0]['value'] * 100000000 == amount1
625+
assert psbt_info['tx']['locktime'] == locktime
626+
627+
result = l1.rpc.newoutput(amount2, result['psbt'])
628+
n = result['outnum']
629+
630+
psbt_info = bitcoind.rpc.decodepsbt(l1.rpc.setpsbtversion(result['psbt'], 0)['psbt'])
631+
632+
assert len(psbt_info['tx']['vout']) == 2
633+
assert psbt_info['tx']['vout'][n]['value'] * 100000000 == amount2
634+
assert psbt_info['tx']['vout'][n]['n'] == result['outnum']
635+
636+
610637
def test_utxopsbt(node_factory, bitcoind, chainparams):
611638
amount = 1000000
612639
l1 = node_factory.get_node()

wallet/reservation.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,94 @@ static const struct json_command fundpsbt_command = {
654654
};
655655
AUTODATA(json_command, &fundpsbt_command);
656656

657+
static struct command_result *json_newoutput(struct command *cmd,
658+
const char *buffer,
659+
const jsmntok_t *obj UNNEEDED,
660+
const jsmntok_t *params)
661+
{
662+
struct json_stream *response;
663+
struct amount_sat *amount;
664+
struct wally_psbt *psbt;
665+
u32 *locktime;
666+
ssize_t outnum;
667+
u32 weight;
668+
struct pubkey pubkey;
669+
s64 keyidx;
670+
u8 *b32script;
671+
672+
if (!param(cmd, buffer, params,
673+
p_req("satoshi", param_sat, &amount),
674+
p_opt("initialpsbt", param_psbt, &psbt),
675+
p_opt("locktime", param_number, &locktime),
676+
NULL))
677+
return command_param_failed();
678+
679+
if (!psbt) {
680+
if (!locktime) {
681+
locktime = tal(cmd, u32);
682+
*locktime = default_locktime(cmd->ld->topology);
683+
}
684+
psbt = create_psbt(cmd, 0, 0, *locktime);
685+
}
686+
else if(locktime) {
687+
return command_fail(cmd, FUNDING_PSBT_INVALID,
688+
"Can't set locktime of an existing {initialpsbt}");
689+
}
690+
691+
if (!validate_psbt(psbt))
692+
return command_fail(cmd,
693+
FUNDING_PSBT_INVALID,
694+
"PSBT failed to validate.");
695+
696+
if (amount_sat_less(*amount, chainparams->dust_limit))
697+
return command_fail(cmd, FUND_OUTPUT_IS_DUST,
698+
"Receive amount is below dust limit (%s)",
699+
type_to_string(tmpctx,
700+
struct amount_sat,
701+
&chainparams->dust_limit));
702+
703+
/* Get a change adddress */
704+
keyidx = wallet_get_newindex(cmd->ld);
705+
if (keyidx < 0)
706+
return command_fail(cmd, LIGHTNINGD,
707+
"Failed to generate change address."
708+
" Keys exhausted.");
709+
710+
if (chainparams->is_elements) {
711+
bip32_pubkey(cmd->ld, &pubkey, keyidx);
712+
b32script = scriptpubkey_p2wpkh(tmpctx, &pubkey);
713+
} else {
714+
b32script = p2tr_for_keyidx(tmpctx, cmd->ld, keyidx);
715+
}
716+
if (!b32script) {
717+
return command_fail(cmd, LIGHTNINGD,
718+
"Failed to generate change address."
719+
" Keys generation failure");
720+
}
721+
txfilter_add_scriptpubkey(cmd->ld->owned_txfilter, b32script);
722+
723+
outnum = psbt->num_outputs;
724+
psbt_append_output(psbt, b32script, *amount);
725+
/* Add additional weight of output */
726+
weight = bitcoin_tx_output_weight(
727+
chainparams->is_elements ? BITCOIN_SCRIPTPUBKEY_P2WPKH_LEN : BITCOIN_SCRIPTPUBKEY_P2TR_LEN);
728+
729+
response = json_stream_success(cmd);
730+
json_add_psbt(response, "psbt", psbt);
731+
json_add_num(response, "estimated_added_weight", weight);
732+
json_add_num(response, "outnum", outnum);
733+
return command_success(cmd, response);
734+
}
735+
736+
static const struct json_command newoutput_command = {
737+
"newoutput",
738+
"bitcoin",
739+
json_newoutput,
740+
"Create a PSBT (or modify existing {initialpsbt}) with an output receiving {satoshi} amount.",
741+
false
742+
};
743+
AUTODATA(json_command, &newoutput_command);
744+
657745
static struct command_result *param_txout(struct command *cmd,
658746
const char *name,
659747
const char *buffer,

0 commit comments

Comments
 (0)