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

Running it: change and fees

Problem. Run the manifest you just wrote, and account for every satoshi it moves — including the ones you did not think about.

You have a manifest (Splitting a UTXO) but you have not run anything yet. Doing so is how you meet the rule that governs every transaction a manifest builds: every output a transaction carries has to come from the manifest. The fee is the single exception.

Run it — and watch it fail

First, a funded wallet. If you have not done this yet, create one and fill it from the faucet (Creating a wallet):

txw info --wallet wallet.json     # copy the receive address

Paste that address into the Liquid testnet faucet, request the coins, then pick them up:

txw sync --wallet wallet.json

Now run the action. It is worth checking the file parses first — validate touches no network and no wallet:

txw validate txmanifest.json

txw run txmanifest.json Split \
  --network testnet --wallet wallet.json

You will be prompted for amount_each. Give it something well under a quarter of your balance, and the build stops:

[error] PSET build failed:
  0: L-BTC does not balance: inputs exceed outputs by 600 sat, but the fee is 486 sat, leaving 114 sat unaccounted for.
This action does not permit L-BTC change, so there is nowhere for it to go. Either set "allow_change": "lbtc_only" on the action, declare a change output, or size the input to outputs + fee exactly.

Nothing was broadcast and nothing was spent — this failed while building the transaction, before any signature existed.

Where does the surplus go?

The input you selected is worth more than the four outputs plus the fee. That leftover has to go somewhere, and the engine will not decide for you.

It could send it to your change address — but you did not ask for that. It could add it to the fee — but then 114 satoshis leave your wallet to a miner, in an amount nobody wrote down. Every output a transaction carries has to come from the manifest, so rather than invent one, it stops and tells you the three ways out.

Fix it: declare a change output

The usual answer. Add a fifth output:

        { "id": "change_out", "destination": "change", "asset": "lbtc", "optional": true }

It differs from the four above in three ways, and all three matter:

  • destination: "change" sends to your change address rather than your receive address.
  • No amount_sat. A change output takes whatever is left after the other outputs and the fee. It is the only kind of output that computes its own amount.
  • optional: true lets the action drop it, because that remainder can be zero — and a zero-satoshi output is not something you can broadcast.

With it declared, your input only has to be big enough. Too small and you get the other error, which is the same arithmetic from the other side:

Insufficient L-BTC: have 41000 sat, need 42126 sat (outputs 40000 + fee 2126)

Or: let the engine add one

The other answer from the error message. Add allow_change to the action, beside description:

      "allow_change": "lbtc_only"

This permits the engine to return an L-BTC surplus without a declared output. "lbtc_only" is the setting worth reaching for: a surplus in any other asset is still an error, so a protocol token you failed to account for cannot quietly walk out. The default is "none", which is why you saw the error at all.

Which one

Declare a change output when you want the change to be part of the transaction you described — as Split does. Set allow_change when the surplus is an artefact of fee estimation rather than something the action is about.

And sometimes neither is right. An action with no change output must size its outputs to the input exactly, fee included, which sounds impractical until you meet a recursive covenant: the Last Will refresh path recreates its own covenant and may have only two outputs, so it computes its amount as will_in.amount_sat - fee and declares no change at all.

Take the first fix — add change_out — and carry on.

Where is the fee?

Elements transactions pay the fee as a real output — an explicit L-BTC output with an empty script. So this transaction will have six outputs, not five.

The fee is the one output the engine appends on its own, because its amount is only known once the transaction's size is, and that is after everything else is decided. It is always last.

That exception is worth remembering when you read a covenant. A program that asserts num_outputs == 2 against a manifest declaring a single output is not wrong — it is counting the fee.

If you want the fee visible in the manifest anyway, an output can declare "destination": { "type": "fee" }. It produces nothing on its own; it is a note to the reader that the fee leg is expected here. None of the examples use it, but a covenant that counts outputs is the case where it earns its place.

Run it again

txw run txmanifest.json Split \
  --network testnet --wallet wallet.json

This time it selects an input, builds four outputs plus change, signs, and broadcasts. Afterwards your wallet holds four fresh UTXOs:

txw sync --wallet wallet.json
txw get-balance --wallet wallet.json

The built-in shortcut. Because splitting is so common, the CLI ships it as a first-class command — no manifest needed:

txw split -n 4 --asset lbtc --amount-each 10000 --wallet wallet.json

And prepare will split automatically when an action needs more UTXOs than the wallet currently has. You have just written the long way round, which is the point — the next recipe does something no built-in command can.

The finished file

The complete file
{
  "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" },
        { "id": "change_out", "destination": "change", "asset": "lbtc", "optional": true }
      ]
    }
  }
}

Try next

You have written a manifest and broadcast a transaction from it, and you know where every satoshi went. The next recipe adds the first real Simplicity covenant — a UTXO type, a script, and the witnesses to spend it: Hello World: Pay-to-Public-Key.