sauce sign signs a cook on your Pot without sending it: another account sends the transaction it prints, pays the gas, and the Pot runs exactly the program you signed.
Synopsis
Description
sauce sign compiles the file with main’s arguments, as sauce tx does, and prints what the Pot’s owner signs so that another account can submit the cook through the Pot’s cookSigned (Signed cooks). The owner signs EIP-712 typed data, a Cook under the domain { name: "Sauce Pot", version: "1", chainId, verifyingContract: <the Pot> }, so a signature is good for one Pot on one chain. The Cook binds:
With a signer (the Pot’s owner, by
--wallet, --keystore or --private-key) it signs, and cook is the transaction to send: { chainId, to: <the Pot>, data: cookSigned(…), value }, spelled as sauce tx spells a transaction. Without a signer (--owner or --pot) it prints the typed data for any wallet’s eth_signTypedData_v4. Given that signature with --signature, it checks that the owner made it and builds cook. An owner that is a contract, such as a Safe or an EIP-7702 account, is asked through its own ERC-1271 isValidSignature, which needs an endpoint. A signature that does not check is refused.
Sending it. Any account sends cook with exactly its value: the Pot checks the value it receives against the signed one. The sender pays the gas and nothing else. The Pot pays the Kitchen’s fee from its own balance, which includes the value sent, up to the signed ceiling, so keep native coin in the deployed Pot or sign a value that covers the fee. A cook that reverts leaves its nonce unused, and the same signature can be sent again until the deadline. Whoever holds the output can send it until then.
The Pot. With an endpoint the command reads the Pot first:
- A Pot with no code yet that
--owner(or the signer) and the salt name is still signed for, and the output carriesdeployPot, the Kitchen’sdeployPotcall, which any account sends first. - A Pot without
cookSigned, a Shared Pot, a contract that is not a Pot, and a Pot named only by--potthat has no code are refused, with the reason. - It reads the Kitchen’s fee and the Pot’s balance, and warns when the two do not cover the fee.
--offline reads nothing, so it then needs --max-fee.
Cancelling. --cancel <nonce> (repeatable) cancels instead of signing: one invalidateNonces(word, mask) to the Pot per word of nonces, from its owner. With a signer and an endpoint they are sent. Without a signer they are printed unsigned, for the owner to sign and sauce send to send. A cancelled nonce stays cancelled.
A wallet’s policy. From a wallet with a spending policy, the policy’s chains and to apply, with the Pot as the address. A native or tokens limit refuses the signature, naming the limit: a signed cook runs when someone else sends it, on the chain’s state then, so what it moves cannot be measured when it is signed.
sauce sign signs and cancels only on a supported EVM chain: Ethereum, Base and Arc, and the testnets Arc Testnet, Base Sepolia and Ethereum Sepolia. On a preview chain it is refused before anything is read or signed, with a message that names the supported chains and how to request the refused one. On Solana, Solana Devnet and Solana Testnet there is no signed cook, because the Kitchen’s cook already takes the owner’s signature and a separate fee payer: the command refuses it.
Prerequisites
- A Pot created by the current Kitchen, or one not deployed yet. The release’s engine must be deployed on the chain:
sauce pot --chain <chain> --owner <address>reports whether it is. - The Pot’s owner as a signer, or the owner’s address and a wallet elsewhere that signs EIP-712 typed data.
Options
Low-level options
--help lists these apart, with the note “a program’s inputs are main’s parameters, given with —args”.
Output
Signing a cook prints one JSON object on stdout, with--json or without, and its notes on stderr either way, the fee warning among them: the Pot and its owner, who may send the cook and until when, whether the Pot needs deploying first, what the sender sends, the Kitchen’s fee beside the signed ceiling and what the Pot holds, and the command that cancels it.
With
--cancel, unsigned, the object is { chainId, pot, owner, nonces, transactions }, one entry of transactions per word of nonces: { what, chainId, to, data, value, word, mask }. With --json it also carries sendCalls, { chain, calls: [{ to, value, data }] }: the same calls as one batch, for a wallet that sends a batch (EIP-5792). Without --json there is no sendCalls. Sent, it is { chainId, pot, from, nonces, sent } with --json, each entry of sent { what, word, mask, hash, explorer, trace, status, gasUsed, blockNumber }. Without --json, the command prints one line per cancellation instead (its hash, status, gas used and block), followed by its explorer and trace links where the chain has them.
Exit codes: 0 when the cook is signed or printed, or the cancellations sent or printed; 1 on a refusal (a preview chain, a Pot that does not take signed cooks, a signature that does not check, a wallet’s policy); 2 for a usage error, such as --offline without --max-fee, or Solana.
Examples
The examples ran on a local fork of Base at block 52991146 with the current contracts. They sign the cook ofpay.sauce.ts, which pays 1 USDC from the Pot, from the throwaway wallet agent, whose Pot had no code. Long numbers are cut short, and the engine’s and Kitchen’s addresses are shown as placeholders (see deployments).
Sign with a wallet
deployPot has landed: send deployPot, fund the deployed Pot, then submit the cook (Funding a Pot).
Send it from another account
On a live EVM chain, put nothing at the Pot’s address before it is deployed. SenddeployPot on its own (sauce send <signed deployPot> --rpc-url <url>), fund the deployed Pot, then send the signed cook.
On a local fork the Pot’s address already held 5 USDC and 0.001 ETH when a second account signed deployPot and cook as ordinary transactions and sent both with one command:
NonceUsed().
Sign with a wallet kept elsewhere
Without a signer, the command prints the typed data and signs nothing:stderr
typedData, then run the same line with the --max-fee, --nonce and --deadline the first run printed, so it builds the same cook:
signature and cook. A signature made by another key is refused, and the command exits 1:
Let one account send it
<relayer>, the cook reverts with WrongSubmitter(). The submitter is the Pot’s immediate caller: through a forwarding contract, it is that contract.
Cancel a signature
NonceUsed(). With --owner <owner> instead of a signer, the command prints the invalidateNonces call unsigned. On a local fork of Base at block 53116964 with the current contracts, for a deployed Pot, sauce sign --cancel 12345 --chain base --owner <owner> printed:
--json, the same object ends with "sendCalls": { "chain": "base", "calls": [{ "to": "0x9DCA499989B0965700717C28311c427b3aE2A23c", "value": "0x0", "data": "0x2bce68d2…" }] }.
Refusals
A wallet whose policy limits a token:1):
2):
Next steps
- Pot: what the Pot checks for a signed cook.
- Security considerations: replay, deadlines, the submitter and the fee ceiling.
sauce send: broadcast the transactions a signer signed.- Cook transactions: the same in TypeScript.
- MCP tools:
sauce_signed_cookandsauce_sign.
