XCreateAccount
XCreateAccount is an experimental API that creates a new named account within the wallet, deriving the account's keys from the wallet's master key.
In contrast to ImportAccount, which registers a watch-only account from an externally supplied extended public key, the account created here is fully owned by the wallet: it derives its own addresses and can sign for its own outputs. That makes it usable as an isolated pocket of funds inside a single wallet, because coin selection, change, balance and address derivation can all be scoped to it by name.
NOTE: The X prefix marks this API as experimental: it may change or be removed without the usual deprecation period. It additionally requires i_know_what_i_am_doing on release builds, because a seed-only restore does not rediscover the funds an account created here holds; see the recovery note below. That second gate comes off once recovery handles these accounts, at which point the X can be dropped too.
NOTE: The wallet must be unlocked, as deriving the account key requires access to the master private key.
NOTE: The call is not idempotent, and the account is created before the response is sent. A client that cancels or times out may still have had the account created, in which case its retry fails with "already exists" — indistinguishable from a genuine name clash. Check ListAccounts before retrying.
NOTE: The account's address type is permanent and also fixes the type of its change outputs. lnd resolves a custom account name within the key scope implied by the requested address type, so every later call must ask for the address type that maps to the same scope or the account will appear not to exist. NextAddr and NewAddress take lnrpc.AddressType, which has no HYBRID_NESTED_WITNESS_PUBKEY_HASH member: an account created as HYBRID_NESTED_WITNESS_PUBKEY_HASH must be addressed with NESTED_PUBKEY_HASH, which maps to the same BIP-0049Plus scope. TAPROOT_PUBKEY and WITNESS_PUBKEY_HASH map across unchanged.
NOTE: Funds held in an account created here are not rediscovered by a seed-only recovery, because lnd's recovery scan only rederives addresses for the wallet's default account (btcwallet's RecoveryManager hardcodes waddrmgr.DefaultAccountNum). They are still recoverable, but only by reconstructing the account first, and the account name is not what has to be reproduced: accounts are derived from an index that btcwallet assigns sequentially per key scope, shared with accounts created by ImportAccount.
To keep an account recoverable, record its key scope, the account index (the account's derivation_path in the response), and how many addresses it has issued. To restore: re-create every account in that key scope in their original order so the index counter lands on the same value, re-derive at least as many addresses as were previously issued with NextAddr — a rescan only searches for addresses already present in the wallet database, and a freshly created account has none — and only then rescan with --reset-wallet-transactions.
Source: walletrpc/walletkit.proto
gRPC
rpc XCreateAccount (XCreateAccountRequest) returns (XCreateAccountResponse);
REST
| HTTP Method | Path |
|---|---|
| POST | /v2/wallet/accounts/create |
Code Samples
- gRPC
- REST
- lncli
- Javascript
- Python
- grpcurl
const fs = require('fs');
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const GRPC_HOST = 'localhost:10009'
const MACAROON_PATH = 'LND_DIR/data/chain/bitcoin/regtest/admin.macaroon'
const TLS_PATH = 'LND_DIR/tls.cert'
const loaderOptions = {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
};
const packageDefinition = protoLoader.loadSync(['lightning.proto', 'walletrpc/walletkit.proto'], loaderOptions);
const walletrpc = grpc.loadPackageDefinition(packageDefinition).walletrpc;
process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA';
const tlsCert = fs.readFileSync(TLS_PATH);
const sslCreds = grpc.credentials.createSsl(tlsCert);
const macaroon = fs.readFileSync(MACAROON_PATH).toString('hex');
const macaroonCreds = grpc.credentials.createFromMetadataGenerator(function(args, callback) {
let metadata = new grpc.Metadata();
metadata.add('macaroon', macaroon);
callback(null, metadata);
});
let creds = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds);
let client = new walletrpc.WalletKit(GRPC_HOST, creds);
let request = {
name: <string>,
address_type: <AddressType>,
i_know_what_i_am_doing: <bool>,
};
client.xCreateAccount(request, function(err, response) {
console.log(response);
});
// Console output:
// {
// "account": <Account>,
// }
import codecs, grpc, os
# Generate the following 2 modules by compiling the walletrpc/walletkit.proto with the grpcio-tools.
# See https://github.com/lightningnetwork/lnd/blob/master/docs/grpc/python.md for instructions.
import walletkit_pb2 as walletrpc, walletkit_pb2_grpc as walletkitstub
GRPC_HOST = 'localhost:10009'
MACAROON_PATH = 'LND_DIR/data/chain/bitcoin/regtest/admin.macaroon'
TLS_PATH = 'LND_DIR/tls.cert'
# create macaroon credentials
macaroon = codecs.encode(open(MACAROON_PATH, 'rb').read(), 'hex')
def metadata_callback(context, callback):
callback([('macaroon', macaroon)], None)
auth_creds = grpc.metadata_call_credentials(metadata_callback)
# create SSL credentials
os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA'
cert = open(TLS_PATH, 'rb').read()
ssl_creds = grpc.ssl_channel_credentials(cert)
# combine macaroon and SSL credentials
combined_creds = grpc.composite_channel_credentials(ssl_creds, auth_creds)
# make the request
channel = grpc.secure_channel(GRPC_HOST, combined_creds)
stub = walletkitstub.WalletKitStub(channel)
request = walletrpc.XCreateAccountRequest(
name=<string>,
address_type=<AddressType>,
i_know_what_i_am_doing=<bool>,
)
response = stub.XCreateAccount(request)
print(response)
# {
# "account": <Account>,
# }
# grpcurl docs: https://github.com/fullstorydev/grpcurl
# Proto source: https://github.com/lightningnetwork/lnd
GRPC_HOST=localhost:10009
LND_DIR=~/.lnd
LND_SOURCE=path/to/lnd
NETWORK=mainnet
MACAROON_PATH="$LND_DIR/data/chain/bitcoin/$NETWORK/admin.macaroon"
TLS_PATH="$LND_DIR/tls.cert"
grpcurl \
-import-path $LND_SOURCE/lnrpc/ \
-proto walletrpc/walletkit.proto \
-cacert $TLS_PATH \
-H "macaroon: $(xxd -ps -u -c 1000 $MACAROON_PATH)" \
-d '{ "name": <string>, "address_type": <AddressType>, "i_know_what_i_am_doing": <bool> }' \
$GRPC_HOST \
walletrpc.WalletKit/XCreateAccount
- Javascript
- Python
- curl
const fs = require('fs');
const request = require('request');
const REST_HOST = 'localhost:8080'
const MACAROON_PATH = 'LND_DIR/data/chain/bitcoin/regtest/admin.macaroon'
let requestBody = {
name: <string>, // <string>
address_type: <string>, // <AddressType>
i_know_what_i_am_doing: <boolean>, // <bool>
};
let options = {
url: `https://${REST_HOST}/v2/wallet/accounts/create`,
// Work-around for self-signed certificates.
rejectUnauthorized: false,
json: true,
headers: {
'Grpc-Metadata-macaroon': fs.readFileSync(MACAROON_PATH).toString('hex'),
},
form: JSON.stringify(requestBody),
}
request.post(options, function(error, response, body) {
console.log(body);
});
// Console output:
// {
// "account": <object>, // <Account>
// }
import base64, codecs, json, requests
REST_HOST = 'localhost:8080'
MACAROON_PATH = 'LND_DIR/data/chain/bitcoin/regtest/admin.macaroon'
TLS_PATH = 'LND_DIR/tls.cert'
url = f'https://{REST_HOST}/v2/wallet/accounts/create'
macaroon = codecs.encode(open(MACAROON_PATH, 'rb').read(), 'hex')
headers = {'Grpc-Metadata-macaroon': macaroon}
data = {
'name': <string>,
'address_type': <AddressType>,
'i_know_what_i_am_doing': <bool>,
}
r = requests.post(url, headers=headers, data=json.dumps(data), verify=TLS_PATH)
print(r.json())
# {
# "account": <Account>,
# }
REST_HOST=localhost:8080
LND_DIR=~/.lnd
NETWORK=mainnet
MACAROON_PATH="$LND_DIR/data/chain/bitcoin/$NETWORK/admin.macaroon"
TLS_PATH="$LND_DIR/tls.cert"
curl -X POST \
--cacert $TLS_PATH \
-H "Grpc-Metadata-macaroon: $(xxd -ps -u -c 1000 $MACAROON_PATH)" \
-d '{ "name": <string>, "address_type": <AddressType>, "i_know_what_i_am_doing": <bool> }' \
https://$REST_HOST/v2/wallet/accounts/create
$ lncli wallet accounts create --help
NAME:
lncli wallet accounts create - Create a new on-chain wallet account (experimental).
USAGE:
lncli wallet accounts create [command options] name
DESCRIPTION:
Creates a new named account within the wallet, deriving the account's
keys from the wallet's master key.
This wraps the experimental XCreateAccount RPC: the X prefix marks it
as an API that may change or be removed without the usual deprecation
period, and it is gated as described below until recovery handles
these accounts.
Unlike 'accounts import', which registers a watch-only account from an
extended public key, the account created here is fully owned by the
wallet: it derives its own addresses and can sign for its own outputs.
Coin selection, change, balance and address derivation can then all be
scoped to the account by passing its name, which makes it usable as an
isolated pocket of funds inside a single wallet.
The address type permanently fixes the key scope the account lives in,
and therefore the address type of both its receive and its change
outputs. It defaults to taproot and cannot be changed afterwards.
IMPORTANT: funds held in an account created here are NOT found by a
seed-only restore, because the wallet's recovery scan only rederives
addresses for the default account. Recovering them additionally
requires the account's key scope and index, and re-deriving the
addresses it had issued, before rescanning. Record the derivation path
printed below alongside your seed before depositing to this account.
OPTIONS:
--address_type value (optional) the address type the account holds, one of: p2wkh, np2wkh-p2wkh, p2tr; defaults to p2tr
--i_know_what_i_am_doing required on a release build, confirming you accept that a seed-only restore will not rediscover this account's funds
Messages
walletrpc.XCreateAccountRequest
Source: walletrpc/walletkit.proto
| Field | gRPC Type | REST Type | REST Placement |
|---|---|---|---|
name | string | string | body |
address_type | AddressType | string | body |
i_know_what_i_am_doing | bool | boolean | body |
walletrpc.XCreateAccountResponse
Source: walletrpc/walletkit.proto
| Field | gRPC Type | REST Type |
|---|---|---|
account | Account | object |
Nested Messages
walletrpc.Account
| Field | gRPC Type | REST Type |
|---|---|---|
name | string | string |
address_type | AddressType | string |
extended_public_key | string | string |
master_key_fingerprint | bytes | string |
derivation_path | string | string |
external_key_count | uint32 | integer |
internal_key_count | uint32 | integer |
watch_only | bool | boolean |
Enums
walletrpc.AddressType
| Name | Number |
|---|---|
UNKNOWN | 0 |
WITNESS_PUBKEY_HASH | 1 |
NESTED_WITNESS_PUBKEY_HASH | 2 |
HYBRID_NESTED_WITNESS_PUBKEY_HASH | 3 |
TAPROOT_PUBKEY | 4 |