Skip to content
Logo

Emitting Outputs

While handling a request your application can emit four kinds of outputs. They differ in what they can do and in what can be proven about them on-chain:

OutputProvablePurpose
VoucheryesExecute a transaction on-chain (transfer assets, call a contract)
NoticeyesAttest a fact about the application state
ReportnoLogs, diagnostics, inspect responses
ExceptionnoSignal that the request could not be processed

Vouchers and notices are added to the application's outputs merkle tree, so their existence can later be proven and they can be executed/validated on-chain. Reports are plain data made available off-chain.

Vouchers: act on-chain

A voucher is a one-shot transaction the application authorizes: when the epoch containing it settles, anyone can execute it through the application contract. Use it to transfer assets out, call DeFi protocols, or trigger any on-chain effect:

// transfer 1 ETH from the application contract to a user
const  = .({
    : "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
    : 1_000_000_000_000_000_000n, // wei
    : "0x", // no calldata — plain value transfer
});

To call a contract, put the EVM calldata in payload — for example an ERC-20 transfer(address,uint256):

const  = "f39Fd6e51aad88F6F4ce6aB8827279cffFb92266".(64, "0");
const  = (10n ** 18n).(16).(64, "0");
 
.({
    : "0x6B175474E89094C44Da98b954EedeAC495271d0F", // token contract
    : `0xa9059cbb${}${}`, // transfer(address,uint256) selector + args
});

There is also a delegate-call variant that executes the target's code in the application contract's own storage context — an advanced tool for upgrade-style patterns.

Notices: attest facts

A notice carries no on-chain action — it is a verifiable statement that "the application, having processed inputs up to this point, says X". Typical uses: settlement results, state snapshots, anything an off-chain consumer wants to trust without re-executing the machine:

const  = { : "0xf39F…2266", : "1000" };
.(.(.()));

Reports: observe and debug

Reports are for everything that doesn't need a proof: log lines, error messages, and answers to inspect queries. They cost nothing on-chain and cannot be verified:

.(.("debug: cache warmed up"));

Exceptions: give up loudly

An exception tells the outside world the request could not be processed at all — a structural failure rather than a business-rule rejection. Prefer returning false from a handler (or throwing, which run turns into a report + rejection); reach for emitException only when the application cannot continue in a meaningful way.

Output indices and proofs

emitVoucher, emitDelegateCallVoucher and emitNotice return the output's index — its position among all provable outputs ever produced by the application. Persist or report it if an off-chain component needs to locate the output later to fetch its proof and execute/validate it:

const indexArrow
= .(.("state-root:abc123"));

Byte and number conventions

All payload-like arguments accept three shapes, converted automatically:

.("0xdeadbeef"); // 0x-prefixed hex string
.(.("hello")); // Buffer
.(new ([1, 2, 3])); // Uint8Array
  • Addresses (destination, msgSender, …) must be exactly 20 bytes; as strings they are 0x-prefixed hex.
  • 256-bit values (value) accept bigint, number, or 32 bytes.
  • Outgoing data is always Buffer (payloads) / bigint (numbers) / 0x-hex strings (addresses).

Hex strings are typed as `0x${string}` (Hex), so values from viemHex from encodeFunctionData, Address, bigint from parseEther — pass in directly, and request addresses pass back into viem without casts.

Invalid lengths or malformed hex throw TypeError/RangeError before anything reaches the device.

When libcmt fails

Every method can throw a RollupError carrying the libcmt error code if the underlying call fails — for example emitting an output larger than the machine's output buffer. The negative errno is available on error.errno and the failed call name on error.syscall.