Wallet registration
Wallet registration submits a Liquid descriptor to AMP2 and returns a wallet ID. The page explains xpub retrieval, descriptor construction, funding, and edge cases.
Before you can issue or send assets, you need at least one registered wallet. Registration submits a Liquid descriptor to AMP2 and returns a wallet ID (WID) used in all subsequent operations.
Why you need the AMP xpub
AMP2 wallets are 2-of-2 multisig descriptors combining your key and AMP2's server key. To construct the descriptor, you need AMP2's keyorigin xpub:
import { AmpActions } from '@blockstream/amp-registry';
const xpubResponse = await broadcastRequest<
typeof AmpActions.xpub
>(
{
action: 'get',
resource: '/amp/xpub',
details: {},
},
broadcaster,
);
const ampKeyoriginXpub = xpubResponse.details.keyorigin_xpub;The returned xpub looks like:
[c67f5991/87'/1'/0']tpubDC4SUtWGWcMQPtwjgQQ4DYnFmAYhiKxw3f3KKCvMGT9sojZNvHsQ4rVW6nQeCPtk4rLAxGKeuAzMmBmH92X3HDgLho3nRWpvuJrpCmYgeQjCreate a signer and get your xpub
Use LWK to create a signer and extract your keyorigin xpub:
use lwk_signer::SwSigner;
use lwk_common::Bip;
let signer = SwSigner::new("your mnemonic words here", false).unwrap();
let keyorigin_xpub = signer.keyorigin_xpub(&Bip::new_bip84()).unwrap();Or import an existing xpub directly:
let keyorigin_xpub = "[b805d768/87h/1h/0h]tpubDCYEgnLyCH2okSittQNNB8JHLwPgmoEAoKcMrJDHP9dFVamsadPAFJQ77C1htgR8ksie3VksLXoryng9AUaPZSF8FwTwEv6CaHp8j2YCrds";Build the descriptor
Combine your xpub with AMP2's to form the 2-of-2 multisig descriptor. Using the AMP2 Rust client:
let amp2 = Amp2::new(keyorigin_xpub.to_string(), amp_url.to_string()).unwrap();
let descriptor = amp2.descriptor_from_str(keyorigin_xpub).unwrap();
let descriptor_str = descriptor.descriptor().to_string();The resulting descriptor has the form:
ct(elip151,elwsh(multi(2,
[<your_fingerprint>/87'/1'/0']<your_xpub>/<0;1>/*,
[<amp_fingerprint>/87'/1'/0']<amp_xpub>/<0;1>/*
)))Register the wallet
Submit the descriptor to AMP2:
import { AmpWalletRequestSchema } from '@blockstream/amp-registry';
const registerResponse = await broadcastRequest(
AmpWalletRequestSchema.parse({
action: 'add',
resource: '/amp/wallets',
details: {
descriptor: descriptor_str,
},
}),
broadcaster,
);
if (registerResponse.status !== 'success') {
throw new Error(
`Wallet registration failed: ec=${registerResponse.error_code} message=${registerResponse.message}`
);
}
const wid = registerResponse.details.wid;
console.log('Registered wallet:', wid);The wid is a deterministic identifier derived from the descriptor. Store it. You will use it in every asset operation.
Verify registration
Check that the wallet is registered by querying its balance:
const balanceResponse = await broadcastRequest<
typeof AmpActions.walletBalance
>(
{
action: 'get',
resource: `/amp/wallets/${wid}/balance`,
details: { with_names: true },
},
broadcaster,
);
console.log('Wallet balance:', balanceResponse.details);A newly registered wallet has zero balances.
Fund the wallet with LBTC
Every AMP2 wallet needs LBTC to pay Liquid transaction fees. Use LWK to derive a receive address locally from the descriptor:
use lwk_wollet::Wollet;
let wollet = Wollet::new(network, &descriptor)?;
let address = wollet.address(None)?;
println!("Fund this address with LBTC: {}", address);Send LBTC to this address from an external source (e.g., peg in from Bitcoin or transfer from another Liquid wallet).
Verify LBTC balance by scanning the chain with LWK:
use lwk_wollet::ElectrumClient;
let client = ElectrumClient::new(&ElectrumUrl::new("blockstream.info:995", true, true))?;
wollet.full_scan(&client)?;
let balance = wollet.balance()?;
println!("LBTC balance: {:?}", balance);Multiple wallets
You can register multiple wallets for different purposes:
- Treasury wallet: holds the main asset supply and reissuance tokens.
- Distribution wallet: used for sending assets to investors.
- Fee wallet: dedicated LBTC wallet for fee management.
Each wallet has its own WID and can be independently funded, locked, and monitored.
Registering investor wallets
As an issuer, you also register wallets for your investors. When an investor provides their wallet descriptor (through your onboarding channel), register it the same way:
const investorRegResponse = await broadcastRequest(
AmpWalletRequestSchema.parse({
action: 'add',
resource: '/amp/wallets',
details: {
descriptor: investorDescriptorStr,
},
}),
broadcaster,
);
const investorWid = investorRegResponse.details.wid;The investor's WID is then used as a recipient in send operations and as a member of restriction groups.
Registration edge cases
- Same descriptor, same blinding keys: returns the existing WID without error (idempotent).
- Same descriptor, different blinding keys: returns
403 Forbidden. - Invalid descriptor format: returns a validation error.
Next steps
- Asset Issuance: mint your first asset using the registered wallet
- Wallets and Descriptors: deep dive on descriptor format
Authentication
AMP2 authentication uses a two-phase process: one-time device authorization with invite data, followed by durable broadcaster creation for subsequent API calls.
Asset issuance
Asset issuance creates a new Liquid asset from a funded wallet. The page explains issuance request setup, PSET handling, cosigning, broadcast, and verification.