Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

AMP0 in LWK

AMP0 (Asset Management Platform version 0) is a service for issuers that allows to enforce specific rules on certain Liquid assets (AMP0 assets).

AMP0 is based on a legacy system and it does not fit the LWK model perfectly. That is reflected in the LWK AMP0 interface which could be a bit cumbersome to use.

Limitations

LWK has partial support for AMP0. For instance it does not allow to issue AMP0 asset, or use accounts with 2FA.

LWKGDKAMP0 API
Create AMP0 accounts
Receive on AMP0 accounts
Monitor AMP0 accounts
Send from AMP0 accounts
Account with 2FA
issue, reissue, burn AMP0 assets
set restriction for AMP0 assets

If you need full support for AMP0, use GDK and the AMP0 issuer API.

Overview

To use AMP0 with LWK you need:

  • 👀 some Green Watch-Only credentials (username and password) for a Green Wallet with an AMP account
  • 🔑 the corresponding signer available (e.g. Jade or software with the BIP39 mnemonic)

Then you can:

  • get addresses for the AMP0 account (👀)
  • monitor the AMP0 account (get balance and transactions) (👀)
  • create AMP0 transactions (👀)
  • sign AMP0 transactions (🔑)
  • ask AMP0 to cosign transactions (👀)
  • broadcast AMP0 transactions (👀)

Using AMP0 with LWK you can keep the signer separated and operate it accoriding to the desired degree of security and isolation.

⚠️ AMP0 is based on a legacy system and it has some pitfalls. We put some mechanism in order to make it harder to do the wrong thing, anyway callers should be careful when getting new addresses and syncing the wallet.

Setup

To use AMP0 with LWK you need to:

  1. Create a Liquid wallet (backup its mnemonic/seed)
  2. Create an AMP account (AMP ID)
  3. Create a Liquid Watch-Only (username and password)

1. Create Liquid wallet

Create a Signer and backup it's mnemonic/seed. From the signer get its signer_data using Signer::amp0_signer_data().

Create a Amp0Connected::new() passing the signer_data. You now need to authenticate with AMP0 server. First get the server challenge with Amp0Connected::get_challenge(). Sign the challenge with Signer::amp0_sign_challenge(). You can now call Amp0Connected::login() passing the signature. This function returns a Amp0LoggedIn instance, which can be used to create new AMP0 accounts and watch-only entries.

2. Create an AMP account

Obtain the number of the next account using Amp0LoggedIn::next_account(). Use the signer to get the corresponding xpub Signer::amp0_accont_xpub(). Now you can create a new AMP0 account with Amp0LoggedIn::create_amp0_account(), which returns the AMP ID.

3. Create a Liquid Watch-Only

Choose your your AMP0 Watch-Only credentials username and password and call Amp0LoggedIn::create_watch_only().

Now that you have mnemonic/seed (or Jade), AMP ID and Watch-Only credentials (username and password), you're ready to use AMP0 with LWK.

If you're using lwk_node, polyfill the websocket

const WebSocket = require('ws');
global.WebSocket = WebSocket;
const lwk = require('lwk_node');
Rust
use lwk_common::{Amp0Signer, Network};
use lwk_signer::SwSigner;
use lwk_wollet::amp0::blocking::{Amp0, Amp0Connected};

// Create signer and watch only credentials
let network = Network::TestnetLiquid;
let is_mainnet = false;
let (signer, mnemonic) = SwSigner::random(is_mainnet)?;
let username = "<username>";
let password = "<password>";

// Collect signer data
let signer_data = signer.amp0_signer_data()?;

// Connect to AMP0
let amp0 = Amp0Connected::new(network, signer_data)?;

// Obtain and sign the authentication challenge
let challenge = amp0.get_challenge()?;
let sig = signer.amp0_sign_challenge(&challenge)?;

// Login
let mut amp0 = amp0.login(&sig)?;

// Create a new AMP0 account
let pointer = amp0.next_account()?;
let account_xpub = signer.amp0_account_xpub(pointer)?;
let amp_id = amp0.create_amp0_account(pointer, &account_xpub)?;

// Create watch only entries
amp0.create_watch_only(&username, &password)?;

// Use watch only credentials to interact with AMP0
let amp0 = Amp0::new(network, &username, &password, &amp_id)?;
Python
Javascript
const mnemonic = "<mnemonic>";
const m = new lwk.Mnemonic(mnemonic);
const network = lwk.Network.testnet();
const signer = new lwk.Signer(m, network);
const username = "<username>";
const password = "<password>";

// Collect signer data
const signer_data = signer.amp0SignerData();
// Connect to AMP0
const amp0connected = await new lwk.Amp0Connected(network, signer_data);
// Obtain and sign the authentication challenge
const challenge = await amp0connected.getChallenge();
const sig = signer.amp0SignChallenge(challenge);
// Login
const amp0loggedin = await amp0connected.login(sig);
// Create a new AMP0 account
const pointer = amp0loggedin.nextAccount();
const account_xpub = signer.amp0AccountXpub(pointer);
const amp_id = await amp0loggedin.createAmp0Account(pointer, account_xpub);
// Create watch only entries
await amp0loggedin.createWatchOnly(username, password);
// Use watch only credentials to interact with AMP0
const amp0 = await new lwk.Amp0(network, username, password, amp_id);

Alternative setup

It's possible to setup an AMP0 account using GDK based apps:

  1. Blockstream App (easiest, GUI, mobile, desktop, Jade support), or
  2. green_cli (CLI, Jade support), or
  3. GDK directly (fastest, example)

AMP0 daily operations

LWK allows to manage created AMP0 accounts. You can receive funds, monitor transactions and send to other wallets.

Receive

To receive funds you need an address, you can get addresses with Amp0::address().

