Outputs & destinations
Problem. Send a transaction's value to the right place — a wallet, a change address, a covenant, a raw script hash, or an
OP_RETURN— and control whether each output is blinded.
Every action so far produced two outputs: a covenant output and a change output. Those are only two of the destinations available. This recipe is a tour of all of them, plus the rules for output confidentiality.
The output descriptor
An output descriptor has these fields:
| Field | Required | Purpose |
|---|---|---|
id | yes | Unique name within the action. |
destination | yes | Where the value goes. See below. |
amount_sat | no | Amount in satoshis — a literal or a formula. Omit it for a change destination, which auto-computes the remainder. |
asset | no | Asset ID — "lbtc", a 64-char hex ID, or a reference. Omit only where the destination implies it. |
description | no | Human-readable purpose. |
optional | no | If true, the output may be omitted (e.g. zero change). Default false. |
confidential | no | Whether to blind this output. See the rules below. |
blinding | no | Pin this output's asset_bf / value_bf instead of letting the builder choose. |
data | no | OP_RETURN payload — only valid with the op_return destination. |
ui | no | Clear-signing label and role for this leg. |
required_index | no | Documents the index the covenant expects. Not enforced — see below. |
Recipe: every destination type
Wallet and change
{ "id": "to_me", "destination": "wallet", "amount_sat": "...", "asset": "lbtc" }
{ "id": "change_out", "destination": "change", "asset": "lbtc", "optional": true }
wallet is your primary receive address; change is your change address. A
change output needs no amount_sat — the tool sends whatever is left after the
other outputs and fees. It is almost always optional too, since that remainder
can be zero.
An address supplied at run time
{ "id": "recipient_output", "destination": "params.recipient_address", "amount_sat": "params.send_amount_sat", "asset": "lbtc" }
The destination is a bare string referencing an action param of type address —
the way to pay an arbitrary recipient address that the user supplies at run time.
A covenant UTXO type
{ "id": "p2pk_out", "destination": { "utxo_type": "p2pk_output" }, "amount_sat": "params.amount_sat", "asset": "lbtc" }
The tool computes the named UTXO type's P2TR address from its .simf source and
compile params, and locks the output there. This is how a transaction creates the
protocol's on-chain states — exactly what Pay in
recipe 1 does with p2pk_output.
A raw script hash from a compile param
{ "id": "relocked", "destination": { "script_hash": "instance.PARAMETERS_NFT_OUTPUT_SCRIPT_HASH" }, "amount_sat": 1, "asset": "instance.FIRST_PARAMETERS_NFT_ASSET_ID" }
When you already hold a 32-byte covenant script hash as a derived compile param, embed it directly as a P2TR output without recompiling. The lending protocol uses this to re-lock NFTs under a script-auth covenant.
OP_RETURN
{
"id": "indexer_op_return",
"destination": { "type": "op_return" },
"amount_sat": 0,
"asset": "lbtc",
"data": "concat(instance.BORROWER_PUB_KEY, instance.PRINCIPAL_ASSET_ID)"
}
An OP_RETURN output is provably unspendable — its value is destroyed. Two common
uses:
-
On-chain discovery. Publish protocol metadata (here, the borrower's pubkey and principal asset, 64 bytes) so an indexer can list the contract without an off-chain database.
datatakes either theconcat(...)string above, or an object of typed parts when the exact byte layout matters:"data": { "parts": [ { "type": "bytes32", "value": "instance.BORROWER_PUB_KEY" }, { "type": "u64", "value": "instance.PRINCIPAL_AMOUNT" } ] }Use the object form when a field's width or endianness has to be pinned; the string form is shorthand for concatenating 32-byte references.
-
Burning a token. Spend an NFT into
OP_RETURNto destroy it — the lending protocol burns auth NFTs this way to prevent reuse.
burn
{ "id": "nft_burned", "destination": { "type": "burn" }, "asset": "instance.LENDER_NFT_ASSET_ID", "amount_sat": 1 }
The engine treats burn exactly as op_return: a bare OP_RETURN unless the
output carries a data field, in which case the bytes are embedded. The two are
interchangeable, so pick the one that says what you mean — burn when the point
is destroying a token, op_return when the point is publishing bytes.
fee
{ "id": "network_fee", "destination": { "type": "fee" } }
A declaration, not an output. Elements pays the fee as a real explicit L-BTC output, but the engine always appends that itself — its amount is only known once the transaction's size is. This destination produces nothing of its own.
Declare it when a covenant counts outputs and you want the manifest to show the leg the program is counting. Otherwise leave it out; every example does.
Surplus, change and fees
An output with "destination": "change" is a declared change output, and takes
whatever is left after the other outputs and the fee. An action can also permit
the engine to add a change output it never declared, with allow_change:
"none" (the default), "lbtc_only", or "any".
Both, and the arithmetic behind them, are covered where you first meet them: Running it: change and fees.
How confidentiality is decided
On Liquid, outputs can be blinded (amount and asset hidden). Blinding is decided per output:
- The output's own
confidentialfield, if present. - Otherwise the chain default:
falsefor Bitcoin,truefor Liquid/Elements.
There is no file-level or per-utxo_type default to fall back on, and that is
the right granularity: blinding is a property of an output, not of an address.
One covenant address can hold a blinded reissuance token beside an explicit
collateral UTXO, so only the output itself can answer the question.
An output may also carry a blinding block pinning the asset_bf and value_bf
scalars the builder would otherwise choose at random. That matters when a
covenant verifies its own UTXOs as Pedersen commitments and so has to know the
factors in advance.
OP_RETURN outputs are always explicit — there is nothing to hide in a
provably unspendable output.
Blinding a covenant output is allowed, and usually wrong. Setting
confidential: trueon autxo_typedestination does work: the output is blinded with the wallet's change blinding key, and a later action can spend it by declaring the factors it was created with. But a Simplicity covenant reads explicit amounts and asset IDs through jets likecurrent_amountandcurrent_asset, and cannot read a Pedersen commitment. So blind a covenant output only when the program never introspects the value — and when it does need to, pin the factors withblindingso the spend can reconstruct the prevout.
Output order, and required_index
Covenants that introspect the transaction often require outputs in an exact order
("collateral at output 0, principal at output 1"). What puts an output at a
given index is the order you declare it in — outputs are built in the order
they appear in the outputs array. If the covenant wants the collateral first,
write the collateral output first.
required_index records that expectation:
{ "id": "lending_collateral_out", "required_index": 0, "destination": { "utxo_type": "lending_collateral" }, ... }
{ "id": "principal_to_borrower", "required_index": 1, "destination": "params.borrower_address", ... }
required_indexis documentation. Nothing checks it. The tool parses the field and never compares it against where the output actually landed, so a value that disagrees with the declaration order is silently ignored and the covenant sees the declared order. Treat it as a comment that tells the next reader — and you, six months later — which positions the program depends on. The thing that has to be right is the order of the array.The same field exists on inputs, with the same caveat. Input order likewise comes from the
inputsarray.
Run it
Outputs are exercised by every action; there is no standalone command. To inspect exactly what an action will produce without broadcasting, export the PSET and decode it:
txw run examples/p2pk/txmanifest.json Pay \
--network testnet --wallet wallet.json --export-pset pay.pset.json
The exported file lists every output with its amount, asset, and scriptPubKey, so you can confirm the destinations resolved as you intended.
Try next
We've now described value flowing out. Spending a covenant requires witnesses to satisfy its Simplicity program — signatures, path selectors, and computed values. That's the next recipe: Witnesses.