> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Survey ETH prices on Ethereum

> A read-only Ethereum program that reads ETH's price from Chainlink's ETH/USD feed, two Uniswap V3 pools and the Uniswap V2 pair, run with sauce simulate.

The program reads the price of ETH in USDC from Chainlink's ETH/USD feed, the Uniswap V3 WETH/USDC pools at 0.05% and 0.3%, and the Uniswap V2 WETH/USDC pair, and returns the four prices, the feed's age and the gaps between them. It reverts when `amount` is 0, the feed has no price, a V3 pool is missing or has no liquidity, or the pair is missing.

**Target chains:** Ethereum. Run this read-only example with `sauce simulate`; it does not require a funded wallet or send a transaction.

## Prerequisites

You need the `sauce` command (`npm install -g @eco-incorp/sauce`). No wallet and no tokens: the simulation signs nothing and the program moves no token. See [Installation](https://docs.eco.com/sauce-sdk/installation.md).

## Program

```ts price-survey.sauce.ts theme={null}
// Reads the price of ETH in USDC from four sources on Ethereum: Chainlink's ETH/USD feed, the
// Uniswap V3 WETH/USDC pools at 0.05% and 0.3%, and the Uniswap V2 WETH/USDC pair. Returns each
// price in USDC base units per WETH (2390000000 is 2,390 USDC), the feed's age in seconds, the
// widest gap among the three pools and the gap from the 0.05% pool to the feed, in basis points.
// Reverts when `amount` is 0, the feed has no price, a V3 pool is missing or has no liquidity,
// or the V2 pair is missing. The feed's USD price is read as USDC, at 1 USDC = 1 USD.
//
//   sauce simulate price-survey.sauce.ts --chain ethereum --owner <address> --args <amount>
//
//   amount: Uint256  the WETH the V2 price is quoted for, in wei (18 decimals):
//                    1000000000000000000 is 1 WETH. A V2 price for a trade includes its own
//                    price impact.
//
// The program writes no state and moves no token. `sauce simulate` signs and sends nothing, so
// there is no `sauce run` line and the owner needs no balance. On Ethereum, priced for 1 WETH:
//
//   sauce simulate price-survey.sauce.ts --chain ethereum \
//     --owner 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --args 1000000000000000000

// USDC base units per WETH at a V3 pool's sqrt price (token1 per token0, times 2^96).
function v3Price(pool: Address): Uint256 {
  require(pool != 0, "no such pool");
  require(UniswapV3.Pool(pool).liquidity() > 0, "a pool has no liquidity");
  const sqrtPriceX96 = UniswapV3.Pool(pool).slot0().sqrtPriceX96;
  const ratio = Math.mulDiv(sqrtPriceX96, sqrtPriceX96, 1 << 96); // token1 per token0, times 2^96
  return WETH < USDC
    ? Math.mulDiv(ratio, 10 ** 18, 1 << 96)
    : Math.mulDiv(10 ** 18, 1 << 96, ratio);
}

function main(amount: Uint256): {
  oracle: Uint256;
  oracleAge: Uint256;
  v3Fee500: Uint256;
  v3Fee3000: Uint256;
  v2: Uint256;
  spreadBps: Uint256;
  oracleGapBps: Uint256;
} {
  require(amount > 0, "amount is 0");
  const round = Chainlink.EthUsdFeed.latestRoundData();
  require(round.answer > 0, "no oracle price");
  // `answer` is an Int256. After the check above, Uint256() converts it without failing. USD per
  // ETH, rescaled from 8 to 6 decimals; compared with USDC prices as if 1 USDC = 1 USD.
  const oracle = Uint256(round.answer) / 100;

  const v3Fee500 = v3Price(UniswapV3.Factory.getPool(WETH, USDC, 500));
  const v3Fee3000 = v3Price(UniswapV3.Factory.getPool(WETH, USDC, 3000));

  // What the pair pays for `amount` WETH with its 0.3% fee taken, per whole WETH.
  const pair = UniswapV2.Factory.getPair(WETH, USDC);
  require(pair != 0, "no WETH/USDC pair");
  const reserves = UniswapV2.Pair(pair).getReserves();
  const wethReserve = WETH < USDC ? reserves._reserve0 : reserves._reserve1;
  const usdcReserve = WETH < USDC ? reserves._reserve1 : reserves._reserve0;
  const afterFee = amount * 997;
  const out = Math.mulDiv(afterFee, usdcReserve, wethReserve * 1000 + afterFee);
  const v2 = Math.mulDiv(out, 10 ** 18, amount);

  let low = v3Fee500;
  let high = v3Fee500;
  for (const price of [v3Fee3000, v2]) {
    if (price < low) {
      low = price;
    }
    if (price > high) {
      high = price;
    }
  }
  // Both gaps in basis points of the lower price of the two.
  const oracleGap =
    v3Fee500 > oracle
      ? Math.mulDiv(v3Fee500 - oracle, 10000, oracle)
      : Math.mulDiv(oracle - v3Fee500, 10000, v3Fee500);
  return {
    oracle,
    oracleAge: ctx.timestamp() - round.updatedAt,
    v3Fee500,
    v3Fee3000,
    v2,
    spreadBps: Math.mulDiv(high - low, 10000, low),
    oracleGapBps: oracleGap,
  };
}
```

## How it works

* `v3Price` squares `slot0().sqrtPriceX96` with `Math.mulDiv(sqrtPriceX96, sqrtPriceX96, 1 << 96)`, which keeps the full product before dividing. `WETH < USDC` compares two registry addresses, so it is fixed for the chain and picks which way up the ratio is read.
* `v2` is not the pair's mid price. It is what the pair pays for `amount` WETH after its 0.3% fee (`amount * 997` against `reserve * 1000`), per whole WETH, so it includes that trade's price impact: a larger `amount` gives a lower `v2`.
* The feed's `answer` is an `Int256`, the ABI's signed `int256`, and it does not mix with the unsigned prices. The program requires it above 0, then converts it with `Uint256(round.answer)`, a checked conversion that reverts on a negative value, and divides by 100 to rescale it from 8 to 6 decimals. It is still a USD price: `oracleGapBps` compares it with the pool's USDC price as if 1 USDC = 1 USD.
* `oracleAge` is `ctx.timestamp() - round.updatedAt`, the seconds since the feed's last update. `spreadBps` is the gap between the highest and lowest of the three pool prices, and `oracleGapBps` the gap between the 0.05% pool and the feed, each in basis points of the lower price.

## Run the example

**1. Compile**

```bash theme={null}
sauce compile price-survey.sauce.ts --chain ethereum
```

**2. Simulate**

```bash theme={null}
sauce simulate price-survey.sauce.ts --chain ethereum --owner <your-address> \
  --args 1000000000000000000
```

Any address works as the owner: the simulation deploys its Pot first if it has none and gives it the cook's value. The result lists what `main` returned, by name.

## Simulation result

Simulated on a local fork of Ethereum at block 26252095 with the current contracts. Nothing was signed or sent; values change with the block.

```text theme={null}
The cook succeeds from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266: gas used 169167 for the cook, after the Pot's deploy (1. below), returns 0x000000000000000000000000000000000000000000000000000000008df08990000000000000000000000000000000000000000000000000000000000000051a000000000000000000000000000000000000000000000000000000008e044eb4000000000000000000000000000000000000000000000000000000008e4a41b6000000000000000000000000000000000000000000000000000000008ddb56ed000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000005
main returns {
  oracle: Uint256;
  oracleAge: Uint256;
  v3Fee500: Uint256;
  v3Fee3000: Uint256;
  v2: Uint256;
  spreadBps: Uint256;
  oracleGapBps: Uint256;
}: { oracle: 2381351312, oracleAge: 1306, v3Fee500: 2382646964, v3Fee3000: 2387231158, v2: 2379962093, spreadBps: 30, oracleGapBps: 5 }
...
```

On the fork the feed's price was 2,381.351312 USD per ETH, updated 1,306 seconds earlier. The 0.05% pool priced WETH at 2,382.646964 USDC, the 0.3% pool at 2,387.231158, and the V2 pair paid 2,379.962093 for 1 WETH: 30 basis points between the highest and lowest pool, and 5 between the 0.05% pool and the feed. No token moved. The full output lists two native transfers of 0.00003728943124295 ETH: the cook's value from the owner to the Pot, and the Kitchen's fee from the Pot to the Kitchen.

## Next steps

* [Oracle-guarded swap](https://docs.eco.com/quickstarts/sauce/oracle-guarded-swap.md): the same feed and 0.05% pool reads, used to decide whether to sell WETH.
* [Quote two pools, swap, supply](https://docs.eco.com/quickstarts/sauce/quote-swap-supply.md): quotes a trade with Uniswap's QuoterV2 instead of reading pool prices.
* [`sauce simulate`](https://docs.eco.com/sauce-sdk/cli/simulate), [`sauce pools`](https://docs.eco.com/sauce-sdk/cli/pools), [Built-ins](https://docs.eco.com/sauce/builtins.md), [Names](https://docs.eco.com/sauce/names.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.