Issuance

Asset issuance on Liquid allows you to create new digital assets. When you issue an asset, you create a certain amount of that asset and optionally you also create another asset, called reissuance token, that allows you to create more of the asset later.

Understanding Asset Issuance

When issuing an asset, you need to specify:

  • Asset amount: The number of units (in satoshis) of the asset to create
  • Asset receiver: The address that will receive the newly issued asset (optional, defaults to a wallet address)
  • Token amount: The number of reissuance tokens to create (optional, can be 0)
  • Token receiver: The address that will receive the reissuance tokens (optional, defaults to a wallet address)
  • Contract: Metadata about the asset (optional, necessary for asset registry)

The asset receiver address and token receiver address can belong to different wallets with different spending policies, allowing for a more secure and customizable setup according to the issuer's needs.

The asset ID is deterministically derived from the transaction input and contract metadata (if provided). This means that if you use the same contract and transaction input, you'll get the same asset ID.

Creating a Contract

A contract defines metadata about your asset, such as its name, ticker, precision, and issuer information. While contracts are optional, they are highly recommended as they allow your asset to be registered in the Liquid Asset Registry and displayed with proper metadata in wallets.

A contract contains:

  • domain: The domain of the asset issuer (e.g., "example.com")
  • issuer_pubkey: The public key of the issuer (33 bytes, hex-encoded)
  • name: The name of the asset (1-255 ASCII characters)
  • precision: Decimal precision (0-8, where 8 is like Bitcoin)
  • ticker: The ticker symbol (3-24 characters, letters, numbers, dots, and hyphens)
  • version: Contract version (currently only 0 is supported)

Amounts expressed in satoshi are always whole numbers without any decimal places. If you want to represent decimal values, you should use the "precision" variable in the contract. This variable determines the number of decimal places, i.e., how many digits appear after the decimal point. The following table shows different precision values with the issuance of 1 million satoshi.

SatoshiPrecisionAsset units
1.000.00001.000.000
1.000.0001100.000,0
1.000.000210.000,00
1.000.00031.000,000
1.000.0004100,000
1.000.000510,00000
1.000.00061,000000
1.000.00080.1000000
1.000.00080.01000000
Rust
let contract_str = "{\"entity\":{\"domain\":\"ciao.it\"},\"issuer_pubkey\":\"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904\",\"name\":\"name\",\"precision\":8,\"ticker\":\"TTT\",\"version\":0}";
let contract = Contract::from_str(contract_str)?;
Python
contract = Contract(
    domain = "ciao.it", \
    issuer_pubkey = "0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904", \
    name = "name", \
    precision = 8, 
    ticker = "TTT", 
    version = 0)

Issuing an Asset

To issue an asset, use TxBuilder::issue_asset() before calling finish(). You need to have some LBTC in your wallet to pay for the transaction fees.

Rust
// Issue asset
let issued_asset = 10_000;
let reissuance_tokens = 1;

// Create a transaction builder and the issuance transaction
let builder = wollet.tx_builder();
//  isue asset
let mut pset = builder
    .issue_asset(
        issued_asset,
        None, // None -> a wallet from the address is used
        reissuance_tokens,
        None, // None -> a wallet from the address is used
        Some(contract.clone()),
    )?
    .finish()?;

// Sign the transaction and finalize it
let signatures_added = signer.sign(&mut pset).expect("signing failed");
let _ = wollet.finalize(&mut pset)?;
let tx = pset.extract_tx()?;

// Broadcast the transaction
let txid = client.broadcast(&tx)?;
Python
issued_asset = 10000
reissuance_tokens = 1

# Create an issuance transaction 
builder = network.tx_builder()
builder.issue_asset(issued_asset, wollet_adddress, reissuance_tokens, wollet_adddress, contract)
unsigned_pset = builder.finish(wollet)

Getting Asset and Token IDs

After creating the issuance PSET, you can extract the asset ID and reissuance token ID from the transaction input:

