Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add Oracle Pricing Data #135

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions abis/VeloPriceOracleABI.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"inputs": [
{
"internalType": "uint8",
"name": "src_len",
"type": "uint8"
},
{
"internalType": "contract IERC20Metadata[]",
"name": "connectors",
"type": "address[]"
}
],
"name": "getManyRatesWithConnectors",
"outputs": [
{
"internalType": "uint256[]",
"name": "rates",
"type": "uint256[]"
}
],
"stateMutability": "view",
"type": "function"
}
]
12 changes: 12 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ type LiquidityPoolWeeklySnapshot {
# By nature this entity saves the latest state of the token, and its state at different times should be attained from the snapshot entities
type Token {
id: ID! # token address
address: String! # token address
symbol: String! # token symbol
name: String! # token name
chainID: BigInt! @config(precision: 76) # chain id of the token
Expand Down Expand Up @@ -404,3 +405,14 @@ type CLPool_Swap {
timestamp: Timestamp! @index
chainId: Int! @index
}

scalar Decimal

type TokenPrice {
id: ID! # Unique identifier for the record, could be a combination of token address and chain ID
name: String! # Name of the token
address: String! @index # Address of the token
price: Float! # Price of the token
chainID: Int! @index # Chain ID where the token is located
lastUpdatedTimestamp: Timestamp! @index # Timestamp of the last update
}
53 changes: 47 additions & 6 deletions src/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,75 +11,113 @@ export const SECONDS_IN_AN_HOUR = BigInt(3600);
export const SECONDS_IN_A_DAY = BigInt(86400);
export const SECONDS_IN_A_WEEK = BigInt(604800);

export type PriceOracleKeys = keyof typeof PRICE_ORACLE;

export const PRICE_ORACLE = {
10: {
startBlock: 120445435,
updateDelta: 60 * 60 // 1 hour
},
8453: {
startBlock: 20250164,
updateDelta: 60 * 60 // 1 hour
}
};

// export const STATE_STORE_ID = "STATE";

// Hardcoded WETH, USDC and OP token addresses with decimals
export const WETH: TokenInfo = {
address: "0x4200000000000000000000000000000000000006",
symbol: "WETH",
unit: "ether"
};

// TODO change this name to usdc.e and import native usdc from base
export const USDC: TokenInfo = {
address: "0x7F5c764cBc14f9669B88837ca1490cCa17c31607",
symbol: "USDC.e",
unit: "ether"
};

export const NATIVE_USDC: TokenInfo = {
address: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
symbol: "USDC",
unit: "ether"
};

export const NATIVE_USDC_BASE: TokenInfo = {
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
symbol: "USDC",
unit: "ether"
};

const USDC_BASE: TokenInfo = {
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
symbol: "USDC",
unit: "ether"
};

export const OP: TokenInfo = {
address: "0x4200000000000000000000000000000000000042",
symbol: "OP",
unit: "ether"
};

// beware not checksummed.
const LUSD: TokenInfo = {
address: "0xc40f949f8a4e094d1b49a23ea9241d289b7b2819",
symbol: "LUSD",
unit: "ether"
};

export const VELO: TokenInfo = {
address: "0x9560e827aF36c94D2Ac33a39bCE1Fe78631088Db",
symbol: "VELO",
unit: "ether"
};

const USDbC: TokenInfo = {
address: "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA",
symbol: "USCbC",
unit: "ether"
};

// NB issue!! DAI address on base, Lyra address on optimism!!
const DAI: TokenInfo = {
address: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
symbol: "DAI",
unit: "ether"
};

const AERO: TokenInfo = {
address: "0x940181a94A35A4569E4529A3CDfB74e38FD98631",
symbol: "AERO",
unit: "ether"
};

const DOLA: TokenInfo = {
address: "0x4621b7A9c75199271F773Ebd9A499dbd165c3191",
symbol: "DOLA",
unit: "ether"
};
// list of WHITELISTED tokens with their symbol and decimals to be used in pricing
const OPTIMISM_WHITELISTED_TOKENS: TokenInfo[] = [WETH, USDC, VELO, OP, LUSD];

const BASE_WHITELISTED_TOKENS: TokenInfo[] = [
export const OPTIMISM_WHITELISTED_TOKENS: TokenInfo[] = [
VELO,
OP,
LUSD,
WETH,
USDC,
NATIVE_USDC
];

export const BASE_WHITELISTED_TOKENS: TokenInfo[] = [
USDbC,
USDC_BASE,
DAI,
DOLA,
WETH,
USDC_BASE,
NATIVE_USDC_BASE
];

// List of stablecoin pools with their token0, token1 and name
Expand Down Expand Up @@ -135,6 +173,7 @@ type chainConstants = {
eth: TokenInfo;
usdc: TokenInfo;
firstPriceFetchedBlockNumber: number;
priceOracle: string;
rewardToken: TokenInfo;
rpcURL: string;
stablecoinPools: Pool[];
Expand All @@ -147,8 +186,9 @@ type chainConstants = {
// Constants for Optimism
const OPTIMISM_CONSTANTS: chainConstants = {
eth: WETH,
usdc: USDC,
usdc: NATIVE_USDC,
firstPriceFetchedBlockNumber: 106247807,
priceOracle: "0x6a3af44e23395d2470f7c81331add6ede8597306",
rewardToken: VELO,
rpcURL: process.env.OPTIMISM_RPC_URL || "https://rpc.ankr.com/optimism",
stablecoinPools: OPTIMISM_STABLECOIN_POOLS,
Expand All @@ -165,8 +205,9 @@ const OPTIMISM_CONSTANTS: chainConstants = {
// Constants for Base
const BASE_CONSTANTS: chainConstants = {
eth: WETH,
usdc: USDbC,
usdc: NATIVE_USDC_BASE,
firstPriceFetchedBlockNumber: 3347620,
priceOracle: "0xcbf5b6abf55fb87271338097fdd03e9d82a9d63f",
rewardToken: AERO,
rpcURL: process.env.BASE_RPC_URL || "https://base.publicnode.com",
stablecoinPools: BASE_STABLECOIN_POOLS,
Expand Down
11 changes: 11 additions & 0 deletions src/CustomTypes.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { Token } from "./src/Types.gen";

// Ensure units is an array of valid unit types
export type validUnit = "noether" | "wei" | "kwei" | "Kwei" | "babbage" | "femtoether" | "mwei" | "Mwei" |
"lovelace" | "picoether" | "gwei" | "Gwei" | "shannon" | "nanoether" | "nano" | "szabo" | "microether" |
"micro" | "finney" | "milliether" | "milli" | "ether" | "kether" | "grand" | "mether" | "gether" | "tether";


// Token type to contain minimal information about a token
export type TokenInfo = {
address: string;
symbol: string;
unit: validUnit;
};

export type PricedTokenInfo = TokenInfo & {
price: number;
};

export type TokenEntityMapping = {
Expand Down
57 changes: 55 additions & 2 deletions src/EventHandlers/NFPM.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import {
NFPM,
NFPM_Transfer
} from "generated";
NFPM_Transfer,
TokenPrice
} from "generated";

import { setPricesLastUpdated, getPricesLastUpdated, get_whitelisted_prices } from "../PriceOracle/controller";
import { PRICE_ORACLE, PriceOracleKeys } from "../Constants";

/**
* @title NonfungiblePositionManager
Expand Down Expand Up @@ -30,4 +34,53 @@ NFPM.Transfer.handler(async ({ event, context }) => {
};

context.NFPM_Transfer.set(entity);

const lastUpdated = getPricesLastUpdated(event.chainId);

const currentDatetime = entity.timestamp;


// Check if the chainId is in the PRICE_ORACLE settings and has been deployed.
if (!(event.chainId in PRICE_ORACLE)) {
return;
}

// Check if the block number is greater than the start block of the price oracle.
// This is just to ensure the price oracle starts at a "good" block and we aren't
// wasting RPC calls.
let startBlock = PRICE_ORACLE[event.chainId as PriceOracleKeys].startBlock || Number.MAX_SAFE_INTEGER;
if (event.block.number < startBlock) {
return;
}

// Only update the prices if the last update was more than updateDelta seconds ago.
const timeDelta = PRICE_ORACLE[event.chainId as PriceOracleKeys].updateDelta * 1000;

if (!lastUpdated || (currentDatetime.getTime() - lastUpdated.getTime()) > timeDelta) {
let tokenData: any[] = [];
try {
tokenData = await get_whitelisted_prices(event.chainId, event.block.number);
} catch (error) {
console.error("Error fetching whitelisted prices:", error);
}

for (const token of tokenData) {
if (token.price) {
const tokenPrice: TokenPrice = {
id: `${event.chainId}_${token.address}_${event.block.number}`,
name: token.symbol,
address: token.address,
price: token.price,
chainID: event.chainId,
lastUpdatedTimestamp: currentDatetime,
};
try {
context.TokenPrice.set(tokenPrice);
} catch (error) {
console.error("Error setting token price:", error);
}
}
}
setPricesLastUpdated(event.chainId, currentDatetime); // Update the last datetime
}
});
60 changes: 33 additions & 27 deletions src/EventHandlers/Pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
getTokenSnapshotByInterval,
} from "./../IntervalSnapshots";
import { SnapshotInterval } from "./../CustomTypes";
import { PRICE_ORACLE, PriceOracleKeys } from "../Constants";
import { get_token_price } from "../PriceOracle/controller";

Pool.Mint.handler(async ({ event, context }) => {
const entity: Pool_Mint = {
Expand Down Expand Up @@ -180,12 +182,8 @@ Pool.Swap.handlerWithLoader({
Number(token1Instance.decimals)
);

let token0Price = 0n;
let token1Price = 0n;
if (netAmount0 != 0n && netAmount1 != 0n) {
token0Price = divideBase1e18(netAmount1, netAmount0);
token1Price = divideBase1e18(netAmount0, netAmount1);
}
let token0Price = token0Instance.pricePerUSDNew;
let token1Price = token1Instance.pricePerUSDNew;

// Calculate amounts in USD
// We don't double count volume, we use USD of first token if possible to
Expand Down Expand Up @@ -233,12 +231,8 @@ Pool.Swap.handlerWithLoader({
totalVolume0: liquidityPoolNew.totalVolume0 + netAmount0,
totalVolume1: liquidityPoolNew.totalVolume1 + netAmount1,
totalVolumeUSD: liquidityPoolNew.totalVolumeUSD + volumeInUSD,
token0Price: liquidityPoolNew.isStable
? token0Price
: liquidityPoolNew.token0Price,
token1Price: liquidityPoolNew.isStable
? token1Price
: liquidityPoolNew.token1Price,
token0Price,
token1Price,
numberOfSwaps: liquidityPoolNew.numberOfSwaps + 1n,
lastUpdatedTimestamp: new Date(event.block.timestamp * 1000),
};
Expand Down Expand Up @@ -291,21 +285,33 @@ Pool.Sync.handlerWithLoader({
Number(token1Instance.decimals)
);

let token0Price = liquidityPoolNew.token0Price;
let token1Price = liquidityPoolNew.token1Price;

// Only if the pool is not stable does this token price hold, otherwise uses previous price
if (
normalizedReserve0 != 0n &&
normalizedReserve1 != 0n &&
!liquidityPoolNew.isStable
) {
token0Price = divideBase1e18(normalizedReserve1, normalizedReserve0);
token1Price = divideBase1e18(normalizedReserve0, normalizedReserve1);
let token0Price = token0Instance.pricePerUSDNew;
let token1Price = token1Instance.pricePerUSDNew;
// Only fetch prices if the pool was updated more than updateDelta seconds ago
const timeDelta = PRICE_ORACLE[event.chainId as PriceOracleKeys].updateDelta * 1000;

const tokensNeedUpdate = !token0Instance.lastUpdatedTimestamp ||
!token1Instance.lastUpdatedTimestamp ||
(entity.timestamp.getTime() - token0Instance.lastUpdatedTimestamp.getTime()) > timeDelta ||
(entity.timestamp.getTime() - token1Instance.lastUpdatedTimestamp.getTime()) > timeDelta;

const oracleAvailable = PRICE_ORACLE[event.chainId as PriceOracleKeys].startBlock < event.block.number;

if (tokensNeedUpdate && oracleAvailable) {
try {
const token0FetchedPrice = await get_token_price(token0Instance.address, event.chainId, event.block.number);
const token1FetchedPrice = await get_token_price(token1Instance.address, event.chainId, event.block.number);
token0Price = BigInt(token0FetchedPrice);
token1Price = BigInt(token1FetchedPrice);
} catch (error) {
console.log("Error fetching prices", error);
return;
}
}

let token0PricePerUSDNew = token0Instance.pricePerUSDNew;
let token1PricePerUSDNew = token1Instance.pricePerUSDNew;

let token0PricePerUSDNew = BigInt(token0Price);
let token1PricePerUSDNew = BigInt(token1Price);

let totalLiquidityUSD = 0n;
// Only non-zero this figure if we don't have a price for both tokens(?)
Expand All @@ -319,8 +325,8 @@ Pool.Sync.handlerWithLoader({
reserve0: normalizedReserve0,
reserve1: normalizedReserve1,
totalLiquidityUSD: totalLiquidityUSD,
token0Price: token0Price,
token1Price: token1Price,
token0Price: BigInt(token0Price),
token1Price: BigInt(token1Price),
lastUpdatedTimestamp: new Date(event.block.timestamp * 1000),
};

Expand Down
1 change: 1 addition & 0 deletions src/EventHandlers/PoolFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ PoolFactory.PoolCreated.handlerWithLoader({
// Create new instances of Token to be updated in the DB
const tokenInstance: Token = {
id: poolTokenAddressMapping.address + "-" + event.chainId.toString(),
address: poolTokenAddressMapping.address,
symbol: tokenSymbol,
name: tokenName,
decimals: BigInt(tokenDecimals),
Expand Down
Loading