# Ethereum: Get token price at a specific block number (onchain)

> After trying to answer the question at [stackexchange](https://ethereum.stackexchange.com/questions/120309/how-can-i-get-access-to-token-price-data-at-a-specific-block) I've decided to write this short article, as the topic is indeed quite interesting.


## 1. Archive Node

The most simple is the last part of the question: `at a specific block (onchain)`. As you may know, ethereum's `geth` node can be launched with various `--gcmode` values: `full` or `archive`.  The `archive` mode doesn't remove any historical state data, it means - we can perform read queries specifying the `blockNumber` and getting the result back, as it was at that block.

#### Node-as-a-Service

All popular node providers, like [infura.io](https://infura.io), [quicknode.com](https://quicknode.com), [alchemy.com](https://www.alchemy.com/) can give you access to the archive node, but it won't be free of charge.

#### Self Hosting

As already mentioned, if you use `geth` include additionally `--syncmode full --gcmode archive` flags. But you have to be patient - it takes time to full sync the node, and it will occupy `>10.2TB` storage: [etherscan/chainarchive](https://etherscan.io/chartsync/chainarchive)

[**Erigon 🔗**](https://github.com/ledgerwatch/erigon)

I use `erigon` as it allows us to specify the number of blocks we want to keep with the historical state data

```bash
./erigon.exe --datadir D:/Erigon --chain mainnet --private.api.addr=127.0.0.1:9090 --prune=hrtc --prune.h.older=400000 --prune.r.older=400000 --prune.t.older=400000 --prune.c.older=400000
```

This will keep `400000` blocks, which with the average blocktime of `13s` will keep the data for 2 months, and it takes `680GB` of storage.

##### Intermediate summary

> When you perform `READ` actions at blockchain you always get the result back which refers to some `blockNumber`. Even if you don't specify the block number, you get the result just for the latest block, but you can set any previous block your node supports.


## 2. Token Price

To retrieve the price onchain, there are 2 options - `a)` oracles: 3rd party contracts, which accumulate the prices in the correct way, and `b)` direct DEX sources - like Uniswap.

### 2.1 Oracles

The **most convenient** way, as it provides direct _`TOKEN/USD`_ pair access, and **most correct** way, as it provides the most accurate price information at a given time point (`blockNumber`).

I suggest the **⬡ Chainlink** price feed contracts: [data.chain.link/crypto-usd](https://data.chain.link/ethereum/mainnet/crypto-usd). For an example,  lets get the `ETH` price from the feed: 

[data.chain.link/eth-usd](https://data.chain.link/ethereum/mainnet/crypto-usd/eth-usd) 
 ([`0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419`](https://etherscan.io/address/0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419))


> Here I will use [`0xweb`](https://0xweb.org) 📦 package manager to create contract classes and to read data from the blockchain.

```bash
# install 0xweb as global util
$ npm i 0xweb -g
# initialize dependencies in the current folder, for API usage. For CLI not required 
$ 0xweb init

# download and generate classes for the contract
$ 0xweb install 0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419 --name chainlink/oracle-eth
```

After the contract classes are generated, you can access onchain data via CLI or API

> ❗❣️❗ There are default KEYs for etherscan/co and infura. They are rate-limited. Please, create and insert your keys: `0xweb config -e`, replace Node URLs for `eth`.

- `cli`

```bash
$ 0xweb contract read chainlink/oracle-eth latestAnswer
```

- `api`

```typescript
import { ChainlinkOracleEth } from './0xweb/eth/chainlink/oracle-eth/oracle-eth';
import { Config } from '@dequanto/Config';
import { $bigint } from '@dequanto/utils/$bigint';

async function example () {
    await Config.fetch();

    const oracle = new ChainlinkOracleEth();
    const decimals = await oracle.decimals();
    const price: bigint = await oracle.latestAnswer();

    console.log(`ETH Price: ${ $bigint.toEther(price, decimals) } USD`);
}
example();
```
---

These examples return current `ETH` price. How to get the price **at a specific block**? Easy, by defining the `block` number.


```bash
$ 0xweb contract read chainlink/oracle-eth latestAnswer --block 14450000
```
```typescript
 const price: bigint = await oracle.forBlock(14450000).latestAnswer();
```

> ⚠️ Ethereum 🗄️node URLs in default configuration are not archive nodes, so it won't work, you should replace them with archive node URLs.

##### Intermediate summary

> Though ⬡ Chainlink is the most accurate and easy way to get the prices, but it doesn't contain all those thousands of tokens out there. That's why let's have a look at DEXes.


### 2.2 DEX

If a token is traded, that means we can get the price from there. Looks simple, but has its caveats. First of all, I'll show how to get the prices from `UniswapV2` using `0xweb` cli and `dequanto` libraries, and afterward, I briefly explain how it works under the hood.

- `cli`

0xweb has already the built-in token command, which can retrieve prices from `UniswapV2`

```bash
$ 0xweb token price LINK
# or with block number (archive node required)
$ 0xweb token price LINK --block 14450000
# or by address
$ 0xweb token price 0x514910771af9ca656af840dff83e8264ecf986ca
# example of the command return  
Symbol   LINK
Address  0x514910771af9ca656af840dff83e8264ecf986ca
Decimals 18
Price    15.715912
```

- `api`

```typescript
import di from 'a-di';
import { Config } from '@dequanto/Config';
import { PlatformFactory } from '@dequanto/chains/PlatformFactory';
import { TokenPriceService } from '@dequanto/tokens/TokenPriceService';

async function example () {
    // we need this once: to find and read all configurations
    await Config.fetch();

    let token = 'LINK';
    // any unknown tokens are also supported
    let token = { address: "0x12345abcd", decimals: 18 };
    let chain = await di.resolve(PlatformFactory).get('eth')
    let service = new TokenPriceService(chain.client, chain.explorer);
    let { price } = await service.getPrice(token, {
        block: 14450000
    });
    console.log(price);
}
example();
```

----

_A couple of words about how to run the scripts from api examples._ I use [atma](https://www.npmjs.com/package/atma) global package to run the scripts. It supports file middlewares, in particular the [atma-loader-ts](https://www.npmjs.com/package/atma-loader-ts), it will compile, cache and execute the TS files on the fly: `atma run ./example.ts`. All required packages and loader configuration will be created on `0xweb init`. **But** you can use any other loader/builder you already use for TypeScript.

----

**⚙️ How it works**

### 2.2.1 `UniswapV2` interfaces

```solidity
interface IUniswapV2Factory {
    function getPair(address tokenA, address tokenB) external view returns (address pair);
}
interface IUniswapV2Pair {
    function token0() external view returns (address);
    function token1() external view returns (address);
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
}
```

#### 1. How to get the price of a token e.g.`WETH` from the pair `WETH/USDC`? 

This step is trivial, the [pair](https://etherscan.io/address/0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc#readContract), has reserves of each token - amount of tokens in the pool. And the price is just a ratio of these amounts. `price(WETH) = totalEther(USDC)/totalEther(WETH)`. 

- ⚠️ Be aware, that each `ERC20` token can have different values for **`decimals`**, so just convert values to `ether`
- ✨ By having two tokens, e.g. `0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48`(`USDC`)/`0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2`(`WETH`) which one is `reserve0` and `reserve1`? Uniswap stores the tokens in a sorted way: `token0 < token1`

    ```typescript    
    const USDC = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
    const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
    const lpReserves = await poolPair
        .forBlock(opts?.block ?? opts?.date)
        .getReserves();
        
    let reserves = [lpReserves.reserve0, lpReserves.reserve1];
    let sorted = BigInt(USDC) < BigInt(WETH);
    if (sorted === false) {
        reserves.reverse();
    }
    let [ usdc, weth ] = reserves;
    ```
- ✨ From the previous example, you have noticed the `.fromBlock(opts?.block ?? opts?.date)` method - so yes, you can specify the `BlockNumber` or `Date`(_which will be resolved to the `BlockNumber`_).

#### 2. Okay, but how do I find out the address of the pair contract?

The `UniswapV2Factory` contract has the method `getPair`, which accepts any two tokens, and returns the pair contract (_if exists ❗_).

```typescript
// tokens order doesn't matter here
factoryContract.getPair(USDC, WETH)
```

#### 3. What if I want to get the price for a token, but there is no `TOKEN/USDC` pair?

If we stick to on-chain solution only, you could index all pairs in the `Factory`, then you have the list of all pairs for a token. But this is unnecessary, as it is enough to check all major stable coins and the wrapped eth with that token: `USDC, USDT, DAI, WETH`. 

- ⚠️ Often only `TOKEN/WETH` pair is present, or stable pairs have small liquidity, but as we already know - we can get the price for `WETH` from Chainlink or any of `WETH/STABLE` pairs

----

Following points you have to think about:
- ✨ to get the price from the pool with the most liquidity
- ✨ to get the price for one or two nearby blocks and take the average
- ✨ this gives the price in stable coins, those are not always equal to `1$`

---
🏁







