pot.USDC is the Pot’s USDC balance, and what a call pays to its caller lands in the Pot. Only the owner can cook on it, or authorize a cook that another account submits (Signed cooks). The owner is fixed when the Pot is created. A program reads it as pot.owner, the address the Pot’s owner() returns. A Shared Pot has no owner: there pot.owner is the zero address.
On Solana a Pot is an account the Kitchen derives and signs for. A Pot on Solana covers the differences.
Its address
A Pot’s address follows from the Kitchen, the owner and a 32-byte salt. For a Pot thatdeployPot(owner, salt) creates, the kind the sauce command and the SDK’s helpers use:
deployPot(owner, salt, true), hashes abi.encode(owner, salt, true) instead, so the same owner and salt give a different address; a Shared Pot hashes abi.encode(address(0), salt, true). The Kitchen’s predictPot(owner, salt, true) and predictSharedPot(salt) return them. For the owner 0x1111…1111 and the default salt, on a local fork of Base at block 52992458 with the current contracts, predictPot returned 0x6c91…D3Fe and 0xF993…7a27, and the formula gives the same two addresses.
- On EVM, it is known before the Pot exists, as the prediction of the Kitchen the chain runs. Tokens sent to a predicted address that no Pot is deployed at are out of reach of every cook. Deploy the Pot before you send it funds, with
sauce pot --chain <chain> --deployand the owner’s signer, or withsauce run --fund, which deploys the Pot before it funds it (Funding a Pot).sauce tx --fundplans the deployment first only when its endpoint shows the Pot has no code; with--offlineit cannot check, so deploy first. Without--offline,sauce potalso reports whether the chain has the Kitchen, and refuses the deployment on a chain thesaucecommand does not send on. - A deployed Pot’s address never changes.
- The Kitchen has the same address on every EVM chain it is on, so where each chain runs the same Kitchen release, an owner’s Pot under one salt has the same address on each of them.
0x1111…1111, both print the same Pot, 0x0828…Ec99, under the salt 0xcbd8…d8ac. --offline derives the address without reading a chain, so it is not checked against the deployed Kitchen, and the command says so.
Salts
An owner has one Pot per salt.- The default salt is zero: the owner’s default Pot. In TypeScript it is
DEFAULT_POT_SALT. --salt <label>names another Pot: its salt is the keccak256 of the label’s UTF-8 bytes. In TypeScript,potSalt(label).--salt-hex <bytes32>gives the salt exactly.
deployPot(owner, salt) creates the same Pot, owned by owner, and no one can create a different contract at that address.
v12PredictPot reads no chain.
Deploying it
The Kitchen’sdeployPot(owner, salt) deploys a Pot (Kitchen). Any account may send it, the Pot is the owner’s whoever sends it, and a second send returns the Pot already there. It carries no fee. On a local fork of Base at block 52965000 with the current contracts, a simulated deployment used 667,003 gas.
You rarely send it yourself:
sauce rundeploys the Pot before the first cook that needs it, as a transaction of its own, after asking (or with--yes).sauce txprints a plan when the Pot has no code yet:deployPotfirst, markeddeploysPot, then the cook.sauce simulatedeploys the Pot inside the simulation first.sauce pot --deploysends the deployment alone.
What it holds
On EVM, a deployed Pot is funded like any address: send it tokens or native coin from any wallet once its deployment has landed (Its address), or letsauce run --fund, which deploys the Pot first, pull them from the wallet in the same transaction as the cook. Offline, sauce tx --fund cannot check deployment and adds no deployment step.
What a cook produces lands where the program sends it, and a call that credits its caller credits the Pot. A swap with no other recipient, a lending market’s receipt tokens supplied for pot (Aave’s aTokens with onBehalfOf the Pot), and vault shares minted to the caller all stay in the Pot. They are the owner’s, but a wallet app does not show them, and only a later cook moves them.
To deliver a result to a wallet, the program names the wallet as the recipient or beneficiary, from a main parameter:
supply.sauce.ts
onBehalfOf, the aUSDC was minted to the owner’s wallet and the 100 USDC left the Pot. Keeping a position in the Pot is a choice too: a later cook can withdraw it or borrow against it without the wallet moving anything back first.
No withdraw function
A Pot has no function that withdraws or transfers what it holds. What a Pot holds leaves only through a cook: a program that sends it.sweep.sauce.ts
Who can do what
The table describes EVM Pots. On Solana the Kitchen’s upgrade authority can replace the program that signs for every Pot, and must be trusted with what Pots hold (Trust on Solana).
An EVM Pot’s code never changes once it is deployed: nothing upgrades it.
Signed cooks
A Pot created by the current Kitchen has a second way to run a program. The owner signs a cook off-chain, and any account submits it withcookSigned: a relayer that pays the gas, a service that sends it later, a second account of the owner’s. The Pot runs it as it runs the owner’s own cook, if and only if the owner signed that engine, that program with its arguments, that value, that fee ceiling, that nonce, that deadline and that submitter, for that Pot, on that chain.
Cook under the domain { name: "Sauce Pot", version: "1", chainId, verifyingContract: <the Pot> }, so one signature is good for one Pot on one chain:
Who submits when
submitter is zero, when it is submitted up to the deadline, the gas and the transaction’s place in its block are not signed. The program sees the submitter as ctx.msgSender(), so it can pay whoever submitted it.
The fee. A signed cook pays the Kitchen’s fee once, as a cook does, but the Pot pays it from its own balance, which includes the call’s value, and never more than maxExecutionFee. The Kitchen’s owner sets the fee and may raise it after the owner signs. A fee above the smaller of the Pot’s balance and the ceiling fails with FeeNotPaid. The submitter sends exactly the signed value and pays the gas.
Nonces. Nonces are unordered: a used nonce sets bit nonce & 255 of word nonce >> 8 in the Pot’s storage, in any order, before the program runs. A second submission fails with NonceUsed(). A cook that fails reverts everything, its nonce included, so the same signature can be submitted again until its deadline. invalidateNonces(word, mask) sets bits to cancel authorizations not yet used, and emits NoncesInvalidated(uint256 indexed word, uint256 mask). The owner can call it, and so can the Pot itself, so a signed cook can cancel others. A signed cook emits SignedCook(uint256 indexed nonce, address indexed submitter).
Whose signature counts. The owner’s: a 65-byte signature that ecrecover recovers to the owner, or, when the owner has code, whatever the owner’s ERC-1271 isValidSignature accepts. A Safe or an EIP-7702 account signs this way. Anything else fails with BadSignature().
Which Pots take it. Private Pots created by the current Kitchen, with a fixed Router or not. A Shared Pot has no owner to sign and refuses every signed cook.
The SDK builds each part: sauce sign signs a cook with a wallet or prints the typed data for one kept elsewhere, and cancels one; the MCP server has sauce_signed_cook and, over stdio, sauce_sign; and TypeScript has signedCookTypedData, encodeCookSigned, encodeInvalidateNonces, verifySignedCook and checkSignedCookPot from @eco-incorp/sauce/evm/engine (Cook transactions). Security considerations covers what to watch for.
The Pot’s interface
v12PotAbi from @eco-incorp/sauce/evm/engine is the complete ABI. The parts a developer uses:
The ABI also declares the event
SignedCook(nonce, submitter) and the errors BadSignature(), Expired(), NonceUsed(), WrongSubmitter(), EmptyProgram(), EngineHasNoCode(), FeeTransferFailed(), NotInFlightEngine(), RouterFixed(), RouterHasNoCode(), RouterUnset() and SharedRouterNotFixed(). sauce simulate prints a Pot’s or the Kitchen’s revert by its error’s name and arguments, such as FeeNotPaid(1234, 0).
The Pot’s Router
Some pools call their caller back in the middle of a swap to be paid: Uniswap V3 and its forks, Uniswap V4, Maverick V2 and PancakeSwap Infinity. A program cannot answer a callback. The Pot answers it: a call the Pot does not recognize goes to its fallback, which delegatecalls the Pot’s Router, and the Router pays the pool from the Pot exactly what the pool asks for, up to the swap’s input amount. The@sauce/router functions for those venues call the Router through the Pot.
Venues that are paid first, Uniswap V2, Curve, Balancer V2, DODO V2, Trader Joe LB and WOOFi, are called directly by the program and do not use the Router.
- A new Pot starts on the Router the Kitchen names in
router()when the Pot is created. - After that, only the Pot’s owner can change it, with the Pot’s
setRouter. A call from anyone else reverts withNotOwner(). - A Pot created with
deployPot(owner, salt, true)keeps its Router for good, and so does every Shared Pot. - The Kitchen’s owner changes the Router that Pots created afterwards start on, and nothing else. A Router change moves no Pot address.
v12RouterAddress() from @eco-incorp/sauce/deployments returns the current Router; compare a Pot’s router() with it by address.
After a new Router is released, potRouterUpdate(client, pot) from @eco-incorp/sauce/evm/engine reads the Pot’s Router and the Kitchen’s through your client. It returns the setRouter call for the owner to sign, or nothing when the Pot is current. It sends nothing, and it throws for a Pot that the current Kitchen did not create.
A Pot on Solana
- Address. A Pot is a program-derived address of the Kitchen program, from the seeds
"pot", the owner and a 32-byte salt. It is known before the Pot exists, and the default salt is zero, as on EVM chains. - Creation. The Kitchen’s
create_potcreates it. The owner signs that instruction and pays the rent, Solana’s deposit for storing an account.sauce runasks before creating it, andsauce txputs it in the setup it prints. - Storage. A Pot keeps 10,240 bytes of storage for programs, fixed when it is created. In a simulation in the
saucecommand’s local copy (LiteSVM) of the cluster at slot 460261665, its rent was 52,669,440 lamports, and the rent of the Pot’s USDC token account 1,488,440 lamports. - Cook instructions. The Kitchen’s
cookandcook_from_accountcarry the Pot’s salt and then its bump, the byte that makes the Pot’s address a valid program-derived address. The SDK’s builders and thesaucecommand derive the bump. An instruction serialized for the Kitchen before the current one has the earlier format and names the earlier engine: build cooks with the installed SDK. - Signing. The current Kitchen requires the owner to sign each cook, and signs for the Pot, so the program can spend tokens whose authority is the Pot. The Kitchen program is upgradeable: its upgrade authority can replace that check, and must be trusted with what the Pot holds (Trust on Solana).
- Token balances. The Pot’s tokens sit in its token accounts, one per mint, at the associated token account under the mint’s token program.
sauce pot --chain solanalists them. potandctx.self(). In a program on Solana,ctx.self()is the engine program’s id, not the Pot.potis the Pot: its key, its signer slot where anAccountis taken, and the holder inpot.USDC.pot.owneris the owner’s account;pot.owner.writable().signer()gives it both roles.Account(pot.address).ownerreads the program that owns the Pot account instead.- No Pot. A cook sent directly to the engine runs with no Pot, and has only the signers its transaction gives it. A program that uses
pot,pot.balanceorpot.<TOKEN>is refused there; one that reads onlypot.owneris not.
Next steps
- cook(): the call that runs a program on a Pot.
- Kitchen: who creates Pots and sets the fee.
- Security considerations: what to check before you sign a cook.
sauce pot: reading and deploying a Pot.
