Merge pull request #4389 from guggero/psbt-signing
walletrpc: add new PSBT creation+signing RPCs
This commit is contained in:
commit
f8e3b41d10
@ -4,6 +4,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
@ -14,6 +17,20 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// psbtCommand is a wallet subcommand that is responsible for PSBT
|
||||||
|
// operations.
|
||||||
|
psbtCommand = cli.Command{
|
||||||
|
Name: "psbt",
|
||||||
|
Usage: "Interact with partially signed bitcoin transactions " +
|
||||||
|
"(PSBTs).",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
fundPsbtCommand,
|
||||||
|
finalizePsbtCommand,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// walletCommands will return the set of commands to enable for walletrpc
|
// walletCommands will return the set of commands to enable for walletrpc
|
||||||
// builds.
|
// builds.
|
||||||
func walletCommands() []cli.Command {
|
func walletCommands() []cli.Command {
|
||||||
@ -29,6 +46,8 @@ func walletCommands() []cli.Command {
|
|||||||
bumpCloseFeeCommand,
|
bumpCloseFeeCommand,
|
||||||
listSweepsCommand,
|
listSweepsCommand,
|
||||||
labelTxCommand,
|
labelTxCommand,
|
||||||
|
releaseOutputCommand,
|
||||||
|
psbtCommand,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -305,7 +324,6 @@ func getWaitingCloseCommitments(client lnrpc.LightningClient,
|
|||||||
|
|
||||||
var listSweepsCommand = cli.Command{
|
var listSweepsCommand = cli.Command{
|
||||||
Name: "listsweeps",
|
Name: "listsweeps",
|
||||||
Category: "On-chain",
|
|
||||||
Usage: "Lists all sweeps that have been published by our node.",
|
Usage: "Lists all sweeps that have been published by our node.",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
@ -396,3 +414,330 @@ func labelTransaction(ctx *cli.Context) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fundPsbtResponse is a struct that contains JSOn annotations for nice result
|
||||||
|
// serialization.
|
||||||
|
type fundPsbtResponse struct {
|
||||||
|
Psbt string `json:"psbt"`
|
||||||
|
ChangeOutputIndex int32 `json:"change_output_index"`
|
||||||
|
Locks []*walletrpc.UtxoLease `json:"locks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var fundPsbtCommand = cli.Command{
|
||||||
|
Name: "fund",
|
||||||
|
Usage: "Fund a Partially Signed Bitcoin Transaction (PSBT).",
|
||||||
|
ArgsUsage: "[--template_psbt=T | [--outputs=O [--inputs=I]]] " +
|
||||||
|
"[--conf_target=C | --sat_per_vbyte=S]",
|
||||||
|
Description: `
|
||||||
|
The fund command creates a fully populated PSBT that contains enough
|
||||||
|
inputs to fund the outputs specified in either the PSBT or the
|
||||||
|
--outputs flag.
|
||||||
|
|
||||||
|
If there are no inputs specified in the template (or --inputs flag),
|
||||||
|
coin selection is performed automatically. If inputs are specified, the
|
||||||
|
wallet assumes that full coin selection happened externally and it will
|
||||||
|
not add any additional inputs to the PSBT. If the specified inputs
|
||||||
|
aren't enough to fund the outputs with the given fee rate, an error is
|
||||||
|
returned.
|
||||||
|
|
||||||
|
After either selecting or verifying the inputs, all input UTXOs are
|
||||||
|
locked with an internal app ID.
|
||||||
|
|
||||||
|
The 'outputs' flag decodes addresses and the amount to send respectively
|
||||||
|
in the following JSON format:
|
||||||
|
|
||||||
|
--outputs='{"ExampleAddr": NumCoinsInSatoshis, "SecondAddr": Sats}'
|
||||||
|
|
||||||
|
The optional 'inputs' flag decodes a JSON list of UTXO outpoints as
|
||||||
|
returned by the listunspent command for example:
|
||||||
|
|
||||||
|
--inputs='["<txid1>:<output-index1>","<txid2>:<output-index2>",...]'
|
||||||
|
`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "template_psbt",
|
||||||
|
Usage: "the outputs to fund and optional inputs to " +
|
||||||
|
"spend provided in the base64 PSBT format",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "outputs",
|
||||||
|
Usage: "a JSON compatible map of destination " +
|
||||||
|
"addresses to amounts to send, must not " +
|
||||||
|
"include a change address as that will be " +
|
||||||
|
"added automatically by the wallet",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "inputs",
|
||||||
|
Usage: "an optional JSON compatible list of UTXO " +
|
||||||
|
"outpoints to use as the PSBT's inputs",
|
||||||
|
},
|
||||||
|
cli.Uint64Flag{
|
||||||
|
Name: "conf_target",
|
||||||
|
Usage: "the number of blocks that the transaction " +
|
||||||
|
"should be confirmed on-chain within",
|
||||||
|
Value: 6,
|
||||||
|
},
|
||||||
|
cli.Uint64Flag{
|
||||||
|
Name: "sat_per_vbyte",
|
||||||
|
Usage: "a manual fee expressed in sat/vbyte that " +
|
||||||
|
"should be used when creating the transaction",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: actionDecorator(fundPsbt),
|
||||||
|
}
|
||||||
|
|
||||||
|
func fundPsbt(ctx *cli.Context) error {
|
||||||
|
// Display the command's help message if there aren't any flags
|
||||||
|
// specified.
|
||||||
|
if ctx.NumFlags() == 0 {
|
||||||
|
return cli.ShowCommandHelp(ctx, "fund")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &walletrpc.FundPsbtRequest{}
|
||||||
|
|
||||||
|
// Parse template flags.
|
||||||
|
switch {
|
||||||
|
// The PSBT flag is mutally exclusive with the outputs/inputs flags.
|
||||||
|
case ctx.IsSet("template_psbt") &&
|
||||||
|
(ctx.IsSet("inputs") || ctx.IsSet("outputs")):
|
||||||
|
|
||||||
|
return fmt.Errorf("cannot set template_psbt and inputs/" +
|
||||||
|
"outputs flags at the same time")
|
||||||
|
|
||||||
|
// Use a pre-existing PSBT as the transaction template.
|
||||||
|
case len(ctx.String("template_psbt")) > 0:
|
||||||
|
psbtBase64 := ctx.String("template_psbt")
|
||||||
|
psbtBytes, err := base64.StdEncoding.DecodeString(psbtBase64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Template = &walletrpc.FundPsbtRequest_Psbt{
|
||||||
|
Psbt: psbtBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
// The user manually specified outputs and optional inputs in JSON
|
||||||
|
// format.
|
||||||
|
case len(ctx.String("outputs")) > 0:
|
||||||
|
var (
|
||||||
|
tpl = &walletrpc.TxTemplate{}
|
||||||
|
amountToAddr map[string]uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse the address to amount map as JSON now. At least one
|
||||||
|
// entry must be present.
|
||||||
|
jsonMap := []byte(ctx.String("outputs"))
|
||||||
|
if err := json.Unmarshal(jsonMap, &amountToAddr); err != nil {
|
||||||
|
return fmt.Errorf("error parsing outputs JSON: %v",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
if len(amountToAddr) == 0 {
|
||||||
|
return fmt.Errorf("at least one output must be " +
|
||||||
|
"specified")
|
||||||
|
}
|
||||||
|
tpl.Outputs = amountToAddr
|
||||||
|
|
||||||
|
// Inputs are optional.
|
||||||
|
if len(ctx.String("inputs")) > 0 {
|
||||||
|
var inputs []string
|
||||||
|
|
||||||
|
jsonList := []byte(ctx.String("inputs"))
|
||||||
|
if err := json.Unmarshal(jsonList, &inputs); err != nil {
|
||||||
|
return fmt.Errorf("error parsing inputs JSON: "+
|
||||||
|
"%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, input := range inputs {
|
||||||
|
op, err := NewProtoOutPoint(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing "+
|
||||||
|
"UTXO outpoint %d: %v", idx,
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
tpl.Inputs = append(tpl.Inputs, op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Template = &walletrpc.FundPsbtRequest_Raw{
|
||||||
|
Raw: tpl,
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("must specify either template_psbt or " +
|
||||||
|
"outputs flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse fee flags.
|
||||||
|
switch {
|
||||||
|
case ctx.IsSet("conf_target") && ctx.IsSet("sat_per_vbyte"):
|
||||||
|
return fmt.Errorf("cannot set conf_target and sat_per_vbyte " +
|
||||||
|
"at the same time")
|
||||||
|
|
||||||
|
case ctx.Uint64("conf_target") > 0:
|
||||||
|
req.Fees = &walletrpc.FundPsbtRequest_TargetConf{
|
||||||
|
TargetConf: uint32(ctx.Uint64("conf_target")),
|
||||||
|
}
|
||||||
|
|
||||||
|
case ctx.Uint64("sat_per_vbyte") > 0:
|
||||||
|
req.Fees = &walletrpc.FundPsbtRequest_SatPerVbyte{
|
||||||
|
SatPerVbyte: uint32(ctx.Uint64("sat_per_vbyte")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
walletClient, cleanUp := getWalletClient(ctx)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
response, err := walletClient.FundPsbt(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printJSON(&fundPsbtResponse{
|
||||||
|
Psbt: base64.StdEncoding.EncodeToString(
|
||||||
|
response.FundedPsbt,
|
||||||
|
),
|
||||||
|
ChangeOutputIndex: response.ChangeOutputIndex,
|
||||||
|
Locks: response.LockedUtxos,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalizePsbtResponse is a struct that contains JSON annotations for nice
|
||||||
|
// result serialization.
|
||||||
|
type finalizePsbtResponse struct {
|
||||||
|
Psbt string `json:"psbt"`
|
||||||
|
FinalTx string `json:"final_tx"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var finalizePsbtCommand = cli.Command{
|
||||||
|
Name: "finalize",
|
||||||
|
Usage: "Finalize a Partially Signed Bitcoin Transaction (PSBT).",
|
||||||
|
ArgsUsage: "funded_psbt",
|
||||||
|
Description: `
|
||||||
|
The finalize command expects a partial transaction with all inputs
|
||||||
|
and outputs fully declared and tries to sign all inputs that belong to
|
||||||
|
the wallet. Lnd must be the last signer of the transaction. That means,
|
||||||
|
if there are any unsigned non-witness inputs or inputs without UTXO
|
||||||
|
information attached or inputs without witness data that do not belong
|
||||||
|
to lnd's wallet, this method will fail. If no error is returned, the
|
||||||
|
PSBT is ready to be extracted and the final TX within to be broadcast.
|
||||||
|
|
||||||
|
This method does NOT publish the transaction after it's been finalized
|
||||||
|
successfully.
|
||||||
|
`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "funded_psbt",
|
||||||
|
Usage: "the base64 encoded PSBT to finalize",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: actionDecorator(finalizePsbt),
|
||||||
|
}
|
||||||
|
|
||||||
|
func finalizePsbt(ctx *cli.Context) error {
|
||||||
|
// Display the command's help message if we do not have the expected
|
||||||
|
// number of arguments/flags.
|
||||||
|
if ctx.NArg() != 1 && ctx.NumFlags() != 1 {
|
||||||
|
return cli.ShowCommandHelp(ctx, "finalize")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
args = ctx.Args()
|
||||||
|
psbtBase64 string
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
case ctx.IsSet("funded_psbt"):
|
||||||
|
psbtBase64 = ctx.String("funded_psbt")
|
||||||
|
case args.Present():
|
||||||
|
psbtBase64 = args.First()
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("funded_psbt argument missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
psbtBytes, err := base64.StdEncoding.DecodeString(psbtBase64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req := &walletrpc.FinalizePsbtRequest{
|
||||||
|
FundedPsbt: psbtBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
walletClient, cleanUp := getWalletClient(ctx)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
response, err := walletClient.FinalizePsbt(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printJSON(&finalizePsbtResponse{
|
||||||
|
Psbt: base64.StdEncoding.EncodeToString(response.SignedPsbt),
|
||||||
|
FinalTx: hex.EncodeToString(response.RawFinalTx),
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var releaseOutputCommand = cli.Command{
|
||||||
|
Name: "releaseoutput",
|
||||||
|
Usage: "Release an output previously locked by lnd.",
|
||||||
|
ArgsUsage: "outpoint",
|
||||||
|
Description: `
|
||||||
|
The releaseoutput command unlocks an output, allowing it to be available
|
||||||
|
for coin selection if it remains unspent.
|
||||||
|
|
||||||
|
The internal lnd app lock ID is used when releasing the output.
|
||||||
|
Therefore only UTXOs locked by the fundpsbt command can currently be
|
||||||
|
released with this command.
|
||||||
|
`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "outpoint",
|
||||||
|
Usage: "the output to unlock",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: actionDecorator(releaseOutput),
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseOutput(ctx *cli.Context) error {
|
||||||
|
// Display the command's help message if we do not have the expected
|
||||||
|
// number of arguments/flags.
|
||||||
|
if ctx.NArg() != 1 && ctx.NumFlags() != 1 {
|
||||||
|
return cli.ShowCommandHelp(ctx, "releaseoutput")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
args = ctx.Args()
|
||||||
|
outpointStr string
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
case ctx.IsSet("outpoint"):
|
||||||
|
outpointStr = ctx.String("outpoint")
|
||||||
|
case args.Present():
|
||||||
|
outpointStr = args.First()
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("outpoint argument missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
outpoint, err := NewProtoOutPoint(outpointStr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing outpoint: %v", err)
|
||||||
|
}
|
||||||
|
req := &walletrpc.ReleaseOutputRequest{
|
||||||
|
Outpoint: outpoint,
|
||||||
|
Id: walletrpc.LndInternalLockID[:],
|
||||||
|
}
|
||||||
|
|
||||||
|
walletClient, cleanUp := getWalletClient(ctx)
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
response, err := walletClient.ReleaseOutput(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printRespJSON(response)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
352
docs/psbt.md
352
docs/psbt.md
@ -1,13 +1,294 @@
|
|||||||
# PSBT
|
# PSBT
|
||||||
|
|
||||||
This document describes various use cases around the topic of Partially Signed
|
This document describes various use cases around the topic of Partially Signed
|
||||||
Bitcoin Transactions (PSBTs). Currently only channel funding is possible with
|
Bitcoin Transactions (PSBTs). `lnd`'s wallet now features a full set of PSBT
|
||||||
PSBTs but more features are planned.
|
functionality, including creating, signing and funding channels with PSBTs.
|
||||||
|
|
||||||
See [BIP174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) for
|
See [BIP174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) for
|
||||||
a full description of the PSBT format and the different _roles_ that a
|
a full description of the PSBT format and the different _roles_ that a
|
||||||
participant in a PSBT can have.
|
participant in a PSBT can have.
|
||||||
|
|
||||||
|
## Creating/funding a PSBT
|
||||||
|
|
||||||
|
The first step for every transaction that is constructed using a PSBT flow is to
|
||||||
|
select inputs (UTXOs) to fund the desired output and to add a change output that
|
||||||
|
sends the remaining funds back to the own wallet.
|
||||||
|
|
||||||
|
This `wallet psbt fund` command is very similar to `bitcoind`'s
|
||||||
|
`walletcreatefundedpsbt` command. One main difference is that you can specify a
|
||||||
|
template PSBT in the `lncli` variant that contains the output(s) and optional
|
||||||
|
inputs. Another difference is that for the `--outputs` flag, `lncli` expects the
|
||||||
|
amounts to be in satoshis instead of fractions of a bitcoin.
|
||||||
|
|
||||||
|
### Simple example: fund PSBT that sends to address
|
||||||
|
|
||||||
|
Let's start with a very simple example and assume we want to send half a coin
|
||||||
|
to the address `bcrt1qjrdns4f5zwkv29ln86plqzs092yd5fg6nsz8re`:
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ lncli wallet psbt fund --outputs='{"bcrt1qjrdns4f5zwkv29ln86plqzs092yd5fg6nsz8re":50000000}'
|
||||||
|
|
||||||
|
{
|
||||||
|
"psbt": "cHNidP8BAHECAAAAAeJQY2VLRtutKgQYFUajEKpjFfl0Uyrm6x23OumDpe/4AQAAAAD/////AkxREgEAAAAAFgAUv6pTgbKHN60CZ+RQn5yOuH6c2WiA8PoCAAAAABYAFJDbOFU0E6zFF/M+g/AKDyqI2iUaAAAAAAABAOsCAAAAAAEBbxqXgEf9DlzcqqNM610s5pL1X258ra6+KJ22etb7HAcBAAAAAAAAAAACACT0AAAAAAAiACC7U1W0iJGhQ6o7CexDh5k36V6v3256xpA9/xmB2BybTFZdDQQAAAAAFgAUKp2ThzhswyM2QHlyvmMB6tQB7V0CSDBFAiEA4Md8RIZYqFdUPsgDyomlzMJL9bJ6Ho23JGTihXtEelgCIAeNXRLyt88SOuuWFVn3IodCE4U5D6DojIHesRmikF28ASEDHYFzMEAxfmfq98eSSnZtUwb1w7mAtHG65y8qiRFNnIkAAAAAAQEfVl0NBAAAAAAWABQqnZOHOGzDIzZAeXK+YwHq1AHtXQEDBAEAAAAAAAA=",
|
||||||
|
"change_output_index": 0,
|
||||||
|
"locks": [
|
||||||
|
{
|
||||||
|
"id": "ede19a92ed321a4705f8a1cccc1d4f6182545d4bb4fae08bd5937831b7e38f98",
|
||||||
|
"outpoint": {
|
||||||
|
"txid_bytes": "e25063654b46dbad2a04181546a310aa6315f974532ae6eb1db73ae983a5eff8",
|
||||||
|
"txid_str": "f8efa583e93ab71debe62a5374f91563aa10a3461518042aaddb464b656350e2:1",
|
||||||
|
"output_index": 1
|
||||||
|
},
|
||||||
|
"expiration": 1601553408
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The first thing we notice in the response is that an outpoint was locked.
|
||||||
|
That means, the UTXO that was chosen to fund the PSBT is currently locked and
|
||||||
|
cannot be used by the internal wallet or any other RPC call. This lock will be
|
||||||
|
released automatically either after 10 minutes (timeout) or once a transaction
|
||||||
|
that spends the UTXO is published.
|
||||||
|
|
||||||
|
If we inspect the PSBT that was created, we see that the locked input was indeed
|
||||||
|
selected, the UTXO information was attached and a change output (at index 0) was
|
||||||
|
created as well:
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ bitcoin-cli decodepsbt cHNidP8BAHECAAAAAeJQY2VLRtutKgQYFUajEKpjFfl0Uyrm6x23OumDpe/4AQAAAAD/////AkxREgEAAAAAFgAUv6pTgbKHN60CZ+RQn5yOuH6c2WiA8PoCAAAAABYAFJDbOFU0E6zFF/M+g/AKDyqI2iUaAAAAAAABAOsCAAAAAAEBbxqXgEf9DlzcqqNM610s5pL1X258ra6+KJ22etb7HAcBAAAAAAAAAAACACT0AAAAAAAiACC7U1W0iJGhQ6o7CexDh5k36V6v3256xpA9/xmB2BybTFZdDQQAAAAAFgAUKp2ThzhswyM2QHlyvmMB6tQB7V0CSDBFAiEA4Md8RIZYqFdUPsgDyomlzMJL9bJ6Ho23JGTihXtEelgCIAeNXRLyt88SOuuWFVn3IodCE4U5D6DojIHesRmikF28ASEDHYFzMEAxfmfq98eSSnZtUwb1w7mAtHG65y8qiRFNnIkAAAAAAQEfVl0NBAAAAAAWABQqnZOHOGzDIzZAeXK+YwHq1AHtXQEDBAEAAAAAAAA=
|
||||||
|
{
|
||||||
|
"tx": {
|
||||||
|
"txid": "33a316d62ddf74656967754d26ea83a3cb89e03ae44578d965156d4b71b1fce7",
|
||||||
|
"hash": "33a316d62ddf74656967754d26ea83a3cb89e03ae44578d965156d4b71b1fce7",
|
||||||
|
"version": 2,
|
||||||
|
"size": 113,
|
||||||
|
"vsize": 113,
|
||||||
|
"weight": 452,
|
||||||
|
"locktime": 0,
|
||||||
|
"vin": [
|
||||||
|
{
|
||||||
|
"txid": "f8efa583e93ab71debe62a5374f91563aa10a3461518042aaddb464b656350e2",
|
||||||
|
"vout": 1,
|
||||||
|
"scriptSig": {
|
||||||
|
"asm": "",
|
||||||
|
"hex": ""
|
||||||
|
},
|
||||||
|
"sequence": 4294967295
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vout": [
|
||||||
|
{
|
||||||
|
"value": 0.17977676,
|
||||||
|
"n": 0,
|
||||||
|
"scriptPubKey": {
|
||||||
|
"asm": "0 bfaa5381b28737ad0267e4509f9c8eb87e9cd968",
|
||||||
|
"hex": "0014bfaa5381b28737ad0267e4509f9c8eb87e9cd968",
|
||||||
|
"reqSigs": 1,
|
||||||
|
"type": "witness_v0_keyhash",
|
||||||
|
"addresses": [
|
||||||
|
"bcrt1qh7498qdjsum66qn8u3gfl8ywhplfektg6mutfs"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": 0.50000000,
|
||||||
|
"n": 1,
|
||||||
|
"scriptPubKey": {
|
||||||
|
"asm": "0 90db38553413acc517f33e83f00a0f2a88da251a",
|
||||||
|
"hex": "001490db38553413acc517f33e83f00a0f2a88da251a",
|
||||||
|
"reqSigs": 1,
|
||||||
|
"type": "witness_v0_keyhash",
|
||||||
|
"addresses": [
|
||||||
|
"bcrt1qjrdns4f5zwkv29ln86plqzs092yd5fg6nsz8re"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unknown": {
|
||||||
|
},
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"witness_utxo": {
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"non_witness_utxo": {
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"sighash": "ALL"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"fee": 0.00007050
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced example: fund PSBT with manual coin selection
|
||||||
|
|
||||||
|
Let's now look at how we can implement manual coin selection by using the `fund`
|
||||||
|
command. We again want to send half a coin to
|
||||||
|
`bcrt1qjrdns4f5zwkv29ln86plqzs092yd5fg6nsz8re` but we want to select our inputs
|
||||||
|
manually.
|
||||||
|
|
||||||
|
The first step is to look at all available UTXOs and choose. To do so, we use
|
||||||
|
the `listunspent` command:
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ lncli listunspent
|
||||||
|
|
||||||
|
{
|
||||||
|
"utxos": [
|
||||||
|
{
|
||||||
|
"address_type": 0,
|
||||||
|
"address": "bcrt1qmsq36rtc6ap3m0m6jryu0ez923et6kxrv46t4w",
|
||||||
|
"amount_sat": 100000000,
|
||||||
|
"pk_script": "0014dc011d0d78d7431dbf7a90c9c7e4455472bd58c3",
|
||||||
|
"outpoint": "3597b451ff56bc901eb806e8c644a004e934b4c208679756b4cddc455c768c48:1",
|
||||||
|
"confirmations": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address_type": 0,
|
||||||
|
"address": "bcrt1q92we8pecdnpjxdjq09etuccpat2qrm2acu4256",
|
||||||
|
"amount_sat": 67984726,
|
||||||
|
"pk_script": "00142a9d9387386cc32336407972be6301ead401ed5d",
|
||||||
|
"outpoint": "f8efa583e93ab71debe62a5374f91563aa10a3461518042aaddb464b656350e2:1",
|
||||||
|
"confirmations": 24
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, we choose these two inputs and create the PSBT:
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ lncli wallet psbt fund --outputs='{"bcrt1qjrdns4f5zwkv29ln86plqzs092yd5fg6nsz8re":50000000}' \
|
||||||
|
--inputs='["3597b451ff56bc901eb806e8c644a004e934b4c208679756b4cddc455c768c48:1","f8efa583e93ab71debe62a5374f91563aa10a3461518042aaddb464b656350e2:1"]'
|
||||||
|
|
||||||
|
{
|
||||||
|
"psbt": "cHNidP8BAJoCAAAAAkiMdlxF3M20VpdnCMK0NOkEoETG6Aa4HpC8Vv9RtJc1AQAAAAAAAAAA4lBjZUtG260qBBgVRqMQqmMV+XRTKubrHbc66YOl7/gBAAAAAAAAAAACgPD6AgAAAAAWABSQ2zhVNBOsxRfzPoPwCg8qiNolGtIkCAcAAAAAFgAUuvRP5r7qAvj0egDxyX9/FH+vukgAAAAAAAEA3gIAAAAAAQEr9IZcho/gV/6fH8C8P+yhNRZP+l3YuxsyatdYcS0S6AEAAAAA/v///wLI/8+yAAAAABYAFDXoRFwgXNO5VVtVq2WpaENh6blAAOH1BQAAAAAWABTcAR0NeNdDHb96kMnH5EVUcr1YwwJHMEQCIDqugtYLp4ebJAZvOdieshLi1lLuPl2tHQG4jM4ybwEGAiBeMpCkbHBmzYvljxb1JBQyVAMuoco0xIfi+5OQdHuXaAEhAnH96NhTW09X0npE983YBsHUoMPI4U4xBtHenpZVTEqpVwAAAAEBHwDh9QUAAAAAFgAU3AEdDXjXQx2/epDJx+RFVHK9WMMBAwQBAAAAAAEA6wIAAAAAAQFvGpeAR/0OXNyqo0zrXSzmkvVfbnytrr4onbZ61vscBwEAAAAAAAAAAAIAJPQAAAAAACIAILtTVbSIkaFDqjsJ7EOHmTfpXq/fbnrGkD3/GYHYHJtMVl0NBAAAAAAWABQqnZOHOGzDIzZAeXK+YwHq1AHtXQJIMEUCIQDgx3xEhlioV1Q+yAPKiaXMwkv1snoejbckZOKFe0R6WAIgB41dEvK3zxI665YVWfcih0IThTkPoOiMgd6xGaKQXbwBIQMdgXMwQDF+Z+r3x5JKdm1TBvXDuYC0cbrnLyqJEU2ciQAAAAABAR9WXQ0EAAAAABYAFCqdk4c4bMMjNkB5cr5jAerUAe1dAQMEAQAAAAAAAA==",
|
||||||
|
"change_output_index": 1,
|
||||||
|
"locks": [
|
||||||
|
{
|
||||||
|
"id": "ede19a92ed321a4705f8a1cccc1d4f6182545d4bb4fae08bd5937831b7e38f98",
|
||||||
|
"outpoint": {
|
||||||
|
"txid_bytes": "488c765c45dccdb456976708c2b434e904a044c6e806b81e90bc56ff51b49735",
|
||||||
|
"txid_str": "3597b451ff56bc901eb806e8c644a004e934b4c208679756b4cddc455c768c48:1",
|
||||||
|
"output_index": 1
|
||||||
|
},
|
||||||
|
"expiration": 1601560626
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ede19a92ed321a4705f8a1cccc1d4f6182545d4bb4fae08bd5937831b7e38f98",
|
||||||
|
"outpoint": {
|
||||||
|
"txid_bytes": "e25063654b46dbad2a04181546a310aa6315f974532ae6eb1db73ae983a5eff8",
|
||||||
|
"txid_str": "f8efa583e93ab71debe62a5374f91563aa10a3461518042aaddb464b656350e2:1",
|
||||||
|
"output_index": 1
|
||||||
|
},
|
||||||
|
"expiration": 1601560626
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspecting this PSBT, we notice that the two inputs were chosen and a large
|
||||||
|
change change output was added at index 1:
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ bitcoin-cli decodepsbt cHNidP8BAJoCAAAAAkiMdlxF3M20VpdnCMK0NOkEoETG6Aa4HpC8Vv9RtJc1AQAAAAAAAAAA4lBjZUtG260qBBgVRqMQqmMV+XRTKubrHbc66YOl7/gBAAAAAAAAAAACgPD6AgAAAAAWABSQ2zhVNBOsxRfzPoPwCg8qiNolGtIkCAcAAAAAFgAUuvRP5r7qAvj0egDxyX9/FH+vukgAAAAAAAEA3gIAAAAAAQEr9IZcho/gV/6fH8C8P+yhNRZP+l3YuxsyatdYcS0S6AEAAAAA/v///wLI/8+yAAAAABYAFDXoRFwgXNO5VVtVq2WpaENh6blAAOH1BQAAAAAWABTcAR0NeNdDHb96kMnH5EVUcr1YwwJHMEQCIDqugtYLp4ebJAZvOdieshLi1lLuPl2tHQG4jM4ybwEGAiBeMpCkbHBmzYvljxb1JBQyVAMuoco0xIfi+5OQdHuXaAEhAnH96NhTW09X0npE983YBsHUoMPI4U4xBtHenpZVTEqpVwAAAAEBHwDh9QUAAAAAFgAU3AEdDXjXQx2/epDJx+RFVHK9WMMBAwQBAAAAAAEA6wIAAAAAAQFvGpeAR/0OXNyqo0zrXSzmkvVfbnytrr4onbZ61vscBwEAAAAAAAAAAAIAJPQAAAAAACIAILtTVbSIkaFDqjsJ7EOHmTfpXq/fbnrGkD3/GYHYHJtMVl0NBAAAAAAWABQqnZOHOGzDIzZAeXK+YwHq1AHtXQJIMEUCIQDgx3xEhlioV1Q+yAPKiaXMwkv1snoejbckZOKFe0R6WAIgB41dEvK3zxI665YVWfcih0IThTkPoOiMgd6xGaKQXbwBIQMdgXMwQDF+Z+r3x5JKdm1TBvXDuYC0cbrnLyqJEU2ciQAAAAABAR9WXQ0EAAAAABYAFCqdk4c4bMMjNkB5cr5jAerUAe1dAQMEAQAAAAAAAA==
|
||||||
|
|
||||||
|
{
|
||||||
|
"tx": {
|
||||||
|
"txid": "e62356b99c3097eaa1241ff8e39b996917e66b13e4c0ccba3698982d746c3b76",
|
||||||
|
"hash": "e62356b99c3097eaa1241ff8e39b996917e66b13e4c0ccba3698982d746c3b76",
|
||||||
|
"version": 2,
|
||||||
|
"size": 154,
|
||||||
|
"vsize": 154,
|
||||||
|
"weight": 616,
|
||||||
|
"locktime": 0,
|
||||||
|
"vin": [
|
||||||
|
{
|
||||||
|
"txid": "3597b451ff56bc901eb806e8c644a004e934b4c208679756b4cddc455c768c48",
|
||||||
|
"vout": 1,
|
||||||
|
"scriptSig": {
|
||||||
|
"asm": "",
|
||||||
|
"hex": ""
|
||||||
|
},
|
||||||
|
"sequence": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"txid": "f8efa583e93ab71debe62a5374f91563aa10a3461518042aaddb464b656350e2",
|
||||||
|
"vout": 1,
|
||||||
|
"scriptSig": {
|
||||||
|
"asm": "",
|
||||||
|
"hex": ""
|
||||||
|
},
|
||||||
|
"sequence": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vout": [
|
||||||
|
{
|
||||||
|
"value": 0.50000000,
|
||||||
|
"n": 0,
|
||||||
|
"scriptPubKey": {
|
||||||
|
"asm": "0 90db38553413acc517f33e83f00a0f2a88da251a",
|
||||||
|
"hex": "001490db38553413acc517f33e83f00a0f2a88da251a",
|
||||||
|
"reqSigs": 1,
|
||||||
|
"type": "witness_v0_keyhash",
|
||||||
|
"addresses": [
|
||||||
|
"bcrt1qjrdns4f5zwkv29ln86plqzs092yd5fg6nsz8re"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": 1.17974226,
|
||||||
|
"n": 1,
|
||||||
|
"scriptPubKey": {
|
||||||
|
"asm": "0 baf44fe6beea02f8f47a00f1c97f7f147fafba48",
|
||||||
|
"hex": "0014baf44fe6beea02f8f47a00f1c97f7f147fafba48",
|
||||||
|
"reqSigs": 1,
|
||||||
|
"type": "witness_v0_keyhash",
|
||||||
|
"addresses": [
|
||||||
|
"bcrt1qht6yle47agp03ar6qrcujlmlz3l6lwjgjv36zl"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unknown": {
|
||||||
|
},
|
||||||
|
"inputs": [
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"fee": 0.00010500
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Signing and finalizing a PSBT
|
||||||
|
|
||||||
|
Assuming we now want to sign the transaction that we created in the previous
|
||||||
|
example, we simply pass it to the `finalize` sub command of the wallet:
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ lncli wallet psbt finalize cHNidP8BAJoCAAAAAkiMdlxF3M20VpdnCMK0NOkEoETG6Aa4HpC8Vv9RtJc1AQAAAAAAAAAA4lBjZUtG260qBBgVRqMQqmMV+XRTKubrHbc66YOl7/gBAAAAAAAAAAACgPD6AgAAAAAWABSQ2zhVNBOsxRfzPoPwCg8qiNolGtIkCAcAAAAAFgAUuvRP5r7qAvj0egDxyX9/FH+vukgAAAAAAAEA3gIAAAAAAQEr9IZcho/gV/6fH8C8P+yhNRZP+l3YuxsyatdYcS0S6AEAAAAA/v///wLI/8+yAAAAABYAFDXoRFwgXNO5VVtVq2WpaENh6blAAOH1BQAAAAAWABTcAR0NeNdDHb96kMnH5EVUcr1YwwJHMEQCIDqugtYLp4ebJAZvOdieshLi1lLuPl2tHQG4jM4ybwEGAiBeMpCkbHBmzYvljxb1JBQyVAMuoco0xIfi+5OQdHuXaAEhAnH96NhTW09X0npE983YBsHUoMPI4U4xBtHenpZVTEqpVwAAAAEBHwDh9QUAAAAAFgAU3AEdDXjXQx2/epDJx+RFVHK9WMMBAwQBAAAAAAEA6wIAAAAAAQFvGpeAR/0OXNyqo0zrXSzmkvVfbnytrr4onbZ61vscBwEAAAAAAAAAAAIAJPQAAAAAACIAILtTVbSIkaFDqjsJ7EOHmTfpXq/fbnrGkD3/GYHYHJtMVl0NBAAAAAAWABQqnZOHOGzDIzZAeXK+YwHq1AHtXQJIMEUCIQDgx3xEhlioV1Q+yAPKiaXMwkv1snoejbckZOKFe0R6WAIgB41dEvK3zxI665YVWfcih0IThTkPoOiMgd6xGaKQXbwBIQMdgXMwQDF+Z+r3x5JKdm1TBvXDuYC0cbrnLyqJEU2ciQAAAAABAR9WXQ0EAAAAABYAFCqdk4c4bMMjNkB5cr5jAerUAe1dAQMEAQAAAAAAAA==
|
||||||
|
|
||||||
|
{
|
||||||
|
"psbt": "cHNidP8BAJoCAAAAAkiMdlxF3M20VpdnCMK0NOkEoETG6Aa4HpC8Vv9RtJc1AQAAAAAAAAAA4lBjZUtG260qBBgVRqMQqmMV+XRTKubrHbc66YOl7/gBAAAAAAAAAAACgPD6AgAAAAAWABSQ2zhVNBOsxRfzPoPwCg8qiNolGtIkCAcAAAAAFgAUuvRP5r7qAvj0egDxyX9/FH+vukgAAAAAAAEA3gIAAAAAAQEr9IZcho/gV/6fH8C8P+yhNRZP+l3YuxsyatdYcS0S6AEAAAAA/v///wLI/8+yAAAAABYAFDXoRFwgXNO5VVtVq2WpaENh6blAAOH1BQAAAAAWABTcAR0NeNdDHb96kMnH5EVUcr1YwwJHMEQCIDqugtYLp4ebJAZvOdieshLi1lLuPl2tHQG4jM4ybwEGAiBeMpCkbHBmzYvljxb1JBQyVAMuoco0xIfi+5OQdHuXaAEhAnH96NhTW09X0npE983YBsHUoMPI4U4xBtHenpZVTEqpVwAAAAEBHwDh9QUAAAAAFgAU3AEdDXjXQx2/epDJx+RFVHK9WMMBCGwCSDBFAiEAuiv52IX5wZlYJqqVGsQPfeQ/kneCNRD34v5yplNpuMYCIECHVUhjHPKSiWSsYEKD4JWGAyUwQHgDytA1whFOyLclASECg7PDfGE/uURta5/R42Vso6QKmVAgYMhjWlXENkE/x+QAAQDrAgAAAAABAW8al4BH/Q5c3KqjTOtdLOaS9V9ufK2uviidtnrW+xwHAQAAAAAAAAAAAgAk9AAAAAAAIgAgu1NVtIiRoUOqOwnsQ4eZN+ler99uesaQPf8Zgdgcm0xWXQ0EAAAAABYAFCqdk4c4bMMjNkB5cr5jAerUAe1dAkgwRQIhAODHfESGWKhXVD7IA8qJpczCS/Wyeh6NtyRk4oV7RHpYAiAHjV0S8rfPEjrrlhVZ9yKHQhOFOQ+g6IyB3rEZopBdvAEhAx2BczBAMX5n6vfHkkp2bVMG9cO5gLRxuucvKokRTZyJAAAAAAEBH1ZdDQQAAAAAFgAUKp2ThzhswyM2QHlyvmMB6tQB7V0BCGwCSDBFAiEAqK7FSrqWe2non0kl96yu2+gSXGPYPC7ZjzVZEMMWtpYCIGTzCDHZhJYGPrsnBWU8o0Eyd4nBa+6d037xGFcGUYJLASECORgkj75Xu8+DTh8bqYBIvNx1hSxV7VSJOwY6jam6LY8AAAA=",
|
||||||
|
"final_tx": "02000000000102488c765c45dccdb456976708c2b434e904a044c6e806b81e90bc56ff51b49735010000000000000000e25063654b46dbad2a04181546a310aa6315f974532ae6eb1db73ae983a5eff80100000000000000000280f0fa020000000016001490db38553413acc517f33e83f00a0f2a88da251ad224080700000000160014baf44fe6beea02f8f47a00f1c97f7f147fafba4802483045022100ba2bf9d885f9c1995826aa951ac40f7de43f9277823510f7e2fe72a65369b8c6022040875548631cf2928964ac604283e09586032530407803cad035c2114ec8b72501210283b3c37c613fb9446d6b9fd1e3656ca3a40a99502060c8635a55c436413fc7e402483045022100a8aec54aba967b69e89f4925f7acaedbe8125c63d83c2ed98f355910c316b696022064f30831d98496063ebb2705653ca341327789c16bee9dd37ef118570651824b0121023918248fbe57bbcf834e1f1ba98048bcdc75852c55ed54893b063a8da9ba2d8f00000000"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That final transaction can now, in theory, be broadcast. But **it is very
|
||||||
|
important** that you **do not** publish it manually if any of the involved
|
||||||
|
outputs are used to fund a channel. See
|
||||||
|
[the safety warning below](#safety-warning) to learn the reason for this.
|
||||||
|
|
||||||
## Opening a channel by using a PSBT
|
## Opening a channel by using a PSBT
|
||||||
|
|
||||||
This is a step-by-step guide on how to open a channel with `lnd` by using a PSBT
|
This is a step-by-step guide on how to open a channel with `lnd` by using a PSBT
|
||||||
@ -49,7 +330,7 @@ The new `--psbt` flag in the `openchannel` command starts an interactive dialog
|
|||||||
between `lncli` and the user. Below the command you see an example output from
|
between `lncli` and the user. Below the command you see an example output from
|
||||||
a regtest setup. Of course all values will be different.
|
a regtest setup. Of course all values will be different.
|
||||||
|
|
||||||
```bash
|
```shell script
|
||||||
$ lncli openchannel --node_key 03db1e56e5f76bc4018cf6f03d1bb98a7ae96e3f18535e929034f85e7f1ca2b8ac --local_amt 1234567 --psbt
|
$ lncli openchannel --node_key 03db1e56e5f76bc4018cf6f03d1bb98a7ae96e3f18535e929034f85e7f1ca2b8ac --local_amt 1234567 --psbt
|
||||||
|
|
||||||
Starting PSBT funding flow with pending channel ID fc7853889a04d33b8115bd79ebc99c5eea80d894a0bead40fae5a06bcbdccd3d.
|
Starting PSBT funding flow with pending channel ID fc7853889a04d33b8115bd79ebc99c5eea80d894a0bead40fae5a06bcbdccd3d.
|
||||||
@ -71,14 +352,14 @@ The command line now waits until a PSBT is entered. We'll create one in the next
|
|||||||
step. Make sure to use a new shell window/tab for the next commands and leave
|
step. Make sure to use a new shell window/tab for the next commands and leave
|
||||||
the prompt from the `openchannel` running as is.
|
the prompt from the `openchannel` running as is.
|
||||||
|
|
||||||
### 2. Use `bitcoind` to create a funding transaction
|
### 2a. Use `bitcoind` to create a funding transaction
|
||||||
|
|
||||||
The output of the last command already gave us an example command to use with
|
The output of the last command already gave us an example command to use with
|
||||||
`bitcoind`. We'll go ahead and execute it now. The meaning of this command is
|
`bitcoind`. We'll go ahead and execute it now. The meaning of this command is
|
||||||
something like "bitcoind, give me a PSBT that sends the given amount to the
|
something like "bitcoind, give me a PSBT that sends the given amount to the
|
||||||
given address, choose any input you see fit":
|
given address, choose any input you see fit":
|
||||||
|
|
||||||
```bash
|
```shell script
|
||||||
$ bitcoin-cli walletcreatefundedpsbt [] '[{"bcrt1qh33ghvgjj3ef625nl9jxz6nnrz2z9e65vsdey7w5msrklgr6rc0sv0s08q":0.01234567}]'
|
$ bitcoin-cli walletcreatefundedpsbt [] '[{"bcrt1qh33ghvgjj3ef625nl9jxz6nnrz2z9e65vsdey7w5msrklgr6rc0sv0s08q":0.01234567}]'
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -96,7 +377,7 @@ in fees. Fee estimation/calculation can be changed with parameters of the
|
|||||||
If we want to know what exactly is in this PSBT, we can look at it with the
|
If we want to know what exactly is in this PSBT, we can look at it with the
|
||||||
`decodepsbt` command:
|
`decodepsbt` command:
|
||||||
|
|
||||||
```bash
|
```shell script
|
||||||
$ bitcoin-cli decodepsbt cHNidP8BAH0CAAAAAbxLLf9+AYfqfF69QAQuETnL6cas7GDiWBZF+3xxc/Y/AAAAAAD+////AofWEgAAAAAAIgAgvGKLsRKUcp0qk/lkYWpzGJQi51RkG5J51NwHb6B6Hh+1If0jAQAAABYAFL+6THEGhybJnOkFGSRFbtCcPOG8AAAAAAABAR8wBBAkAQAAABYAFHemJ11XF7CU7WXBIJLD/qZF+6jrAAAA
|
$ bitcoin-cli decodepsbt cHNidP8BAH0CAAAAAbxLLf9+AYfqfF69QAQuETnL6cas7GDiWBZF+3xxc/Y/AAAAAAD+////AofWEgAAAAAAIgAgvGKLsRKUcp0qk/lkYWpzGJQi51RkG5J51NwHb6B6Hh+1If0jAQAAABYAFL+6THEGhybJnOkFGSRFbtCcPOG8AAAAAAABAR8wBBAkAQAAABYAFHemJ11XF7CU7WXBIJLD/qZF+6jrAAAA
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -177,13 +458,41 @@ This tells us that we got a PSBT with a big input, the channel output and a
|
|||||||
change output for the rest. Everything is there but the signatures/witness data,
|
change output for the rest. Everything is there but the signatures/witness data,
|
||||||
which is exactly what we need.
|
which is exactly what we need.
|
||||||
|
|
||||||
|
### 2b. Use `lnd` to create a funding transaction
|
||||||
|
|
||||||
|
Starting with version `v0.12.0`, `lnd` can also create PSBTs. This assumes a
|
||||||
|
scenario where one instance of `lnd` only has public keys (watch only mode) and
|
||||||
|
a secondary, hardened and firewalled `lnd` instance has the corresponding
|
||||||
|
private keys. On the watching only mode, the following command can be used to
|
||||||
|
create the funding PSBT:
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ lncli wallet psbt fund --outputs='{"bcrt1qh33ghvgjj3ef625nl9jxz6nnrz2z9e65vsdey7w5msrklgr6rc0sv0s08q":1234567}'
|
||||||
|
|
||||||
|
{
|
||||||
|
"psbt": "cHNidP8BAH0CAAAAAUiMdlxF3M20VpdnCMK0NOkEoETG6Aa4HpC8Vv9RtJc1AQAAAAD/////AofWEgAAAAAAIgAgvGKLsRKUcp0qk/lkYWpzGJQi51RkG5J51NwHb6B6Hh+X7OIFAAAAABYAFNigOB6EbCLRi+Evlv4r2yJx63NxAAAAAAABAN4CAAAAAAEBK/SGXIaP4Ff+nx/AvD/soTUWT/pd2LsbMmrXWHEtEugBAAAAAP7///8CyP/PsgAAAAAWABQ16ERcIFzTuVVbVatlqWhDYem5QADh9QUAAAAAFgAU3AEdDXjXQx2/epDJx+RFVHK9WMMCRzBEAiA6roLWC6eHmyQGbznYnrIS4tZS7j5drR0BuIzOMm8BBgIgXjKQpGxwZs2L5Y8W9SQUMlQDLqHKNMSH4vuTkHR7l2gBIQJx/ejYU1tPV9J6RPfN2AbB1KDDyOFOMQbR3p6WVUxKqVcAAAABAR8A4fUFAAAAABYAFNwBHQ1410Mdv3qQycfkRVRyvVjDAQMEAQAAAAAAAA==",
|
||||||
|
"change_output_index": 1,
|
||||||
|
"locks": [
|
||||||
|
{
|
||||||
|
"id": "ede19a92ed321a4705f8a1cccc1d4f6182545d4bb4fae08bd5937831b7e38f98",
|
||||||
|
"outpoint": {
|
||||||
|
"txid_bytes": "488c765c45dccdb456976708c2b434e904a044c6e806b81e90bc56ff51b49735",
|
||||||
|
"txid_str": "3597b451ff56bc901eb806e8c644a004e934b4c208679756b4cddc455c768c48:1",
|
||||||
|
"output_index": 1
|
||||||
|
},
|
||||||
|
"expiration": 1601562037
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### 3. Verify and sign the PSBT
|
### 3. Verify and sign the PSBT
|
||||||
|
|
||||||
Now that we have a valid PSBT that has everything but the final
|
Now that we have a valid PSBT that has everything but the final
|
||||||
signatures/witness data, we can paste it into the prompt in `lncli` that is
|
signatures/witness data, we can paste it into the prompt in `lncli` that is
|
||||||
still waiting for our input.
|
still waiting for our input.
|
||||||
|
|
||||||
```bash
|
```shell script
|
||||||
...
|
...
|
||||||
Base64 encoded PSBT: cHNidP8BAH0CAAAAAbxLLf9+AYfqfF69QAQuETnL6cas7GDiWBZF+3xxc/Y/AAAAAAD+////AofWEgAAAAAAIgAgvGKLsRKUcp0qk/lkYWpzGJQi51RkG5J51NwHb6B6Hh+1If0jAQAAABYAFL+6THEGhybJnOkFGSRFbtCcPOG8AAAAAAABAR8wBBAkAQAAABYAFHemJ11XF7CU7WXBIJLD/qZF+6jrAAAA
|
Base64 encoded PSBT: cHNidP8BAH0CAAAAAbxLLf9+AYfqfF69QAQuETnL6cas7GDiWBZF+3xxc/Y/AAAAAAD+////AofWEgAAAAAAIgAgvGKLsRKUcp0qk/lkYWpzGJQi51RkG5J51NwHb6B6Hh+1If0jAQAAABYAFL+6THEGhybJnOkFGSRFbtCcPOG8AAAAAAABAR8wBBAkAQAAABYAFHemJ11XF7CU7WXBIJLD/qZF+6jrAAAA
|
||||||
|
|
||||||
@ -200,7 +509,7 @@ perhaps `bitcoind` would only know the public keys and couldn't sign for the
|
|||||||
transaction itself. Again, this is only an example and can't reflect all
|
transaction itself. Again, this is only an example and can't reflect all
|
||||||
real-world use cases.
|
real-world use cases.
|
||||||
|
|
||||||
```bash
|
```shell script
|
||||||
$ bitcoin-cli walletprocesspsbt cHNidP8BAH0CAAAAAbxLLf9+AYfqfF69QAQuETnL6cas7GDiWBZF+3xxc/Y/AAAAAAD+////AofWEgAAAAAAIgAgvGKLsRKUcp0qk/lkYWpzGJQi51RkG5J51NwHb6B6Hh+1If0jAQAAABYAFL+6THEGhybJnOkFGSRFbtCcPOG8AAAAAAABAR8wBBAkAQAAABYAFHemJ11XF7CU7WXBIJLD/qZF+6jrAAAA
|
$ bitcoin-cli walletprocesspsbt cHNidP8BAH0CAAAAAbxLLf9+AYfqfF69QAQuETnL6cas7GDiWBZF+3xxc/Y/AAAAAAD+////AofWEgAAAAAAIgAgvGKLsRKUcp0qk/lkYWpzGJQi51RkG5J51NwHb6B6Hh+1If0jAQAAABYAFL+6THEGhybJnOkFGSRFbtCcPOG8AAAAAAABAR8wBBAkAQAAABYAFHemJ11XF7CU7WXBIJLD/qZF+6jrAAAA
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -209,6 +518,19 @@ $ bitcoin-cli walletprocesspsbt cHNidP8BAH0CAAAAAbxLLf9+AYfqfF69QAQuETnL6cas7GDi
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you are using the two `lnd` node model as described in
|
||||||
|
[2b](#2b-use-lnd-to-create-a-funding-transaction), you can achieve the same
|
||||||
|
result with the following command:
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ lncli wallet psbt finalize cHNidP8BAH0CAAAAAUiMdlxF3M20VpdnCMK0NOkEoETG6Aa4HpC8Vv9RtJc1AQAAAAD/////AofWEgAAAAAAIgAgvGKLsRKUcp0qk/lkYWpzGJQi51RkG5J51NwHb6B6Hh+X7OIFAAAAABYAFNigOB6EbCLRi+Evlv4r2yJx63NxAAAAAAABAN4CAAAAAAEBK/SGXIaP4Ff+nx/AvD/soTUWT/pd2LsbMmrXWHEtEugBAAAAAP7///8CyP/PsgAAAAAWABQ16ERcIFzTuVVbVatlqWhDYem5QADh9QUAAAAAFgAU3AEdDXjXQx2/epDJx+RFVHK9WMMCRzBEAiA6roLWC6eHmyQGbznYnrIS4tZS7j5drR0BuIzOMm8BBgIgXjKQpGxwZs2L5Y8W9SQUMlQDLqHKNMSH4vuTkHR7l2gBIQJx/ejYU1tPV9J6RPfN2AbB1KDDyOFOMQbR3p6WVUxKqVcAAAABAR8A4fUFAAAAABYAFNwBHQ1410Mdv3qQycfkRVRyvVjDAQMEAQAAAAAAAA==
|
||||||
|
|
||||||
|
{
|
||||||
|
"psbt": "cHNidP8BAH0CAAAAAUiMdlxF3M20VpdnCMK0NOkEoETG6Aa4HpC8Vv9RtJc1AQAAAAD/////AofWEgAAAAAAIgAgvGKLsRKUcp0qk/lkYWpzGJQi51RkG5J51NwHb6B6Hh+X7OIFAAAAABYAFNigOB6EbCLRi+Evlv4r2yJx63NxAAAAAAABAN4CAAAAAAEBK/SGXIaP4Ff+nx/AvD/soTUWT/pd2LsbMmrXWHEtEugBAAAAAP7///8CyP/PsgAAAAAWABQ16ERcIFzTuVVbVatlqWhDYem5QADh9QUAAAAAFgAU3AEdDXjXQx2/epDJx+RFVHK9WMMCRzBEAiA6roLWC6eHmyQGbznYnrIS4tZS7j5drR0BuIzOMm8BBgIgXjKQpGxwZs2L5Y8W9SQUMlQDLqHKNMSH4vuTkHR7l2gBIQJx/ejYU1tPV9J6RPfN2AbB1KDDyOFOMQbR3p6WVUxKqVcAAAABAR8A4fUFAAAAABYAFNwBHQ1410Mdv3qQycfkRVRyvVjDAQhrAkcwRAIgU3Ow7cLkKrg8BJe0U0n9qFLPizqEzY0JtjVlpWOEk14CID/4AFNfgwNENN2LoOs0C6uHgt4sk8rNoZG+VMGzOC/HASECg7PDfGE/uURta5/R42Vso6QKmVAgYMhjWlXENkE/x+QAAAA=",
|
||||||
|
"final_tx": "02000000000101488c765c45dccdb456976708c2b434e904a044c6e806b81e90bc56ff51b497350100000000ffffffff0287d6120000000000220020bc628bb11294729d2a93f964616a73189422e754641b9279d4dc076fa07a1e1f97ece20500000000160014d8a0381e846c22d18be12f96fe2bdb2271eb73710247304402205373b0edc2e42ab83c0497b45349fda852cf8b3a84cd8d09b63565a56384935e02203ff800535f83034434dd8ba0eb340bab8782de2c93cacda191be54c1b3382fc701210283b3c37c613fb9446d6b9fd1e3656ca3a40a99502060c8635a55c436413fc7e400000000"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Interpreting the output, we now have a complete, final, and signed transaction
|
Interpreting the output, we now have a complete, final, and signed transaction
|
||||||
inside the PSBT.
|
inside the PSBT.
|
||||||
|
|
||||||
@ -220,7 +542,7 @@ LOST**!
|
|||||||
|
|
||||||
Let's give it to `lncli` to continue:
|
Let's give it to `lncli` to continue:
|
||||||
|
|
||||||
```bash
|
```shell script
|
||||||
...
|
...
|
||||||
Base64 encoded PSBT: cHNidP8BAH0CAAAAAbxLLf9+AYfqfF69QAQuETnL6cas7GDiWBZF+3xxc/Y/AAAAAAD+////AofWEgAAAAAAIgAgvGKLsRKUcp0qk/lkYWpzGJQi51RkG5J51NwHb6B6Hh+1If0jAQAAABYAFL+6THEGhybJnOkFGSRFbtCcPOG8AAAAAAABAR8wBBAkAQAAABYAFHemJ11XF7CU7WXBIJLD/qZF+6jrAQhrAkcwRAIgHKQbenZYvgADRd9TKGVO36NnaIgW3S12OUg8XGtSrE8CICmeaYoJ/U7Ecm+/GneY8i2hu2QCaQnuomJgzn+JAnrDASEDUBmCLcsybA5qXSRBBdZ0Uk/FQiay9NgOpv4D26yeJpAAAAA=
|
Base64 encoded PSBT: cHNidP8BAH0CAAAAAbxLLf9+AYfqfF69QAQuETnL6cas7GDiWBZF+3xxc/Y/AAAAAAD+////AofWEgAAAAAAIgAgvGKLsRKUcp0qk/lkYWpzGJQi51RkG5J51NwHb6B6Hh+1If0jAQAAABYAFL+6THEGhybJnOkFGSRFbtCcPOG8AAAAAAABAR8wBBAkAQAAABYAFHemJ11XF7CU7WXBIJLD/qZF+6jrAQhrAkcwRAIgHKQbenZYvgADRd9TKGVO36NnaIgW3S12OUg8XGtSrE8CICmeaYoJ/U7Ecm+/GneY8i2hu2QCaQnuomJgzn+JAnrDASEDUBmCLcsybA5qXSRBBdZ0Uk/FQiay9NgOpv4D26yeJpAAAAA=
|
||||||
{
|
{
|
||||||
@ -244,18 +566,18 @@ However, the `bitcoin-cli` examples from the command line can be combined into
|
|||||||
a single command. For example:
|
a single command. For example:
|
||||||
|
|
||||||
Channel 1:
|
Channel 1:
|
||||||
```bash
|
```shell script
|
||||||
bitcoin-cli walletcreatefundedpsbt [] '[{"tb1qywvazres587w9wyy8uw03q8j9ek6gc9crwx4jvhqcmew4xzsvqcq3jjdja":0.01000000}]'
|
$ bitcoin-cli walletcreatefundedpsbt [] '[{"tb1qywvazres587w9wyy8uw03q8j9ek6gc9crwx4jvhqcmew4xzsvqcq3jjdja":0.01000000}]'
|
||||||
```
|
```
|
||||||
|
|
||||||
Channel 2:
|
Channel 2:
|
||||||
```bash
|
```shell script
|
||||||
bitcoin-cli walletcreatefundedpsbt [] '[{"tb1q53626fcwwtcdc942zaf4laqnr3vg5gv4g0hakd2h7fw2pmz6428sk3ezcx":0.01000000}]'
|
$ bitcoin-cli walletcreatefundedpsbt [] '[{"tb1q53626fcwwtcdc942zaf4laqnr3vg5gv4g0hakd2h7fw2pmz6428sk3ezcx":0.01000000}]'
|
||||||
```
|
```
|
||||||
|
|
||||||
Combined command to get batch PSBT:
|
Combined command to get batch PSBT:
|
||||||
```bash
|
```shell script
|
||||||
bitcoin-cli walletcreatefundedpsbt [] '[{"tb1q53626fcwwtcdc942zaf4laqnr3vg5gv4g0hakd2h7fw2pmz6428sk3ezcx":0.01000000},{"tb1qywvazres587w9wyy8uw03q8j9ek6gc9crwx4jvhqcmew4xzsvqcq3jjdja":0.01000000}]'
|
$ bitcoin-cli walletcreatefundedpsbt [] '[{"tb1q53626fcwwtcdc942zaf4laqnr3vg5gv4g0hakd2h7fw2pmz6428sk3ezcx":0.01000000},{"tb1qywvazres587w9wyy8uw03q8j9ek6gc9crwx4jvhqcmew4xzsvqcq3jjdja":0.01000000}]'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Safety warning about batch transactions
|
### Safety warning about batch transactions
|
||||||
|
2
go.mod
2
go.mod
@ -9,7 +9,7 @@ require (
|
|||||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
|
||||||
github.com/btcsuite/btcutil v1.0.2
|
github.com/btcsuite/btcutil v1.0.2
|
||||||
github.com/btcsuite/btcutil/psbt v1.0.3-0.20200826194809-5f93e33af2b0
|
github.com/btcsuite/btcutil/psbt v1.0.3-0.20200826194809-5f93e33af2b0
|
||||||
github.com/btcsuite/btcwallet v0.11.1-0.20200904022754-2c5947a45222
|
github.com/btcsuite/btcwallet v0.11.1-0.20201002003944-e6d01202cb6b
|
||||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0
|
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0
|
||||||
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0
|
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0
|
||||||
github.com/btcsuite/btcwallet/walletdb v1.3.3
|
github.com/btcsuite/btcwallet/walletdb v1.3.3
|
||||||
|
4
go.sum
4
go.sum
@ -37,8 +37,8 @@ github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2ut
|
|||||||
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
|
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
|
||||||
github.com/btcsuite/btcutil/psbt v1.0.3-0.20200826194809-5f93e33af2b0 h1:3Zumkyl6PWyHuVJ04me0xeD9CnPOhNgeGpapFbzy7O4=
|
github.com/btcsuite/btcutil/psbt v1.0.3-0.20200826194809-5f93e33af2b0 h1:3Zumkyl6PWyHuVJ04me0xeD9CnPOhNgeGpapFbzy7O4=
|
||||||
github.com/btcsuite/btcutil/psbt v1.0.3-0.20200826194809-5f93e33af2b0/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ=
|
github.com/btcsuite/btcutil/psbt v1.0.3-0.20200826194809-5f93e33af2b0/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ=
|
||||||
github.com/btcsuite/btcwallet v0.11.1-0.20200904022754-2c5947a45222 h1:rh1FQAhh+BeR29twIFDM0RLOFpDK62tsABtUkWctTXw=
|
github.com/btcsuite/btcwallet v0.11.1-0.20201002003944-e6d01202cb6b h1:gblgCqJNcFulA2eiQLweSbfB8H/0SgviQ0Bkx7ADLwE=
|
||||||
github.com/btcsuite/btcwallet v0.11.1-0.20200904022754-2c5947a45222/go.mod h1:owv9oZqM0HnUW+ByF7VqOgfs2eb0ooiePW/+Tl/i/Nk=
|
github.com/btcsuite/btcwallet v0.11.1-0.20201002003944-e6d01202cb6b/go.mod h1:owv9oZqM0HnUW+ByF7VqOgfs2eb0ooiePW/+Tl/i/Nk=
|
||||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c=
|
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c=
|
||||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
|
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
|
||||||
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w=
|
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w=
|
||||||
|
@ -279,6 +279,12 @@ http:
|
|||||||
- selector: walletrpc.WalletKit.LabelTransaction
|
- selector: walletrpc.WalletKit.LabelTransaction
|
||||||
post: "/v2/wallet/tx/label"
|
post: "/v2/wallet/tx/label"
|
||||||
body: "*"
|
body: "*"
|
||||||
|
- selector: walletrpc.WalletKit.FundPsbt
|
||||||
|
post: "/v2/wallet/psbt/fund"
|
||||||
|
body: "*"
|
||||||
|
- selector: walletrpc.WalletKit.FinalizePsbt
|
||||||
|
post: "/v2/wallet/psbt/finalize"
|
||||||
|
body: "*"
|
||||||
|
|
||||||
# watchtowerrpc/watchtower.proto
|
# watchtowerrpc/watchtower.proto
|
||||||
- selector: watchtowerrpc.Watchtower.GetInfo
|
- selector: watchtowerrpc.Watchtower.GetInfo
|
||||||
|
89
lnrpc/walletrpc/psbt.go
Normal file
89
lnrpc/walletrpc/psbt.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// +build walletrpc
|
||||||
|
|
||||||
|
package walletrpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil/psbt"
|
||||||
|
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultMinConf = 1
|
||||||
|
defaultMaxConf = math.MaxInt32
|
||||||
|
)
|
||||||
|
|
||||||
|
// utxoLock is a type that contains an outpoint of an UTXO and its lock lease
|
||||||
|
// information.
|
||||||
|
type utxoLock struct {
|
||||||
|
lockID wtxmgr.LockID
|
||||||
|
outpoint wire.OutPoint
|
||||||
|
expiration time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyInputsUnspent checks that all inputs are contained in the list of
|
||||||
|
// known, non-locked UTXOs given.
|
||||||
|
func verifyInputsUnspent(inputs []*wire.TxIn, utxos []*lnwallet.Utxo) error {
|
||||||
|
// TODO(guggero): Pass in UTXOs as a map to make lookup more efficient.
|
||||||
|
for idx, txIn := range inputs {
|
||||||
|
found := false
|
||||||
|
for _, u := range utxos {
|
||||||
|
if u.OutPoint == txIn.PreviousOutPoint {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("input %d not found in list of non-"+
|
||||||
|
"locked UTXO", idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lockInputs requests a lock lease for all inputs specified in a PSBT packet
|
||||||
|
// by using the internal, static lock ID of lnd's wallet.
|
||||||
|
func lockInputs(w lnwallet.WalletController, packet *psbt.Packet) ([]*utxoLock,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
locks := make([]*utxoLock, len(packet.UnsignedTx.TxIn))
|
||||||
|
for idx, rawInput := range packet.UnsignedTx.TxIn {
|
||||||
|
lock := &utxoLock{
|
||||||
|
lockID: LndInternalLockID,
|
||||||
|
outpoint: rawInput.PreviousOutPoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
expiration, err := w.LeaseOutput(lock.lockID, lock.outpoint)
|
||||||
|
if err != nil {
|
||||||
|
// If we run into a problem with locking one output, we
|
||||||
|
// should try to unlock those that we successfully
|
||||||
|
// locked so far. If that fails as well, there's not
|
||||||
|
// much we can do.
|
||||||
|
for i := 0; i < idx; i++ {
|
||||||
|
op := locks[i].outpoint
|
||||||
|
if err := w.ReleaseOutput(
|
||||||
|
LndInternalLockID, op,
|
||||||
|
); err != nil {
|
||||||
|
|
||||||
|
log.Errorf("could not release the "+
|
||||||
|
"lock on %v: %v", op, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("could not lease a lock on "+
|
||||||
|
"UTXO: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lock.expiration = expiration
|
||||||
|
locks[idx] = lock
|
||||||
|
}
|
||||||
|
|
||||||
|
return locks, nil
|
||||||
|
}
|
@ -1368,6 +1368,400 @@ func (m *LabelTransactionResponse) XXX_DiscardUnknown() {
|
|||||||
|
|
||||||
var xxx_messageInfo_LabelTransactionResponse proto.InternalMessageInfo
|
var xxx_messageInfo_LabelTransactionResponse proto.InternalMessageInfo
|
||||||
|
|
||||||
|
type FundPsbtRequest struct {
|
||||||
|
// Types that are valid to be assigned to Template:
|
||||||
|
// *FundPsbtRequest_Psbt
|
||||||
|
// *FundPsbtRequest_Raw
|
||||||
|
Template isFundPsbtRequest_Template `protobuf_oneof:"template"`
|
||||||
|
// Types that are valid to be assigned to Fees:
|
||||||
|
// *FundPsbtRequest_TargetConf
|
||||||
|
// *FundPsbtRequest_SatPerVbyte
|
||||||
|
Fees isFundPsbtRequest_Fees `protobuf_oneof:"fees"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FundPsbtRequest) Reset() { *m = FundPsbtRequest{} }
|
||||||
|
func (m *FundPsbtRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*FundPsbtRequest) ProtoMessage() {}
|
||||||
|
func (*FundPsbtRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6cc6942ac78249e5, []int{24}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FundPsbtRequest) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_FundPsbtRequest.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *FundPsbtRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_FundPsbtRequest.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *FundPsbtRequest) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_FundPsbtRequest.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *FundPsbtRequest) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_FundPsbtRequest.Size(m)
|
||||||
|
}
|
||||||
|
func (m *FundPsbtRequest) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_FundPsbtRequest.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_FundPsbtRequest proto.InternalMessageInfo
|
||||||
|
|
||||||
|
type isFundPsbtRequest_Template interface {
|
||||||
|
isFundPsbtRequest_Template()
|
||||||
|
}
|
||||||
|
|
||||||
|
type FundPsbtRequest_Psbt struct {
|
||||||
|
Psbt []byte `protobuf:"bytes,1,opt,name=psbt,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FundPsbtRequest_Raw struct {
|
||||||
|
Raw *TxTemplate `protobuf:"bytes,2,opt,name=raw,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*FundPsbtRequest_Psbt) isFundPsbtRequest_Template() {}
|
||||||
|
|
||||||
|
func (*FundPsbtRequest_Raw) isFundPsbtRequest_Template() {}
|
||||||
|
|
||||||
|
func (m *FundPsbtRequest) GetTemplate() isFundPsbtRequest_Template {
|
||||||
|
if m != nil {
|
||||||
|
return m.Template
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FundPsbtRequest) GetPsbt() []byte {
|
||||||
|
if x, ok := m.GetTemplate().(*FundPsbtRequest_Psbt); ok {
|
||||||
|
return x.Psbt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FundPsbtRequest) GetRaw() *TxTemplate {
|
||||||
|
if x, ok := m.GetTemplate().(*FundPsbtRequest_Raw); ok {
|
||||||
|
return x.Raw
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type isFundPsbtRequest_Fees interface {
|
||||||
|
isFundPsbtRequest_Fees()
|
||||||
|
}
|
||||||
|
|
||||||
|
type FundPsbtRequest_TargetConf struct {
|
||||||
|
TargetConf uint32 `protobuf:"varint,3,opt,name=target_conf,json=targetConf,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FundPsbtRequest_SatPerVbyte struct {
|
||||||
|
SatPerVbyte uint32 `protobuf:"varint,4,opt,name=sat_per_vbyte,json=satPerVbyte,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*FundPsbtRequest_TargetConf) isFundPsbtRequest_Fees() {}
|
||||||
|
|
||||||
|
func (*FundPsbtRequest_SatPerVbyte) isFundPsbtRequest_Fees() {}
|
||||||
|
|
||||||
|
func (m *FundPsbtRequest) GetFees() isFundPsbtRequest_Fees {
|
||||||
|
if m != nil {
|
||||||
|
return m.Fees
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FundPsbtRequest) GetTargetConf() uint32 {
|
||||||
|
if x, ok := m.GetFees().(*FundPsbtRequest_TargetConf); ok {
|
||||||
|
return x.TargetConf
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FundPsbtRequest) GetSatPerVbyte() uint32 {
|
||||||
|
if x, ok := m.GetFees().(*FundPsbtRequest_SatPerVbyte); ok {
|
||||||
|
return x.SatPerVbyte
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX_OneofWrappers is for the internal use of the proto package.
|
||||||
|
func (*FundPsbtRequest) XXX_OneofWrappers() []interface{} {
|
||||||
|
return []interface{}{
|
||||||
|
(*FundPsbtRequest_Psbt)(nil),
|
||||||
|
(*FundPsbtRequest_Raw)(nil),
|
||||||
|
(*FundPsbtRequest_TargetConf)(nil),
|
||||||
|
(*FundPsbtRequest_SatPerVbyte)(nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FundPsbtResponse struct {
|
||||||
|
//
|
||||||
|
//The funded but not yet signed PSBT packet.
|
||||||
|
FundedPsbt []byte `protobuf:"bytes,1,opt,name=funded_psbt,json=fundedPsbt,proto3" json:"funded_psbt,omitempty"`
|
||||||
|
//
|
||||||
|
//The index of the added change output or -1 if no change was left over.
|
||||||
|
ChangeOutputIndex int32 `protobuf:"varint,2,opt,name=change_output_index,json=changeOutputIndex,proto3" json:"change_output_index,omitempty"`
|
||||||
|
//
|
||||||
|
//The list of lock leases that were acquired for the inputs in the funded PSBT
|
||||||
|
//packet.
|
||||||
|
LockedUtxos []*UtxoLease `protobuf:"bytes,3,rep,name=locked_utxos,json=lockedUtxos,proto3" json:"locked_utxos,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FundPsbtResponse) Reset() { *m = FundPsbtResponse{} }
|
||||||
|
func (m *FundPsbtResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*FundPsbtResponse) ProtoMessage() {}
|
||||||
|
func (*FundPsbtResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6cc6942ac78249e5, []int{25}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FundPsbtResponse) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_FundPsbtResponse.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *FundPsbtResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_FundPsbtResponse.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *FundPsbtResponse) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_FundPsbtResponse.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *FundPsbtResponse) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_FundPsbtResponse.Size(m)
|
||||||
|
}
|
||||||
|
func (m *FundPsbtResponse) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_FundPsbtResponse.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_FundPsbtResponse proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *FundPsbtResponse) GetFundedPsbt() []byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.FundedPsbt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FundPsbtResponse) GetChangeOutputIndex() int32 {
|
||||||
|
if m != nil {
|
||||||
|
return m.ChangeOutputIndex
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FundPsbtResponse) GetLockedUtxos() []*UtxoLease {
|
||||||
|
if m != nil {
|
||||||
|
return m.LockedUtxos
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TxTemplate struct {
|
||||||
|
//
|
||||||
|
//An optional list of inputs to use. Every input must be an UTXO known to the
|
||||||
|
//wallet that has not been locked before. The sum of all inputs must be
|
||||||
|
//sufficiently greater than the sum of all outputs to pay a miner fee with the
|
||||||
|
//fee rate specified in the parent message.
|
||||||
|
//
|
||||||
|
//If no inputs are specified, coin selection will be performed instead and
|
||||||
|
//inputs of sufficient value will be added to the resulting PSBT.
|
||||||
|
Inputs []*lnrpc.OutPoint `protobuf:"bytes,1,rep,name=inputs,proto3" json:"inputs,omitempty"`
|
||||||
|
//
|
||||||
|
//A map of all addresses and the amounts to send to in the funded PSBT.
|
||||||
|
Outputs map[string]uint64 `protobuf:"bytes,2,rep,name=outputs,proto3" json:"outputs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TxTemplate) Reset() { *m = TxTemplate{} }
|
||||||
|
func (m *TxTemplate) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*TxTemplate) ProtoMessage() {}
|
||||||
|
func (*TxTemplate) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6cc6942ac78249e5, []int{26}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TxTemplate) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_TxTemplate.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *TxTemplate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_TxTemplate.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *TxTemplate) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_TxTemplate.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *TxTemplate) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_TxTemplate.Size(m)
|
||||||
|
}
|
||||||
|
func (m *TxTemplate) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_TxTemplate.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_TxTemplate proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *TxTemplate) GetInputs() []*lnrpc.OutPoint {
|
||||||
|
if m != nil {
|
||||||
|
return m.Inputs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TxTemplate) GetOutputs() map[string]uint64 {
|
||||||
|
if m != nil {
|
||||||
|
return m.Outputs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UtxoLease struct {
|
||||||
|
//
|
||||||
|
//A 32 byte random ID that identifies the lease.
|
||||||
|
Id []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
// The identifying outpoint of the output being leased.
|
||||||
|
Outpoint *lnrpc.OutPoint `protobuf:"bytes,2,opt,name=outpoint,proto3" json:"outpoint,omitempty"`
|
||||||
|
//
|
||||||
|
//The absolute expiration of the output lease represented as a unix timestamp.
|
||||||
|
Expiration uint64 `protobuf:"varint,3,opt,name=expiration,proto3" json:"expiration,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UtxoLease) Reset() { *m = UtxoLease{} }
|
||||||
|
func (m *UtxoLease) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*UtxoLease) ProtoMessage() {}
|
||||||
|
func (*UtxoLease) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6cc6942ac78249e5, []int{27}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UtxoLease) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_UtxoLease.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *UtxoLease) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_UtxoLease.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *UtxoLease) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_UtxoLease.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *UtxoLease) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_UtxoLease.Size(m)
|
||||||
|
}
|
||||||
|
func (m *UtxoLease) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_UtxoLease.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_UtxoLease proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *UtxoLease) GetId() []byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.Id
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UtxoLease) GetOutpoint() *lnrpc.OutPoint {
|
||||||
|
if m != nil {
|
||||||
|
return m.Outpoint
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UtxoLease) GetExpiration() uint64 {
|
||||||
|
if m != nil {
|
||||||
|
return m.Expiration
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type FinalizePsbtRequest struct {
|
||||||
|
//
|
||||||
|
//A PSBT that should be signed and finalized. The PSBT must contain all
|
||||||
|
//required inputs, outputs, UTXO data and partial signatures of all other
|
||||||
|
//signers.
|
||||||
|
FundedPsbt []byte `protobuf:"bytes,1,opt,name=funded_psbt,json=fundedPsbt,proto3" json:"funded_psbt,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FinalizePsbtRequest) Reset() { *m = FinalizePsbtRequest{} }
|
||||||
|
func (m *FinalizePsbtRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*FinalizePsbtRequest) ProtoMessage() {}
|
||||||
|
func (*FinalizePsbtRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6cc6942ac78249e5, []int{28}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FinalizePsbtRequest) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_FinalizePsbtRequest.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *FinalizePsbtRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_FinalizePsbtRequest.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *FinalizePsbtRequest) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_FinalizePsbtRequest.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *FinalizePsbtRequest) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_FinalizePsbtRequest.Size(m)
|
||||||
|
}
|
||||||
|
func (m *FinalizePsbtRequest) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_FinalizePsbtRequest.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_FinalizePsbtRequest proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *FinalizePsbtRequest) GetFundedPsbt() []byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.FundedPsbt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type FinalizePsbtResponse struct {
|
||||||
|
// The fully signed and finalized transaction in PSBT format.
|
||||||
|
SignedPsbt []byte `protobuf:"bytes,1,opt,name=signed_psbt,json=signedPsbt,proto3" json:"signed_psbt,omitempty"`
|
||||||
|
// The fully signed and finalized transaction in the raw wire format.
|
||||||
|
RawFinalTx []byte `protobuf:"bytes,2,opt,name=raw_final_tx,json=rawFinalTx,proto3" json:"raw_final_tx,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FinalizePsbtResponse) Reset() { *m = FinalizePsbtResponse{} }
|
||||||
|
func (m *FinalizePsbtResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*FinalizePsbtResponse) ProtoMessage() {}
|
||||||
|
func (*FinalizePsbtResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6cc6942ac78249e5, []int{29}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FinalizePsbtResponse) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_FinalizePsbtResponse.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *FinalizePsbtResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_FinalizePsbtResponse.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *FinalizePsbtResponse) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_FinalizePsbtResponse.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *FinalizePsbtResponse) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_FinalizePsbtResponse.Size(m)
|
||||||
|
}
|
||||||
|
func (m *FinalizePsbtResponse) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_FinalizePsbtResponse.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_FinalizePsbtResponse proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *FinalizePsbtResponse) GetSignedPsbt() []byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.SignedPsbt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FinalizePsbtResponse) GetRawFinalTx() []byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.RawFinalTx
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
proto.RegisterEnum("walletrpc.WitnessType", WitnessType_name, WitnessType_value)
|
proto.RegisterEnum("walletrpc.WitnessType", WitnessType_name, WitnessType_value)
|
||||||
proto.RegisterType((*ListUnspentRequest)(nil), "walletrpc.ListUnspentRequest")
|
proto.RegisterType((*ListUnspentRequest)(nil), "walletrpc.ListUnspentRequest")
|
||||||
@ -1395,104 +1789,132 @@ func init() {
|
|||||||
proto.RegisterType((*ListSweepsResponse_TransactionIDs)(nil), "walletrpc.ListSweepsResponse.TransactionIDs")
|
proto.RegisterType((*ListSweepsResponse_TransactionIDs)(nil), "walletrpc.ListSweepsResponse.TransactionIDs")
|
||||||
proto.RegisterType((*LabelTransactionRequest)(nil), "walletrpc.LabelTransactionRequest")
|
proto.RegisterType((*LabelTransactionRequest)(nil), "walletrpc.LabelTransactionRequest")
|
||||||
proto.RegisterType((*LabelTransactionResponse)(nil), "walletrpc.LabelTransactionResponse")
|
proto.RegisterType((*LabelTransactionResponse)(nil), "walletrpc.LabelTransactionResponse")
|
||||||
|
proto.RegisterType((*FundPsbtRequest)(nil), "walletrpc.FundPsbtRequest")
|
||||||
|
proto.RegisterType((*FundPsbtResponse)(nil), "walletrpc.FundPsbtResponse")
|
||||||
|
proto.RegisterType((*TxTemplate)(nil), "walletrpc.TxTemplate")
|
||||||
|
proto.RegisterMapType((map[string]uint64)(nil), "walletrpc.TxTemplate.OutputsEntry")
|
||||||
|
proto.RegisterType((*UtxoLease)(nil), "walletrpc.UtxoLease")
|
||||||
|
proto.RegisterType((*FinalizePsbtRequest)(nil), "walletrpc.FinalizePsbtRequest")
|
||||||
|
proto.RegisterType((*FinalizePsbtResponse)(nil), "walletrpc.FinalizePsbtResponse")
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { proto.RegisterFile("walletrpc/walletkit.proto", fileDescriptor_6cc6942ac78249e5) }
|
func init() { proto.RegisterFile("walletrpc/walletkit.proto", fileDescriptor_6cc6942ac78249e5) }
|
||||||
|
|
||||||
var fileDescriptor_6cc6942ac78249e5 = []byte{
|
var fileDescriptor_6cc6942ac78249e5 = []byte{
|
||||||
// 1460 bytes of a gzipped FileDescriptorProto
|
// 1793 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x6f, 0x6f, 0x1a, 0x47,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x58, 0xef, 0x6e, 0x22, 0xc9,
|
||||||
0x13, 0x8f, 0xff, 0x61, 0x98, 0x03, 0x8c, 0x17, 0x6c, 0x13, 0xe2, 0xc4, 0xce, 0x45, 0xcf, 0x53,
|
0x11, 0x5f, 0x0c, 0xc6, 0x50, 0x80, 0x8d, 0x1b, 0xbc, 0x66, 0x59, 0xef, 0xd9, 0x3b, 0x97, 0xe4,
|
||||||
0xab, 0x49, 0xb0, 0xea, 0x28, 0x55, 0x92, 0x4a, 0x55, 0x6d, 0x38, 0x0b, 0x0b, 0x0c, 0xce, 0x81,
|
0x9c, 0xdc, 0x1d, 0x56, 0xbc, 0xba, 0xcb, 0x9e, 0x13, 0x45, 0xb1, 0xf1, 0x58, 0x58, 0x60, 0xf0,
|
||||||
0x63, 0xa5, 0x7d, 0x71, 0x3a, 0xb8, 0x0d, 0x3e, 0x19, 0xee, 0x2e, 0x7b, 0x4b, 0x38, 0xde, 0xf5,
|
0x35, 0x78, 0xad, 0x4d, 0x3e, 0x8c, 0x06, 0xa6, 0x6d, 0x8f, 0x0c, 0x33, 0x73, 0x33, 0x8d, 0x19,
|
||||||
0x53, 0x54, 0xca, 0x77, 0xe9, 0xa7, 0xe8, 0x27, 0xaa, 0x76, 0xf7, 0x38, 0xf6, 0x00, 0xa7, 0xaa,
|
0xf2, 0x29, 0x4f, 0x11, 0xe9, 0xa4, 0xbc, 0xc3, 0xbd, 0x40, 0x1e, 0x28, 0x8f, 0x11, 0xf5, 0x1f,
|
||||||
0xd4, 0x57, 0xbe, 0x9d, 0xdf, 0xcc, 0x6f, 0x67, 0x67, 0xc6, 0x33, 0x03, 0x3c, 0x1c, 0x9b, 0x83,
|
0x86, 0x1e, 0xc0, 0x7b, 0x8a, 0x72, 0x9f, 0x4c, 0xd7, 0xaf, 0xea, 0xd7, 0xd5, 0x55, 0x35, 0x5d,
|
||||||
0x01, 0xa6, 0xc4, 0xeb, 0x1d, 0x8b, 0xaf, 0x3b, 0x9b, 0x96, 0x3d, 0xe2, 0x52, 0x17, 0xa5, 0x22,
|
0xd5, 0x86, 0x57, 0x13, 0x73, 0x38, 0x24, 0xd4, 0xf7, 0x06, 0x47, 0xe2, 0xd7, 0xa3, 0x4d, 0x6b,
|
||||||
0xa8, 0x94, 0x22, 0x5e, 0x4f, 0x48, 0x4b, 0x05, 0xdf, 0xee, 0x3b, 0x4c, 0x9d, 0xfd, 0xc5, 0x44,
|
0x9e, 0xef, 0x52, 0x17, 0x65, 0x23, 0xa8, 0x9a, 0xf5, 0xbd, 0x81, 0x90, 0x56, 0xcb, 0x81, 0x7d,
|
||||||
0x48, 0xd5, 0x26, 0xa0, 0x86, 0xed, 0xd3, 0x6b, 0xc7, 0xf7, 0xb0, 0x43, 0x75, 0xfc, 0x79, 0x84,
|
0xef, 0x30, 0x75, 0xf6, 0x97, 0xf8, 0x42, 0xaa, 0xb5, 0x01, 0xb5, 0xec, 0x80, 0xde, 0x38, 0x81,
|
||||||
0x7d, 0x8a, 0x1e, 0x41, 0x6a, 0x68, 0x3b, 0x46, 0xcf, 0x75, 0x3e, 0xf9, 0xc5, 0x95, 0xc3, 0x95,
|
0x47, 0x1c, 0x8a, 0xc9, 0x0f, 0x63, 0x12, 0x50, 0xf4, 0x1a, 0xb2, 0x23, 0xdb, 0x31, 0x06, 0xae,
|
||||||
0xa3, 0x0d, 0x3d, 0x39, 0xb4, 0x9d, 0x0a, 0x3b, 0x73, 0xd0, 0x0c, 0x42, 0x70, 0x35, 0x04, 0xcd,
|
0x73, 0x17, 0x54, 0x12, 0x07, 0x89, 0xc3, 0x75, 0x9c, 0x19, 0xd9, 0x4e, 0x9d, 0xad, 0x39, 0x68,
|
||||||
0x80, 0x83, 0xea, 0x1b, 0xc8, 0xc7, 0xf8, 0x7c, 0xcf, 0x75, 0x7c, 0x8c, 0x9e, 0xc2, 0xc6, 0x88,
|
0x86, 0x12, 0x5c, 0x93, 0xa0, 0x19, 0x72, 0x50, 0x7b, 0x0f, 0xa5, 0x18, 0x5f, 0xe0, 0xb9, 0x4e,
|
||||||
0x06, 0x2e, 0x23, 0x5b, 0x3b, 0x52, 0x4e, 0x94, 0xf2, 0x80, 0xb9, 0x52, 0xbe, 0xa6, 0x81, 0xab,
|
0x40, 0xd0, 0x5b, 0x58, 0x1f, 0xd3, 0xd0, 0x65, 0x64, 0xc9, 0xc3, 0xdc, 0x71, 0xae, 0x36, 0x64,
|
||||||
0x0b, 0x44, 0x7d, 0x0f, 0xa8, 0x81, 0x4d, 0x1f, 0xb7, 0x46, 0xd4, 0x1b, 0x45, 0x9e, 0x64, 0x61,
|
0xae, 0xd4, 0x6e, 0x68, 0xe8, 0x62, 0x81, 0x68, 0xdf, 0x03, 0x6a, 0x11, 0x33, 0x20, 0x9d, 0x31,
|
||||||
0xd5, 0xb6, 0xb8, 0x0b, 0x69, 0x7d, 0xd5, 0xb6, 0xd0, 0x73, 0x48, 0xba, 0x23, 0xea, 0xb9, 0xb6,
|
0xf5, 0xc6, 0x91, 0x27, 0x9b, 0xb0, 0x66, 0x5b, 0xdc, 0x85, 0x3c, 0x5e, 0xb3, 0x2d, 0xf4, 0x25,
|
||||||
0x43, 0xf9, 0xdd, 0xca, 0xc9, 0x56, 0xc8, 0xd5, 0x1a, 0xd1, 0x2b, 0x26, 0xd6, 0x23, 0x05, 0xf5,
|
0x64, 0xdc, 0x31, 0xf5, 0x5c, 0xdb, 0xa1, 0x7c, 0xef, 0xdc, 0xf1, 0x96, 0xe4, 0xea, 0x8c, 0xe9,
|
||||||
0x35, 0xe4, 0x63, 0x94, 0xa1, 0x33, 0x4f, 0x00, 0x70, 0xe0, 0xd9, 0xc4, 0xa4, 0xb6, 0xeb, 0x70,
|
0x35, 0x13, 0xe3, 0x48, 0x41, 0xfb, 0x06, 0x4a, 0x31, 0x4a, 0xe9, 0xcc, 0x67, 0x00, 0x24, 0xf4,
|
||||||
0xee, 0x75, 0x5d, 0x92, 0xa8, 0x6d, 0x28, 0xe8, 0x78, 0xf0, 0x1f, 0xfb, 0xb2, 0x07, 0x3b, 0x73,
|
0x6c, 0xdf, 0xa4, 0xb6, 0xeb, 0x70, 0xee, 0x14, 0x56, 0x24, 0x5a, 0x17, 0xca, 0x98, 0x0c, 0x7f,
|
||||||
0xa4, 0xc2, 0x1b, 0xf5, 0x3d, 0x24, 0xea, 0x78, 0xa2, 0xe3, 0xcf, 0xe8, 0x08, 0x72, 0x77, 0x78,
|
0x61, 0x5f, 0x76, 0x61, 0x67, 0x81, 0x54, 0x78, 0xa3, 0x7d, 0x0f, 0xe9, 0x26, 0x99, 0x62, 0xf2,
|
||||||
0x62, 0x7c, 0xb2, 0x9d, 0x3e, 0x26, 0x86, 0x47, 0x18, 0xaf, 0x08, 0x7e, 0xf6, 0x0e, 0x4f, 0xce,
|
0x03, 0x3a, 0x84, 0xe2, 0x23, 0x99, 0x1a, 0x77, 0xb6, 0x73, 0x4f, 0x7c, 0xc3, 0xf3, 0x19, 0xaf,
|
||||||
0xb9, 0xf8, 0x8a, 0x49, 0xd1, 0x63, 0x00, 0xae, 0x69, 0x0e, 0xed, 0xc1, 0x24, 0xcc, 0x41, 0x8a,
|
0x08, 0xfe, 0xe6, 0x23, 0x99, 0x5e, 0x70, 0xf1, 0x35, 0x93, 0xa2, 0x37, 0x00, 0x5c, 0xd3, 0x1c,
|
||||||
0xe9, 0x70, 0x81, 0x9a, 0x01, 0xe5, 0xd4, 0xb2, 0x48, 0xe8, 0xb7, 0xaa, 0x42, 0x5a, 0x1c, 0xc3,
|
0xd9, 0xc3, 0xa9, 0xcc, 0x41, 0x96, 0xe9, 0x70, 0x81, 0x56, 0x80, 0xdc, 0xa9, 0x65, 0xf9, 0xd2,
|
||||||
0xf7, 0x23, 0x58, 0x37, 0x2d, 0x8b, 0x70, 0xee, 0x94, 0xce, 0xbf, 0xd5, 0x77, 0xa0, 0x74, 0x88,
|
0x6f, 0x4d, 0x83, 0xbc, 0x58, 0xca, 0xf3, 0x23, 0x48, 0x99, 0x96, 0xe5, 0x73, 0xee, 0x2c, 0xe6,
|
||||||
0xe9, 0xf8, 0x66, 0x8f, 0x85, 0x00, 0xed, 0x40, 0x82, 0x06, 0xc6, 0x2d, 0x0e, 0xc2, 0xe7, 0x6e,
|
0xbf, 0xb5, 0x13, 0xc8, 0xf5, 0x7c, 0xd3, 0x09, 0xcc, 0x01, 0x0b, 0x01, 0xda, 0x81, 0x34, 0x0d,
|
||||||
0xd0, 0xa0, 0x86, 0x03, 0x54, 0x80, 0x8d, 0x81, 0xd9, 0xc5, 0x03, 0x7e, 0x65, 0x4a, 0x17, 0x07,
|
0x8d, 0x07, 0x12, 0xca, 0xe3, 0xae, 0xd3, 0xb0, 0x41, 0x42, 0x54, 0x86, 0xf5, 0xa1, 0xd9, 0x27,
|
||||||
0xf5, 0x47, 0xd8, 0xba, 0x1a, 0x75, 0x07, 0xb6, 0x7f, 0x1b, 0x5d, 0xf1, 0x0c, 0x32, 0x9e, 0x10,
|
0x43, 0xbe, 0x65, 0x16, 0x8b, 0x85, 0xf6, 0x2d, 0x6c, 0x5d, 0x8f, 0xfb, 0x43, 0x3b, 0x78, 0x88,
|
||||||
0x19, 0x98, 0x10, 0x77, 0x7a, 0x57, 0x3a, 0x14, 0x6a, 0x4c, 0xa6, 0xfe, 0xb9, 0x02, 0xa8, 0x8d,
|
0xb6, 0xf8, 0x1c, 0x0a, 0x9e, 0x10, 0x19, 0xc4, 0xf7, 0xdd, 0xd9, 0x5e, 0x79, 0x29, 0xd4, 0x99,
|
||||||
0x1d, 0x4b, 0x04, 0xc4, 0x9f, 0x86, 0x79, 0x1f, 0xc0, 0x37, 0xa9, 0xe1, 0x61, 0x62, 0xdc, 0x8d,
|
0x4c, 0xfb, 0x77, 0x02, 0x50, 0x97, 0x38, 0x96, 0x08, 0x48, 0x30, 0x0b, 0xf3, 0x1e, 0x40, 0x60,
|
||||||
0xb9, 0xe1, 0x9a, 0x9e, 0xf4, 0x4d, 0x7a, 0x85, 0x49, 0x7d, 0x8c, 0x8e, 0x60, 0xd3, 0x15, 0xfa,
|
0x52, 0xc3, 0x23, 0xbe, 0xf1, 0x38, 0xe1, 0x86, 0x49, 0x9c, 0x09, 0x4c, 0x7a, 0x4d, 0xfc, 0xe6,
|
||||||
0xc5, 0x55, 0x5e, 0x4b, 0xd9, 0x72, 0x58, 0xd8, 0xe5, 0x4e, 0xd0, 0x1a, 0x51, 0x7d, 0x0a, 0xcf,
|
0x04, 0x1d, 0xc2, 0x86, 0x2b, 0xf4, 0x2b, 0x6b, 0xbc, 0x96, 0x36, 0x6b, 0xb2, 0xb0, 0x6b, 0xbd,
|
||||||
0x9c, 0x5d, 0x93, 0x9c, 0x8d, 0x97, 0xf6, 0xfa, 0x5c, 0x69, 0x3f, 0x87, 0x6d, 0x56, 0xb7, 0x96,
|
0xb0, 0x33, 0xa6, 0x78, 0x06, 0xcf, 0x9d, 0x4d, 0x2a, 0xce, 0xc6, 0x4b, 0x3b, 0xb5, 0x50, 0xda,
|
||||||
0x31, 0x72, 0x98, 0x82, 0x4d, 0x86, 0xd8, 0x2a, 0x6e, 0x1c, 0xae, 0x1c, 0x25, 0xf5, 0x1c, 0x07,
|
0x5f, 0xc2, 0x36, 0xab, 0x5b, 0xcb, 0x18, 0x3b, 0x4c, 0xc1, 0xf6, 0x47, 0xc4, 0xaa, 0xac, 0x1f,
|
||||||
0xae, 0x67, 0x72, 0xf5, 0x05, 0xe4, 0x63, 0xde, 0x87, 0x4f, 0xdf, 0x81, 0x04, 0x31, 0xc7, 0x06,
|
0x24, 0x0e, 0x33, 0xb8, 0xc8, 0x81, 0x9b, 0xb9, 0x5c, 0xfb, 0x0a, 0x4a, 0x31, 0xef, 0xe5, 0xd1,
|
||||||
0x8d, 0x42, 0x47, 0xcc, 0x71, 0x27, 0x50, 0x5f, 0x03, 0xd2, 0x7c, 0x6a, 0x0f, 0x4d, 0x8a, 0xcf,
|
0x77, 0x20, 0xed, 0x9b, 0x13, 0x83, 0x46, 0xa1, 0xf3, 0xcd, 0x49, 0x2f, 0xd4, 0xbe, 0x01, 0xa4,
|
||||||
0x31, 0x9e, 0xbe, 0xf5, 0x00, 0x14, 0x46, 0x68, 0x50, 0x93, 0xf4, 0xf1, 0x34, 0xdb, 0xc0, 0x44,
|
0x07, 0xd4, 0x1e, 0x99, 0x94, 0x5c, 0x10, 0x32, 0x3b, 0xeb, 0x3e, 0xe4, 0x18, 0xa1, 0x41, 0x4d,
|
||||||
0x1d, 0x2e, 0x51, 0x5f, 0x41, 0x3e, 0x66, 0x16, 0x5e, 0xf2, 0xcd, 0x18, 0xa9, 0x5f, 0xd7, 0x20,
|
0xff, 0x9e, 0xcc, 0xb2, 0x0d, 0x4c, 0xd4, 0xe3, 0x12, 0xed, 0x1d, 0x94, 0x62, 0x66, 0x72, 0x93,
|
||||||
0x7d, 0x85, 0x1d, 0xcb, 0x76, 0xfa, 0xed, 0x31, 0xc6, 0x5e, 0xac, 0x52, 0x57, 0xfe, 0xa1, 0x52,
|
0x4f, 0xc6, 0x48, 0xfb, 0x31, 0x09, 0xf9, 0x6b, 0xe2, 0x58, 0xb6, 0x73, 0xdf, 0x9d, 0x10, 0xe2,
|
||||||
0xd1, 0x5b, 0x48, 0x8f, 0x6d, 0xea, 0x60, 0xdf, 0x37, 0xe8, 0xc4, 0xc3, 0x3c, 0xd7, 0xd9, 0x93,
|
0xc5, 0x2a, 0x35, 0xf1, 0x33, 0x95, 0x8a, 0xbe, 0x83, 0xfc, 0xc4, 0xa6, 0x0e, 0x09, 0x02, 0x83,
|
||||||
0xdd, 0x72, 0xd4, 0x55, 0xca, 0x37, 0x02, 0xee, 0x4c, 0x3c, 0xac, 0x2b, 0xe3, 0xd9, 0x81, 0xd5,
|
0x4e, 0x3d, 0xc2, 0x73, 0xbd, 0x79, 0xfc, 0xb2, 0x16, 0xdd, 0x2a, 0xb5, 0x5b, 0x01, 0xf7, 0xa6,
|
||||||
0xa5, 0x39, 0x74, 0x47, 0x0e, 0x35, 0x7c, 0x93, 0xf2, 0xb8, 0x67, 0xf4, 0x94, 0x90, 0xb4, 0x4d,
|
0x1e, 0xc1, 0xb9, 0xc9, 0x7c, 0xc1, 0xea, 0xd2, 0x1c, 0xb9, 0x63, 0x87, 0x1a, 0x81, 0x49, 0x79,
|
||||||
0x8a, 0x0e, 0x21, 0x3d, 0xf5, 0xba, 0x3b, 0xa1, 0x98, 0x87, 0x3f, 0xa3, 0x83, 0xf0, 0xfb, 0x6c,
|
0xdc, 0x0b, 0x38, 0x2b, 0x24, 0x5d, 0x93, 0xa2, 0x03, 0xc8, 0xcf, 0xbc, 0xee, 0x4f, 0x29, 0xe1,
|
||||||
0x42, 0x31, 0x7a, 0x09, 0xa8, 0x4b, 0x5c, 0xd3, 0xea, 0x99, 0x3e, 0x35, 0x4c, 0x4a, 0xf1, 0xd0,
|
0xe1, 0x2f, 0x60, 0x10, 0x7e, 0x9f, 0x4d, 0x29, 0x41, 0x5f, 0x03, 0xea, 0xfb, 0xae, 0x69, 0x0d,
|
||||||
0xa3, 0x3e, 0xcf, 0x40, 0x46, 0xdf, 0x8e, 0x90, 0xd3, 0x10, 0x40, 0x27, 0xb0, 0xe3, 0xe0, 0x80,
|
0xcc, 0x80, 0x1a, 0x26, 0xa5, 0x64, 0xe4, 0xd1, 0x80, 0x67, 0xa0, 0x80, 0xb7, 0x23, 0xe4, 0x54,
|
||||||
0x1a, 0x33, 0x9b, 0x5b, 0x6c, 0xf7, 0x6f, 0x69, 0x31, 0xc1, 0x2d, 0xf2, 0x0c, 0x3c, 0x9b, 0x62,
|
0x02, 0xe8, 0x18, 0x76, 0x1c, 0x12, 0x52, 0x63, 0x6e, 0xf3, 0x40, 0xec, 0xfb, 0x07, 0x5a, 0x49,
|
||||||
0x35, 0x0e, 0x31, 0x1b, 0x22, 0xa2, 0x8f, 0x2d, 0x43, 0x0e, 0x7e, 0x52, 0xd8, 0x44, 0x60, 0x25,
|
0x73, 0x8b, 0x12, 0x03, 0xcf, 0x66, 0x58, 0x83, 0x43, 0xcc, 0xc6, 0x17, 0xd1, 0x27, 0x96, 0xa1,
|
||||||
0xca, 0x02, 0x7a, 0x05, 0xbb, 0x33, 0x9b, 0xd8, 0x13, 0x52, 0x73, 0x46, 0xed, 0xd9, 0x5b, 0x0a,
|
0x06, 0x3f, 0x23, 0x6c, 0x22, 0xb0, 0x1e, 0x65, 0x01, 0xbd, 0x83, 0x97, 0x73, 0x9b, 0xd8, 0x11,
|
||||||
0xb0, 0xf1, 0xc9, 0x25, 0x3d, 0x5c, 0xdc, 0xe4, 0x05, 0x24, 0x0e, 0xea, 0x2e, 0x14, 0xe4, 0xd4,
|
0xb2, 0x0b, 0x46, 0xdd, 0xf9, 0x59, 0xca, 0xb0, 0x7e, 0xe7, 0xfa, 0x03, 0x52, 0xd9, 0xe0, 0x05,
|
||||||
0x4c, 0xab, 0x5e, 0xbd, 0x81, 0x9d, 0x39, 0x79, 0x98, 0xea, 0x9f, 0x21, 0xeb, 0x09, 0xc0, 0xf0,
|
0x24, 0x16, 0xda, 0x4b, 0x28, 0xab, 0xa9, 0x99, 0x55, 0xbd, 0x76, 0x0b, 0x3b, 0x0b, 0x72, 0x99,
|
||||||
0x39, 0x12, 0xf6, 0xd0, 0x3d, 0x29, 0x21, 0xb2, 0xa5, 0x9e, 0xf1, 0x64, 0x1e, 0xf5, 0x8f, 0x15,
|
0xea, 0x3f, 0xc3, 0xa6, 0x27, 0x00, 0x23, 0xe0, 0x88, 0xbc, 0x43, 0x77, 0x95, 0x84, 0xa8, 0x96,
|
||||||
0xc8, 0x9e, 0x8d, 0x86, 0x9e, 0x54, 0x75, 0xff, 0xaa, 0x1c, 0x0e, 0x40, 0x11, 0x01, 0xe2, 0xc1,
|
0xb8, 0xe0, 0xa9, 0x3c, 0xda, 0x3f, 0x13, 0xb0, 0x79, 0x36, 0x1e, 0x79, 0x4a, 0xd5, 0xfd, 0x4f,
|
||||||
0xe2, 0xd5, 0x90, 0xd1, 0x41, 0x88, 0x58, 0x88, 0x16, 0xb2, 0xba, 0xb6, 0x90, 0xd5, 0x28, 0x12,
|
0xe5, 0xb0, 0x0f, 0x39, 0x11, 0x20, 0x1e, 0x2c, 0x5e, 0x0d, 0x05, 0x0c, 0x42, 0xc4, 0x42, 0xb4,
|
||||||
0xeb, 0x72, 0x24, 0xb6, 0x61, 0x2b, 0xf2, 0x2b, 0xec, 0x85, 0x2f, 0x61, 0x9b, 0x4d, 0x8f, 0x58,
|
0x94, 0xd5, 0xe4, 0x52, 0x56, 0xa3, 0x48, 0xa4, 0xd4, 0x48, 0x6c, 0xc3, 0x56, 0xe4, 0x97, 0xbc,
|
||||||
0x64, 0x50, 0x11, 0x36, 0xbf, 0x60, 0xd2, 0x75, 0x7d, 0xcc, 0x9d, 0x4d, 0xea, 0xd3, 0xa3, 0xfa,
|
0x0b, 0xbf, 0x86, 0x6d, 0xd6, 0x3d, 0x62, 0x91, 0x41, 0x15, 0xd8, 0x78, 0x22, 0x7e, 0xdf, 0x0d,
|
||||||
0xfb, 0xaa, 0x98, 0x5e, 0x73, 0x11, 0x6b, 0x40, 0x9e, 0xce, 0x7a, 0x99, 0x61, 0x61, 0x6a, 0xda,
|
0x08, 0x77, 0x36, 0x83, 0x67, 0x4b, 0xed, 0x1f, 0x6b, 0xa2, 0x7b, 0x2d, 0x44, 0xac, 0x05, 0x25,
|
||||||
0x03, 0x3f, 0x7c, 0xe9, 0xc3, 0xf0, 0xa5, 0x52, 0xb7, 0xab, 0x0a, 0x85, 0xda, 0x03, 0x1d, 0xd1,
|
0x3a, 0xbf, 0xcb, 0x0c, 0x8b, 0x50, 0xd3, 0x1e, 0x06, 0xf2, 0xa4, 0xaf, 0xe4, 0x49, 0x95, 0xdb,
|
||||||
0x05, 0x29, 0xba, 0x81, 0x2d, 0x99, 0xcd, 0xb6, 0xfc, 0xb0, 0xd9, 0xbf, 0x90, 0x12, 0xb0, 0xe8,
|
0xee, 0x5c, 0x28, 0x34, 0x5e, 0x60, 0x44, 0x97, 0xa4, 0xe8, 0x16, 0xb6, 0x54, 0x36, 0xdb, 0x0a,
|
||||||
0x85, 0x7c, 0xc1, 0x45, 0x95, 0x91, 0x67, 0x25, 0x9a, 0x0b, 0xcb, 0x2f, 0xbd, 0x85, 0x6c, 0x5c,
|
0xe4, 0x65, 0xff, 0x95, 0x92, 0x80, 0x65, 0x2f, 0xd4, 0x0d, 0x2e, 0xcf, 0x19, 0xf9, 0xa6, 0x42,
|
||||||
0x07, 0x7d, 0xb7, 0x78, 0x15, 0xcb, 0x75, 0x6a, 0xde, 0xf4, 0x2c, 0x09, 0x09, 0x51, 0x0b, 0xaa,
|
0x73, 0x69, 0x05, 0xd5, 0xef, 0x60, 0x33, 0xae, 0x83, 0xbe, 0x58, 0xde, 0x8a, 0xe5, 0x3a, 0xbb,
|
||||||
0x09, 0x7b, 0x0d, 0xd6, 0xd7, 0x24, 0xa6, 0x69, 0xdc, 0x10, 0xac, 0xd3, 0x20, 0x1a, 0x58, 0xfc,
|
0x68, 0x7a, 0x96, 0x81, 0xb4, 0xa8, 0x05, 0xcd, 0x84, 0xdd, 0x16, 0xbb, 0xd7, 0x14, 0xa6, 0x59,
|
||||||
0x7b, 0x79, 0x03, 0x47, 0xfb, 0x90, 0x72, 0xbf, 0x60, 0x32, 0x26, 0x76, 0x98, 0xbe, 0xa4, 0x3e,
|
0xdc, 0x10, 0xa4, 0x68, 0x18, 0x35, 0x2c, 0xfe, 0x7b, 0xf5, 0x05, 0x8e, 0xf6, 0x20, 0xeb, 0x3e,
|
||||||
0x13, 0xa8, 0x25, 0x28, 0x2e, 0x5e, 0x21, 0x1e, 0xf9, 0xfd, 0xd7, 0x35, 0x50, 0xa4, 0x6e, 0x80,
|
0x11, 0x7f, 0xe2, 0xdb, 0x32, 0x7d, 0x19, 0x3c, 0x17, 0x68, 0x55, 0xa8, 0x2c, 0x6f, 0x21, 0x13,
|
||||||
0xf2, 0xb0, 0x75, 0xdd, 0xac, 0x37, 0x5b, 0x37, 0x4d, 0xe3, 0xe6, 0xa2, 0xd3, 0xd4, 0xda, 0xed,
|
0xf6, 0x53, 0x02, 0xb6, 0x2e, 0xc6, 0x8e, 0x75, 0x1d, 0xf4, 0xa3, 0x36, 0x59, 0x86, 0x94, 0x17,
|
||||||
0xdc, 0x03, 0x54, 0x84, 0x42, 0xa5, 0x75, 0x79, 0x79, 0xd1, 0xb9, 0xd4, 0x9a, 0x1d, 0xa3, 0x73,
|
0xf4, 0x45, 0x65, 0xe5, 0x1b, 0x2f, 0x30, 0x5f, 0xa1, 0xdf, 0x42, 0xd2, 0x37, 0x27, 0x32, 0x74,
|
||||||
0x71, 0xa9, 0x19, 0x8d, 0x56, 0xa5, 0x9e, 0x5b, 0x41, 0x7b, 0x90, 0x97, 0x90, 0x66, 0xcb, 0xa8,
|
0x3b, 0x4a, 0xe8, 0x7a, 0x61, 0x8f, 0x8c, 0xbc, 0xa1, 0x49, 0x49, 0xe3, 0x05, 0x66, 0x3a, 0xe8,
|
||||||
0x6a, 0x8d, 0xd3, 0x8f, 0xb9, 0x55, 0xb4, 0x03, 0xdb, 0x12, 0xa0, 0x6b, 0x1f, 0x5a, 0x75, 0x2d,
|
0x6d, 0xbc, 0xe2, 0x78, 0x3d, 0x35, 0x12, 0xb1, 0x9a, 0xfb, 0x15, 0x14, 0x66, 0x35, 0xf7, 0x34,
|
||||||
0xb7, 0xc6, 0xf4, 0x6b, 0x9d, 0x46, 0xc5, 0x68, 0x9d, 0x9f, 0x6b, 0xba, 0x56, 0x9d, 0x02, 0xeb,
|
0xbf, 0x4a, 0x1a, 0x09, 0x9c, 0x13, 0x65, 0xf7, 0x81, 0x09, 0xcf, 0x00, 0x32, 0x54, 0x72, 0x9f,
|
||||||
0xec, 0x0a, 0x0e, 0x9c, 0x56, 0x2a, 0xda, 0x55, 0x67, 0x86, 0x6c, 0xa0, 0xff, 0xc1, 0xd3, 0x98,
|
0xa5, 0x21, 0x75, 0x47, 0x48, 0xa0, 0xfd, 0x2b, 0x01, 0xc5, 0xb9, 0xc7, 0xb2, 0x62, 0xf6, 0x21,
|
||||||
0x09, 0xbb, 0xbe, 0x75, 0xdd, 0x31, 0xda, 0x5a, 0xa5, 0xd5, 0xac, 0x1a, 0x0d, 0xed, 0x83, 0xd6,
|
0x77, 0x37, 0x76, 0x2c, 0x62, 0x19, 0x73, 0xcf, 0x31, 0x08, 0x11, 0x53, 0x44, 0x35, 0x28, 0x0d,
|
||||||
0xc8, 0x25, 0xd0, 0xff, 0x41, 0x8d, 0x13, 0xb4, 0xaf, 0x2b, 0x15, 0xad, 0xdd, 0x8e, 0xeb, 0x6d,
|
0x1e, 0x4c, 0xe7, 0x9e, 0x18, 0xa2, 0xbb, 0x18, 0xb6, 0x63, 0x91, 0x50, 0x76, 0xde, 0x6d, 0x01,
|
||||||
0xa2, 0x03, 0x78, 0x34, 0xe7, 0xc1, 0x65, 0xab, 0xa3, 0x4d, 0x59, 0x73, 0x49, 0x74, 0x08, 0xfb,
|
0x89, 0x46, 0x70, 0xc9, 0x00, 0xf4, 0x07, 0xc8, 0x0f, 0xdd, 0xc1, 0x23, 0xb1, 0x0c, 0x31, 0xf6,
|
||||||
0xf3, 0x9e, 0x70, 0x8d, 0x90, 0x2f, 0x97, 0x42, 0xfb, 0x50, 0xe4, 0x1a, 0x32, 0xf3, 0xd4, 0x5f,
|
0x24, 0xf9, 0x27, 0x5b, 0x56, 0x8e, 0xcd, 0x46, 0x1f, 0x3e, 0x9c, 0xe0, 0x9c, 0xd0, 0xbc, 0xe1,
|
||||||
0x40, 0x05, 0xc8, 0x85, 0x91, 0x33, 0xea, 0xda, 0x47, 0xa3, 0x76, 0xda, 0xae, 0xe5, 0x14, 0xf4,
|
0x53, 0xd0, 0x4f, 0x09, 0x80, 0x79, 0x44, 0xd0, 0x17, 0x90, 0xb6, 0x1d, 0xde, 0xec, 0xc4, 0x47,
|
||||||
0x08, 0xf6, 0x9a, 0x5a, 0x9b, 0xd1, 0x2d, 0x80, 0xe9, 0xb9, 0x60, 0x9d, 0x36, 0x2b, 0xb5, 0x96,
|
0xbf, 0xf4, 0x9d, 0x4a, 0x18, 0xfd, 0x69, 0xb1, 0x2d, 0x6a, 0x2b, 0x43, 0x5c, 0x93, 0xdd, 0x4a,
|
||||||
0x9e, 0xcb, 0x9c, 0xfc, 0xb5, 0x09, 0xa9, 0x1b, 0x5e, 0xa1, 0x75, 0x9b, 0xa2, 0x06, 0x28, 0xd2,
|
0x77, 0xa8, 0x3f, 0x8d, 0x5a, 0x65, 0xf5, 0x04, 0xf2, 0x2a, 0x80, 0x8a, 0x90, 0x7c, 0x24, 0x53,
|
||||||
0x62, 0x86, 0x1e, 0xcf, 0x15, 0x6f, 0x7c, 0x01, 0x2c, 0x3d, 0xb9, 0x0f, 0x8e, 0xfe, 0xc5, 0x14,
|
0xd9, 0xb4, 0xd9, 0x4f, 0x56, 0x38, 0x4f, 0xe6, 0x70, 0x2c, 0xba, 0x41, 0x0a, 0x8b, 0xc5, 0xc9,
|
||||||
0x69, 0xb3, 0x8a, 0xb3, 0x2d, 0x2c, 0x4e, 0x71, 0xb6, 0x25, 0x0b, 0x99, 0x0e, 0x99, 0xd8, 0x6e,
|
0xda, 0xfb, 0x84, 0xf6, 0x00, 0xd9, 0xe8, 0x2c, 0xff, 0xd7, 0x88, 0xb4, 0x30, 0x97, 0x25, 0x97,
|
||||||
0x84, 0x0e, 0x24, 0x83, 0x65, 0xab, 0x58, 0xe9, 0xf0, 0x7e, 0x85, 0x90, 0xf3, 0x1d, 0x64, 0xaa,
|
0xe6, 0xb2, 0x6f, 0xa1, 0x74, 0x61, 0x3b, 0xe6, 0xd0, 0xfe, 0x3b, 0x51, 0xeb, 0xed, 0xe7, 0x92,
|
||||||
0x98, 0xd8, 0x5f, 0x70, 0x13, 0x07, 0xb4, 0x8e, 0x27, 0x68, 0x5b, 0x32, 0x11, 0x0b, 0x57, 0x69,
|
0xa7, 0x7d, 0x84, 0x72, 0xdc, 0x6e, 0x9e, 0x75, 0x3e, 0x0b, 0xc7, 0x0d, 0x85, 0x88, 0x67, 0xfd,
|
||||||
0x37, 0x5a, 0x1d, 0xea, 0x78, 0x52, 0xc5, 0x7e, 0x8f, 0xd8, 0x1e, 0x75, 0x09, 0x7a, 0x03, 0x29,
|
0x00, 0xf2, 0xac, 0x95, 0xdf, 0x31, 0x63, 0xd6, 0xd0, 0xd7, 0x84, 0x86, 0x6f, 0x4e, 0x38, 0x5f,
|
||||||
0x61, 0xcb, 0xec, 0xf2, 0xb2, 0x52, 0xc3, 0xed, 0x99, 0xd4, 0x25, 0xf7, 0x5a, 0xfe, 0x04, 0x49,
|
0x2f, 0xfc, 0xdd, 0x8f, 0x49, 0xc8, 0x29, 0xdd, 0x10, 0x95, 0x60, 0xeb, 0xa6, 0xdd, 0x6c, 0x77,
|
||||||
0x76, 0x1f, 0x5b, 0xb7, 0x90, 0x3c, 0x31, 0xa5, 0x75, 0xac, 0xb4, 0xb7, 0x20, 0x0f, 0x5d, 0xae,
|
0x6e, 0xdb, 0xc6, 0xed, 0x65, 0xaf, 0xad, 0x77, 0xbb, 0xc5, 0x17, 0xa8, 0x02, 0xe5, 0x7a, 0xe7,
|
||||||
0x01, 0x0a, 0xf7, 0x28, 0x79, 0x15, 0x93, 0x69, 0x24, 0x79, 0xa9, 0x24, 0xf7, 0xff, 0xb9, 0xf5,
|
0xea, 0xea, 0xb2, 0x77, 0xa5, 0xb7, 0x7b, 0x46, 0xef, 0xf2, 0x4a, 0x37, 0x5a, 0x9d, 0x7a, 0xb3,
|
||||||
0xab, 0x01, 0x8a, 0xb4, 0x9a, 0xc4, 0xd2, 0xb3, 0xb8, 0x70, 0xc5, 0xd2, 0xb3, 0x6c, 0xa3, 0x69,
|
0x98, 0x40, 0xbb, 0x50, 0x52, 0x90, 0x76, 0xc7, 0x38, 0xd7, 0x5b, 0xa7, 0x1f, 0x8b, 0x6b, 0x68,
|
||||||
0x80, 0x22, 0xed, 0x20, 0x31, 0xb6, 0xc5, 0x95, 0x26, 0xc6, 0xb6, 0x6c, 0x75, 0xd1, 0x21, 0x13,
|
0x07, 0xb6, 0x15, 0x00, 0xeb, 0x1f, 0x3a, 0x4d, 0xbd, 0x98, 0x64, 0xfa, 0x8d, 0x5e, 0xab, 0x6e,
|
||||||
0x1b, 0x74, 0xb1, 0x64, 0x2f, 0x1b, 0x8d, 0xb1, 0x64, 0x2f, 0x9f, 0x91, 0xbf, 0xc0, 0x66, 0x38,
|
0x74, 0x2e, 0x2e, 0x74, 0xac, 0x9f, 0xcf, 0x80, 0x14, 0xdb, 0x82, 0x03, 0xa7, 0xf5, 0xba, 0x7e,
|
||||||
0x4a, 0xd0, 0x43, 0x49, 0x39, 0x3e, 0xf6, 0x62, 0x11, 0x9b, 0x9b, 0x3c, 0xe8, 0x02, 0x60, 0xd6,
|
0xdd, 0x9b, 0x23, 0xeb, 0xe8, 0xd7, 0xf0, 0x36, 0x66, 0xc2, 0xb6, 0xef, 0xdc, 0xf4, 0x8c, 0xae,
|
||||||
0xc3, 0xd1, 0xfe, 0x3d, 0xad, 0x5d, 0xf0, 0x3c, 0xfe, 0x66, 0xe3, 0x47, 0xbf, 0x41, 0x6e, 0xbe,
|
0x5e, 0xef, 0xb4, 0xcf, 0x8d, 0x96, 0xfe, 0x41, 0x6f, 0x15, 0xd3, 0xe8, 0x37, 0xa0, 0xc5, 0x09,
|
||||||
0x5f, 0x22, 0x55, 0x36, 0x59, 0xde, 0xaf, 0x4b, 0xcf, 0xbe, 0xa9, 0x23, 0xc8, 0xcf, 0x7e, 0xf8,
|
0xba, 0x37, 0xf5, 0xba, 0xde, 0xed, 0xc6, 0xf5, 0x36, 0xd0, 0x3e, 0xbc, 0x5e, 0xf0, 0xe0, 0xaa,
|
||||||
0xf5, 0xb8, 0x6f, 0xd3, 0xdb, 0x51, 0xb7, 0xdc, 0x73, 0x87, 0xc7, 0x03, 0xb6, 0xd1, 0x38, 0xb6,
|
0xd3, 0xd3, 0x67, 0xac, 0xc5, 0x0c, 0x3a, 0x80, 0xbd, 0x45, 0x4f, 0xb8, 0x86, 0xe4, 0x2b, 0x66,
|
||||||
0xd3, 0x77, 0x30, 0x1d, 0xbb, 0xe4, 0xee, 0x78, 0xe0, 0x58, 0xc7, 0x7c, 0xbe, 0x1d, 0x47, 0x5c,
|
0xd1, 0x1e, 0x54, 0xb8, 0x86, 0xca, 0x3c, 0xf3, 0x17, 0x50, 0x19, 0x8a, 0x32, 0x72, 0x46, 0x53,
|
||||||
0xdd, 0x04, 0xff, 0xa5, 0xf7, 0xea, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x49, 0x6e, 0x8a, 0xc5,
|
0xff, 0x68, 0x34, 0x4e, 0xbb, 0x8d, 0x62, 0x0e, 0xbd, 0x86, 0xdd, 0xb6, 0xde, 0x65, 0x74, 0x4b,
|
||||||
0x32, 0x0e, 0x00, 0x00,
|
0x60, 0x7e, 0x21, 0x58, 0xa7, 0xed, 0x7a, 0xa3, 0x83, 0x8b, 0x85, 0xe3, 0xff, 0x64, 0x20, 0x7b,
|
||||||
|
0xcb, 0xbf, 0x81, 0xa6, 0x4d, 0x51, 0x0b, 0x72, 0xca, 0xc3, 0x04, 0xbd, 0x59, 0xb8, 0xbc, 0xe3,
|
||||||
|
0x0f, 0xa0, 0xea, 0x67, 0xcf, 0xc1, 0x51, 0x8b, 0xc9, 0x29, 0x2f, 0x8b, 0x38, 0xdb, 0xd2, 0xc3,
|
||||||
|
0x21, 0xce, 0xb6, 0xe2, 0x41, 0x82, 0xa1, 0x10, 0x7b, 0x1b, 0xa0, 0x7d, 0xc5, 0x60, 0xd5, 0x53,
|
||||||
|
0xa4, 0x7a, 0xf0, 0xbc, 0x82, 0xe4, 0x3c, 0x81, 0xc2, 0x39, 0xf1, 0xed, 0x27, 0xd2, 0x26, 0x21,
|
||||||
|
0x6d, 0x92, 0x29, 0xda, 0x56, 0x4c, 0xc4, 0x83, 0xa3, 0xfa, 0x32, 0x1a, 0x9d, 0x9b, 0x64, 0x7a,
|
||||||
|
0x4e, 0x82, 0x81, 0x6f, 0x7b, 0xd4, 0xf5, 0xd1, 0x7b, 0xc8, 0x0a, 0x5b, 0x66, 0x57, 0x52, 0x95,
|
||||||
|
0x5a, 0xee, 0xc0, 0xa4, 0xae, 0xff, 0xac, 0xe5, 0x1f, 0x21, 0xc3, 0xf6, 0x63, 0xcf, 0x0d, 0xa4,
|
||||||
|
0x4e, 0x8c, 0xca, 0x73, 0xa4, 0xba, 0xbb, 0x24, 0x97, 0x2e, 0x37, 0x00, 0xc9, 0x77, 0x84, 0xfa,
|
||||||
|
0x14, 0x51, 0x69, 0x14, 0x79, 0xb5, 0xaa, 0xce, 0x3f, 0x0b, 0xcf, 0x8f, 0x16, 0xe4, 0x94, 0xd1,
|
||||||
|
0x3c, 0x96, 0x9e, 0xe5, 0x07, 0x47, 0x2c, 0x3d, 0xab, 0x26, 0xfa, 0x16, 0xe4, 0x94, 0x19, 0x3c,
|
||||||
|
0xc6, 0xb6, 0x3c, 0xd2, 0xc7, 0xd8, 0x56, 0x8d, 0xee, 0x18, 0x0a, 0xb1, 0x41, 0x2f, 0x96, 0xec,
|
||||||
|
0x55, 0xa3, 0x61, 0x2c, 0xd9, 0xab, 0x67, 0xc4, 0xbf, 0xc0, 0x86, 0x1c, 0xa5, 0xd0, 0x2b, 0x45,
|
||||||
|
0x39, 0x3e, 0xf6, 0xc5, 0x22, 0xb6, 0x30, 0x79, 0xa1, 0x4b, 0x80, 0xf9, 0x0c, 0x83, 0xf6, 0x9e,
|
||||||
|
0x19, 0x6d, 0x04, 0xcf, 0x9b, 0x4f, 0x0e, 0x3e, 0xe8, 0x6f, 0x50, 0x5c, 0x9c, 0x17, 0x90, 0xda,
|
||||||
|
0x8d, 0x9e, 0x99, 0x57, 0xaa, 0x9f, 0x7f, 0x52, 0x47, 0x92, 0xd7, 0x21, 0x33, 0xeb, 0xde, 0x48,
|
||||||
|
0x3d, 0xcf, 0xc2, 0x10, 0x52, 0x7d, 0xbd, 0x12, 0x93, 0x24, 0x1d, 0xc8, 0xab, 0x0d, 0x01, 0xa9,
|
||||||
|
0x29, 0x5b, 0xd1, 0x61, 0xaa, 0xfb, 0xcf, 0xe2, 0x82, 0xf0, 0xec, 0xf7, 0x7f, 0x3d, 0xba, 0xb7,
|
||||||
|
0xe9, 0xc3, 0xb8, 0x5f, 0x1b, 0xb8, 0xa3, 0xa3, 0x21, 0x7b, 0x67, 0x38, 0xb6, 0x73, 0xef, 0x10,
|
||||||
|
0x3a, 0x71, 0xfd, 0xc7, 0xa3, 0xa1, 0x63, 0x1d, 0xf1, 0xae, 0x77, 0x14, 0xf1, 0xf4, 0xd3, 0xfc,
|
||||||
|
0xff, 0x2f, 0xef, 0xfe, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x88, 0xe3, 0xa5, 0xe6, 0xc8, 0x11, 0x00,
|
||||||
|
0x00,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reference imports to suppress errors if they are not otherwise used.
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
@ -1599,6 +2021,39 @@ type WalletKitClient interface {
|
|||||||
//overwrite the exiting transaction label. Labels must not be empty, and
|
//overwrite the exiting transaction label. Labels must not be empty, and
|
||||||
//cannot exceed 500 characters.
|
//cannot exceed 500 characters.
|
||||||
LabelTransaction(ctx context.Context, in *LabelTransactionRequest, opts ...grpc.CallOption) (*LabelTransactionResponse, error)
|
LabelTransaction(ctx context.Context, in *LabelTransactionRequest, opts ...grpc.CallOption) (*LabelTransactionResponse, error)
|
||||||
|
//
|
||||||
|
//FundPsbt creates a fully populated PSBT that contains enough inputs to fund
|
||||||
|
//the outputs specified in the template. There are two ways of specifying a
|
||||||
|
//template: Either by passing in a PSBT with at least one output declared or
|
||||||
|
//by passing in a raw TxTemplate message.
|
||||||
|
//
|
||||||
|
//If there are no inputs specified in the template, coin selection is
|
||||||
|
//performed automatically. If the template does contain any inputs, it is
|
||||||
|
//assumed that full coin selection happened externally and no additional
|
||||||
|
//inputs are added. If the specified inputs aren't enough to fund the outputs
|
||||||
|
//with the given fee rate, an error is returned.
|
||||||
|
//
|
||||||
|
//After either selecting or verifying the inputs, all input UTXOs are locked
|
||||||
|
//with an internal app ID.
|
||||||
|
//
|
||||||
|
//NOTE: If this method returns without an error, it is the caller's
|
||||||
|
//responsibility to either spend the locked UTXOs (by finalizing and then
|
||||||
|
//publishing the transaction) or to unlock/release the locked UTXOs in case of
|
||||||
|
//an error on the caller's side.
|
||||||
|
FundPsbt(ctx context.Context, in *FundPsbtRequest, opts ...grpc.CallOption) (*FundPsbtResponse, error)
|
||||||
|
//
|
||||||
|
//FinalizePsbt expects a partial transaction with all inputs and outputs fully
|
||||||
|
//declared and tries to sign all inputs that belong to the wallet. Lnd must be
|
||||||
|
//the last signer of the transaction. That means, if there are any unsigned
|
||||||
|
//non-witness inputs or inputs without UTXO information attached or inputs
|
||||||
|
//without witness data that do not belong to lnd's wallet, this method will
|
||||||
|
//fail. If no error is returned, the PSBT is ready to be extracted and the
|
||||||
|
//final TX within to be broadcast.
|
||||||
|
//
|
||||||
|
//NOTE: This method does NOT publish the transaction once finalized. It is the
|
||||||
|
//caller's responsibility to either publish the transaction on success or
|
||||||
|
//unlock/release any locked UTXOs in case of an error in this method.
|
||||||
|
FinalizePsbt(ctx context.Context, in *FinalizePsbtRequest, opts ...grpc.CallOption) (*FinalizePsbtResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type walletKitClient struct {
|
type walletKitClient struct {
|
||||||
@ -1726,6 +2181,24 @@ func (c *walletKitClient) LabelTransaction(ctx context.Context, in *LabelTransac
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *walletKitClient) FundPsbt(ctx context.Context, in *FundPsbtRequest, opts ...grpc.CallOption) (*FundPsbtResponse, error) {
|
||||||
|
out := new(FundPsbtResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/FundPsbt", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *walletKitClient) FinalizePsbt(ctx context.Context, in *FinalizePsbtRequest, opts ...grpc.CallOption) (*FinalizePsbtResponse, error) {
|
||||||
|
out := new(FinalizePsbtResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/FinalizePsbt", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// WalletKitServer is the server API for WalletKit service.
|
// WalletKitServer is the server API for WalletKit service.
|
||||||
type WalletKitServer interface {
|
type WalletKitServer interface {
|
||||||
//
|
//
|
||||||
@ -1820,6 +2293,39 @@ type WalletKitServer interface {
|
|||||||
//overwrite the exiting transaction label. Labels must not be empty, and
|
//overwrite the exiting transaction label. Labels must not be empty, and
|
||||||
//cannot exceed 500 characters.
|
//cannot exceed 500 characters.
|
||||||
LabelTransaction(context.Context, *LabelTransactionRequest) (*LabelTransactionResponse, error)
|
LabelTransaction(context.Context, *LabelTransactionRequest) (*LabelTransactionResponse, error)
|
||||||
|
//
|
||||||
|
//FundPsbt creates a fully populated PSBT that contains enough inputs to fund
|
||||||
|
//the outputs specified in the template. There are two ways of specifying a
|
||||||
|
//template: Either by passing in a PSBT with at least one output declared or
|
||||||
|
//by passing in a raw TxTemplate message.
|
||||||
|
//
|
||||||
|
//If there are no inputs specified in the template, coin selection is
|
||||||
|
//performed automatically. If the template does contain any inputs, it is
|
||||||
|
//assumed that full coin selection happened externally and no additional
|
||||||
|
//inputs are added. If the specified inputs aren't enough to fund the outputs
|
||||||
|
//with the given fee rate, an error is returned.
|
||||||
|
//
|
||||||
|
//After either selecting or verifying the inputs, all input UTXOs are locked
|
||||||
|
//with an internal app ID.
|
||||||
|
//
|
||||||
|
//NOTE: If this method returns without an error, it is the caller's
|
||||||
|
//responsibility to either spend the locked UTXOs (by finalizing and then
|
||||||
|
//publishing the transaction) or to unlock/release the locked UTXOs in case of
|
||||||
|
//an error on the caller's side.
|
||||||
|
FundPsbt(context.Context, *FundPsbtRequest) (*FundPsbtResponse, error)
|
||||||
|
//
|
||||||
|
//FinalizePsbt expects a partial transaction with all inputs and outputs fully
|
||||||
|
//declared and tries to sign all inputs that belong to the wallet. Lnd must be
|
||||||
|
//the last signer of the transaction. That means, if there are any unsigned
|
||||||
|
//non-witness inputs or inputs without UTXO information attached or inputs
|
||||||
|
//without witness data that do not belong to lnd's wallet, this method will
|
||||||
|
//fail. If no error is returned, the PSBT is ready to be extracted and the
|
||||||
|
//final TX within to be broadcast.
|
||||||
|
//
|
||||||
|
//NOTE: This method does NOT publish the transaction once finalized. It is the
|
||||||
|
//caller's responsibility to either publish the transaction on success or
|
||||||
|
//unlock/release any locked UTXOs in case of an error in this method.
|
||||||
|
FinalizePsbt(context.Context, *FinalizePsbtRequest) (*FinalizePsbtResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnimplementedWalletKitServer can be embedded to have forward compatible implementations.
|
// UnimplementedWalletKitServer can be embedded to have forward compatible implementations.
|
||||||
@ -1865,6 +2371,12 @@ func (*UnimplementedWalletKitServer) ListSweeps(ctx context.Context, req *ListSw
|
|||||||
func (*UnimplementedWalletKitServer) LabelTransaction(ctx context.Context, req *LabelTransactionRequest) (*LabelTransactionResponse, error) {
|
func (*UnimplementedWalletKitServer) LabelTransaction(ctx context.Context, req *LabelTransactionRequest) (*LabelTransactionResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method LabelTransaction not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method LabelTransaction not implemented")
|
||||||
}
|
}
|
||||||
|
func (*UnimplementedWalletKitServer) FundPsbt(ctx context.Context, req *FundPsbtRequest) (*FundPsbtResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method FundPsbt not implemented")
|
||||||
|
}
|
||||||
|
func (*UnimplementedWalletKitServer) FinalizePsbt(ctx context.Context, req *FinalizePsbtRequest) (*FinalizePsbtResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method FinalizePsbt not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func RegisterWalletKitServer(s *grpc.Server, srv WalletKitServer) {
|
func RegisterWalletKitServer(s *grpc.Server, srv WalletKitServer) {
|
||||||
s.RegisterService(&_WalletKit_serviceDesc, srv)
|
s.RegisterService(&_WalletKit_serviceDesc, srv)
|
||||||
@ -2104,6 +2616,42 @@ func _WalletKit_LabelTransaction_Handler(srv interface{}, ctx context.Context, d
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _WalletKit_FundPsbt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(FundPsbtRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(WalletKitServer).FundPsbt(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/walletrpc.WalletKit/FundPsbt",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(WalletKitServer).FundPsbt(ctx, req.(*FundPsbtRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _WalletKit_FinalizePsbt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(FinalizePsbtRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(WalletKitServer).FinalizePsbt(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/walletrpc.WalletKit/FinalizePsbt",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(WalletKitServer).FinalizePsbt(ctx, req.(*FinalizePsbtRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
var _WalletKit_serviceDesc = grpc.ServiceDesc{
|
var _WalletKit_serviceDesc = grpc.ServiceDesc{
|
||||||
ServiceName: "walletrpc.WalletKit",
|
ServiceName: "walletrpc.WalletKit",
|
||||||
HandlerType: (*WalletKitServer)(nil),
|
HandlerType: (*WalletKitServer)(nil),
|
||||||
@ -2160,6 +2708,14 @@ var _WalletKit_serviceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "LabelTransaction",
|
MethodName: "LabelTransaction",
|
||||||
Handler: _WalletKit_LabelTransaction_Handler,
|
Handler: _WalletKit_LabelTransaction_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "FundPsbt",
|
||||||
|
Handler: _WalletKit_FundPsbt_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "FinalizePsbt",
|
||||||
|
Handler: _WalletKit_FinalizePsbt_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "walletrpc/walletkit.proto",
|
Metadata: "walletrpc/walletkit.proto",
|
||||||
|
@ -476,6 +476,74 @@ func local_request_WalletKit_LabelTransaction_0(ctx context.Context, marshaler r
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func request_WalletKit_FundPsbt_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq FundPsbtRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||||
|
if berr != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||||
|
}
|
||||||
|
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.FundPsbt(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_WalletKit_FundPsbt_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq FundPsbtRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||||
|
if berr != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||||
|
}
|
||||||
|
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := server.FundPsbt(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func request_WalletKit_FinalizePsbt_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq FinalizePsbtRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||||
|
if berr != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||||
|
}
|
||||||
|
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.FinalizePsbt(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_WalletKit_FinalizePsbt_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq FinalizePsbtRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||||
|
if berr != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||||
|
}
|
||||||
|
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := server.FinalizePsbt(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterWalletKitHandlerServer registers the http handlers for service WalletKit to "mux".
|
// RegisterWalletKitHandlerServer registers the http handlers for service WalletKit to "mux".
|
||||||
// UnaryRPC :call WalletKitServer directly.
|
// UnaryRPC :call WalletKitServer directly.
|
||||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||||
@ -741,6 +809,46 @@ func RegisterWalletKitHandlerServer(ctx context.Context, mux *runtime.ServeMux,
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_WalletKit_FundPsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_WalletKit_FundPsbt_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_WalletKit_FundPsbt_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_WalletKit_FinalizePsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_WalletKit_FinalizePsbt_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_WalletKit_FinalizePsbt_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1042,6 +1150,46 @@ func RegisterWalletKitHandlerClient(ctx context.Context, mux *runtime.ServeMux,
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_WalletKit_FundPsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_WalletKit_FundPsbt_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_WalletKit_FundPsbt_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_WalletKit_FinalizePsbt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_WalletKit_FinalizePsbt_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_WalletKit_FinalizePsbt_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1071,6 +1219,10 @@ var (
|
|||||||
pattern_WalletKit_ListSweeps_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "sweeps"}, "", runtime.AssumeColonVerbOpt(true)))
|
pattern_WalletKit_ListSweeps_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "sweeps"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||||
|
|
||||||
pattern_WalletKit_LabelTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "tx", "label"}, "", runtime.AssumeColonVerbOpt(true)))
|
pattern_WalletKit_LabelTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "tx", "label"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||||
|
|
||||||
|
pattern_WalletKit_FundPsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "fund"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||||
|
|
||||||
|
pattern_WalletKit_FinalizePsbt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "psbt", "finalize"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -1099,4 +1251,8 @@ var (
|
|||||||
forward_WalletKit_ListSweeps_0 = runtime.ForwardResponseMessage
|
forward_WalletKit_ListSweeps_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_WalletKit_LabelTransaction_0 = runtime.ForwardResponseMessage
|
forward_WalletKit_LabelTransaction_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_WalletKit_FundPsbt_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_WalletKit_FinalizePsbt_0 = runtime.ForwardResponseMessage
|
||||||
)
|
)
|
||||||
|
@ -128,6 +128,43 @@ service WalletKit {
|
|||||||
*/
|
*/
|
||||||
rpc LabelTransaction (LabelTransactionRequest)
|
rpc LabelTransaction (LabelTransactionRequest)
|
||||||
returns (LabelTransactionResponse);
|
returns (LabelTransactionResponse);
|
||||||
|
|
||||||
|
/*
|
||||||
|
FundPsbt creates a fully populated PSBT that contains enough inputs to fund
|
||||||
|
the outputs specified in the template. There are two ways of specifying a
|
||||||
|
template: Either by passing in a PSBT with at least one output declared or
|
||||||
|
by passing in a raw TxTemplate message.
|
||||||
|
|
||||||
|
If there are no inputs specified in the template, coin selection is
|
||||||
|
performed automatically. If the template does contain any inputs, it is
|
||||||
|
assumed that full coin selection happened externally and no additional
|
||||||
|
inputs are added. If the specified inputs aren't enough to fund the outputs
|
||||||
|
with the given fee rate, an error is returned.
|
||||||
|
|
||||||
|
After either selecting or verifying the inputs, all input UTXOs are locked
|
||||||
|
with an internal app ID.
|
||||||
|
|
||||||
|
NOTE: If this method returns without an error, it is the caller's
|
||||||
|
responsibility to either spend the locked UTXOs (by finalizing and then
|
||||||
|
publishing the transaction) or to unlock/release the locked UTXOs in case of
|
||||||
|
an error on the caller's side.
|
||||||
|
*/
|
||||||
|
rpc FundPsbt (FundPsbtRequest) returns (FundPsbtResponse);
|
||||||
|
|
||||||
|
/*
|
||||||
|
FinalizePsbt expects a partial transaction with all inputs and outputs fully
|
||||||
|
declared and tries to sign all inputs that belong to the wallet. Lnd must be
|
||||||
|
the last signer of the transaction. That means, if there are any unsigned
|
||||||
|
non-witness inputs or inputs without UTXO information attached or inputs
|
||||||
|
without witness data that do not belong to lnd's wallet, this method will
|
||||||
|
fail. If no error is returned, the PSBT is ready to be extracted and the
|
||||||
|
final TX within to be broadcast.
|
||||||
|
|
||||||
|
NOTE: This method does NOT publish the transaction once finalized. It is the
|
||||||
|
caller's responsibility to either publish the transaction on success or
|
||||||
|
unlock/release any locked UTXOs in case of an error in this method.
|
||||||
|
*/
|
||||||
|
rpc FinalizePsbt (FinalizePsbtRequest) returns (FinalizePsbtResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListUnspentRequest {
|
message ListUnspentRequest {
|
||||||
@ -461,3 +498,103 @@ message LabelTransactionRequest {
|
|||||||
|
|
||||||
message LabelTransactionResponse {
|
message LabelTransactionResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message FundPsbtRequest {
|
||||||
|
oneof template {
|
||||||
|
/*
|
||||||
|
Use an existing PSBT packet as the template for the funded PSBT.
|
||||||
|
|
||||||
|
The packet must contain at least one non-dust output. If one or more
|
||||||
|
inputs are specified, no coin selection is performed. In that case every
|
||||||
|
input must be an UTXO known to the wallet that has not been locked
|
||||||
|
before. The sum of all inputs must be sufficiently greater than the sum
|
||||||
|
of all outputs to pay a miner fee with the specified fee rate. A change
|
||||||
|
output is added to the PSBT if necessary.
|
||||||
|
*/
|
||||||
|
bytes psbt = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Use the outputs and optional inputs from this raw template.
|
||||||
|
*/
|
||||||
|
TxTemplate raw = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
oneof fees {
|
||||||
|
/*
|
||||||
|
The target number of blocks that the transaction should be confirmed in.
|
||||||
|
*/
|
||||||
|
uint32 target_conf = 3;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The fee rate, expressed in sat/vbyte, that should be used to spend the
|
||||||
|
input with.
|
||||||
|
*/
|
||||||
|
uint32 sat_per_vbyte = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message FundPsbtResponse {
|
||||||
|
/*
|
||||||
|
The funded but not yet signed PSBT packet.
|
||||||
|
*/
|
||||||
|
bytes funded_psbt = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The index of the added change output or -1 if no change was left over.
|
||||||
|
*/
|
||||||
|
int32 change_output_index = 2;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The list of lock leases that were acquired for the inputs in the funded PSBT
|
||||||
|
packet.
|
||||||
|
*/
|
||||||
|
repeated UtxoLease locked_utxos = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TxTemplate {
|
||||||
|
/*
|
||||||
|
An optional list of inputs to use. Every input must be an UTXO known to the
|
||||||
|
wallet that has not been locked before. The sum of all inputs must be
|
||||||
|
sufficiently greater than the sum of all outputs to pay a miner fee with the
|
||||||
|
fee rate specified in the parent message.
|
||||||
|
|
||||||
|
If no inputs are specified, coin selection will be performed instead and
|
||||||
|
inputs of sufficient value will be added to the resulting PSBT.
|
||||||
|
*/
|
||||||
|
repeated lnrpc.OutPoint inputs = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
A map of all addresses and the amounts to send to in the funded PSBT.
|
||||||
|
*/
|
||||||
|
map<string, uint64> outputs = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UtxoLease {
|
||||||
|
/*
|
||||||
|
A 32 byte random ID that identifies the lease.
|
||||||
|
*/
|
||||||
|
bytes id = 1;
|
||||||
|
|
||||||
|
// The identifying outpoint of the output being leased.
|
||||||
|
lnrpc.OutPoint outpoint = 2;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The absolute expiration of the output lease represented as a unix timestamp.
|
||||||
|
*/
|
||||||
|
uint64 expiration = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FinalizePsbtRequest {
|
||||||
|
/*
|
||||||
|
A PSBT that should be signed and finalized. The PSBT must contain all
|
||||||
|
required inputs, outputs, UTXO data and partial signatures of all other
|
||||||
|
signers.
|
||||||
|
*/
|
||||||
|
bytes funded_psbt = 1;
|
||||||
|
}
|
||||||
|
message FinalizePsbtResponse {
|
||||||
|
// The fully signed and finalized transaction in PSBT format.
|
||||||
|
bytes signed_psbt = 1;
|
||||||
|
|
||||||
|
// The fully signed and finalized transaction in the raw wire format.
|
||||||
|
bytes raw_final_tx = 2;
|
||||||
|
}
|
||||||
|
@ -177,6 +177,74 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v2/wallet/psbt/finalize": {
|
||||||
|
"post": {
|
||||||
|
"summary": "FinalizePsbt expects a partial transaction with all inputs and outputs fully\ndeclared and tries to sign all inputs that belong to the wallet. Lnd must be\nthe last signer of the transaction. That means, if there are any unsigned\nnon-witness inputs or inputs without UTXO information attached or inputs\nwithout witness data that do not belong to lnd's wallet, this method will\nfail. If no error is returned, the PSBT is ready to be extracted and the\nfinal TX within to be broadcast.",
|
||||||
|
"description": "NOTE: This method does NOT publish the transaction once finalized. It is the\ncaller's responsibility to either publish the transaction on success or\nunlock/release any locked UTXOs in case of an error in this method.",
|
||||||
|
"operationId": "FinalizePsbt",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/walletrpcFinalizePsbtResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/runtimeError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/walletrpcFinalizePsbtRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"WalletKit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v2/wallet/psbt/fund": {
|
||||||
|
"post": {
|
||||||
|
"summary": "FundPsbt creates a fully populated PSBT that contains enough inputs to fund\nthe outputs specified in the template. There are two ways of specifying a\ntemplate: Either by passing in a PSBT with at least one output declared or\nby passing in a raw TxTemplate message.",
|
||||||
|
"description": "If there are no inputs specified in the template, coin selection is\nperformed automatically. If the template does contain any inputs, it is\nassumed that full coin selection happened externally and no additional\ninputs are added. If the specified inputs aren't enough to fund the outputs\nwith the given fee rate, an error is returned.\n\nAfter either selecting or verifying the inputs, all input UTXOs are locked\nwith an internal app ID.\n\nNOTE: If this method returns without an error, it is the caller's\nresponsibility to either spend the locked UTXOs (by finalizing and then\npublishing the transaction) or to unlock/release the locked UTXOs in case of\nan error on the caller's side.",
|
||||||
|
"operationId": "FundPsbt",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/walletrpcFundPsbtResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/runtimeError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/walletrpcFundPsbtRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"WalletKit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v2/wallet/send": {
|
"/v2/wallet/send": {
|
||||||
"post": {
|
"post": {
|
||||||
"summary": "SendOutputs is similar to the existing sendmany call in Bitcoind, and\nallows the caller to create a transaction that sends to several outputs at\nonce. This is ideal when wanting to batch create a set of transactions.",
|
"summary": "SendOutputs is similar to the existing sendmany call in Bitcoind, and\nallows the caller to create a transaction that sends to several outputs at\nonce. This is ideal when wanting to batch create a set of transactions.",
|
||||||
@ -689,6 +757,77 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"walletrpcFinalizePsbtRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"funded_psbt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte",
|
||||||
|
"description": "A PSBT that should be signed and finalized. The PSBT must contain all\nrequired inputs, outputs, UTXO data and partial signatures of all other\nsigners."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"walletrpcFinalizePsbtResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"signed_psbt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte",
|
||||||
|
"description": "The fully signed and finalized transaction in PSBT format."
|
||||||
|
},
|
||||||
|
"raw_final_tx": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte",
|
||||||
|
"description": "The fully signed and finalized transaction in the raw wire format."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"walletrpcFundPsbtRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"psbt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte",
|
||||||
|
"description": "Use an existing PSBT packet as the template for the funded PSBT.\n\nThe packet must contain at least one non-dust output. If one or more\ninputs are specified, no coin selection is performed. In that case every\ninput must be an UTXO known to the wallet that has not been locked\nbefore. The sum of all inputs must be sufficiently greater than the sum\nof all outputs to pay a miner fee with the specified fee rate. A change\noutput is added to the PSBT if necessary."
|
||||||
|
},
|
||||||
|
"raw": {
|
||||||
|
"$ref": "#/definitions/walletrpcTxTemplate",
|
||||||
|
"description": "Use the outputs and optional inputs from this raw template."
|
||||||
|
},
|
||||||
|
"target_conf": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "The target number of blocks that the transaction should be confirmed in."
|
||||||
|
},
|
||||||
|
"sat_per_vbyte": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "The fee rate, expressed in sat/vbyte, that should be used to spend the\ninput with."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"walletrpcFundPsbtResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"funded_psbt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte",
|
||||||
|
"description": "The funded but not yet signed PSBT packet."
|
||||||
|
},
|
||||||
|
"change_output_index": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"description": "The index of the added change output or -1 if no change was left over."
|
||||||
|
},
|
||||||
|
"locked_utxos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/walletrpcUtxoLease"
|
||||||
|
},
|
||||||
|
"description": "The list of lock leases that were acquired for the inputs in the funded PSBT\npacket."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"walletrpcKeyReq": {
|
"walletrpcKeyReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -914,6 +1053,45 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"walletrpcTxTemplate": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"inputs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/lnrpcOutPoint"
|
||||||
|
},
|
||||||
|
"description": "An optional list of inputs to use. Every input must be an UTXO known to the\nwallet that has not been locked before. The sum of all inputs must be\nsufficiently greater than the sum of all outputs to pay a miner fee with the\nfee rate specified in the parent message.\n\nIf no inputs are specified, coin selection will be performed instead and\ninputs of sufficient value will be added to the resulting PSBT."
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64"
|
||||||
|
},
|
||||||
|
"description": "A map of all addresses and the amounts to send to in the funded PSBT."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"walletrpcUtxoLease": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte",
|
||||||
|
"description": "A 32 byte random ID that identifies the lease."
|
||||||
|
},
|
||||||
|
"outpoint": {
|
||||||
|
"$ref": "#/definitions/lnrpcOutPoint",
|
||||||
|
"description": "The identifying outpoint of the output being leased."
|
||||||
|
},
|
||||||
|
"expiration": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64",
|
||||||
|
"description": "The absolute expiration of the output lease represented as a unix timestamp."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"walletrpcWitnessType": {
|
"walletrpcWitnessType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
@ -15,6 +15,8 @@ import (
|
|||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/btcsuite/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
@ -114,12 +116,32 @@ var (
|
|||||||
Entity: "onchain",
|
Entity: "onchain",
|
||||||
Action: "read",
|
Action: "read",
|
||||||
}},
|
}},
|
||||||
|
"/walletrpc.WalletKit/FundPsbt": {{
|
||||||
|
Entity: "onchain",
|
||||||
|
Action: "write",
|
||||||
|
}},
|
||||||
|
"/walletrpc.WalletKit/FinalizePsbt": {{
|
||||||
|
Entity: "onchain",
|
||||||
|
Action: "write",
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultWalletKitMacFilename is the default name of the wallet kit
|
// DefaultWalletKitMacFilename is the default name of the wallet kit
|
||||||
// macaroon that we expect to find via a file handle within the main
|
// macaroon that we expect to find via a file handle within the main
|
||||||
// configuration file in this package.
|
// configuration file in this package.
|
||||||
DefaultWalletKitMacFilename = "walletkit.macaroon"
|
DefaultWalletKitMacFilename = "walletkit.macaroon"
|
||||||
|
|
||||||
|
// LndInternalLockID is the binary representation of the SHA256 hash of
|
||||||
|
// the string "lnd-internal-lock-id" and is used for UTXO lock leases to
|
||||||
|
// identify that we ourselves are locking an UTXO, for example when
|
||||||
|
// giving out a funded PSBT. The ID corresponds to the hex value of
|
||||||
|
// ede19a92ed321a4705f8a1cccc1d4f6182545d4bb4fae08bd5937831b7e38f98.
|
||||||
|
LndInternalLockID = wtxmgr.LockID{
|
||||||
|
0xed, 0xe1, 0x9a, 0x92, 0xed, 0x32, 0x1a, 0x47,
|
||||||
|
0x05, 0xf8, 0xa1, 0xcc, 0xcc, 0x1d, 0x4f, 0x61,
|
||||||
|
0x82, 0x54, 0x5d, 0x4b, 0xb4, 0xfa, 0xe0, 0x8b,
|
||||||
|
0xd5, 0x93, 0x78, 0x31, 0xb7, 0xe3, 0x8f, 0x98,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrZeroLabel is returned when an attempt is made to label a transaction with
|
// ErrZeroLabel is returned when an attempt is made to label a transaction with
|
||||||
@ -307,6 +329,12 @@ func (w *WalletKit) LeaseOutput(ctx context.Context,
|
|||||||
return nil, errors.New("id must be 32 random bytes")
|
return nil, errors.New("id must be 32 random bytes")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't allow our internal ID to be used externally for locking. Only
|
||||||
|
// unlocking is allowed.
|
||||||
|
if lockID == LndInternalLockID {
|
||||||
|
return nil, errors.New("reserved id cannot be used")
|
||||||
|
}
|
||||||
|
|
||||||
op, err := unmarshallOutPoint(req.Outpoint)
|
op, err := unmarshallOutPoint(req.Outpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -830,3 +858,262 @@ func (w *WalletKit) LabelTransaction(ctx context.Context,
|
|||||||
err = w.cfg.Wallet.LabelTransaction(*hash, req.Label, req.Overwrite)
|
err = w.cfg.Wallet.LabelTransaction(*hash, req.Label, req.Overwrite)
|
||||||
return &LabelTransactionResponse{}, err
|
return &LabelTransactionResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FundPsbt creates a fully populated PSBT that contains enough inputs to fund
|
||||||
|
// the outputs specified in the template. There are two ways of specifying a
|
||||||
|
// template: Either by passing in a PSBT with at least one output declared or
|
||||||
|
// by passing in a raw TxTemplate message. If there are no inputs specified in
|
||||||
|
// the template, coin selection is performed automatically. If the template does
|
||||||
|
// contain any inputs, it is assumed that full coin selection happened
|
||||||
|
// externally and no additional inputs are added. If the specified inputs aren't
|
||||||
|
// enough to fund the outputs with the given fee rate, an error is returned.
|
||||||
|
// After either selecting or verifying the inputs, all input UTXOs are locked
|
||||||
|
// with an internal app ID.
|
||||||
|
//
|
||||||
|
// NOTE: If this method returns without an error, it is the caller's
|
||||||
|
// responsibility to either spend the locked UTXOs (by finalizing and then
|
||||||
|
// publishing the transaction) or to unlock/release the locked UTXOs in case of
|
||||||
|
// an error on the caller's side.
|
||||||
|
func (w *WalletKit) FundPsbt(_ context.Context,
|
||||||
|
req *FundPsbtRequest) (*FundPsbtResponse, error) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
packet *psbt.Packet
|
||||||
|
feeSatPerKW chainfee.SatPerKWeight
|
||||||
|
locks []*utxoLock
|
||||||
|
rawPsbt bytes.Buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
// There are two ways a user can specify what we call the template (a
|
||||||
|
// list of inputs and outputs to use in the PSBT): Either as a PSBT
|
||||||
|
// packet directly or as a special RPC message. Find out which one the
|
||||||
|
// user wants to use, they are mutually exclusive.
|
||||||
|
switch {
|
||||||
|
// The template is specified as a PSBT. All we have to do is parse it.
|
||||||
|
case req.GetPsbt() != nil:
|
||||||
|
r := bytes.NewReader(req.GetPsbt())
|
||||||
|
packet, err = psbt.NewFromRawBytes(r, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse PSBT: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The template is specified as a RPC message. We need to create a new
|
||||||
|
// PSBT and copy the RPC information over.
|
||||||
|
case req.GetRaw() != nil:
|
||||||
|
tpl := req.GetRaw()
|
||||||
|
if len(tpl.Outputs) == 0 {
|
||||||
|
return nil, fmt.Errorf("no outputs specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
txOut := make([]*wire.TxOut, 0, len(tpl.Outputs))
|
||||||
|
for addrStr, amt := range tpl.Outputs {
|
||||||
|
addr, err := btcutil.DecodeAddress(
|
||||||
|
addrStr, w.cfg.ChainParams,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing address "+
|
||||||
|
"%s for network %s: %v", addrStr,
|
||||||
|
w.cfg.ChainParams.Name, err)
|
||||||
|
}
|
||||||
|
pkScript, err := txscript.PayToAddrScript(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting pk "+
|
||||||
|
"script for address %s: %v", addrStr,
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
txOut = append(txOut, &wire.TxOut{
|
||||||
|
Value: int64(amt),
|
||||||
|
PkScript: pkScript,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
txIn := make([]*wire.OutPoint, len(tpl.Inputs))
|
||||||
|
for idx, in := range tpl.Inputs {
|
||||||
|
op, err := unmarshallOutPoint(in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing "+
|
||||||
|
"outpoint: %v", err)
|
||||||
|
}
|
||||||
|
txIn[idx] = op
|
||||||
|
}
|
||||||
|
|
||||||
|
sequences := make([]uint32, len(txIn))
|
||||||
|
packet, err = psbt.New(txIn, txOut, 2, 0, sequences)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not create PSBT: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("transaction template missing, need " +
|
||||||
|
"to specify either PSBT or raw TX template")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the desired transaction fee.
|
||||||
|
switch {
|
||||||
|
// Estimate the fee by the target number of blocks to confirmation.
|
||||||
|
case req.GetTargetConf() != 0:
|
||||||
|
targetConf := req.GetTargetConf()
|
||||||
|
if targetConf < 2 {
|
||||||
|
return nil, fmt.Errorf("confirmation target must be " +
|
||||||
|
"greater than 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
feeSatPerKW, err = w.cfg.FeeEstimator.EstimateFeePerKW(
|
||||||
|
targetConf,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not estimate fee: %v",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the fee to sat/kW from the specified sat/vByte.
|
||||||
|
case req.GetSatPerVbyte() != 0:
|
||||||
|
feeSatPerKW = chainfee.SatPerKVByte(
|
||||||
|
req.GetSatPerVbyte() * 1000,
|
||||||
|
).FeePerKWeight()
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("fee definition missing, need to " +
|
||||||
|
"specify either target_conf or set_per_vbyte")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The RPC parsing part is now over. Several of the following operations
|
||||||
|
// require us to hold the global coin selection lock so we do the rest
|
||||||
|
// of the tasks while holding the lock. The result is a list of locked
|
||||||
|
// UTXOs.
|
||||||
|
changeIndex := int32(-1)
|
||||||
|
err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
|
||||||
|
// In case the user did specify inputs, we need to make sure
|
||||||
|
// they are known to us, still unspent and not yet locked.
|
||||||
|
if len(packet.UnsignedTx.TxIn) > 0 {
|
||||||
|
// Get a list of all unspent witness outputs.
|
||||||
|
utxos, err := w.cfg.Wallet.ListUnspentWitness(
|
||||||
|
defaultMinConf, defaultMaxConf,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate all inputs against our known list of UTXOs
|
||||||
|
// now.
|
||||||
|
err = verifyInputsUnspent(packet.UnsignedTx.TxIn, utxos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We made sure the input from the user is as sane as possible.
|
||||||
|
// We can now ask the wallet to fund the TX. This will not yet
|
||||||
|
// lock any coins but might still change the wallet DB by
|
||||||
|
// generating a new change address.
|
||||||
|
changeIndex, err = w.cfg.Wallet.FundPsbt(packet, feeSatPerKW)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("wallet couldn't fund PSBT: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we can properly serialize the packet. If this goes
|
||||||
|
// wrong then something isn't right with the inputs and we
|
||||||
|
// probably shouldn't try to lock any of them.
|
||||||
|
err = packet.Serialize(&rawPsbt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error serializing funded PSBT: %v",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we have obtained a set of coins that can be used to fund
|
||||||
|
// the TX. Let's lock them to be sure they aren't spent by the
|
||||||
|
// time the PSBT is published. This is the action we do here
|
||||||
|
// that could cause an error. Therefore if some of the UTXOs
|
||||||
|
// cannot be locked, the rollback of the other's locks also
|
||||||
|
// happens in this function. If we ever need to do more after
|
||||||
|
// this function, we need to extract the rollback needs to be
|
||||||
|
// extracted into a defer.
|
||||||
|
locks, err = lockInputs(w.cfg.Wallet, packet)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not lock inputs: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the lock leases to the RPC format.
|
||||||
|
rpcLocks := make([]*UtxoLease, len(locks))
|
||||||
|
for idx, lock := range locks {
|
||||||
|
rpcLocks[idx] = &UtxoLease{
|
||||||
|
Id: lock.lockID[:],
|
||||||
|
Outpoint: &lnrpc.OutPoint{
|
||||||
|
TxidBytes: lock.outpoint.Hash[:],
|
||||||
|
TxidStr: lock.outpoint.String(),
|
||||||
|
OutputIndex: lock.outpoint.Index,
|
||||||
|
},
|
||||||
|
Expiration: uint64(lock.expiration.Unix()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FundPsbtResponse{
|
||||||
|
FundedPsbt: rawPsbt.Bytes(),
|
||||||
|
ChangeOutputIndex: changeIndex,
|
||||||
|
LockedUtxos: rpcLocks,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
|
||||||
|
// declared and tries to sign all inputs that belong to the wallet. Lnd must be
|
||||||
|
// the last signer of the transaction. That means, if there are any unsigned
|
||||||
|
// non-witness inputs or inputs without UTXO information attached or inputs
|
||||||
|
// without witness data that do not belong to lnd's wallet, this method will
|
||||||
|
// fail. If no error is returned, the PSBT is ready to be extracted and the
|
||||||
|
// final TX within to be broadcast.
|
||||||
|
//
|
||||||
|
// NOTE: This method does NOT publish the transaction once finalized. It is the
|
||||||
|
// caller's responsibility to either publish the transaction on success or
|
||||||
|
// unlock/release any locked UTXOs in case of an error in this method.
|
||||||
|
func (w *WalletKit) FinalizePsbt(_ context.Context,
|
||||||
|
req *FinalizePsbtRequest) (*FinalizePsbtResponse, error) {
|
||||||
|
|
||||||
|
// Parse the funded PSBT. No additional checks are required at this
|
||||||
|
// level as the wallet will perform all of them.
|
||||||
|
packet, err := psbt.NewFromRawBytes(
|
||||||
|
bytes.NewReader(req.FundedPsbt), false,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing PSBT: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let the wallet do the heavy lifting. This will sign all inputs that
|
||||||
|
// we have the UTXO for. If some inputs can't be signed and don't have
|
||||||
|
// witness data attached, this will fail.
|
||||||
|
err = w.cfg.Wallet.FinalizePsbt(packet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error finalizing PSBT: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
finalPsbtBytes bytes.Buffer
|
||||||
|
finalTxBytes bytes.Buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
// Serialize the finalized PSBT in both the packet and wire format.
|
||||||
|
err = packet.Serialize(&finalPsbtBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error serializing PSBT: %v", err)
|
||||||
|
}
|
||||||
|
finalTx, err := psbt.Extract(packet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to extract final TX: %v", err)
|
||||||
|
}
|
||||||
|
err = finalTx.Serialize(&finalTxBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error serializing final TX: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FinalizePsbtResponse{
|
||||||
|
SignedPsbt: finalPsbtBytes.Bytes(),
|
||||||
|
RawFinalTx: finalTxBytes.Bytes(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -8,9 +8,9 @@ import (
|
|||||||
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/btcsuite/btcutil/psbt"
|
|
||||||
"github.com/lightningnetwork/lnd"
|
"github.com/lightningnetwork/lnd"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntest"
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -22,53 +22,48 @@ func testPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
const chanSize = lnd.MaxBtcFundingAmount
|
const chanSize = lnd.MaxBtcFundingAmount
|
||||||
|
|
||||||
// First, we'll create two new nodes that we'll use to open channel
|
// First, we'll create two new nodes that we'll use to open channels
|
||||||
// between for this test.
|
// between for this test. Dave gets some coins that will be used to
|
||||||
|
// fund the PSBT, just to make sure that Carol has an empty wallet.
|
||||||
carol, err := net.NewNode("carol", nil)
|
carol, err := net.NewNode("carol", nil)
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("unable to start new node: %v", err)
|
|
||||||
}
|
|
||||||
defer shutdownAndAssert(net, t, carol)
|
defer shutdownAndAssert(net, t, carol)
|
||||||
|
|
||||||
dave, err := net.NewNode("dave", nil)
|
dave, err := net.NewNode("dave", nil)
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("unable to start new node: %v", err)
|
|
||||||
}
|
|
||||||
defer shutdownAndAssert(net, t, dave)
|
defer shutdownAndAssert(net, t, dave)
|
||||||
|
err = net.SendCoins(ctxb, btcutil.SatoshiPerBitcoin, dave)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to send coins to dave: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Before we start the test, we'll ensure both sides are connected so
|
// Before we start the test, we'll ensure both sides are connected so
|
||||||
// the funding flow can be properly executed.
|
// the funding flow can be properly executed.
|
||||||
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
err = net.EnsureConnected(ctxt, carol, dave)
|
err = net.EnsureConnected(ctxt, carol, dave)
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("unable to connect peers: %v", err)
|
|
||||||
}
|
|
||||||
err = net.EnsureConnected(ctxt, carol, net.Alice)
|
err = net.EnsureConnected(ctxt, carol, net.Alice)
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("unable to connect peers: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point, we can begin our PSBT channel funding workflow. We'll
|
// At this point, we can begin our PSBT channel funding workflow. We'll
|
||||||
// start by generating a pending channel ID externally that will be used
|
// start by generating a pending channel ID externally that will be used
|
||||||
// to track this new funding type.
|
// to track this new funding type.
|
||||||
var pendingChanID [32]byte
|
var pendingChanID [32]byte
|
||||||
if _, err := rand.Read(pendingChanID[:]); err != nil {
|
_, err = rand.Read(pendingChanID[:])
|
||||||
t.Fatalf("unable to gen pending chan ID: %v", err)
|
require.NoError(t.t, err)
|
||||||
}
|
|
||||||
|
|
||||||
// We'll also test batch funding of two channels so we need another ID.
|
// We'll also test batch funding of two channels so we need another ID.
|
||||||
var pendingChanID2 [32]byte
|
var pendingChanID2 [32]byte
|
||||||
if _, err := rand.Read(pendingChanID2[:]); err != nil {
|
_, err = rand.Read(pendingChanID2[:])
|
||||||
t.Fatalf("unable to gen pending chan ID: %v", err)
|
require.NoError(t.t, err)
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we have the pending channel ID, Carol will open the channel
|
// Now that we have the pending channel ID, Carol will open the channel
|
||||||
// by specifying a PSBT shim. We use the NoPublish flag here to avoid
|
// by specifying a PSBT shim. We use the NoPublish flag here to avoid
|
||||||
// publishing the whole batch TX too early.
|
// publishing the whole batch TX too early.
|
||||||
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
chanUpdates, psbtBytes, err := openChannelPsbt(
|
chanUpdates, tempPsbt, err := openChannelPsbt(
|
||||||
ctxt, carol, dave, lntest.OpenChannelParams{
|
ctxt, carol, dave, lntest.OpenChannelParams{
|
||||||
Amt: chanSize,
|
Amt: chanSize,
|
||||||
FundingShim: &lnrpc.FundingShim{
|
FundingShim: &lnrpc.FundingShim{
|
||||||
@ -81,16 +76,11 @@ func testPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("unable to open channel to dave: %v", err)
|
|
||||||
}
|
|
||||||
packet, err := psbt.NewFromRawBytes(bytes.NewReader(psbtBytes), false)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to parse returned PSBT: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's add a second channel to the batch. This time between carol and
|
// Let's add a second channel to the batch. This time between Carol and
|
||||||
// alice. We will the batch TX once this channel funding is complete.
|
// Alice. We will publish the batch TX once this channel funding is
|
||||||
|
// complete.
|
||||||
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
chanUpdates2, psbtBytes2, err := openChannelPsbt(
|
chanUpdates2, psbtBytes2, err := openChannelPsbt(
|
||||||
@ -101,59 +91,29 @@ func testPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
PsbtShim: &lnrpc.PsbtShim{
|
PsbtShim: &lnrpc.PsbtShim{
|
||||||
PendingChanId: pendingChanID2[:],
|
PendingChanId: pendingChanID2[:],
|
||||||
NoPublish: false,
|
NoPublish: false,
|
||||||
|
BasePsbt: tempPsbt,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("unable to open channel to alice: %v", err)
|
|
||||||
}
|
|
||||||
packet2, err := psbt.NewFromRawBytes(bytes.NewReader(psbtBytes2), false)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to parse returned PSBT: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We'll now create a fully signed transaction that sends to the outputs
|
// We'll now ask Dave's wallet to fund the PSBT for us. This will return
|
||||||
// encoded in the PSBT. We'll let the miner do it and convert the final
|
// a packet with inputs and outputs set but without any witness data.
|
||||||
// TX into a PSBT, that's way easier than assembling a PSBT manually.
|
// This is exactly what we need for the next step.
|
||||||
allOuts := append(packet.UnsignedTx.TxOut, packet2.UnsignedTx.TxOut...)
|
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
finalTx, err := net.Miner.CreateTransaction(allOuts, 5, true)
|
defer cancel()
|
||||||
if err != nil {
|
fundReq := &walletrpc.FundPsbtRequest{
|
||||||
t.Fatalf("unable to create funding transaction: %v", err)
|
Template: &walletrpc.FundPsbtRequest_Psbt{
|
||||||
}
|
Psbt: psbtBytes2,
|
||||||
|
},
|
||||||
// The helper function splits the final TX into the non-witness data
|
Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
|
||||||
// encoded in a PSBT and the witness data returned separately.
|
SatPerVbyte: 2,
|
||||||
unsignedPsbt, scripts, witnesses, err := createPsbtFromSignedTx(finalTx)
|
},
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to convert funding transaction into PSBT: %v",
|
|
||||||
err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The PSBT will also be checked if there are large enough inputs
|
|
||||||
// present. We need to add some fake UTXO information to the PSBT to
|
|
||||||
// tell it what size of inputs we have.
|
|
||||||
for idx, txIn := range unsignedPsbt.UnsignedTx.TxIn {
|
|
||||||
utxPrevOut := txIn.PreviousOutPoint.Index
|
|
||||||
fakeUtxo := &wire.MsgTx{
|
|
||||||
Version: 2,
|
|
||||||
TxIn: []*wire.TxIn{{}},
|
|
||||||
TxOut: make([]*wire.TxOut, utxPrevOut+1),
|
|
||||||
}
|
|
||||||
for idx := range fakeUtxo.TxOut {
|
|
||||||
fakeUtxo.TxOut[idx] = &wire.TxOut{}
|
|
||||||
}
|
|
||||||
fakeUtxo.TxOut[utxPrevOut].Value = 10000000000
|
|
||||||
unsignedPsbt.Inputs[idx].NonWitnessUtxo = fakeUtxo
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize the PSBT with the faked UTXO information.
|
|
||||||
var buf bytes.Buffer
|
|
||||||
err = unsignedPsbt.Serialize(&buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error serializing PSBT: %v", err)
|
|
||||||
}
|
}
|
||||||
|
fundResp, err := dave.WalletKitClient.FundPsbt(ctxt, fundReq)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
// We have a PSBT that has no witness data yet, which is exactly what we
|
// We have a PSBT that has no witness data yet, which is exactly what we
|
||||||
// need for the next step: Verify the PSBT with the funding intents.
|
// need for the next step: Verify the PSBT with the funding intents.
|
||||||
@ -161,67 +121,50 @@ func testPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
|
Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
|
||||||
PsbtVerify: &lnrpc.FundingPsbtVerify{
|
PsbtVerify: &lnrpc.FundingPsbtVerify{
|
||||||
PendingChanId: pendingChanID[:],
|
PendingChanId: pendingChanID[:],
|
||||||
FundedPsbt: buf.Bytes(),
|
FundedPsbt: fundResp.FundedPsbt,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("error verifying PSBT with funding intent: %v", err)
|
|
||||||
}
|
|
||||||
_, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{
|
_, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{
|
||||||
Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
|
Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
|
||||||
PsbtVerify: &lnrpc.FundingPsbtVerify{
|
PsbtVerify: &lnrpc.FundingPsbtVerify{
|
||||||
PendingChanId: pendingChanID2[:],
|
PendingChanId: pendingChanID2[:],
|
||||||
FundedPsbt: buf.Bytes(),
|
FundedPsbt: fundResp.FundedPsbt,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("error verifying PSBT with funding intent 2: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we'll add the witness data back into the PSBT to make it a
|
// Now we'll ask Dave's wallet to sign the PSBT so we can finish the
|
||||||
// complete and signed transaction that can be finalized. We'll trick
|
// funding flow.
|
||||||
// a bit by putting the script sig back directly, because we know we
|
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
// will only get non-witness outputs from the miner wallet.
|
defer cancel()
|
||||||
for idx := range finalTx.TxIn {
|
finalizeReq := &walletrpc.FinalizePsbtRequest{
|
||||||
if len(witnesses[idx]) > 0 {
|
FundedPsbt: fundResp.FundedPsbt,
|
||||||
t.Fatalf("unexpected witness inputs in wallet TX")
|
|
||||||
}
|
|
||||||
unsignedPsbt.Inputs[idx].FinalScriptSig = scripts[idx]
|
|
||||||
}
|
}
|
||||||
|
finalizeRes, err := dave.WalletKitClient.FinalizePsbt(ctxt, finalizeReq)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
// We've signed our PSBT now, let's pass it to the intent again.
|
// We've signed our PSBT now, let's pass it to the intent again.
|
||||||
buf.Reset()
|
|
||||||
err = unsignedPsbt.Serialize(&buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error serializing PSBT: %v", err)
|
|
||||||
}
|
|
||||||
_, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{
|
_, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{
|
||||||
Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{
|
Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{
|
||||||
PsbtFinalize: &lnrpc.FundingPsbtFinalize{
|
PsbtFinalize: &lnrpc.FundingPsbtFinalize{
|
||||||
PendingChanId: pendingChanID[:],
|
PendingChanId: pendingChanID[:],
|
||||||
SignedPsbt: buf.Bytes(),
|
SignedPsbt: finalizeRes.SignedPsbt,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("error finalizing PSBT with funding intent: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume the "channel pending" update. This waits until the funding
|
// Consume the "channel pending" update. This waits until the funding
|
||||||
// transaction was fully compiled.
|
// transaction was fully compiled.
|
||||||
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
updateResp, err := receiveChanUpdate(ctxt, chanUpdates)
|
updateResp, err := receiveChanUpdate(ctxt, chanUpdates)
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("unable to consume channel update message: %v", err)
|
|
||||||
}
|
|
||||||
upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
|
upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
|
||||||
if !ok {
|
require.True(t.t, ok)
|
||||||
t.Fatalf("expected PSBT funding update, instead got %v",
|
|
||||||
updateResp)
|
|
||||||
}
|
|
||||||
chanPoint := &lnrpc.ChannelPoint{
|
chanPoint := &lnrpc.ChannelPoint{
|
||||||
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
|
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
|
||||||
FundingTxidBytes: upd.ChanPending.Txid,
|
FundingTxidBytes: upd.ChanPending.Txid,
|
||||||
@ -231,29 +174,21 @@ func testPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
|
|
||||||
// No transaction should have been published yet.
|
// No transaction should have been published yet.
|
||||||
mempool, err := net.Miner.Node.GetRawMempool()
|
mempool, err := net.Miner.Node.GetRawMempool()
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("error querying mempool: %v", err)
|
require.Equal(t.t, 0, len(mempool))
|
||||||
}
|
|
||||||
if len(mempool) != 0 {
|
|
||||||
t.Fatalf("unexpected txes in mempool: %v", mempool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's progress the second channel now. This time we'll use the raw
|
// Let's progress the second channel now. This time we'll use the raw
|
||||||
// wire format transaction directly.
|
// wire format transaction directly.
|
||||||
buf.Reset()
|
|
||||||
err = finalTx.Serialize(&buf)
|
|
||||||
require.NoError(t.t, err)
|
require.NoError(t.t, err)
|
||||||
_, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{
|
_, err = carol.FundingStateStep(ctxb, &lnrpc.FundingTransitionMsg{
|
||||||
Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{
|
Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{
|
||||||
PsbtFinalize: &lnrpc.FundingPsbtFinalize{
|
PsbtFinalize: &lnrpc.FundingPsbtFinalize{
|
||||||
PendingChanId: pendingChanID2[:],
|
PendingChanId: pendingChanID2[:],
|
||||||
FinalRawTx: buf.Bytes(),
|
FinalRawTx: finalizeRes.RawFinalTx,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("error finalizing PSBT with funding intent 2: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume the "channel pending" update for the second channel. This
|
// Consume the "channel pending" update for the second channel. This
|
||||||
// waits until the funding transaction was fully compiled and in this
|
// waits until the funding transaction was fully compiled and in this
|
||||||
@ -261,14 +196,9 @@ func testPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
updateResp2, err := receiveChanUpdate(ctxt, chanUpdates2)
|
updateResp2, err := receiveChanUpdate(ctxt, chanUpdates2)
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("unable to consume channel update message: %v", err)
|
|
||||||
}
|
|
||||||
upd2, ok := updateResp2.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
|
upd2, ok := updateResp2.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
|
||||||
if !ok {
|
require.True(t.t, ok)
|
||||||
t.Fatalf("expected PSBT funding update, instead got %v",
|
|
||||||
updateResp2)
|
|
||||||
}
|
|
||||||
chanPoint2 := &lnrpc.ChannelPoint{
|
chanPoint2 := &lnrpc.ChannelPoint{
|
||||||
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
|
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
|
||||||
FundingTxidBytes: upd2.ChanPending.Txid,
|
FundingTxidBytes: upd2.ChanPending.Txid,
|
||||||
@ -278,19 +208,19 @@ func testPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
|
|
||||||
// Great, now we can mine a block to get the transaction confirmed, then
|
// Great, now we can mine a block to get the transaction confirmed, then
|
||||||
// wait for the new channel to be propagated through the network.
|
// wait for the new channel to be propagated through the network.
|
||||||
|
var finalTx wire.MsgTx
|
||||||
|
err = finalTx.Deserialize(bytes.NewReader(finalizeRes.RawFinalTx))
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
txHash := finalTx.TxHash()
|
txHash := finalTx.TxHash()
|
||||||
block := mineBlocks(t, net, 6, 1)[0]
|
block := mineBlocks(t, net, 6, 1)[0]
|
||||||
assertTxInBlock(t, block, &txHash)
|
assertTxInBlock(t, block, &txHash)
|
||||||
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("carol didn't report channel: %v", err)
|
|
||||||
}
|
|
||||||
err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint2)
|
err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint2)
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("carol didn't report channel 2: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the channel open, ensure that it is counted towards Carol's
|
// With the channel open, ensure that it is counted towards Carol's
|
||||||
// total channel balance.
|
// total channel balance.
|
||||||
@ -298,12 +228,8 @@ func testPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
balRes, err := carol.ChannelBalance(ctxt, balReq)
|
balRes, err := carol.ChannelBalance(ctxt, balReq)
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("unable to get carol's balance: %v", err)
|
require.NotEqual(t.t, int64(0), balRes.LocalBalance.Sat)
|
||||||
}
|
|
||||||
if balRes.LocalBalance.Sat == 0 {
|
|
||||||
t.Fatalf("carol has an empty channel balance")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next, to make sure the channel functions as normal, we'll make some
|
// Next, to make sure the channel functions as normal, we'll make some
|
||||||
// payments within the channel.
|
// payments within the channel.
|
||||||
@ -314,17 +240,13 @@ func testPsbtChanFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
}
|
}
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
resp, err := dave.AddInvoice(ctxt, invoice)
|
resp, err := dave.AddInvoice(ctxt, invoice)
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("unable to add invoice: %v", err)
|
|
||||||
}
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
err = completePaymentRequests(
|
err = completePaymentRequests(
|
||||||
ctxt, carol, carol.RouterClient, []string{resp.PaymentRequest},
|
ctxt, carol, carol.RouterClient, []string{resp.PaymentRequest},
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
if err != nil {
|
require.NoError(t.t, err)
|
||||||
t.Fatalf("unable to make payments between Carol and Dave")
|
|
||||||
}
|
|
||||||
|
|
||||||
// To conclude, we'll close the newly created channel between Carol and
|
// To conclude, we'll close the newly created channel between Carol and
|
||||||
// Dave. This function will also block until the channel is closed and
|
// Dave. This function will also block until the channel is closed and
|
||||||
@ -421,31 +343,3 @@ func receiveChanUpdate(ctx context.Context,
|
|||||||
return updateMsg, nil
|
return updateMsg, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// createPsbtFromSignedTx is a utility function to create a PSBT from an
|
|
||||||
// already-signed transaction, so we can test reconstructing, signing and
|
|
||||||
// extracting it. Returned are: an unsigned transaction serialization, a list
|
|
||||||
// of scriptSigs, one per input, and a list of witnesses, one per input.
|
|
||||||
func createPsbtFromSignedTx(tx *wire.MsgTx) (*psbt.Packet, [][]byte,
|
|
||||||
[]wire.TxWitness, error) {
|
|
||||||
|
|
||||||
scriptSigs := make([][]byte, 0, len(tx.TxIn))
|
|
||||||
witnesses := make([]wire.TxWitness, 0, len(tx.TxIn))
|
|
||||||
tx2 := tx.Copy()
|
|
||||||
|
|
||||||
// Blank out signature info in inputs
|
|
||||||
for i, tin := range tx2.TxIn {
|
|
||||||
tin.SignatureScript = nil
|
|
||||||
scriptSigs = append(scriptSigs, tx.TxIn[i].SignatureScript)
|
|
||||||
tin.Witness = nil
|
|
||||||
witnesses = append(witnesses, tx.TxIn[i].Witness)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Outputs always contain: (value, scriptPubkey) so don't need
|
|
||||||
// amending. Now tx2 is tx with all signing data stripped out
|
|
||||||
unsignedPsbt, err := psbt.NewFromUnsignedTx(tx2)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
return unsignedPsbt, scriptSigs, witnesses, nil
|
|
||||||
}
|
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/btcsuite/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
||||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||||
|
|
||||||
@ -141,6 +142,18 @@ func (w *WalletController) ReleaseOutput(wtxmgr.LockID, wire.OutPoint) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FundPsbt currently does nothing.
|
||||||
|
func (w *WalletController) FundPsbt(_ *psbt.Packet,
|
||||||
|
_ chainfee.SatPerKWeight) (int32, error) {
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinalizePsbt currently does nothing.
|
||||||
|
func (w *WalletController) FinalizePsbt(_ *psbt.Packet) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// PublishTransaction sends a transaction to the PublishedTransactions chan.
|
// PublishTransaction sends a transaction to the PublishedTransactions chan.
|
||||||
func (w *WalletController) PublishTransaction(tx *wire.MsgTx, _ string) error {
|
func (w *WalletController) PublishTransaction(tx *wire.MsgTx, _ string) error {
|
||||||
w.PublishedTransactions <- tx
|
w.PublishedTransactions <- tx
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/btcsuite/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcwallet/chain"
|
"github.com/btcsuite/btcwallet/chain"
|
||||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||||
base "github.com/btcsuite/btcwallet/wallet"
|
base "github.com/btcsuite/btcwallet/wallet"
|
||||||
@ -690,6 +691,51 @@ func (b *BtcWallet) ListTransactionDetails(startHeight,
|
|||||||
return txDetails, nil
|
return txDetails, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FundPsbt creates a fully populated PSBT packet that contains enough
|
||||||
|
// inputs to fund the outputs specified in the passed in packet with the
|
||||||
|
// specified fee rate. If there is change left, a change output from the
|
||||||
|
// internal wallet is added and the index of the change output is returned.
|
||||||
|
// Otherwise no additional output is created and the index -1 is returned.
|
||||||
|
//
|
||||||
|
// NOTE: If the packet doesn't contain any inputs, coin selection is
|
||||||
|
// performed automatically. If the packet does contain any inputs, it is
|
||||||
|
// assumed that full coin selection happened externally and no
|
||||||
|
// additional inputs are added. If the specified inputs aren't enough to
|
||||||
|
// fund the outputs with the given fee rate, an error is returned.
|
||||||
|
// No lock lease is acquired for any of the selected/validated inputs.
|
||||||
|
// It is in the caller's responsibility to lock the inputs before
|
||||||
|
// handing them out.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) FundPsbt(packet *psbt.Packet,
|
||||||
|
feeRate chainfee.SatPerKWeight) (int32, error) {
|
||||||
|
|
||||||
|
// The fee rate is passed in using units of sat/kw, so we'll convert
|
||||||
|
// this to sat/KB as the CreateSimpleTx method requires this unit.
|
||||||
|
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
|
||||||
|
|
||||||
|
// Let the wallet handle coin selection and/or fee estimation based on
|
||||||
|
// the partial TX information in the packet.
|
||||||
|
return b.wallet.FundPsbt(packet, defaultAccount, feeSatPerKB)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinalizePsbt expects a partial transaction with all inputs and
|
||||||
|
// outputs fully declared and tries to sign all inputs that belong to
|
||||||
|
// the wallet. Lnd must be the last signer of the transaction. That
|
||||||
|
// means, if there are any unsigned non-witness inputs or inputs without
|
||||||
|
// UTXO information attached or inputs without witness data that do not
|
||||||
|
// belong to lnd's wallet, this method will fail. If no error is
|
||||||
|
// returned, the PSBT is ready to be extracted and the final TX within
|
||||||
|
// to be broadcast.
|
||||||
|
//
|
||||||
|
// NOTE: This method does NOT publish the transaction after it's been
|
||||||
|
// finalized successfully.
|
||||||
|
//
|
||||||
|
// This is a part of the WalletController interface.
|
||||||
|
func (b *BtcWallet) FinalizePsbt(packet *psbt.Packet) error {
|
||||||
|
return b.wallet.FinalizePsbt(packet)
|
||||||
|
}
|
||||||
|
|
||||||
// txSubscriptionClient encapsulates the transaction notification client from
|
// txSubscriptionClient encapsulates the transaction notification client from
|
||||||
// the base wallet. Notifications received from the client will be proxied over
|
// the base wallet. Notifications received from the client will be proxied over
|
||||||
// two distinct channels.
|
// two distinct channels.
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
package btcwallet
|
package btcwallet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||||
base "github.com/btcsuite/btcwallet/wallet"
|
|
||||||
"github.com/btcsuite/btcwallet/walletdb"
|
"github.com/btcsuite/btcwallet/walletdb"
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
@ -24,82 +21,29 @@ import (
|
|||||||
//
|
//
|
||||||
// This is a part of the WalletController interface.
|
// This is a part of the WalletController interface.
|
||||||
func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, error) {
|
func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, error) {
|
||||||
// We manually look up the output within the tx store.
|
_, txOut, confirmations, err := b.wallet.FetchInputInfo(prevOut)
|
||||||
txid := &prevOut.Hash
|
|
||||||
txDetail, err := base.UnstableAPI(b.wallet).TxDetails(txid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if txDetail == nil {
|
|
||||||
return nil, lnwallet.ErrNotMine
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the output retrieved, we'll make an additional check to ensure
|
|
||||||
// we actually have control of this output. We do this because the check
|
|
||||||
// above only guarantees that the transaction is somehow relevant to us,
|
|
||||||
// like in the event of us being the sender of the transaction.
|
|
||||||
numOutputs := uint32(len(txDetail.TxRecord.MsgTx.TxOut))
|
|
||||||
if prevOut.Index >= numOutputs {
|
|
||||||
return nil, fmt.Errorf("invalid output index %v for "+
|
|
||||||
"transaction with %v outputs", prevOut.Index, numOutputs)
|
|
||||||
}
|
|
||||||
pkScript := txDetail.TxRecord.MsgTx.TxOut[prevOut.Index].PkScript
|
|
||||||
if _, err := b.fetchOutputAddr(pkScript); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then, we'll populate all of the information required by the struct.
|
// Then, we'll populate all of the information required by the struct.
|
||||||
addressType := lnwallet.UnknownAddressType
|
addressType := lnwallet.UnknownAddressType
|
||||||
switch {
|
switch {
|
||||||
case txscript.IsPayToWitnessPubKeyHash(pkScript):
|
case txscript.IsPayToWitnessPubKeyHash(txOut.PkScript):
|
||||||
addressType = lnwallet.WitnessPubKey
|
addressType = lnwallet.WitnessPubKey
|
||||||
case txscript.IsPayToScriptHash(pkScript):
|
case txscript.IsPayToScriptHash(txOut.PkScript):
|
||||||
addressType = lnwallet.NestedWitnessPubKey
|
addressType = lnwallet.NestedWitnessPubKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the number of confirmations the output currently has.
|
|
||||||
_, currentHeight, err := b.GetBestBlock()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to retrieve current height: %v",
|
|
||||||
err)
|
|
||||||
}
|
|
||||||
confs := int64(0)
|
|
||||||
if txDetail.Block.Height != -1 {
|
|
||||||
confs = int64(currentHeight - txDetail.Block.Height)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &lnwallet.Utxo{
|
return &lnwallet.Utxo{
|
||||||
AddressType: addressType,
|
AddressType: addressType,
|
||||||
Value: btcutil.Amount(
|
Value: btcutil.Amount(txOut.Value),
|
||||||
txDetail.TxRecord.MsgTx.TxOut[prevOut.Index].Value,
|
PkScript: txOut.PkScript,
|
||||||
),
|
Confirmations: confirmations,
|
||||||
PkScript: pkScript,
|
|
||||||
Confirmations: confs,
|
|
||||||
OutPoint: *prevOut,
|
OutPoint: *prevOut,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchOutputAddr attempts to fetch the managed address corresponding to the
|
|
||||||
// passed output script. This function is used to look up the proper key which
|
|
||||||
// should be used to sign a specified input.
|
|
||||||
func (b *BtcWallet) fetchOutputAddr(script []byte) (waddrmgr.ManagedAddress, error) {
|
|
||||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(script, b.netParams)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the case of a multi-sig output, several address may be extracted.
|
|
||||||
// Therefore, we simply select the key for the first address we know
|
|
||||||
// of.
|
|
||||||
for _, addr := range addrs {
|
|
||||||
addr, err := b.wallet.AddressInfo(addr)
|
|
||||||
if err == nil {
|
|
||||||
return addr, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, lnwallet.ErrNotMine
|
|
||||||
}
|
|
||||||
|
|
||||||
// deriveFromKeyLoc attempts to derive a private key using a fully specified
|
// deriveFromKeyLoc attempts to derive a private key using a fully specified
|
||||||
// KeyLocator.
|
// KeyLocator.
|
||||||
func deriveFromKeyLoc(scopedMgr *waddrmgr.ScopedKeyManager,
|
func deriveFromKeyLoc(scopedMgr *waddrmgr.ScopedKeyManager,
|
||||||
@ -273,83 +217,26 @@ func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx,
|
|||||||
func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx,
|
func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx,
|
||||||
signDesc *input.SignDescriptor) (*input.Script, error) {
|
signDesc *input.SignDescriptor) (*input.Script, error) {
|
||||||
|
|
||||||
outputScript := signDesc.Output.PkScript
|
|
||||||
walletAddr, err := b.fetchOutputAddr(outputScript)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pka := walletAddr.(waddrmgr.ManagedPubKeyAddress)
|
|
||||||
privKey, err := pka.PrivKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var witnessProgram []byte
|
|
||||||
inputScript := &input.Script{}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
|
|
||||||
// If we're spending p2wkh output nested within a p2sh output, then
|
|
||||||
// we'll need to attach a sigScript in addition to witness data.
|
|
||||||
case pka.AddrType() == waddrmgr.NestedWitnessPubKey:
|
|
||||||
pubKey := privKey.PubKey()
|
|
||||||
pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed())
|
|
||||||
|
|
||||||
// Next, we'll generate a valid sigScript that will allow us to
|
|
||||||
// spend the p2sh output. The sigScript will contain only a
|
|
||||||
// single push of the p2wkh witness program corresponding to
|
|
||||||
// the matching public key of this address.
|
|
||||||
p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(
|
|
||||||
pubKeyHash, b.netParams,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
witnessProgram, err = txscript.PayToAddrScript(p2wkhAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bldr := txscript.NewScriptBuilder()
|
|
||||||
bldr.AddData(witnessProgram)
|
|
||||||
sigScript, err := bldr.Script()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
inputScript.SigScript = sigScript
|
|
||||||
|
|
||||||
// Otherwise, this is a regular p2wkh output, so we include the
|
|
||||||
// witness program itself as the subscript to generate the proper
|
|
||||||
// sighash digest. As part of the new sighash digest algorithm, the
|
|
||||||
// p2wkh witness program will be expanded into a regular p2kh
|
|
||||||
// script.
|
|
||||||
default:
|
|
||||||
witnessProgram = outputScript
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a tweak (single or double) is specified, then we'll need to use
|
// If a tweak (single or double) is specified, then we'll need to use
|
||||||
// this tweak to derive the final private key to be used for signing
|
// this tweak to derive the final private key to be used for signing
|
||||||
// this output.
|
// this output.
|
||||||
privKey, err = maybeTweakPrivKey(signDesc, privKey)
|
privKeyTweaker := func(k *btcec.PrivateKey) (*btcec.PrivateKey, error) {
|
||||||
if err != nil {
|
return maybeTweakPrivKey(signDesc, k)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a valid witness stack for the input.
|
// Let the wallet compute the input script now.
|
||||||
// TODO(roasbeef): adhere to passed HashType
|
witness, sigScript, err := b.wallet.ComputeInputScript(
|
||||||
witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes,
|
tx, signDesc.Output, signDesc.InputIndex, signDesc.SigHashes,
|
||||||
signDesc.InputIndex, signDesc.Output.Value, witnessProgram,
|
signDesc.HashType, privKeyTweaker,
|
||||||
signDesc.HashType, privKey, true,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
inputScript.Witness = witnessScript
|
return &input.Script{
|
||||||
|
Witness: witness,
|
||||||
return inputScript, nil
|
SigScript: sigScript,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// A compile time check to ensure that BtcWallet implements the Signer
|
// A compile time check to ensure that BtcWallet implements the Signer
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package chanfunding
|
package chanfunding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -226,7 +225,7 @@ func (i *PsbtIntent) Verify(packet *psbt.Packet) error {
|
|||||||
outputSum := int64(0)
|
outputSum := int64(0)
|
||||||
for _, out := range packet.UnsignedTx.TxOut {
|
for _, out := range packet.UnsignedTx.TxOut {
|
||||||
outputSum += out.Value
|
outputSum += out.Value
|
||||||
if txOutsEqual(out, expectedOutput) {
|
if psbt.TxOutsEqual(out, expectedOutput) {
|
||||||
outputFound = true
|
outputFound = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,7 +240,7 @@ func (i *PsbtIntent) Verify(packet *psbt.Packet) error {
|
|||||||
if len(packet.UnsignedTx.TxIn) == 0 {
|
if len(packet.UnsignedTx.TxIn) == 0 {
|
||||||
return fmt.Errorf("PSBT has no inputs")
|
return fmt.Errorf("PSBT has no inputs")
|
||||||
}
|
}
|
||||||
sum, err := sumUtxoInputValues(packet)
|
sum, err := psbt.SumUtxoInputValues(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error determining input sum: %v", err)
|
return fmt.Errorf("error determining input sum: %v", err)
|
||||||
}
|
}
|
||||||
@ -305,11 +304,13 @@ func (i *PsbtIntent) FinalizeRawTX(rawTx *wire.MsgTx) error {
|
|||||||
if i.PendingPsbt == nil {
|
if i.PendingPsbt == nil {
|
||||||
return fmt.Errorf("PSBT was not verified first")
|
return fmt.Errorf("PSBT was not verified first")
|
||||||
}
|
}
|
||||||
err := verifyOutputsEqual(rawTx.TxOut, i.PendingPsbt.UnsignedTx.TxOut)
|
err := psbt.VerifyOutputsEqual(
|
||||||
|
rawTx.TxOut, i.PendingPsbt.UnsignedTx.TxOut,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("outputs differ from verified PSBT: %v", err)
|
return fmt.Errorf("outputs differ from verified PSBT: %v", err)
|
||||||
}
|
}
|
||||||
err = verifyInputPrevOutpointsEqual(
|
err = psbt.VerifyInputPrevOutpointsEqual(
|
||||||
rawTx.TxIn, i.PendingPsbt.UnsignedTx.TxIn,
|
rawTx.TxIn, i.PendingPsbt.UnsignedTx.TxIn,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -472,82 +473,6 @@ func (p *PsbtAssembler) ShouldPublishFundingTx() bool {
|
|||||||
// ConditionalPublishAssembler interface.
|
// ConditionalPublishAssembler interface.
|
||||||
var _ ConditionalPublishAssembler = (*PsbtAssembler)(nil)
|
var _ ConditionalPublishAssembler = (*PsbtAssembler)(nil)
|
||||||
|
|
||||||
// sumUtxoInputValues tries to extract the sum of all inputs specified in the
|
|
||||||
// UTXO fields of the PSBT. An error is returned if an input is specified that
|
|
||||||
// does not contain any UTXO information.
|
|
||||||
func sumUtxoInputValues(packet *psbt.Packet) (int64, error) {
|
|
||||||
// We take the TX ins of the unsigned TX as the truth for how many
|
|
||||||
// inputs there should be, as the fields in the extra data part of the
|
|
||||||
// PSBT can be empty.
|
|
||||||
if len(packet.UnsignedTx.TxIn) != len(packet.Inputs) {
|
|
||||||
return 0, fmt.Errorf("TX input length doesn't match PSBT " +
|
|
||||||
"input length")
|
|
||||||
}
|
|
||||||
inputSum := int64(0)
|
|
||||||
for idx, in := range packet.Inputs {
|
|
||||||
switch {
|
|
||||||
case in.WitnessUtxo != nil:
|
|
||||||
// Witness UTXOs only need to reference the TxOut.
|
|
||||||
inputSum += in.WitnessUtxo.Value
|
|
||||||
|
|
||||||
case in.NonWitnessUtxo != nil:
|
|
||||||
// Non-witness UTXOs reference to the whole transaction
|
|
||||||
// the UTXO resides in.
|
|
||||||
utxOuts := in.NonWitnessUtxo.TxOut
|
|
||||||
txIn := packet.UnsignedTx.TxIn[idx]
|
|
||||||
inputSum += utxOuts[txIn.PreviousOutPoint.Index].Value
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("input %d has no UTXO information",
|
|
||||||
idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return inputSum, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// txOutsEqual returns true if two transaction outputs are equal.
|
|
||||||
func txOutsEqual(out1, out2 *wire.TxOut) bool {
|
|
||||||
if out1 == nil || out2 == nil {
|
|
||||||
return out1 == out2
|
|
||||||
}
|
|
||||||
return out1.Value == out2.Value &&
|
|
||||||
bytes.Equal(out1.PkScript, out2.PkScript)
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyOutputsEqual verifies that the two slices of transaction outputs are
|
|
||||||
// deep equal to each other. We do the length check and manual loop to provide
|
|
||||||
// better error messages to the user than just returning "not equal".
|
|
||||||
func verifyOutputsEqual(outs1, outs2 []*wire.TxOut) error {
|
|
||||||
if len(outs1) != len(outs2) {
|
|
||||||
return fmt.Errorf("number of outputs are different")
|
|
||||||
}
|
|
||||||
for idx, out := range outs1 {
|
|
||||||
// There is a byte slice in the output so we can't use the
|
|
||||||
// equality operator.
|
|
||||||
if !txOutsEqual(out, outs2[idx]) {
|
|
||||||
return fmt.Errorf("output %d is different", idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyInputPrevOutpointsEqual verifies that the previous outpoints of the
|
|
||||||
// two slices of transaction inputs are deep equal to each other. We do the
|
|
||||||
// length check and manual loop to provide better error messages to the user
|
|
||||||
// than just returning "not equal".
|
|
||||||
func verifyInputPrevOutpointsEqual(ins1, ins2 []*wire.TxIn) error {
|
|
||||||
if len(ins1) != len(ins2) {
|
|
||||||
return fmt.Errorf("number of inputs are different")
|
|
||||||
}
|
|
||||||
for idx, in := range ins1 {
|
|
||||||
if in.PreviousOutPoint != ins2[idx].PreviousOutPoint {
|
|
||||||
return fmt.Errorf("previous outpoint of input %d is "+
|
|
||||||
"different", idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyInputsSigned verifies that the given list of inputs is non-empty and
|
// verifyInputsSigned verifies that the given list of inputs is non-empty and
|
||||||
// that all the inputs either contain a script signature or a witness stack.
|
// that all the inputs either contain a script signature or a witness stack.
|
||||||
func verifyInputsSigned(ins []*wire.TxIn) error {
|
func verifyInputsSigned(ins []*wire.TxIn) error {
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/btcsuite/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
||||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
@ -273,6 +274,37 @@ type WalletController interface {
|
|||||||
// is set. Labels must not be empty, and they are limited to 500 chars.
|
// is set. Labels must not be empty, and they are limited to 500 chars.
|
||||||
LabelTransaction(hash chainhash.Hash, label string, overwrite bool) error
|
LabelTransaction(hash chainhash.Hash, label string, overwrite bool) error
|
||||||
|
|
||||||
|
// FundPsbt creates a fully populated PSBT packet that contains enough
|
||||||
|
// inputs to fund the outputs specified in the passed in packet with the
|
||||||
|
// specified fee rate. If there is change left, a change output from the
|
||||||
|
// internal wallet is added and the index of the change output is
|
||||||
|
// returned. Otherwise no additional output is created and the index -1
|
||||||
|
// is returned.
|
||||||
|
//
|
||||||
|
// NOTE: If the packet doesn't contain any inputs, coin selection is
|
||||||
|
// performed automatically. If the packet does contain any inputs, it is
|
||||||
|
// assumed that full coin selection happened externally and no
|
||||||
|
// additional inputs are added. If the specified inputs aren't enough to
|
||||||
|
// fund the outputs with the given fee rate, an error is returned.
|
||||||
|
// No lock lease is acquired for any of the selected/validated inputs.
|
||||||
|
// It is in the caller's responsibility to lock the inputs before
|
||||||
|
// handing them out.
|
||||||
|
FundPsbt(packet *psbt.Packet, feeRate chainfee.SatPerKWeight) (int32,
|
||||||
|
error)
|
||||||
|
|
||||||
|
// FinalizePsbt expects a partial transaction with all inputs and
|
||||||
|
// outputs fully declared and tries to sign all inputs that belong to
|
||||||
|
// the wallet. Lnd must be the last signer of the transaction. That
|
||||||
|
// means, if there are any unsigned non-witness inputs or inputs without
|
||||||
|
// UTXO information attached or inputs without witness data that do not
|
||||||
|
// belong to lnd's wallet, this method will fail. If no error is
|
||||||
|
// returned, the PSBT is ready to be extracted and the final TX within
|
||||||
|
// to be broadcast.
|
||||||
|
//
|
||||||
|
// NOTE: This method does NOT publish the transaction after it's been
|
||||||
|
// finalized successfully.
|
||||||
|
FinalizePsbt(packet *psbt.Packet) error
|
||||||
|
|
||||||
// SubscribeTransactions returns a TransactionSubscription client which
|
// SubscribeTransactions returns a TransactionSubscription client which
|
||||||
// is capable of receiving async notifications as new transactions
|
// is capable of receiving async notifications as new transactions
|
||||||
// related to the wallet are seen within the network, or found in
|
// related to the wallet are seen within the network, or found in
|
||||||
|
Loading…
Reference in New Issue
Block a user