Rust
let asset_id = pset.inputs()[0].issuance_ids().0;
let token_id = pset.inputs()[0].issuance_ids().1;
Python
asset_id = signed_pset.inputs()[0].issuance_asset()
token_id = signed_pset.inputs()[0].issuance_token()

Complete Example

Here's a complete example that issues an asset, signs it, and broadcasts it:

Rust

let mut client = test_client_electrum(&env.electrum_url());

// Create wallet
let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";

let signer = SwSigner::new(mnemonic, false)?;
let desc = signer.wpkh_slip77_descriptor()?;

let mut wollet = Wollet::without_persist(network, WolletDescriptor::from_str(&desc)?)?;
let wollet_address = wollet.address(None)?;
let wallet_address_str = wollet_address.address().to_string();

let txid =

let contract_str = "{\"entity\":{\"domain\":\"ciao.it\"},\"issuer_pubkey\":\"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904\",\"name\":\"name\",\"precision\":8,\"ticker\":\"TTT\",\"version\":0}";
let contract = Contract::from_str(contract_str)?;

// Issue asset
let issued_asset = 10_000;
let reissuance_tokens = 1;

// Create a transaction builder and the issuance transaction
let builder = wollet.tx_builder();
//  isue asset
let mut pset = builder
    .issue_asset(
        issued_asset,
        None, // None -> a wallet from the address is used
        reissuance_tokens,
        None, // None -> a wallet from the address is used
        Some(contract.clone()),
    )?
    .finish()?;

// Sign the transaction and finalize it
let signatures_added = signer.sign(&mut pset).expect("signing failed");
let _ = wollet.finalize(&mut pset)?;
let tx = pset.extract_tx()?;

// Broadcast the transaction
let txid = client.broadcast(&tx)?;

let asset_id = pset.inputs()[0].issuance_ids().0;
let token_id = pset.inputs()[0].issuance_ids().1;
Python
network = Network.regtest_default()
policy_asset = network.policy_asset()
client = ElectrumClient.from_url(node.electrum_url())

# Create wallet
mnemonic = Mnemonic("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about")

signer = Signer(mnemonic, network)
desc = signer.wpkh_slip77_descriptor()

wollet = Wollet(network, desc, datadir=None)
wollet_address_result = wollet.address(0)
wollet_adddress = wollet_address_result.address()


contract = Contract(
    domain = "ciao.it", \
    issuer_pubkey = "0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904", \
    name = "name", \
    precision = 8, 
    ticker = "TTT", 
    version = 0)

issued_asset = 10000
reissuance_tokens = 1

# Create an issuance transaction 
builder = network.tx_builder()
builder.issue_asset(issued_asset, wollet_adddress, reissuance_tokens, wollet_adddress, contract)
unsigned_pset = builder.finish(wollet)
# Sign the transaction and finalize it
signed_pset = signer.sign(unsigned_pset)
finalized_pset = wollet.finalize(signed_pset)
tx = finalized_pset.extract_tx()

# Broadcast the transaction
txid = client.broadcast(tx)

asset_id = signed_pset.inputs()[0].issuance_asset()
token_id = signed_pset.inputs()[0].issuance_token()

Important Notes

  • Asset amount limit: The maximum asset amount is 21,000,000 BTC (2,100,000,000,000,000 satoshis)
  • At least one amount required: Either asset_sats or token_sats must be greater than 0
  • Reissuance tokens: If you want to be able to create more of the asset later, you must issue at least 1 reissuance token. The holder of the reissuance token can use it to reissue more of the asset
  • Contract commitment: If a contract is provided, its metadata is committed in the asset ID. This means the asset ID will be the same if you use the same contract and transaction input
  • Confidential issuance: The issuance amounts can be confidential (blinded) or explicit. LWK handles this automatically

Next Steps

After issuing an asset, you can:

  • Reissue more of the asset if you have reissuance tokens
  • Burn some of the asset to reduce the supply
  • Send the asset to other addresses using regular transaction creation

Next: Reissuance