btcjson,rpcclient: add support for PSBT commands to rpcclient
This commit is contained in:
parent
fff96610aa
commit
6f49f1f194
6 changed files with 283 additions and 3 deletions
|
@ -80,11 +80,24 @@ func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]fl
|
|||
}
|
||||
}
|
||||
|
||||
// ChangeType defines the different output types to use for the change address
|
||||
// of a transaction built by the node.
|
||||
type ChangeType string
|
||||
|
||||
var (
|
||||
// ChangeTypeLegacy indicates a P2PKH change address type.
|
||||
ChangeTypeLegacy ChangeType = "legacy"
|
||||
// ChangeTypeP2SHSegWit indicates a P2WPKH-in-P2SH change address type.
|
||||
ChangeTypeP2SHSegWit ChangeType = "p2sh-segwit"
|
||||
// ChangeTypeBech32 indicates a P2WPKH change address type.
|
||||
ChangeTypeBech32 ChangeType = "bech32"
|
||||
)
|
||||
|
||||
// FundRawTransactionOpts are the different options that can be passed to rawtransaction
|
||||
type FundRawTransactionOpts struct {
|
||||
ChangeAddress *string `json:"changeAddress,omitempty"`
|
||||
ChangePosition *int `json:"changePosition,omitempty"`
|
||||
ChangeType *string `json:"change_type,omitempty"`
|
||||
ChangeType *ChangeType `json:"change_type,omitempty"`
|
||||
IncludeWatching *bool `json:"includeWatching,omitempty"`
|
||||
LockUnspents *bool `json:"lockUnspents,omitempty"`
|
||||
FeeRate *float64 `json:"feeRate,omitempty"` // BTC/kB
|
||||
|
|
|
@ -127,7 +127,7 @@ func TestChainSvrCmds(t *testing.T) {
|
|||
}
|
||||
changeAddress := "bcrt1qeeuctq9wutlcl5zatge7rjgx0k45228cxez655"
|
||||
change := 1
|
||||
changeType := "legacy"
|
||||
changeType := btcjson.ChangeTypeLegacy
|
||||
watching := true
|
||||
lockUnspents := true
|
||||
feeRate := 0.7
|
||||
|
@ -151,7 +151,7 @@ func TestChainSvrCmds(t *testing.T) {
|
|||
unmarshalled: func() interface{} {
|
||||
changeAddress := "bcrt1qeeuctq9wutlcl5zatge7rjgx0k45228cxez655"
|
||||
change := 1
|
||||
changeType := "legacy"
|
||||
changeType := btcjson.ChangeTypeLegacy
|
||||
watching := true
|
||||
lockUnspents := true
|
||||
feeRate := 0.7
|
||||
|
|
|
@ -8,8 +8,11 @@
|
|||
package btcjson
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// AddMultisigAddressCmd defines the addmutisigaddress JSON-RPC command.
|
||||
|
@ -893,6 +896,88 @@ func NewImportMultiCmd(requests []ImportMultiRequest, options *ImportMultiOption
|
|||
}
|
||||
}
|
||||
|
||||
// PsbtInput represents an input to include in the PSBT created by the
|
||||
// WalletCreateFundedPsbtCmd command.
|
||||
type PsbtInput struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
}
|
||||
|
||||
// PsbtOutput represents an output to include in the PSBT created by the
|
||||
// WalletCreateFundedPsbtCmd command.
|
||||
type PsbtOutput map[string]interface{}
|
||||
|
||||
// NewPsbtOutput returns a new instance of a PSBT output to use with the
|
||||
// WalletCreateFundedPsbtCmd command.
|
||||
func NewPsbtOutput(address string, amount btcutil.Amount) PsbtOutput {
|
||||
return PsbtOutput{address: amount.ToBTC()}
|
||||
}
|
||||
|
||||
// NewPsbtDataOutput returns a new instance of a PSBT data output to use with
|
||||
// the WalletCreateFundedPsbtCmd command.
|
||||
func NewPsbtDataOutput(data []byte) PsbtOutput {
|
||||
return PsbtOutput{"data": hex.EncodeToString(data)}
|
||||
}
|
||||
|
||||
// WalletCreateFundedPsbtOpts represents the optional options struct provided
|
||||
// with a WalletCreateFundedPsbtCmd command.
|
||||
type WalletCreateFundedPsbtOpts struct {
|
||||
ChangeAddress *string `json:"changeAddress,omitempty"`
|
||||
ChangePosition *int64 `json:"changePosition,omitempty"`
|
||||
ChangeType *ChangeType `json:"change_type,omitempty"`
|
||||
IncludeWatching *bool `json:"includeWatching,omitempty"`
|
||||
LockUnspents *bool `json:"lockUnspents,omitempty"`
|
||||
FeeRate *int64 `json:"feeRate,omitempty"`
|
||||
SubtractFeeFromOutputs *[]int64 `json:"subtractFeeFromOutputs,omitempty"`
|
||||
Replaceable *bool `json:"replaceable,omitempty"`
|
||||
ConfTarget *int64 `json:"conf_target,omitempty"`
|
||||
EstimateMode *string `json:"estimate_mode,omitempty"`
|
||||
}
|
||||
|
||||
// WalletCreateFundedPsbtCmd defines the walletcreatefundedpsbt JSON-RPC command.
|
||||
type WalletCreateFundedPsbtCmd struct {
|
||||
Inputs []PsbtInput
|
||||
Outputs []PsbtOutput
|
||||
Locktime *uint32
|
||||
Options *WalletCreateFundedPsbtOpts
|
||||
Bip32Derivs *bool
|
||||
}
|
||||
|
||||
// NewWalletCreateFundedPsbtCmd returns a new instance which can be used to issue a
|
||||
// walletcreatefundedpsbt JSON-RPC command.
|
||||
func NewWalletCreateFundedPsbtCmd(
|
||||
inputs []PsbtInput, outputs []PsbtOutput, locktime *uint32,
|
||||
options *WalletCreateFundedPsbtOpts, bip32Derivs *bool,
|
||||
) *WalletCreateFundedPsbtCmd {
|
||||
return &WalletCreateFundedPsbtCmd{
|
||||
Inputs: inputs,
|
||||
Outputs: outputs,
|
||||
Locktime: locktime,
|
||||
Options: options,
|
||||
Bip32Derivs: bip32Derivs,
|
||||
}
|
||||
}
|
||||
|
||||
// WalletProcessPsbtCmd defines the walletprocesspsbt JSON-RPC command.
|
||||
type WalletProcessPsbtCmd struct {
|
||||
Psbt string
|
||||
Sign *bool `jsonrpcdefault:"true"`
|
||||
SighashType *string `jsonrpcdefault:"\"ALL\""`
|
||||
Bip32Derivs *bool
|
||||
}
|
||||
|
||||
// NewWalletProcessPsbtCmd returns a new instance which can be used to issue a
|
||||
// walletprocesspsbt JSON-RPC command.
|
||||
func NewWalletProcessPsbtCmd(psbt string, sign *bool, sighashType *string, bip32Derivs *bool) *WalletProcessPsbtCmd {
|
||||
return &WalletProcessPsbtCmd{
|
||||
Psbt: psbt,
|
||||
Sign: sign,
|
||||
SighashType: sighashType,
|
||||
Bip32Derivs: bip32Derivs,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// The commands in this file are only usable with a wallet server.
|
||||
flags := UFWalletOnly
|
||||
|
@ -939,4 +1024,6 @@ func init() {
|
|||
MustRegisterCmd("walletlock", (*WalletLockCmd)(nil), flags)
|
||||
MustRegisterCmd("walletpassphrase", (*WalletPassphraseCmd)(nil), flags)
|
||||
MustRegisterCmd("walletpassphrasechange", (*WalletPassphraseChangeCmd)(nil), flags)
|
||||
MustRegisterCmd("walletcreatefundedpsbt", (*WalletCreateFundedPsbtCmd)(nil), flags)
|
||||
MustRegisterCmd("walletprocesspsbt", (*WalletProcessPsbtCmd)(nil), flags)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// TestWalletSvrCmds tests all of the wallet server commands marshal and
|
||||
|
@ -1495,6 +1496,81 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "walletcreatefundedpsbt",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd(
|
||||
"walletcreatefundedpsbt",
|
||||
[]btcjson.PsbtInput{
|
||||
{
|
||||
Txid: "1234",
|
||||
Vout: 0,
|
||||
Sequence: 0,
|
||||
},
|
||||
},
|
||||
[]btcjson.PsbtOutput{
|
||||
btcjson.NewPsbtOutput("1234", btcutil.Amount(1234)),
|
||||
btcjson.NewPsbtDataOutput([]byte{1, 2, 3, 4}),
|
||||
},
|
||||
btcjson.Uint32(1),
|
||||
btcjson.WalletCreateFundedPsbtOpts{},
|
||||
btcjson.Bool(true),
|
||||
)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewWalletCreateFundedPsbtCmd(
|
||||
[]btcjson.PsbtInput{
|
||||
{
|
||||
Txid: "1234",
|
||||
Vout: 0,
|
||||
Sequence: 0,
|
||||
},
|
||||
},
|
||||
[]btcjson.PsbtOutput{
|
||||
btcjson.NewPsbtOutput("1234", btcutil.Amount(1234)),
|
||||
btcjson.NewPsbtDataOutput([]byte{1, 2, 3, 4}),
|
||||
},
|
||||
btcjson.Uint32(1),
|
||||
&btcjson.WalletCreateFundedPsbtOpts{},
|
||||
btcjson.Bool(true),
|
||||
)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"walletcreatefundedpsbt","params":[[{"txid":"1234","vout":0,"sequence":0}],[{"1234":0.00001234},{"data":"01020304"}],1,{},true],"id":1}`,
|
||||
unmarshalled: &btcjson.WalletCreateFundedPsbtCmd{
|
||||
Inputs: []btcjson.PsbtInput{
|
||||
{
|
||||
Txid: "1234",
|
||||
Vout: 0,
|
||||
Sequence: 0,
|
||||
},
|
||||
},
|
||||
Outputs: []btcjson.PsbtOutput{
|
||||
btcjson.NewPsbtOutput("1234", btcutil.Amount(1234)),
|
||||
btcjson.NewPsbtDataOutput([]byte{1, 2, 3, 4}),
|
||||
},
|
||||
Locktime: btcjson.Uint32(1),
|
||||
Options: &btcjson.WalletCreateFundedPsbtOpts{},
|
||||
Bip32Derivs: btcjson.Bool(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "walletprocesspsbt",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd(
|
||||
"walletprocesspsbt", "1234", btcjson.Bool(true), btcjson.String("ALL"), btcjson.Bool(true))
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewWalletProcessPsbtCmd(
|
||||
"1234", btcjson.Bool(true), btcjson.String("ALL"), btcjson.Bool(true))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"walletprocesspsbt","params":["1234",true,"ALL",true],"id":1}`,
|
||||
unmarshalled: &btcjson.WalletProcessPsbtCmd{
|
||||
Psbt: "1234",
|
||||
Sign: btcjson.Bool(true),
|
||||
SighashType: btcjson.String("ALL"),
|
||||
Bip32Derivs: btcjson.Bool(true),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
|
|
|
@ -186,3 +186,18 @@ type ImportMultiResults []struct {
|
|||
Error *RPCError `json:"error,omitempty"`
|
||||
Warnings *[]string `json:"warnings,omitempty"`
|
||||
}
|
||||
|
||||
// WalletCreateFundedPsbtResult models the data returned from the
|
||||
// walletcreatefundedpsbtresult command.
|
||||
type WalletCreateFundedPsbtResult struct {
|
||||
Psbt string `json:"psbt"`
|
||||
Fee float64 `json:"fee"`
|
||||
ChangePos int64 `json:"changepos"`
|
||||
}
|
||||
|
||||
// WalletProcessPsbtResult models the data returned from the
|
||||
// walletprocesspsbtresult command.
|
||||
type WalletProcessPsbtResult struct {
|
||||
Psbt string `json:"psbt"`
|
||||
Complete bool `json:"complete"`
|
||||
}
|
||||
|
|
|
@ -2424,6 +2424,95 @@ func (c *Client) GetInfo() (*btcjson.InfoWalletResult, error) {
|
|||
return c.GetInfoAsync().Receive()
|
||||
}
|
||||
|
||||
// FutureImportPubKeyResult is a future promise to deliver the result of an
|
||||
// WalletCreateFundedPsbt RPC invocation (or an applicable error).
|
||||
type FutureWalletCreateFundedPsbtResult chan *response
|
||||
|
||||
// Receive waits for the response promised by the future and returns the
|
||||
// partially signed transaction in PSBT format along with the resulting fee
|
||||
// and change output index.
|
||||
func (r FutureWalletCreateFundedPsbtResult) Receive() (*btcjson.WalletCreateFundedPsbtResult, error) {
|
||||
res, err := receiveFuture(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unmarshal result as a getinfo result object.
|
||||
var psbtRes btcjson.WalletCreateFundedPsbtResult
|
||||
err = json.Unmarshal(res, &psbtRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &psbtRes, nil
|
||||
}
|
||||
|
||||
// WalletCreateFundedPsbtAsync returns an instance of a type that can be used
|
||||
// to get the result of the RPC at some future time by invoking the Receive
|
||||
// function on the returned instance.
|
||||
//
|
||||
// See WalletCreateFundedPsbt for the blocking version and more details.
|
||||
func (c *Client) WalletCreateFundedPsbtAsync(
|
||||
inputs []btcjson.PsbtInput, outputs []btcjson.PsbtOutput, locktime *uint32,
|
||||
options *btcjson.WalletCreateFundedPsbtOpts, bip32Derivs *bool,
|
||||
) FutureWalletCreateFundedPsbtResult {
|
||||
cmd := btcjson.NewWalletCreateFundedPsbtCmd(inputs, outputs, locktime, options, bip32Derivs)
|
||||
return c.sendCmd(cmd)
|
||||
}
|
||||
|
||||
// WalletCreateFundedPsbt creates and funds a transaction in the Partially
|
||||
// Signed Transaction format. Inputs will be added if supplied inputs are not
|
||||
// enough.
|
||||
func (c *Client) WalletCreateFundedPsbt(
|
||||
inputs []btcjson.PsbtInput, outputs []btcjson.PsbtOutput, locktime *uint32,
|
||||
options *btcjson.WalletCreateFundedPsbtOpts, bip32Derivs *bool,
|
||||
) (*btcjson.WalletCreateFundedPsbtResult, error) {
|
||||
return c.WalletCreateFundedPsbtAsync(inputs, outputs, locktime, options, bip32Derivs).Receive()
|
||||
}
|
||||
|
||||
// FutureWalletProcessPsbtResult is a future promise to deliver the result of a
|
||||
// WalletCreateFundedPsb RPC invocation (or an applicable error).
|
||||
type FutureWalletProcessPsbtResult chan *response
|
||||
|
||||
// Receive waits for the response promised by the future and returns an updated
|
||||
// PSBT with signed inputs from the wallet and a boolen indicating if the the
|
||||
// transaction has a complete set of signatures.
|
||||
func (r FutureWalletProcessPsbtResult) Receive() (*btcjson.WalletProcessPsbtResult, error) {
|
||||
res, err := receiveFuture(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unmarshal result as a getinfo result object.
|
||||
var psbtRes btcjson.WalletProcessPsbtResult
|
||||
err = json.Unmarshal(res, &psbtRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &psbtRes, nil
|
||||
}
|
||||
|
||||
// WalletProcessPsbtAsync returns an instance of a type that can be used
|
||||
// to get the result of the RPC at some future time by invoking the Receive
|
||||
// function on the returned instance.
|
||||
//
|
||||
// See WalletProcessPsbt for the blocking version and more details.
|
||||
func (c *Client) WalletProcessPsbtAsync(
|
||||
psbt string, sign *bool, sighashType SigHashType, bip32Derivs *bool,
|
||||
) FutureWalletProcessPsbtResult {
|
||||
cmd := btcjson.NewWalletProcessPsbtCmd(psbt, sign, btcjson.String(sighashType.String()), bip32Derivs)
|
||||
return c.sendCmd(cmd)
|
||||
}
|
||||
|
||||
// WalletProcessPsbt updates a PSBT with input information from our wallet and
|
||||
// then signs inputs.
|
||||
func (c *Client) WalletProcessPsbt(
|
||||
psbt string, sign *bool, sighashType SigHashType, bip32Derivs *bool,
|
||||
) (*btcjson.WalletProcessPsbtResult, error) {
|
||||
return c.WalletProcessPsbtAsync(psbt, sign, sighashType, bip32Derivs).Receive()
|
||||
}
|
||||
|
||||
// TODO(davec): Implement
|
||||
// backupwallet (NYI in btcwallet)
|
||||
// encryptwallet (Won't be supported by btcwallet since it's always encrypted)
|
||||
|
|
Loading…
Reference in a new issue