diff --git a/schema.graphql b/schema.graphql index ae86b774..33671ab8 100644 --- a/schema.graphql +++ b/schema.graphql @@ -7,6 +7,9 @@ type Token @entity { decimals: BigInt! totalLiquidity: BigDecimal! + stablecoinPairs: [Pair!]! + primaryUsdPrice: BigDecimal! + primaryUsdLiquidity: BigDecimal! pairsAsToken0: [Pair!]! @derivedFrom(field: "token0") pairsAsToken1: [Pair!]! @derivedFrom(field: "token1") @@ -28,6 +31,8 @@ type Pair @entity { reserve0: BigDecimal! reserve1: BigDecimal! + primaryUsdLiquidity: BigDecimal! + mints: [Mint!]! @derivedFrom(field: "pair") burns: [Burn!]! @derivedFrom(field: "pair") swaps: [Swap!]! @derivedFrom(field: "pair") diff --git a/src/mappings/factory.ts b/src/mappings/factory.ts index 2d05129a..161935b7 100644 --- a/src/mappings/factory.ts +++ b/src/mappings/factory.ts @@ -5,6 +5,7 @@ import { PairCreated } from '../types/Factory/Factory' import { Pair, Token, UniswapFactory } from '../types/schema' import { Pair as PairTemplate } from '../types/templates' import { FACTORY_ADDRESS, fetchTokenDecimals, ONE_BI, ZERO_BD } from './helpers' +import { getBestPrimaryUsdPrice, isStablecoin } from './pricing' export function handleNewPair(event: PairCreated): void { if (event.params.param3.equals(ONE_BI)) { @@ -12,7 +13,10 @@ export function handleNewPair(event: PairCreated): void { factory.save() } - if (Token.load(event.params.token0.toHexString()) === null) { + let token0 = Token.load(event.params.token0.toHexString()) + let token1 = Token.load(event.params.token1.toHexString()) + + if (token0 === null) { const decimals = fetchTokenDecimals(event.params.token0) if (decimals === null) { log.warning('Could not fetch decimals for token {}, skipping creation of pair {}.', [ @@ -22,13 +26,16 @@ export function handleNewPair(event: PairCreated): void { return } - const token0 = new Token(event.params.token0.toHexString()) + token0 = new Token(event.params.token0.toHexString()) token0.decimals = decimals token0.totalLiquidity = ZERO_BD + token0.stablecoinPairs = [] + token0.primaryUsdPrice = ZERO_BD + token0.primaryUsdLiquidity = ZERO_BD token0.save() } - if (Token.load(event.params.token1.toHexString()) === null) { + if (token1 === null) { const decimals = fetchTokenDecimals(event.params.token1) if (decimals === null) { log.warning('Could not fetch decimals for token {}, skipping creation of pair {}.', [ @@ -37,10 +44,12 @@ export function handleNewPair(event: PairCreated): void { ]) return } - - const token1 = new Token(event.params.token1.toHexString()) + token1 = new Token(event.params.token1.toHexString()) token1.decimals = decimals token1.totalLiquidity = ZERO_BD + token1.stablecoinPairs = [] + token1.primaryUsdPrice = ZERO_BD + token1.primaryUsdLiquidity = ZERO_BD token1.save() } @@ -51,7 +60,23 @@ export function handleNewPair(event: PairCreated): void { pair.createdAtBlockNumber = event.block.number pair.reserve0 = ZERO_BD pair.reserve1 = ZERO_BD + pair.primaryUsdLiquidity = ZERO_BD pair.save() + if (isStablecoin(event.params.token1.toHexString())) { + const stablecoinPairs = token0.stablecoinPairs + stablecoinPairs.push(event.params.pair.toHexString()) + token0.stablecoinPairs = stablecoinPairs + token0.primaryUsdPrice = getBestPrimaryUsdPrice(stablecoinPairs, token0.id) + token0.save() + } + if (isStablecoin(event.params.token0.toHexString())) { + const stablecoinPairs = token1.stablecoinPairs + stablecoinPairs.push(event.params.pair.toHexString()) + token1.stablecoinPairs = stablecoinPairs + token1.primaryUsdPrice = getBestPrimaryUsdPrice(stablecoinPairs, token1.id) + token1.save() + } + PairTemplate.create(event.params.pair) } diff --git a/src/mappings/pair.ts b/src/mappings/pair.ts index feba8892..8a0bea2f 100644 --- a/src/mappings/pair.ts +++ b/src/mappings/pair.ts @@ -4,6 +4,7 @@ import { log } from '@graphprotocol/graph-ts' import { Burn as BurnEntity, Mint as MintEntity, Pair, Swap as SwapEntity, Token } from '../types/schema' import { Burn, Mint, Swap } from '../types/templates/Pair/Pair' import { convertTokenToDecimal } from './helpers' +import { getBestPrimaryUsdPrice } from './pricing' export function handleMint(event: Mint): void { const block = event.block @@ -36,10 +37,21 @@ export function handleMint(event: Mint): void { mint.amount1 = token1Amount as BigDecimal mint.save() + token0.primaryUsdPrice = getBestPrimaryUsdPrice(token0.stablecoinPairs, token0.id) + token1.primaryUsdPrice = getBestPrimaryUsdPrice(token1.stablecoinPairs, token1.id) token0.totalLiquidity = token0.totalLiquidity.plus(token0Amount) token1.totalLiquidity = token1.totalLiquidity.plus(token1Amount) + token0.primaryUsdLiquidity = token0.primaryUsdPrice.times(token0.totalLiquidity) + token1.primaryUsdLiquidity = token1.primaryUsdPrice.times(token1.totalLiquidity) token0.save() token1.save() + + pair.reserve0 = pair.reserve0.plus(token0Amount) + pair.reserve1 = pair.reserve1.plus(token1Amount) + pair.primaryUsdLiquidity = pair.reserve0 + .times(token0.primaryUsdPrice) + .plus(pair.reserve1.times(token1.primaryUsdPrice)) + pair.save() } export function handleBurn(event: Burn): void { @@ -66,10 +78,22 @@ export function handleBurn(event: Burn): void { burn.to = event.params.to burn.save() + + token0.primaryUsdPrice = getBestPrimaryUsdPrice(token0.stablecoinPairs, token0.id) + token1.primaryUsdPrice = getBestPrimaryUsdPrice(token1.stablecoinPairs, token1.id) token0.totalLiquidity = token0.totalLiquidity.minus(token0Amount) token1.totalLiquidity = token1.totalLiquidity.minus(token1Amount) + token0.primaryUsdLiquidity = token0.primaryUsdPrice.times(token0.totalLiquidity) + token1.primaryUsdLiquidity = token1.primaryUsdPrice.times(token1.totalLiquidity) token0.save() token1.save() + + pair.reserve0 = pair.reserve0.minus(token0Amount) + pair.reserve1 = pair.reserve1.minus(token1Amount) + pair.primaryUsdLiquidity = pair.reserve0 + .times(token0.primaryUsdPrice) + .plus(pair.reserve1.times(token1.primaryUsdPrice)) + pair.save() } export function handleSwap(event: Swap): void { @@ -100,8 +124,19 @@ export function handleSwap(event: Swap): void { swap.to = event.params.to swap.save() + token0.primaryUsdPrice = getBestPrimaryUsdPrice(token0.stablecoinPairs, token0.id) + token1.primaryUsdPrice = getBestPrimaryUsdPrice(token1.stablecoinPairs, token1.id) token0.totalLiquidity = token0.totalLiquidity.minus(token0AmountOut).plus(token0AmountIn) token1.totalLiquidity = token1.totalLiquidity.minus(token1AmountOut).plus(token1AmountIn) + token0.primaryUsdLiquidity = token0.primaryUsdPrice.times(token0.totalLiquidity) + token1.primaryUsdLiquidity = token1.primaryUsdPrice.times(token1.totalLiquidity) token0.save() token1.save() -} \ No newline at end of file + + pair.reserve0 = pair.reserve0.minus(token0AmountOut).plus(token0AmountIn) + pair.reserve1 = pair.reserve1.minus(token1AmountOut).plus(token1AmountIn) + pair.primaryUsdLiquidity = pair.reserve0 + .times(token0.primaryUsdPrice) + .plus(pair.reserve1.times(token1.primaryUsdPrice)) + pair.save() +} diff --git a/src/mappings/pricing.ts b/src/mappings/pricing.ts new file mode 100644 index 00000000..ab31dcaa --- /dev/null +++ b/src/mappings/pricing.ts @@ -0,0 +1,87 @@ +import { BigDecimal } from '@graphprotocol/graph-ts' + +import { Pair, Token } from '../types/schema' +import { ZERO_BD } from './helpers' + +const STABLECOINS: string[] = [ + '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC + '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT +] + +export function isStablecoin(token: string): boolean { + return STABLECOINS.includes(token) +} + +export function updatePrimaryUsdPrices(pair: Pair, token0: Token, token1: Token): void { + if (isStablecoin(pair.token0) && isStablecoin(pair.token1)) { + return + } + + if (isStablecoin(pair.token0)) { + token1.primaryUsdPrice = pair.reserve0.div(pair.reserve1) + } else if (isStablecoin(pair.token1)) { + token0.primaryUsdPrice = pair.reserve1.div(pair.reserve0) + } +} + +export function safeDiv(a: BigDecimal, b: BigDecimal): BigDecimal { + if (b.equals(ZERO_BD)) { + return ZERO_BD + } + + return a.div(b) +} + +export function getPrimaryUsdLiquidity(pair: Pair): BigDecimal { + if (isStablecoin(pair.token0) && isStablecoin(pair.token1)) { + return pair.reserve0.plus(pair.reserve1) + } + + if (isStablecoin(pair.token0)) { + return pair.reserve0 + } + + if (isStablecoin(pair.token1)) { + return pair.reserve1 + } + + return ZERO_BD +} + +export function getUsdPrice(pair: Pair, tokenAddress: string): BigDecimal { + if (isStablecoin(tokenAddress)) { + return BigDecimal.fromString('1.0') + } + + if (isStablecoin(pair.token0)) { + return safeDiv(pair.reserve0, pair.reserve1) + } + + if (isStablecoin(pair.token1)) { + return safeDiv(pair.reserve1, pair.reserve0) + } + + return ZERO_BD +} + +// returns [maxUsdLiquidity, bestUsdPrice] +export function getBestPrimaryUsdPrice(stablecoinPairAddresses: string[], tokenAddress: string): [BigDecimal, BigDecimal] { + if (isStablecoin(tokenAddress)) { + return [ZERO_BD, BigDecimal.fromString('1.0')] + } + + let maxUsdLiquidity = ZERO_BD + let bestUsdPrice = ZERO_BD + + for (let i = 0; i < stablecoinPairAddresses.length; i++) { + const pair = Pair.load(stablecoinPairAddresses[i])! + const primaryUsdLiquidity = getPrimaryUsdLiquidity(pair) + if (primaryUsdLiquidity.gt(maxUsdLiquidity)) { + maxUsdLiquidity = primaryUsdLiquidity + bestUsdPrice = getUsdPrice(pair, tokenAddress) + } + } + + return [maxUsdLiquidity, bestUsdPrice] +}