Skip to main content
An Eco route can run a Sauce program after delivering tokens to the destination chain. The route’s destination calls include a cook, the operation that runs a program through a Pot. When a solver fulfills the intent, the destination Portal’s Executor runs those calls in order. The program reads destination-chain state, checks your conditions and calls other protocols within the fulfillment transaction. This guide covers EVM destination chains. You construct destination calls from the sauce command’s output and include them in a custom route.

Prerequisites

The main walkthrough uses an Executor-owned Pot, which other routes can also use. Read the ownership constraints before funding it. Use a signed cook on your own Pot when the program needs to retain funds under your ownership.

What runs atomically

The destination fulfillment is one transaction:
  1. The Portal checks the route against the intent hash, records the claimant, and pulls route.tokens from the solver into its Executor.
  2. The Executor runs route.calls in order. Each call’s target sees the Executor as msg.sender.
  3. One of those calls cooks a program on a Pot. The program’s reads, checks and protocol calls run as the Pot, inside that call.
An unhandled failure at any point reverts the whole fulfillment, the solver’s token delivery included: a call that reverts, a failed require in the program, a protocol’s own revert, or a cook sent without the Kitchen’s fee. Source-chain funding and the proof that releases the solver’s reward are separate transactions. See Destination calls. The route’s calls, and with them the program and its main arguments, are fixed when the intent is published. The program reads state when the solver fulfills, so a minimum output or a balance check holds at execution time.

The destination calls

<executor> is the account every call comes from: read executor() on the destination Portal. Set route.tokens to cover call 2, and route.nativeAmount to the cook’s value: the Portal forwards nativeAmount to the Executor, which sends each call’s value. A value above the Kitchen’s fee stays in the Pot.

Build and verify the route

Save the approval and swap program below as approve-swap.sauce.ts. Replace the command placeholders with your destination Executor, minimum WETH output and recipient. The examples use Base (chain ID 8453); consult supported chains and tokens for Routes availability and the Sauce chain reference for program names.
1

Simulate the cook on the Executor's Pot

--assume-balance gives the Pot the USDC that call 2 will move into it, for this simulation only.
2

Print calls 1 and 3

3

Request and verify the quote

The Routes API takes destination calls in a custom quote, as destination.calls (Request a quote). Verify the quote signature and execution data, check that intent.route.calls contains your three calls in order, and check that intent.route.tokens delivers at least what call 2 moves. Check the native amount, recipient and output constraints before funding.
4

Simulate the fulfillment

Call fulfill on the destination Portal with eth_call, from an account that holds and has approved the route’s tokens, with route.nativeAmount as its value. Estimate gas for the complete fulfillment, including Pot deployment when needed. A successful standalone cook simulation does not validate the other destination calls.
5

Fund and track the intent

After verification and simulation, follow the Routes API integration guide to fund the quote and track its status. If you integrate directly with contracts, use the Portal publishing and funding flow.
On a local fork of Base at block 52985412 with the current contracts, a Portal.fulfill whose route made these three calls with approve-swap.sauce.ts and 100 USDC deployed the Executor’s Pot, swapped, and paid 0.041672 WETH to the recipient. The Pot and the Executor kept no USDC. A route whose program set minWeth above what the pool paid reverted the whole fulfillment, and so did a route whose cook carried no fee.

The Pot the Executor owns

A Pot accepts cook only from its owner, and every call of a route comes from the destination Portal’s Executor, so the Pot a route cooks on belongs to the Executor. That Executor runs the calls of every route fulfilled on its chain. Any route can cook any program on a Pot the Executor owns, whatever its salt.
What stays in the Executor’s Pot after a fulfillment can be taken by the next route. Move into the Pot only what the program spends, and have the program send everything it receives to its recipients in the same cook. An approval the program gives also stays on the Pot.
On a local fork of Base at block 52985412 with the current contracts, 5 USDC left in the Executor’s Pot were taken by another account’s route, whose program transferred the Pot’s USDC to that account. The Executor applies the same rule to its own balance: it does not sweep what a route leaves behind. See Security considerations.

A signed cook on your own Pot

Private Pots also take cookSigned: the Pot’s owner signs one cook, and another account submits it (Signed cooks). In a route, call 2 moves the tokens into your own Pot, and call 3 is cookSigned on that Pot with submitter set to the destination Executor. The program then runs with your Pot’s authority, and what it leaves in your Pot stays yours.
  • A submission of that signature by any account other than the Executor reverts with WrongSubmitter().
  • Your Pot pays the Kitchen’s fee, from its own balance, never more than the signed maxExecutionFee. The signature also fixes the call’s value (sauce sign --value, default 0): call 3 sends exactly that, and route.nativeAmount covers it. With a signed value of 0 the fee comes from native coin your Pot already holds; sign a value of at least the fee for the route to pay it. A fee above the Pot’s balance, the call’s value included, or above maxExecutionFee fails the whole fulfillment with FeeNotPaid.
  • The signature is public once the intent is published, and the Executor submits for every route, so another route can submit it first. Its nonce lets it run once. Write the program to be correct whenever it runs, for example by checking the balances it spends.
  • v12PotAbi from @eco-incorp/sauce/evm/engine includes cookSigned. Build and sign it with sauce sign --submitter <address>, or in TypeScript with signedCookTypedData and encodeCookSigned from @eco-incorp/sauce/evm/engine; checkSignedCookPot refuses an old or Shared Pot before you sign.
On a local fork of Base at block 52985412 with the current contracts, a route whose calls deployed an owner’s Pot, moved 100 USDC into it and submitted the owner’s signed cook of supply.sauce.ts supplied the 100 USDC to Aave V3. A second route submitting the same signature reverted with NonceUsed().

Three programs to start from

Each program compiles for Base and can be the cook in call 3. Simulate it from your own Pot first; sauce simulate needs no key.

A one-line approval

approve.sauce.ts
USDC and UniswapV3.SwapRouter02 are names: compiling for Base resolves them to their addresses there. amount is in USDC’s smallest unit (6 decimals). On the Executor’s Pot, this approval stays in place after the cook, so a route’s program spends what it approves, as the next one does.

An approval and a swap

approve-swap.sauce.ts
It sells amountIn USDC on Uniswap V3’s 0.05% USDC/WETH pool and sends the WETH to to. The swap reverts, and the fulfillment with it, when the pool pays less than minWeth wei. With no price limit, minWeth is the swap’s only bound.

A supply with a balance check

supply.sauce.ts
It supplies amount USDC to Aave V3, credited to beneficiary, and reverts unless the Pot’s USDC fell by exactly that amount.
On a local fork of Base at block 52985412 with the current contracts, each simulation succeeded from an owner with no Pot, deploying it first. The approval used 98,230 gas for the cook, the swap 182,129 gas, paying 0.04168 WETH for 100 USDC, and the supply 270,232 gas, crediting 99.999998 aBasUSDC. Each program also ran as call 3 of a fulfilled route.

Troubleshooting

Next steps

  • Pot: its owner, who can cook on it, and signed cooks.
  • cook(): what the cook call carries and what it pays.
  • sauce tx and sauce pot: every option of the two commands that print the calls.
  • Sauce by example: longer programs, from a payment to a swap and supply.