Skip to main content
Each run reads Chainlink’s ETH/USD feed. When the answer is at or below stop or at or above takeProfit, the program sells the Pot’s WETH for USDC on Uniswap V3’s 0.05% pool, down to a floor of the oracle price less maxSlippageBps. Between the thresholds it returns without trading. The cook reverts on a feed answer older than maxAge seconds, a non-positive answer, or an average sale price below the floor. Target chains: Ethereum. Chainlink.EthUsdFeed is not in Base’s registry, and sauce compile --chain base refuses the file. This program evaluates conditions only when called. To run it repeatedly, use an external scheduler or caller that submits an authorized transaction.

Prerequisites

Install the Sauce CLI to compile and simulate. To send the transaction, configure a wallet with native coin for gas and the Kitchen fee. Fund the Pot once it is deployed, or use --fund; the program sells WETH the Pot holds.

Program

pot is the Pot the program runs on, available without declaring it; pot.USDC and pot.WETH are its balances (Names).
stop-loss.sauce.ts

How it works

  • Chainlink declares answer as int256, so round.answer is an Int256. The program requires it to be positive, then converts it once with Uint256(round.answer). The conversion is checked: a negative value reverts instead of becoming a large unsigned number. Every comparison and Math.mulDiv after it is unsigned.
  • ctx.timestamp() - round.updatedAt <= maxAge rejects a feed answer older than maxAge. The program acts on the oracle’s price, not the pool’s, so a pool pushed away from the oracle within one block does not trigger a sale.
  • The floor is in USDC base units per WETH: price has the feed’s 8 decimals, and dividing by 10000 * 100 both applies maxSlippageBps and drops 2 decimals to USDC’s 6.
  • The swap’s price limit is the floor’s sqrt price moved by sqrt(1 - fee), so the last unit sold still fetches the floor after the pool fee. When the pool is already past that limit, the program returns sold: 0 instead of calling the swap.
  • sold and received are the changes in the Pot’s WETH and USDC balances, and the final require checks the average price from those.
The swap stops at the floor. In a falling market the pool can reach it before all the WETH sells; the rest stays in the Pot until a later run finds a price above the floor.

Run the example

1

Compile

2

Simulate

--assume-balance WETH:1 gives the Pot 1 WETH for this simulation only. A stop of 10,000 USD, above the market, makes this run trigger.
3

Send

sauce tx stop-loss.sauce.ts --chain ethereum --owner <your-address> --args 240000000000 320000000000 100 3600 prints the unsigned transactions instead, for your own signer or scheduler.

Simulation result

Simulated on a local fork of Ethereum at block 26284400 with the current contracts, using the Simulate command above: stop 10,000 USD, no take-profit, 1% slippage, a one-hour maxAge. Nothing was signed or sent.
The feed answered 2,324.91817030 USD. That is below the stop, so the run triggered and sold the whole 1 WETH for 2,329.029303 USDC, above the floor of 2,301.668988 USDC (the oracle price less 1%). On the same fork, with a stop of 2,000 USD and a take-profit of 3,200 USD, the price is between the two and nothing sells:
With maxAge set to 1 second, the feed’s latest answer is too old and the cook reverts:

Next steps