walletrpc: expose wallet import related RPCs

This commit is contained in:
Wilmer Paulino 2021-02-19 17:42:07 -08:00
parent 2d163b788b
commit 5301c7e83f
No known key found for this signature in database
GPG key ID: 6DF57B9F9514972F
6 changed files with 1601 additions and 146 deletions

View file

@ -275,6 +275,9 @@ http:
- selector: walletrpc.WalletKit.DeriveKey - selector: walletrpc.WalletKit.DeriveKey
post: "/v2/wallet/key" post: "/v2/wallet/key"
body: "*" body: "*"
- selector: walletrpc.WalletKit.ImportPublicKey
post: "/v2/wallet/key/import"
body: "*"
- selector: walletrpc.WalletKit.NextAddr - selector: walletrpc.WalletKit.NextAddr
post: "/v2/wallet/address/next" post: "/v2/wallet/address/next"
body: "*" body: "*"
@ -302,6 +305,11 @@ http:
- selector: walletrpc.WalletKit.FinalizePsbt - selector: walletrpc.WalletKit.FinalizePsbt
post: "/v2/wallet/psbt/finalize" post: "/v2/wallet/psbt/finalize"
body: "*" body: "*"
- selector: walletrpc.WalletKit.ListAccounts
get: "/v2/wallet/accounts"
- selector: walletrpc.WalletKit.ImportAccount
post: "/v2/wallet/accounts/import"
body: "*"
# watchtowerrpc/watchtower.proto # watchtowerrpc/watchtower.proto
- selector: watchtowerrpc.Watchtower.GetInfo - selector: watchtowerrpc.Watchtower.GetInfo

File diff suppressed because it is too large Load diff

View file

