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

Asset issuance & NFTs

Problem. Mint a brand-new Liquid asset — including a single-unit NFT — as part of an action, and use its derived asset ID in the same transaction and in later ones.

Every recipe so far has moved assets that already existed (L-BTC, a covenant's collateral). This one creates them. On Liquid, an asset is issued as a property of a transaction input: you point at a wallet UTXO, attach an issuance block, and the transaction mints a new asset whose ID is derived from that input. Two ideas do most of the work:

  1. The new asset's ID comes from the outpoint of the issuing input — so it's unique and unforgeable, but unknown until the input is chosen.
  2. You capture that ID with an on_resolved hook so the rest of the action (and the instance file) can refer to it.

The only example manifest that issues assets is the lending protocol, so the snippets below are drawn from its IssueUtilityNFTs constructor — reduced to one asset at a time. The lending walkthrough shows all four issuances together.

The issuance block

Add issuance to any wallet input to mint an asset as that input is spent:

{
  "id": "nft_issuance_input",
  "utxo_source": "wallet",
  "asset": "lbtc",
  "issuance": { "kind": "new", "asset_amount_sat": 1, "inflation_amount_sat": 0 }
}
FieldRequiredPurpose
kindyes"new" for a first issuance, "reissue" to mint more of an existing asset.
asset_amount_satyesHow many units to issue, in the asset's base denomination. A literal or a formula.
inflation_amount_satnew onlyHow many reissuance tokens to mint. 0 fixes the supply forever — nobody can ever issue more.
entropyreissue onlyThe issuance entropy of the original mint. See below.
issued_assetnoOn a reissue, the asset ID you expect. A check, not an input.

Either amount may be 0: an issuance of reissuance tokens alone, or of a fixed-supply asset with no reissuance rights.

The input is still an ordinary input: it's a wallet UTXO you also spend for its L-BTC (here, to pay the fee). The issuance block just rides along on it.

Why the asset ID comes from the outpoint

A Liquid asset ID is computed from the outpoint (txid + vout) of the input that issues it. That makes the ID globally unique without a registry — no two inputs can ever share an outpoint — but it has a practical consequence:

One issuance per input, and the ID isn't known up front. To mint N distinct assets in one transaction you need N distinct input UTXOs. And because the ID depends on which UTXO the wallet picks, you can't hard-code it — you compute it during the build and capture it (next section).

This is why the lending protocol ships a Prepare helper action that splits one wallet UTXO into four before IssueUtilityNFTs runs: four NFTs need four separate inputs to issue from.

Capturing the new asset ID with on_resolved

Once the build picks the input's UTXO, its outpoint — and therefore the new asset ID — is fixed. An on_resolved hook on the input fires at that moment and lets you stash the ID into a compile param:

{
  "id": "nft_issuance_input",
  "utxo_source": "wallet",
  "asset": "lbtc",
  "issuance": { "kind": "new", "asset_amount_sat": 1, "inflation_amount_sat": 0 },
  "on_resolved": { "set": { "instance.BORROWER_NFT_ASSET_ID": "asset" } }
}

Inside an input's own on_resolved, the bare word asset means this input's resolved asset ID — the freshly minted one. Elsewhere in the action you refer to it by the input's id, as nft_issuance_input.asset (the general formula reference form). From here on, instance.BORROWER_NFT_ASSET_ID is a normal param: you can lock outputs to it, feed it into a covenant's compile params, or write it into the instance file.

Hooks recap. on_resolved runs per-input as soon as that input's UTXO is known; on_pre_broadcast runs once per action just before building. Both run set assignments. See Hooks & tapleaf compute.

Minting more later: reissue

Issuing more of an existing asset means spending its reissuance token, and that needs a value you can only capture at the original mint: the issuance entropy, fast_merkle_root([sha256d(defining outpoint), contract_hash]). The asset ID is derived from it.

It cannot be recovered from the chain. The reissuance-token UTXO carries no trace of the outpoint that created it, so if the constructor doesn't record the entropy, the asset can never be reissued. Capture it in create_instance, where $inputs.<input_id>.issuance_entropy reaches it:

"create_instance": {
  "fields": {
    "TOKEN_ISSUANCE_ENTROPY": "$inputs.defining_in.issuance_entropy",
    "TOKEN_ASSET": "$inputs.defining_in.issued_asset"
  }
}

The reissuing action then hands it back:

{
  "id": "reissue_in",
  "utxo_source": "wallet",
  "asset": "instance.TOKEN_REISSUANCE_TOKEN",
  "issuance": {
    "kind": "reissue",
    "asset_amount_sat": "params.EXTRA_UNITS",
    "entropy": "instance.TOKEN_ISSUANCE_ENTROPY",
    "issued_asset": "instance.TOKEN_ASSET"
  }
}

issued_asset is optional and is worth supplying. The engine re-derives the asset ID from the entropy and refuses to build if the two disagree — and since an entropy is opaque, and block explorers print it in the reverse byte order from the one used here, a transposed value would otherwise build a perfectly broadcastable transaction that reissues the wrong asset.

Run it

Issuance has no standalone example manifest — it's exercised by the lending constructor. IssueUtilityNFTs needs four separate L-BTC UTXOs (one per issuance), which Prepare carves out first:

txw run examples/lending/txmanifest.json Prepare \
  --wallet borrower.json
txw run examples/lending/txmanifest.json IssueUtilityNFTs \
  --wallet borrower.json
txw sync --wallet borrower.json
txw get-balance --wallet borrower.json   # four new single-asset balances

After it broadcasts, the wallet holds four newly minted assets and the instance file records their IDs. To see the transaction's outputs (including the issuance outputs) before broadcasting, add --export-pset issue.pset.json and decode it.

Try next

You've minted assets and captured their derived IDs. The full four-NFT construction — plus packing loan terms into amounts and computing the covenants those NFTs get locked to — is the first phase of the lending walkthrough: Issuing the NFTs & encoding the terms. The hooks that capture and derive these values get their own recipe: Hooks & tapleaf compute.