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

Splitting a UTXO

Problem. Turn one large wallet UTXO into several smaller ones — a handy warm-up, and a common prerequisite for actions that need several discrete input UTXOs.

Before the first real contract, here is the gentlest possible manifest: no covenants, no witnesses, no compile parameters. Just one input and a handful of outputs, all to your own wallet. It does one useful thing — split a UTXO into four equal pieces — and in doing so introduces the bare skeleton every manifest shares.

You build it a piece at a time here, and run it in the next recipe.

Start with a skeleton

Create txmanifest.json with the envelope and one empty section:

{
  "manifest_version": "0.2.0",
  "protocol": "utxo-split",
  "description": "Split one wallet UTXO into four equal wallet UTXOs.",
  "chain": "liquid",

  "actions": {}
}

Only manifest_version and protocol are required; description and chain are here because they are useful. There is no utxo_types section, because nothing is locked to a covenant, and no contract_templates, because nothing is baked into a script. A manifest really can be this small.

Add the action shell

An action is one transaction recipe. Give it a name, a description, and the values it will ask you for. Replace the empty actions with:

  "actions": {
    "Split": {
      "description": "Split a wallet UTXO into four outputs of amount_each, returning any remainder (less fees) as change.",

      "params": {
        "amount_each": {
          "type": "u64",
          "description": "Satoshis to place in each of the four output UTXOs."
        }
      }
    }
  }

Split is the name you will type on the command line. Its one param, amount_each, is a value you supply each time you run the action — the tool prompts for it, and the description is the hint it shows you.

A param affects only the transaction being built. It never changes a script or an address, which is the distinction Parameters makes precise later.

An action with no inputs and no outputs is legal but builds nothing, so it needs both.

Add the input

inputs lists the UTXOs the transaction consumes. This one needs a single wallet UTXO, big enough to cover four shares. Add it inside "Split", after params:

      "inputs": [
        {
          "id": "funding_input",
          "description": "A wallet UTXO large enough to cover four outputs plus fees.",
          "utxo_source": "wallet",
          "asset": "lbtc",
          "amount_sat": { "min_amount": "params.amount_each * 4" }
        }
      ]

Three things are being said here:

  • utxo_source: "wallet" — take any wallet-controlled UTXO. The alternative, which the next recipe uses, is to name a covenant to spend from.
  • id — a handle for this input. Other parts of the action refer to it by this name, and errors quote it.
  • amount_sat as { "min_amount": ... } — not a fixed amount, but a floor. The tool auto-selects a UTXO worth at least params.amount_each * 4. That expression is a formula; params.X reads the param you just declared.

Add the outputs

outputs lists what the transaction creates, in the order they will appear. Add it after inputs:

      "outputs": [
        { "id": "split_0", "destination": "wallet", "amount_sat": "params.amount_each", "asset": "lbtc" },
        { "id": "split_1", "destination": "wallet", "amount_sat": "params.amount_each", "asset": "lbtc" },
        { "id": "split_2", "destination": "wallet", "amount_sat": "params.amount_each", "asset": "lbtc" },
        { "id": "split_3", "destination": "wallet", "amount_sat": "params.amount_each", "asset": "lbtc" }
      ]

Each split_n sends amount_each to destination: "wallet" — your own receive address. They are written out four times rather than looped; a manifest has no loops, and every output a transaction carries is written in the file.

That looks complete — and it will not run. Why not is the next recipe.

What you have so far

An envelope and one action. No utxo_types, because nothing is locked to a covenant; no witnesses, because every input is an ordinary wallet UTXO the wallet signs the usual way. Witnesses appear the moment you spend a covenant, which is two recipes away.

The file so far
{
  "manifest_version": "0.2.0",
  "protocol": "utxo-split",
  "description": "Split one wallet UTXO into four equal wallet UTXOs.",
  "chain": "liquid",

  "actions": {
    "Split": {
      "description": "Split a wallet UTXO into four outputs of amount_each, returning any remainder (less fees) as change.",

      "params": {
        "amount_each": {
          "type": "u64",
          "description": "Satoshis to place in each of the four output UTXOs."
        }
      },

      "inputs": [
        {
          "id": "funding_input",
          "description": "A wallet UTXO large enough to cover four outputs plus fees.",
          "utxo_source": "wallet",
          "asset": "lbtc",
          "amount_sat": { "min_amount": "params.amount_each * 4" }
        }
      ],

      "outputs": [
        { "id": "split_0", "destination": "wallet", "amount_sat": "params.amount_each", "asset": "lbtc" },
        { "id": "split_1", "destination": "wallet", "amount_sat": "params.amount_each", "asset": "lbtc" },
        { "id": "split_2", "destination": "wallet", "amount_sat": "params.amount_each", "asset": "lbtc" },
        { "id": "split_3", "destination": "wallet", "amount_sat": "params.amount_each", "asset": "lbtc" }
      ]
    }
  }
}

Try next

The manifest is written. In the next recipe you run it — and it fails, for a reason worth understanding before you write anything larger: Running it: change and fees.