> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Production checklist

> What to check before a Sauce program moves real funds.

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](https://docs.eco.com/quickstarts/sauce/first-program.md).
* The `sauce` command, installed as in [Installation](https://docs.eco.com/sauce-sdk/installation.md).
* 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:

```bash theme={null}
sauce pot --chain base --owner <owner>
```

```text theme={null}
On base: the engine <engine address> is deployed; the Kitchen <kitchen address> is deployed, its execution fee 3744785386350 wei (0.00000374478538635 ETH); the Pot <pot> is NOT deployed.
```

Where the engine has no code, `sauce pot` says so, and no cook can run there:

```text theme={null}
The released engine has no code on base: a cook there has nothing to run on.
```

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:

```text theme={null}
The released Kitchen has no code on <chain>: no Pot can be deployed there, and no cook runs.
```

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](#supported-chains).

To check an address yourself, ask your endpoint for its code with `eth_getCode`:

```bash theme={null}
cast code <engine address> --rpc-url <your Base endpoint>
```

A result of `0x` means no code at that address. [Deployments](https://docs.eco.com/sauce-sdk/reference/deployments.md) 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.

```bash theme={null}
sauce simulate pay.sauce.ts --chain base --owner <owner> --assume-balance USDC:2 --args <recipient> 1000000
```

```text theme={null}
The cook succeeds from <owner>: gas used 88091 for the cook, after the Pot's deploy (1. below), returns 0x
Left in the Pot, not in the owner's wallet:
  1 USDC (it held 2 USDC before)
…
Simulated with eth_simulateV1 on the state of block <block>; nothing was signed or sent:
…
What moved, in order:
  [1] wallet <owner> -> Pot <pot>      0.00000374478538635 ETH  the cook's value
  [2] Pot            -> Kitchen …      0.00000374478538635 ETH  the Kitchen's fee
  [3] Pot            -> <recipient>    1 USDC
```

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](https://docs.eco.com/sauce-sdk/cli/funding.md).

## Minimum outputs and price limits

Give every swap a minimum output, a price limit, or both, as `main` parameters:

```ts swap.sauce.ts theme={null}
// Swaps USDC for WETH in one Uniswap V3 pool, bounded by a price limit and a minimum output.
// Run: sauce simulate swap.sauce.ts --chain base --owner <owner> --args <pool> <amountIn> <sqrtPriceLimitX96> <minOut>
//   pool: Address, the Uniswap V3 USDC/WETH pool
//   amountIn: Uint256, USDC in smallest units (1000000 is 1 USDC)
//   sqrtPriceLimitX96: Uint256, the price the swap stops at (sauce price prints it)
//   minOut: Uint256, the least WETH, in wei, the swap may return; below it the cook reverts
import { uniswapV3 } from "@sauce/router";

function main(pool: Address, amountIn: Uint256, sqrtPriceLimitX96: Uint256, minOut: Uint256): Uint256 {
  return uniswapV3(pool, USDC, WETH, amountIn, sqrtPriceLimitX96, minOut, pot);
}
```

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](https://docs.eco.com/sauce-sdk/cli/price.md) and [sauce pools](https://docs.eco.com/sauce-sdk/cli/pools.md) compute the limits. [Quote, swap and supply](https://docs.eco.com/quickstarts/sauce/quote-swap-supply.md) 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:

```ts pay.sauce.ts theme={null}
function main(recipient: Address, amount: Uint256) {
  require(pot.USDC >= amount, "not enough USDC");
  USDC.transfer(recipient, amount);
}
```

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:

```bash theme={null}
sauce simulate pay.sauce.ts --chain base --owner <owner> --expect-revert "not enough" --args <recipient> 1000000
```

```text theme={null}
As expected, the cook reverts: the program threw "not enough USDC"
```

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.

```ts guarded.sauce.ts theme={null}
// Supplies USDC to Aave V3 after checking the pool has code, and recovers if the supply fails.
// Run: sauce simulate guarded.sauce.ts --chain base --owner <owner> --args <amount>
//   amount: Uint256, USDC in smallest units (1000000 is 1 USDC)
function main(amount: Uint256): bool {
  require(ctx.isContract(AaveV3.Pool), "no pool code");
  try {
    USDC.approve(AaveV3.Pool, amount);
    AaveV3.Pool.supply(USDC, amount, pot, 0);
    return true;
  } catch (error) {
    return false;
  }
}
```

With 2 USDC in the Pot, supplying 1 USDC succeeds, approves the pool and supplies:

```text theme={null}
main returns bool: true
…
Approvals:
  <pot> (the Pot) lets … (AaveV3.Pool) spend 1 USDC
```

Supplying 5 USDC fails inside the block. The cook still succeeds, and the approval made in the block is rolled back with it:

```text theme={null}
The cook succeeds from <owner>: gas used 198986 for the cook, after the Pot's deploy (1. below), returns 0x0000000000000000000000000000000000000000000000000000000000000000
main returns bool: false
```

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](https://docs.eco.com/sauce-sdk/cli/chains-and-endpoints.md#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](https://docs.eco.com/sauce-sdk/mcp/external-wallets.md).

Give each wallet a policy, `<name>.policy.json` in the wallet directory:

```json ops.policy.json theme={null}
{
  "chains": ["base"],
  "to": ["<your Pot address>"],
  "native": { "perTransaction": "0.01" },
  "tokens": { "USDC": { "perTransaction": "5", "perDay": "20" } }
}
```

* 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](https://docs.eco.com/sauce-sdk/cli/wallets-and-policy.md).

When another signer signs, check the unsigned cook against your file before signing it:

```bash theme={null}
sauce inspect <cook data> --chain base --source pay.sauce.ts
```

```text theme={null}
Pot.cook on engine <engine address> (the released v12 engine <version>)
  program: 287 bytes, keccak256 0x84e9439649a2f4b05b81719c277a45813ed17e0707382cfaecca073b2c22477a
    pay.sauce.ts's program, 223 bytes, then 64 of arguments
      recipient: Address = <recipient>
      amount: Uint256 = 1000000
```

`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:

```json sauce.config.json theme={null}
{ "rpcUrls": { "1": "<your Ethereum endpoint>", "8453": "<your Base endpoint>" } }
```

```bash theme={null}
sauce simulate pay.sauce.ts --chain base --owner <owner> --config sauce.config.json --args <recipient> 1000000
```

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](https://docs.eco.com/sauce-sdk/cli/chains-and-endpoints.md).

## 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](https://docs.eco.com/sauce-sdk/reference/chains.md).

## Next steps

* [Troubleshooting](https://docs.eco.com/sauce-sdk/operating/troubleshooting.md): what each refusal and revert means.
* [Package updates](https://docs.eco.com/sauce-sdk/operating/upgrading.md): recompile and simulate again after updating the package.
* [Security considerations](https://docs.eco.com/sauce/security-considerations.md): what the Pot, the Kitchen and the engine enforce.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.