@ -253,6 +253,107 @@ func local_request_WalletKit_NextAddr_0(ctx context.Context, marshaler runtime.M
} }
var (
filter_WalletKit_ListAccounts_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_WalletKit_ListAccounts_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListAccountsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_ListAccounts_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListAccounts(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_ListAccounts_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListAccountsRequest
var metadata runtime.ServerMetadata
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_WalletKit_ListAccounts_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListAccounts(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_ImportAccount_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ImportAccountRequest
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.ImportAccount(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_ImportAccount_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ImportAccountRequest
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.ImportAccount(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_ImportPublicKey_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ImportPublicKeyRequest
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.ImportPublicKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_ImportPublicKey_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ImportPublicKeyRequest
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.ImportPublicKey(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_PublishTransaction_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { func request_WalletKit_PublishTransaction_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq Transaction var protoReq Transaction
var metadata runtime.ServerMetadata var metadata runtime.ServerMetadata
@ -707,6 +808,66 @@ func RegisterWalletKitHandlerServer(ctx context.Context, mux *runtime.ServeMux,
}) })
mux.Handle("GET", pattern_WalletKit_ListAccounts_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_ListAccounts_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_ListAccounts_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ImportAccount_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_ImportAccount_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_ImportAccount_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ImportPublicKey_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_ImportPublicKey_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_ImportPublicKey_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_PublishTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("POST", pattern_WalletKit_PublishTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
@ -1068,6 +1229,66 @@ func RegisterWalletKitHandlerClient(ctx context.Context, mux *runtime.ServeMux,
}) })
mux.Handle("GET", pattern_WalletKit_ListAccounts_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_ListAccounts_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_ListAccounts_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ImportAccount_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_ImportAccount_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_ImportAccount_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ImportPublicKey_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_ImportPublicKey_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_ImportPublicKey_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_PublishTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("POST", pattern_WalletKit_PublishTransaction_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
@ -1266,6 +1487,12 @@ var (
pattern_WalletKit_NextAddr_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "address", "next"}, "", runtime.AssumeColonVerbOpt(true))) pattern_WalletKit_NextAddr_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "address", "next"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_WalletKit_ListAccounts_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "accounts"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_WalletKit_ImportAccount_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "accounts", "import"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_WalletKit_ImportPublicKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "key", "import"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_WalletKit_PublishTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "tx"}, "", runtime.AssumeColonVerbOpt(true))) pattern_WalletKit_PublishTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "tx"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_WalletKit_SendOutputs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "send"}, "", runtime.AssumeColonVerbOpt(true))) pattern_WalletKit_SendOutputs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "send"}, "", runtime.AssumeColonVerbOpt(true)))
@ -1300,6 +1527,12 @@ var (
forward_WalletKit_NextAddr_0 = runtime.ForwardResponseMessage forward_WalletKit_NextAddr_0 = runtime.ForwardResponseMessage
forward_WalletKit_ListAccounts_0 = runtime.ForwardResponseMessage
forward_WalletKit_ImportAccount_0 = runtime.ForwardResponseMessage
forward_WalletKit_ImportPublicKey_0 = runtime.ForwardResponseMessage
forward_WalletKit_PublishTransaction_0 = runtime.ForwardResponseMessage forward_WalletKit_PublishTransaction_0 = runtime.ForwardResponseMessage
forward_WalletKit_SendOutputs_0 = runtime.ForwardResponseMessage forward_WalletKit_SendOutputs_0 = runtime.ForwardResponseMessage

View file

@ -55,6 +55,50 @@ service WalletKit {
*/ */
rpc NextAddr (AddrRequest) returns (AddrResponse); rpc NextAddr (AddrRequest) returns (AddrResponse);
/*
ListAccounts retrieves all accounts belonging to the wallet by default. A
name and key scope filter can be provided to filter through all of the
wallet accounts and return only those matching.
*/
rpc ListAccounts (ListAccountsRequest) returns (ListAccountsResponse);
/*
ImportAccount imports an account backed by an account extended public key.
The master key fingerprint denotes the fingerprint of the root key
corresponding to the account public key (also known as the key with
derivation path m/). This may be required by some hardware wallets for
proper identification and signing.
The address type can usually be inferred from the key's version, but may be
required for certain keys to map them into the proper scope.
For BIP-0044 keys, an address type must be specified as we intend to not
support importing BIP-0044 keys into the wallet using the legacy
pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
the standard BIP-0049 derivation scheme, while a witness address type will
force the standard BIP-0084 derivation scheme.
For BIP-0049 keys, an address type must also be specified to make a
distinction between the standard BIP-0049 address schema (nested witness
pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
externally, witness pubkeys internally).
NOTE: Events (deposits/spends) for keys derived from an account will only be
detected by lnd if they happen after the import. Rescans to detect past
events will be supported later on.
*/
rpc ImportAccount (ImportAccountRequest) returns (ImportAccountResponse);
/*
ImportPublicKey imports a public key as watch-only into the wallet.
NOTE: Events (deposits/spends) for a key will only be detected by lnd if
they happen after the import. Rescans to detect past events will be
supported later on.
*/
rpc ImportPublicKey (ImportPublicKeyRequest)
returns (ImportPublicKeyResponse);
/* /*
PublishTransaction attempts to publish the passed transaction to the PublishTransaction attempts to publish the passed transaction to the
network. Once this returns without an error, the wallet will continually network. Once this returns without an error, the wallet will continually
@ -250,6 +294,113 @@ message AddrResponse {
string addr = 1; string addr = 1;
} }
enum AddressType {
UNKNOWN = 0;
WITNESS_PUBKEY_HASH = 1;
NESTED_WITNESS_PUBKEY_HASH = 2;
HYBRID_NESTED_WITNESS_PUBKEY_HASH = 3;
}
message Account {
// The name used to identify the account.
string name = 1;
/*
The type of addresses the account supports.
AddressType | External Branch | Internal Branch
---------------------------------------------------------------------
WITNESS_PUBKEY_HASH | P2WPKH | P2WPKH
NESTED_WITNESS_PUBKEY_HASH | NP2WPKH | NP2WPKH
HYBRID_NESTED_WITNESS_PUBKEY_HASH | NP2WPKH | P2WPKH
*/
AddressType address_type = 2;
/*
The public key backing the account that all keys are derived from
represented as an extended key. This will always be empty for the default
imported account in which single public keys are imported into.
*/
string extended_public_key = 3;
/*
The fingerprint of the root key from which the account public key was
derived from. This will always be zero for the default imported account in
which single public keys are imported into.
*/
uint32 master_key_fingerprint = 4;
/*
The derivation path corresponding to the account public key. This will
always be empty for the default imported account in which single public keys
are imported into.
*/
string derivation_path = 5;
/*
The number of keys derived from the external branch of the account public
key. This will always be zero for the default imported account in which
single public keys are imported into.
*/
uint32 external_key_count = 6;
/*
The number of keys derived from the internal branch of the account public
key. This will always be zero for the default imported account in which
single public keys are imported into.
*/
uint32 internal_key_count = 7;
// Whether the wallet stores private keys for the account.
bool watch_only = 8;
}
message ListAccountsRequest {
// An optional filter to only return accounts matching this name.
string name = 1;
// An optional filter to only return accounts matching this address type.
AddressType address_type = 2;
}
message ListAccountsResponse {
repeated Account accounts = 1;
}
message ImportAccountRequest {
// A name to identify the account with.
string name = 1;
/*
A public key that corresponds to a wallet account represented as an extended
key. It must conform to a derivation path of the form
m/purpose'/coin_type'/account'.
*/
string extended_public_key = 2;
/*
The fingerprint of the root key (also known as the key with derivation path
m/) from which the account public key was derived from. This may be required
by some hardware wallets for proper identification and signing.
*/
uint32 master_key_fingerprint = 3;
/*
An address type is only required when the extended account public key has a
legacy version (xpub, tpub, etc.), such that the wallet cannot detect what
address scheme it belongs to.
*/
AddressType address_type = 4;
}
message ImportAccountResponse {
}
message ImportPublicKeyRequest {
// A compressed public key represented as raw bytes.
bytes public_key = 1;
// The type of address that will be generated from the public key.
AddressType address_type = 2;
}
message ImportPublicKeyResponse {
}
message Transaction { message Transaction {
/* /*
The raw serialized transaction. The raw serialized transaction.

View file

@ -11,6 +11,86 @@
"application/json" "application/json"
], ],
"paths": { "paths": {
"/v2/wallet/accounts": {
"get": {
"summary": "ListAccounts retrieves all accounts belonging to the wallet by default. A\nname and key scope filter can be provided to filter through all of the\nwallet accounts and return only those matching.",
"operationId": "ListAccounts",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/walletrpcListAccountsResponse"
}
},
"default": {
"description": "An unexpected error response",
"schema": {
"$ref": "#/definitions/runtimeError"
}
}
},
"parameters": [
{
"name": "name",
"description": "An optional filter to only return accounts matching this name.",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "address_type",
"description": "An optional filter to only return accounts matching this address type.",
"in": "query",
"required": false,
"type": "string",
"enum": [
"UNKNOWN",
"WITNESS_PUBKEY_HASH",
"NESTED_WITNESS_PUBKEY_HASH",
"HYBRID_NESTED_WITNESS_PUBKEY_HASH"
],
"default": "UNKNOWN"
}
],
"tags": [
"WalletKit"
]
}
},
"/v2/wallet/accounts/import": {
"post": {
"summary": "ImportAccount imports an account backed by an account extended public key.\nThe master key fingerprint denotes the fingerprint of the root key\ncorresponding to the account public key (also known as the key with\nderivation path m/). This may be required by some hardware wallets for\nproper identification and signing.",
"description": "The address type can usually be inferred from the key's version, but may be\nrequired for certain keys to map them into the proper scope.\n\nFor BIP-0044 keys, an address type must be specified as we intend to not\nsupport importing BIP-0044 keys into the wallet using the legacy\npay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force\nthe standard BIP-0049 derivation scheme, while a witness address type will\nforce the standard BIP-0084 derivation scheme.\n\nFor BIP-0049 keys, an address type must also be specified to make a\ndistinction between the standard BIP-0049 address schema (nested witness\npubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys\nexternally, witness pubkeys internally).\n\nNOTE: Events (deposits/spends) for keys derived from an account will only be\ndetected by lnd if they happen after the import. Rescans to detect past\nevents will be supported later on.",
"operationId": "ImportAccount",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/walletrpcImportAccountResponse"
}
},
"default": {
"description": "An unexpected error response",
"schema": {
"$ref": "#/definitions/runtimeError"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/walletrpcImportAccountRequest"
}
}
],
"tags": [
"WalletKit"
]
}
},
"/v2/wallet/address/next": { "/v2/wallet/address/next": {
"post": { "post": {
"summary": "NextAddr returns the next unused address within the wallet.", "summary": "NextAddr returns the next unused address within the wallet.",
@ -144,6 +224,40 @@
] ]
} }
}, },
"/v2/wallet/key/import": {
"post": {
"summary": "ImportPublicKey imports a public key as watch-only into the wallet.",
"description": "NOTE: Events (deposits/spends) for a key will only be detected by lnd if\nthey happen after the import. Rescans to detect past events will be\nsupported later on.",
"operationId": "ImportPublicKey",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/walletrpcImportPublicKeyResponse"
}
},
"default": {
"description": "An unexpected error response",
"schema": {
"$ref": "#/definitions/runtimeError"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/walletrpcImportPublicKeyRequest"
}
}
],
"tags": [
"WalletKit"
]
}
},
"/v2/wallet/key/next": { "/v2/wallet/key/next": {
"post": { "post": {
"summary": "DeriveNextKey attempts to derive the *next* key within the key family\n(account in BIP43) specified. This method should return the next external\nchild within this branch.", "summary": "DeriveNextKey attempts to derive the *next* key within the key family\n(account in BIP43) specified. This method should return the next external\nchild within this branch.",
@ -731,6 +845,47 @@
} }
} }
}, },
"walletrpcAccount": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name used to identify the account."
},
"address_type": {
"$ref": "#/definitions/walletrpcAddressType",
"title": "The type of addresses the account supports.\nAddressType | External Branch | Internal Branch\n---------------------------------------------------------------------\nWITNESS_PUBKEY_HASH | P2WPKH | P2WPKH\nNESTED_WITNESS_PUBKEY_HASH | NP2WPKH | NP2WPKH\nHYBRID_NESTED_WITNESS_PUBKEY_HASH | NP2WPKH | P2WPKH"
},
"extended_public_key": {
"type": "string",
"description": "The public key backing the account that all keys are derived from\nrepresented as an extended key. This will always be empty for the default\nimported account in which single public keys are imported into."
},
"master_key_fingerprint": {
"type": "integer",
"format": "int64",
"description": "The fingerprint of the root key from which the account public key was\nderived from. This will always be zero for the default imported account in\nwhich single public keys are imported into."
},
"derivation_path": {
"type": "string",
"description": "The derivation path corresponding to the account public key. This will\nalways be empty for the default imported account in which single public keys\nare imported into."
},
"external_key_count": {
"type": "integer",
"format": "int64",
"description": "The number of keys derived from the external branch of the account public\nkey. This will always be zero for the default imported account in which\nsingle public keys are imported into."
},
"internal_key_count": {
"type": "integer",
"format": "int64",
"description": "The number of keys derived from the internal branch of the account public\nkey. This will always be zero for the default imported account in which\nsingle public keys are imported into."
},
"watch_only": {
"type": "boolean",
"format": "boolean",
"description": "Whether the wallet stores private keys for the account."
}
}
},
"walletrpcAddrRequest": { "walletrpcAddrRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -749,6 +904,16 @@
} }
} }
}, },
"walletrpcAddressType": {
"type": "string",
"enum": [
"UNKNOWN",
"WITNESS_PUBKEY_HASH",
"NESTED_WITNESS_PUBKEY_HASH",
"HYBRID_NESTED_WITNESS_PUBKEY_HASH"
],
"default": "UNKNOWN"
},
"walletrpcBumpFeeRequest": { "walletrpcBumpFeeRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -870,6 +1035,48 @@
} }
} }
}, },
"walletrpcImportAccountRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "A name to identify the account with."
},
"extended_public_key": {
"type": "string",
"description": "A public key that corresponds to a wallet account represented as an extended\nkey. It must conform to a derivation path of the form\nm/purpose'/coin_type'/account'."
},
"master_key_fingerprint": {
"type": "integer",
"format": "int64",
"description": "The fingerprint of the root key (also known as the key with derivation path\nm/) from which the account public key was derived from. This may be required\nby some hardware wallets for proper identification and signing."
},
"address_type": {
"$ref": "#/definitions/walletrpcAddressType",
"description": "An address type is only required when the extended account public key has a\nlegacy version (xpub, tpub, etc.), such that the wallet cannot detect what\naddress scheme it belongs to."
}
}
},
"walletrpcImportAccountResponse": {
"type": "object"
},
"walletrpcImportPublicKeyRequest": {
"type": "object",
"properties": {
"public_key": {
"type": "string",
"format": "byte",
"description": "A compressed public key represented as raw bytes."
},
"address_type": {
"$ref": "#/definitions/walletrpcAddressType",
"description": "The type of address that will be generated from the public key."
}
}
},
"walletrpcImportPublicKeyResponse": {
"type": "object"
},
"walletrpcKeyReq": { "walletrpcKeyReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -936,6 +1143,17 @@
} }
} }
}, },
"walletrpcListAccountsResponse": {
"type": "object",
"properties": {
"accounts": {
"type": "array",
"items": {
"$ref": "#/definitions/walletrpcAccount"
}
}
}
},
"walletrpcListLeasesResponse": { "walletrpcListLeasesResponse": {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -12,11 +12,14 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"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/btcutil/hdkeychain"
"github.com/btcsuite/btcutil/psbt" "github.com/btcsuite/btcutil/psbt"
"github.com/btcsuite/btcwallet/waddrmgr"
"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"
@ -128,6 +131,18 @@ var (
Entity: "onchain", Entity: "onchain",
Action: "write", Action: "write",
}}, }},
"/walletrpc.WalletKit/ListAccounts": {{
Entity: "onchain",
Action: "read",
}},
"/walletrpc.WalletKit/ImportAccount": {{
Entity: "onchain",
Action: "write",
}},
"/walletrpc.WalletKit/ImportPublicKey": {{
Entity: "onchain",
Action: "write",
}},
} }
// DefaultWalletKitMacFilename is the default name of the wallet kit // DefaultWalletKitMacFilename is the default name of the wallet kit
@ -1215,3 +1230,198 @@ func (w *WalletKit) FinalizePsbt(_ context.Context,
RawFinalTx: finalTxBytes.Bytes(), RawFinalTx: finalTxBytes.Bytes(),
}, nil }, nil
} }
// marshalWalletAccount converts the properties of an account into its RPC
// representation.
func marshalWalletAccount(account *waddrmgr.AccountProperties) (*Account, error) {
var addrType AddressType
switch account.KeyScope {
case waddrmgr.KeyScopeBIP0049Plus:
// No address schema present represents the traditional BIP-0049
// address derivation scheme.
if account.AddrSchema == nil {
addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH
break
}
switch account.AddrSchema {
case &waddrmgr.KeyScopeBIP0049AddrSchema:
addrType = AddressType_NESTED_WITNESS_PUBKEY_HASH
default:
return nil, fmt.Errorf("unsupported address schema %v",
*account.AddrSchema)
}
case waddrmgr.KeyScopeBIP0084:
addrType = AddressType_WITNESS_PUBKEY_HASH
default:
return nil, fmt.Errorf("account %v has unsupported "+
"key scope %v", account.AccountName, account.KeyScope)
}
rpcAccount := &Account{
Name: account.AccountName,
AddressType: addrType,
ExternalKeyCount: account.ExternalKeyCount,
InternalKeyCount: account.InternalKeyCount,
WatchOnly: account.IsWatchOnly,
}
// The remaining fields can only be done on accounts other than the
// default imported one existing within each key scope.
if account.AccountName != waddrmgr.ImportedAddrAccountName {
nonHardenedIndex := account.AccountPubKey.ChildIndex() -
hdkeychain.HardenedKeyStart
rpcAccount.ExtendedPublicKey = account.AccountPubKey.String()
rpcAccount.MasterKeyFingerprint = account.MasterKeyFingerprint
rpcAccount.DerivationPath = fmt.Sprintf("%v/%v'",
account.KeyScope, nonHardenedIndex)
}
return rpcAccount, nil
}
// ListAccounts retrieves all accounts belonging to the wallet by default. A
// name and key scope filter can be provided to filter through all of the wallet
// accounts and return only those matching.
func (w *WalletKit) ListAccounts(ctx context.Context,
req *ListAccountsRequest) (*ListAccountsResponse, error) {
// Map the supported address types into their corresponding key scope.
var keyScopeFilter *waddrmgr.KeyScope
switch req.AddressType {
case AddressType_UNKNOWN:
break
case AddressType_WITNESS_PUBKEY_HASH:
keyScope := waddrmgr.KeyScopeBIP0084
keyScopeFilter = &keyScope
case AddressType_NESTED_WITNESS_PUBKEY_HASH,
AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
keyScope := waddrmgr.KeyScopeBIP0049Plus
keyScopeFilter = &keyScope
default:
return nil, fmt.Errorf("unhandled address type %v", req.AddressType)
}
accounts, err := w.cfg.Wallet.ListAccounts(req.Name, keyScopeFilter)
if err != nil {
return nil, err
}
rpcAccounts := make([]*Account, 0, len(accounts))
for _, account := range accounts {
// Don't include the default imported accounts created by the
// wallet in the response if they don't have any keys imported.
if account.AccountName == waddrmgr.ImportedAddrAccountName &&
account.ImportedKeyCount == 0 {
continue
}
rpcAccount, err := marshalWalletAccount(account)
if err != nil {
return nil, err
}
rpcAccounts = append(rpcAccounts, rpcAccount)
}
return &ListAccountsResponse{Accounts: rpcAccounts}, nil
}
// parseAddrType parses an address type from its RPC representation to a
// *waddrmgr.AddressType.
func parseAddrType(addrType AddressType,
required bool) (*waddrmgr.AddressType, error) {
switch addrType {
case AddressType_UNKNOWN:
if required {
return nil, errors.New("an address type must be specified")
}
return nil, nil
case AddressType_WITNESS_PUBKEY_HASH:
addrTyp := waddrmgr.WitnessPubKey
return &addrTyp, nil
case AddressType_NESTED_WITNESS_PUBKEY_HASH:
addrTyp := waddrmgr.NestedWitnessPubKey
return &addrTyp, nil
case AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
addrTyp := waddrmgr.WitnessPubKey
return &addrTyp, nil
default:
return nil, fmt.Errorf("unhandled address type %v", addrType)
}
}
// ImportAccount imports an account backed by an account extended public key.
// The master key fingerprint denotes the fingerprint of the root key
// corresponding to the account public key (also known as the key with
// derivation path m/). This may be required by some hardware wallets for proper
// identification and signing.
//
// The address type can usually be inferred from the key's version, but may be
// required for certain keys to map them into the proper scope.
//
// For BIP-0044 keys, an address type must be specified as we intend to not
// support importing BIP-0044 keys into the wallet using the legacy
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
// the standard BIP-0049 derivation scheme, while a witness address type will
// force the standard BIP-0084 derivation scheme.
//
// For BIP-0049 keys, an address type must also be specified to make a
// distinction between the standard BIP-0049 address schema (nested witness
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
// externally, witness pubkeys internally).
func (w *WalletKit) ImportAccount(ctx context.Context,
req *ImportAccountRequest) (*ImportAccountResponse, error) {
accountPubKey, err := hdkeychain.NewKeyFromString(req.ExtendedPublicKey)
if err != nil {
return nil, err
}
addrType, err := parseAddrType(req.AddressType, false)
if err != nil {
return nil, err
}
err = w.cfg.Wallet.ImportAccount(
req.Name, accountPubKey, req.MasterKeyFingerprint, addrType,
)
if err != nil {
return nil, err
}
return &ImportAccountResponse{}, nil
}
// ImportPublicKey imports a single derived public key into the wallet. The
// address type can usually be inferred from the key's version, but in the case
// of legacy versions (xpub, tpub), an address type must be specified as we
// intend to not support importing BIP-44 keys into the wallet using the legacy
// pay-to-pubkey-hash (P2PKH) scheme.
func (w *WalletKit) ImportPublicKey(ctx context.Context,
req *ImportPublicKeyRequest) (*ImportPublicKeyResponse, error) {
pubKey, err := btcec.ParsePubKey(req.PublicKey, btcec.S256())
if err != nil {
return nil, err
}
addrType, err := parseAddrType(req.AddressType, true)
if err != nil {
return nil, err
}
if err := w.cfg.Wallet.ImportPublicKey(pubKey, *addrType); err != nil {
return nil, err
}
return &ImportPublicKeyResponse{}, nil
}