Skip to main content
A cook is one transaction from a Pot’s owner to the Pot. It calls cook(engine, program): engine is the V12 engine that runs the program, and program is the compiled bytecode with the encoded entry arguments appended. Its value is the Kitchen’s execution fee plus any native coin the program spends. A Pot’s address follows from its owner and a salt, so you can compute it before the Pot exists. The Pot exists once someone has sent the Kitchen’s deployPot(owner, salt), which anyone can send once for each owner and salt. The Pot and cook pages explain both contracts. The functions on this page build transactions and sign nothing. Only the signed-cook checks read a chain, through the client you pass them: checkSignedCookPot, cookNonceUsed, and verifySignedCook for an owner with code. Read the chain through your own client, and sign and send with your own wallet.

Complete example on Base

This script compiles a payment program for Base, encodes its arguments, predicts the owner’s Pot, reads the Kitchen fee and builds the cook. It then simulates the Pot’s deployment and the cook together with viem’s simulateCalls, from the owner, at one block. Nothing is signed or sent.

Prerequisites

  • Node.js 24 or newer, with @eco-incorp/sauce and viem installed.
  • A Base RPC endpoint, passed as the script’s first argument. The library has none of its own.
  • The release’s engine deployed on Base. sauce pot --chain base --owner <address> reports whether it is. The script also checks: it refuses to build a cook when the engine, the Router or the Kitchen has no code, or when the Kitchen predicts a different Pot than the SDK.

The script

cook.ts
Pay 1 USDC:
The owner’s Pot holds no USDC, so the program’s require stopped the cook and its message came back as the revert data. Pay nothing:
Where the release is not deployed, the script stops before building anything:
Your block, fee and gas differ. To run the cook, the owner signs and sends deploy once, where the Pot has no code, and then cook, each as { to, data, value } with any wallet library. Read the fee shortly before building the cook: a fee raised between that read and the cook makes the cook revert.

routes.buildSauceEvmCall

Builds one cook(engine, program) call on a Pot.

Import

Usage

encode-cook.ts

Signature

Parameters

Returns

SauceEvmCall, in both shapes a call is used in:
  • { to, data, value }, the transaction a wallet sends or client.call takes. to is the Pot’s checksummed address.
  • { target, data, value }, a call record. target is the same Pot as a 32-byte, lowercase, left-padded address.
value is a bigint.

Throws

It throws for an engine that the release sets in /deployments record only for compilers other than the one the SDK pins, because that engine cannot run this SDK’s programs. More than one compiler version can share an engine and Kitchen, so an engine recorded for the SDK’s compiler passes even where another set records it too. The message names the engine the SDK’s programs need. An engine no release set names is not refused.

Several programs

routes.buildSauceEvmCalls({ pot, engine, cooks, value }) returns one call per program in cooks, in order, each carrying value. Each call is a separate cook and pays the fee; statements inside one program need one cook.

v12Pot.encodeCook

The calldata encoder buildSauceEvmCall uses: cook(engine, program) as hex. v12Pot also carries the Pot’s ABI as v12Pot.abi; v12PotAbi is the same ABI as a separate export.

Import

Usage

encode-cook.ts above compares it with buildSauceEvmCall:

Signature

Parameters

Returns

Hex: the cook(engine, program) calldata.

Throws

The same refusal as buildSauceEvmCall, for an engine that the release sets record only for other compilers.

Signed cooks

A Private Pot runs a cook its owner signed, sent by any account, through the Pot’s cookSigned (Signed cooks). The owner signs EIP-712 typed data; the sender sends cookSigned with exactly the signed value and pays the gas; the Pot pays the Kitchen’s fee from its own balance, up to the signed ceiling. @eco-incorp/sauce/evm/engine builds each part, and your wallet signs. sauce sign builds the same from a terminal.

Import

Usage

sign-cook.ts
On a local fork of Base at block 52991146 with the current contracts, with program the payment program of the complete example paying 1 USDC, and a freshly deployed Pot holding 5 USDC and 0.001 ETH:
  • before the Pot was deployed, checkSignedCookPot returned { ok: false, reason: "0xFbfB…43E1 has no code: no Pot is deployed there" }, and after it, { ok: true, owner };
  • send was 676 bytes of cookSigned calldata to the Pot, with value 0n. A second account sent it: the cook succeeded with 121110 gas, and the recipient received 1000000 USDC base units;
  • cookNonceUsed(client, pot, cook.nonce) then returned true;
  • a second signature’s cancel, sent by the owner, set its nonce, and its send then reverted with NonceUsed().

Exports

checkSignedCookPot checks the Pot before you sign. The @eco-incorp/sauce reference has each signature. Security considerations covers replay, deadlines, the submitter and the fee ceiling.

v12PredictPot

Computes a Pot’s address from its owner and salt, with no chain read: CREATE2(kitchen, keccak256(abi.encode(owner, salt)), initCodeHash), the init-code hash taken from the pinned release.

Import

Usage

pot.ts

Signature

Parameters

Returns

Hex: the Pot’s address. It is the same on every chain where the Kitchen is deployed at the same address. The address is arithmetic, not proof that a Pot exists: check for code with client.getCode, as the complete example does. A Pot’s address is keyed to the Kitchen, the owner and the salt, and the Pot’s bytecode is part of it. An engine runs only a Pot that its paired Kitchen created (Compiler and engine pairing); a balance never moves between Pots on its own.

DEFAULT_POT_SALT and potSalt

DEFAULT_POT_SALT is the salt of an owner’s default Pot: 32 zero bytes. potSalt(label) is the salt of a Pot the owner names label: keccak256 of the label’s UTF-8 bytes. The sauce command’s --salt treasury is potSalt("treasury"). For an exact salt, pass its 32 bytes to v12PredictPot as they are.

Import

Usage

pot.ts above prints both salts and the two Pots they give one owner.

Signature

Throws

potSalt("") throws: potSalt: a label names a Pot, so it cannot be empty — the default Pot's salt is DEFAULT_POT_SALT.

routes.sauceEvmDeployPotCall

Builds the Kitchen’s deployPot(owner, salt) call, the transaction that creates a Pot. Send it before the first cook on a Pot that has no code yet.

Import

Usage

pot.ts above builds one and checks it against v12Kitchen.encodeDeployPot from /evm/engine.

Signature

Parameters

Returns

SauceEvmCall: { to, target, data, value } with to the Kitchen and value 0n. It reads no chain, so it does not know whether the Kitchen has code where you send it, or whether the Pot already exists.

kitchenExecutionFee

Builds the read of the Kitchen’s executionFee() and its decoder. It performs no request: you send it as an eth_call. The fee can change on chain, so read it shortly before building a cook.

Import

Usage

From the complete example:

Signature

Parameters

Returns

ExecutionFeeCall: to and data for the eth_call, and decode, which turns its result into the fee in the chain’s native unit.

Release addresses

@eco-incorp/sauce/deployments returns the addresses of the pinned V12 release: the same on every chain, recorded rather than read from one. The values are on Deployments. The SDK reads no chain, so check that a chain has code at an address before you rely on it.

Next steps