Skip to main content
Before sending a program with real funds, check its deployment, funding, inputs and failure paths. A successful simulation shows what the program does against the simulated state; you still need to review the recipients, amounts and limits against your intended outcome.

Prerequisites

  • A program that compiles and simulates. See Quickstart.
  • The sauce command, installed as in Installation.
  • The release’s engine and Kitchen deployed on a chain the sauce command sends on. sauce pot --chain <chain> --owner <address> reports both.

The release’s contracts have code on your chain

A cook runs on the engine paired with the compiler in your package, through the Kitchen and your Pot. The SDK records the release’s contract addresses. A recorded address does not prove that code is deployed there on a given chain. sauce pot reads the chain and reports the engine, the Kitchen and your Pot:
Where the engine has no code, sauce pot says so, and no cook can run there:
Where the Kitchen has no code, no Pot can be deployed and no cook runs, and sauce pot says so after printing the Pot’s address:
Deploy the Pot before you send it funds, with sauce pot --chain <chain> --deploy and your signer, or let sauce run --fund deploy it before it funds it. Until a Pot is deployed, its address is the Kitchen’s prediction: a different Kitchen predicts a different address. Funds sent where the Kitchen has no code cannot be spent until a matching Kitchen exists. Run sauce pot with an endpoint: --offline derives the address and reads nothing. A preview chain can have both contracts deployed and still print an address that the sauce command and the MCP server refuse to deploy or cook on; see Supported chains. To check an address yourself, ask your endpoint for its code with eth_getCode:
A result of 0x means no code at that address. Deployments lists the engine, Kitchen and Router addresses. An engine runs only a Pot that its paired Kitchen created. A balance never moves between Pots on its own.

Simulate at the current block

Simulate the file you are about to send, with the same arguments, from the owner who sends it.
Read the whole report: what leaves your wallet, what arrives where, what stays in the Pot, and the gas. The report counts tokens only. A position a protocol records in its own storage, with no token for it, appears in no balance. A price limit from sauce pools or sauce price describes one block. Simulate with --block <number> set to that block, then simulate again without it, at latest, right before you send. The chain can still move between your last simulation and the block that includes the cook. The bounds in the program, below, are what holds the cook to your prices then. --assume-balance exists only in the simulation. Before sending, deploy the Pot and then fund it, or pass --fund, which deploys the Pot first, and simulate that path too. See Funding.

Minimum outputs and price limits

Give every swap a minimum output, a price limit, or both, as main parameters:
swap.sauce.ts
Below minOut, the cook reverts. A non-zero sqrtPriceLimitX96 stops the swap at that price, and the input the pool did not use stays in the Pot. A swap with a zero limit and a zero minimum accepts any price. sauce price and sauce pools compute the limits. Quote, swap and supply shows a full example. Amounts are in the token’s smallest unit: 1000000 is 1 USDC.

require guards for the conditions you rely on

Your program enforces the conditions you write. Before each step that depends on a balance, a price or a time, add a require with a message:
pay.sauce.ts
A failed require reverts the whole cook. Check that each guard fires with --expect-revert, which exits 0 only when the cook reverts with a reason containing the text:
A failed Int256(value) or Uint256(value) conversion also reverts the cook, with no message. Put a require before a conversion that can fail, so the revert says why.

Read the results of the calls you rely on

On EVM chains, the result of a contract call that the program does not read is not decoded or checked. An empty or malformed reply, or a call to an address with no code, then passes. To rely on a call:
  • read its result, which decodes and checks every declared output;
  • check ctx.isContract(address) where the target must have code;
  • move tokens with the SDK’s checked writes (USDC.transfer, Token(address).approve, the @sauce/token functions), which reject a refused transfer and a target with no code. A raw IERC20.at(token).transfer(...) with its result discarded checks neither.

Catch boundaries

A cook is one transaction. An uncaught failure reverts every step of it, and the Kitchen’s fee is not paid. On EVM chains a program can catch a failure, and the catch decides what is undone:
  • A try block of several statements rolls back all of its own calls, writes and assignments before catch runs. Work before the block stays.
  • A try around one external call, or .catch() on a call, covers that call’s failure only. Decoding a successful reply is outside it.
  • The handler receives at most 256 bytes of revert data, possibly empty or truncated. Check its length before decoding it.
  • Solana programs cannot catch: any failure reverts the cook.