⚠️ For AMP0 wallets, do not use Wollet::address() or WolletDescriptor::address(), using them can lead to loss of funds. AMP0 server only monitors addresses that have been returned by the server. If you send funds to an address that was not returned by the server, the AMP0 server will not cosign transactions spending that inputs. Which means that those funds are lost (!), since AMP0 accounts are 2of2.

Monitor

LWK allows to monitor Liquid wallets, including AMP0 accounts.

First you get the AMP0 descriptor with Amp0::wollet_descriptor(). You then create a wallet with Wollet::new().

Once you have the AMP0 Wollet, you can get Wollet::transactions(), Wollet::balance() and other information.

LWK wallets needs to be updated with new data from the Liquid blockchain. First create a blockchain client, for insance EsploraClient::new(). Then get an update with BlockchainBackend::full_scan_to_index() passing the value returned by Amp0::last_index(). Finally update the wallet with Wollet::apply_update().

⚠️ For AMP0 wallets, do not sync the wallet with BlockchainBackend::full_scan(), otherwise some funds might not show up. AMP0 accounts do not have the concept of GAP_LIMIT and they can have several unused address in a row. The default scanning mechanism when it sees enough unused addresses in a row it stops. So it can happen that some transactions are not returned, and the wallet balance could be incorrect.

Send

For AMP0 you can follow the standard LWK transaction flow, with few small differences.

Use the TxBuilder, add recipients TxBuilder::add_recipient(), and use the other available methods if needed.

Then instead of using TxBuilder::finish(), use TxBuilder::finish_for_amp0(). This creates an Amp0Pset which contains the PSET and the blinding_nonces, some extra data needed by the AMP0 cosigner.

Now you need to interact with secret key material (🔑) corresponding to this AMP0 account. Create a signer, using SWSigner or Jade and sign the PSET with the signer, using Signer::sign().

Once the PSET is signed, you need to have it cosigned by AMP0. Construct an Amp0Pset using the signed PSET and the blinding_nonces obtained before. Call Amp0::sign() passing the signed Amp0Pset.

If all the AMP0 rules are respected, the transaction is cosigned by AMP0 and can be broadcast, e.g. with EsploraClient::broadcast().

Rust
use lwk_common::{Network, Signer};
use lwk_signer::SwSigner;
use lwk_wollet::amp0::{blocking::Amp0, Amp0Pset};
use lwk_wollet::{clients::blocking::EsploraClient, ElementsNetwork, Wollet};

// Signer
let mnemonic = "<mnemonic>";
// AMP0 Watch-Only credentials
let username = "<username>";
let password = "<password>";
// AMP ID (optional)
let amp_id = "";

// Create AMP0 context
let network = Network::TestnetLiquid;

let mut amp0 = Amp0::new(network, username, password, amp_id)?;

// Create AMP0 Wollet
let wd = amp0.wollet_descriptor();
let mut wollet = Wollet::without_persist(ElementsNetwork::LiquidTestnet, wd)?;

// Get a new address
let addr = amp0.address(None);

// Update the wallet with (new) blockchain data
let url = "https://blockstream.info/liquidtestnet/api";
let mut client = EsploraClient::new(url, ElementsNetwork::LiquidTestnet)?;
if let Some(update) = client.full_scan_to_index(&wollet, amp0.last_index())? {
    wollet.apply_update(update)?;
}

// Get balance
let balance = wollet.balance()?;

// Construct a PSET sending LBTC back to the wallet
let amp0pset = wollet
    .tx_builder()
    .drain_lbtc_wallet()
    .finish_for_amp0()?;
let mut pset = amp0pset.pset().clone();
let blinding_nonces = amp0pset.blinding_nonces();

// User signs the PSET
let is_mainnet = false;
let signer = SwSigner::new(mnemonic, is_mainnet)?;
let sigs = signer.sign(&mut pset)?;
assert!(sigs > 0);

// Reconstruct the Amp0 PSET with the PSET signed by the user
let amp0pset = Amp0Pset::new(pset, blinding_nonces.to_vec())?;

// AMP0 signs
let tx = amp0.sign(&amp0pset)?;

// Broadcast the transaction
let txid = client.broadcast(&tx)?;
Python
Javascript
const mnemonic = "<mnemonic>";
const m = new lwk.Mnemonic(mnemonic);
const network = lwk.Network.testnet();
const signer = new lwk.Signer(m, network);
const username = "<username>";
const password = "<password>";
const amp_id = "";

// Create AMP0 object
const amp0 = await lwk.Amp0.newTestnet(username, password, amp_id);

// Get an address
const addrResult = await amp0.address(1);

// Create wollet
const wollet = amp0.wollet();

// Sync the wallet
const url = "https://waterfalls.liquidwebwallet.org/liquidtestnet/api";
const client = new lwk.EsploraClient(network, url, true, 4, false);

const last_index = amp0.lastIndex();
const update = await client.fullScanToIndex(wollet, last_index);
if (update) {
    wollet.applyUpdate(update);
}

// Get the wallet transactions
const txs = wollet.transactions();

// Get the balance
const balance = wollet.balance();

// Create a (redeposit) transaction
var b = network.txBuilder();
b = b.drainLbtcWallet();
const amp0pset = b.finishForAmp0(wollet);

// Sign with the user key
const pset = amp0pset.pset();
const signed_pset = signer.sign(pset);

// Ask AMP0 to cosign
const amp0pset_signed = new lwk.Amp0Pset(signed_pset, amp0pset.blindingNonces());
const tx = await amp0.sign(amp0pset_signed);

// Broadcast
const txid = await client.broadcastTx(tx);

Examples

We provide a few examples on how to integrate use AMP0 with LWK: