Skip to main content
Use this recipe when you need raw calldata or a contract interface from your own ABI file. The program wraps ETH into WETH, then reads a Uniswap V3 pool’s price observations through IOracle.json. The observation read has a 200,000 gas limit and runs inside a try block. If that read fails, the program reports observed: false and keeps the successful wrap. Invalid inputs, an unavailable pool or an incorrect wrapped amount revert the whole cook. Target chains: Base. It names WETH, USDC and UniswapV3.Factory; the pool’s address comes from the factory at run time.

Prerequisites

Install the Sauce CLI to compile and simulate. To send the transaction, configure a wallet with ETH for the program and native coin for gas and the Kitchen fee. Keep IOracle.json in the same directory as the program. See Installation.

Program

pot is the Pot the program runs on, available without declaring it (Names).
low-level-call.sauce.ts
The ABI file sits beside the program and declares the one function the program calls, with the pool’s deployed types:
IOracle.json

How it works

  • evm.call(WETH, wrapAmount, hex`d0e30db0`) is the only way to send value with a call: a bound call never sends any. d0e30db0 is the selector of deposit(), and the call has no arguments. The program measures the WETH it received rather than reading a return value.
  • observe is not in the registry’s UniswapV3.Pool ABI, so import IOracle from "./IOracle.json" binds it and IOracle.at(pool) calls it at the address the factory returned. Anything bound this way is checked only against the ABI you supply.
  • The try block holds more than one statement, so it is atomic: if the call, the decode of its int56[] result or the division fails, the block’s assignments to observed and meanTick roll back and the handler runs. Work before the block, the wrap, remains. A .catch() on the call alone would not cover a failure while decoding a successful return.
  • .gas(200000) caps the gas forwarded to observe, not the transaction’s limit. A read that needs more fails inside the try and leaves the rest of the cook its gas.
  • int56 outputs arrive as Int256, so ticks[1] - ticks[0] is a signed difference. Mixed arithmetic needs a conversion, hence Int256(window); signed division truncates toward zero.

Run the example

1

Compile

2

Simulate

--assume-balance ETH:0.01 gives the Pot 0.01 ETH for this simulation only. If your Pot is not deployed yet, the simulation deploys it first.
3

Send

--fund ETH:0.01 adds 0.01 ETH to the cook’s value, which lands in the Pot. To sign elsewhere, build the unsigned transactions instead:

Simulation result

Simulated on a local fork of Base at block 52965104 with the current contracts. Nothing was signed or sent.
The Pot sent 0.01 ETH to WETH and received 0.01 WETH. The mean tick over the last 30 minutes is -198,546, the signed tick change divided by window and truncated toward zero. On the same fork, a window of 4,000,000,000 seconds is older than any observation the pool keeps, so observe reverts inside the try block and the cook still succeeds:
The wrap stands and observed and meanTick keep their initial values. On the same fork, with less ETH in the Pot than wrapAmount (--assume-balance ETH:0.005), the guard reverts the cook:

Next steps