guarded.sauce.ts
With 2 USDC in the Pot, supplying 1 USDC succeeds, approves the pool and supplies:
Supplying 5 USDC fails inside the block. The cook still succeeds, and the approval made in the block is rolled back with it:
The cook pays the Kitchen’s fee in both cases. Simulate every catch path as carefully as the success path: the catch handler determines the final state of a successful cook.

Approvals outside the cook’s rollback

A revert, or a try block’s rollback, covers the cook alone:
  • An approval sent from your wallet in an earlier transaction stands after a reverted cook. --fund-mode allowance sends one. --fund-mode permit2, the default, gives the Pot no allowance.
  • An approval a program makes from the Pot, and does not roll back, outlives the cook that made it. Approve exactly what the next call spends.
Token writes in a program (USDC.approve, the @sauce/token functions and the @sauce/router swaps) follow OpenZeppelin’s SafeERC20 rules: approve resets a standing allowance to zero before it sets the new amount. A raw IERC20.at(token).approve(...) call is not checked this way.

The Kitchen execution fee

Every successful cook through the Kitchen pays its execution fee, regardless of the program’s operations:
  • On the EVM chains it is paid in the chain’s native coin as the cook’s value; on Arc and Arc Testnet that coin is USDC. On the testnets sauce pot read it as 0 (Testnets gives the blocks).
  • The Kitchen’s owner sets the fee onchain, and it can change. sauce tx, sauce run and sauce simulate read it when they build the cook. A cook sent with less reverts with FeeNotPaid.
  • A cook that reverts pays no fee.
  • On Solana the fee is in lamports. --max-execution-fee <lamports> refuses a cook whose fee is above your cap.
sauce pot prints the current fee, as above. The wallet that sends the cook pays the gas and the fee. The first run also pays gas for the Pot’s deployment, which a simulation reports as step 1. On Arc, sauce pot and sauce tx also print the minimum fee cap a transaction needs to be included.

Wallet policy limits

A wallet the sauce command keeps is a hot wallet: whoever holds its file and its password holds the key. Keep small balances in it. For larger amounts, print the transaction with sauce tx and sign it with a wallet that keeps its own key. See External wallets. Give each wallet a policy, <name>.policy.json in the wallet directory:
ops.policy.json
  • Amounts are whole tokens ("5" is 5 USDC). A trailing n gives base units ("5000000n").
  • to lists the addresses a transaction may call. On EVM chains that is usually your Pot. It does not limit whom the program pays: the program and your review do that.
  • Before each send, the command simulates the transaction and measures what leaves the wallet and its Pot, and the allowances it leaves other keys. It refuses a send that breaks a limit, and a send it cannot measure where a limit needs the measurement. A token limit also refuses an approval of Permit2 for that token, so fund such a wallet’s runs with --fund-mode permit, allowance or transfer.
  • The simulation runs on the chain’s head a moment before the send. A bound that must hold in the block the transaction lands in is a require in the program.
  • chmod 600 the policy file and any password file. The command refuses a password file that others can read or write, and a policy file that others can write.
In a script or CI job, name the password with --password-file or --password-command. Never put a key on the command line. See Wallets and policy. When another signer signs, check the unsigned cook against your file before signing it:
sauce inspect confirms the program in the call is the one your file compiles to, and decodes the arguments after it. The transaction’s to must be your Pot.

Your own endpoints

The built-in public endpoints are for trying things. They can be slow or rate-limited. For production use, configure your own endpoint for each chain. Keep configuration containing provider credentials out of version control:
sauce.config.json
Choose endpoints that serve eth_simulateV1. Without it, a simulation reports no transfers, and a wallet policy that needs a measurement refuses the send. See Chains and endpoints.

Supported chains

The sauce command and the MCP server build and send transactions on Ethereum, Base, Arc and Solana and the testnets Arc Testnet, Base Sepolia, Ethereum Sepolia, Solana Devnet and Solana Testnet only. On every other chain a program compiles and the reads work, and a simulation runs wherever the release’s engine and Kitchen are deployed, but building or sending a transaction is refused before anything is signed. Send on a supported chain. See Chains.

Next steps