diff --git a/schema.graphql b/schema.graphql index 54f6dc4e..9d17f34a 100644 --- a/schema.graphql +++ b/schema.graphql @@ -52,6 +52,9 @@ type Token @entity { pairDayDataQuote: [PairDayData!]! @derivedFrom(field: "token1") pairBase: [Pair!]! @derivedFrom(field: "token0") pairQuote: [Pair!]! @derivedFrom(field: "token1") + + # pairs used for USD pricing + priceTrackingPairs: [Pair!]! } type Pair @entity { diff --git a/src/mappings/core.ts b/src/mappings/core.ts index 2862c3dc..49f03f66 100644 --- a/src/mappings/core.ts +++ b/src/mappings/core.ts @@ -16,6 +16,8 @@ import { updatePairDayData, updatePairHourData, updateTokenDayData, updateUniswa import { ADDRESS_ZERO, BI_18, convertTokenToDecimal, createUser, FACTORY_ADDRESS, ONE_BI, ZERO_BD } from './helpers' import { findEthPerToken, getEthPriceInUSD, getTrackedLiquidityUSD, getTrackedVolumeUSD } from './pricing' +const ALMOST_ZERO_BD = BigDecimal.fromString('0.000001') + function isCompleteMint(mintId: string): boolean { return MintEvent.load(mintId)!.sender !== null // sufficient checks } @@ -66,7 +68,10 @@ export function handleTransfer(event: Transfer): void { // this is to make sure all the mints are under the same transaction if (mints.length === 0 || isCompleteMint(mints[mints.length - 1])) { let mint = new MintEvent( - event.transaction.hash.toHexString().concat('-').concat(BigInt.fromI32(mints.length).toString()), + event.transaction.hash + .toHexString() + .concat('-') + .concat(BigInt.fromI32(mints.length).toString()) ) mint.transaction = transaction.id mint.pair = pair.id @@ -92,7 +97,10 @@ export function handleTransfer(event: Transfer): void { if (event.params.to.toHexString() == pair.id) { let burns = transaction.burns let burn = new BurnEvent( - event.transaction.hash.toHexString().concat('-').concat(BigInt.fromI32(burns.length).toString()), + event.transaction.hash + .toHexString() + .concat('-') + .concat(BigInt.fromI32(burns.length).toString()) ) burn.transaction = transaction.id burn.pair = pair.id @@ -128,7 +136,10 @@ export function handleTransfer(event: Transfer): void { burn = currentBurn as BurnEvent } else { burn = new BurnEvent( - event.transaction.hash.toHexString().concat('-').concat(BigInt.fromI32(burns.length).toString()), + event.transaction.hash + .toHexString() + .concat('-') + .concat(BigInt.fromI32(burns.length).toString()) ) burn.transaction = transaction.id burn.needsComplete = false @@ -139,7 +150,10 @@ export function handleTransfer(event: Transfer): void { } } else { burn = new BurnEvent( - event.transaction.hash.toHexString().concat('-').concat(BigInt.fromI32(burns.length).toString()), + event.transaction.hash + .toHexString() + .concat('-') + .concat(BigInt.fromI32(burns.length).toString()) ) burn.transaction = transaction.id burn.needsComplete = false @@ -226,11 +240,11 @@ export function handleSync(event: Sync): void { token0.save() token1.save() - // get tracked liquidity - will be 0 if neither is in whitelist + // get tracked liquidity - will be 0 if neither is a price tracking token let trackedLiquidityETH: BigDecimal if (bundle.ethPrice.notEqual(ZERO_BD)) { trackedLiquidityETH = getTrackedLiquidityUSD(pair.reserve0, token0 as Token, pair.reserve1, token1 as Token).div( - bundle.ethPrice, + bundle.ethPrice ) } else { trackedLiquidityETH = ZERO_BD @@ -407,14 +421,22 @@ export function handleSwap(event: Swap): void { // ETH/USD prices let bundle = Bundle.load('1')! - // get total amounts of derived USD and ETH for tracking - let derivedAmountETH = token1.derivedETH - .times(amount1Total) - .plus(token0.derivedETH.times(amount0Total)) - .div(BigDecimal.fromString('2')) + const derivedEthToken1 = token1.derivedETH.times(amount1Total) + const derivedEthToken0 = token0.derivedETH.times(amount0Total) + + // default to legacy calculation + let derivedAmountETH = derivedEthToken1.plus(derivedEthToken0).div(BigDecimal.fromString('2')) + + // Only divide by 2 if both derivedETH values are non-zero + if (derivedEthToken0.le(ALMOST_ZERO_BD) || derivedEthToken1.le(ALMOST_ZERO_BD)) { + derivedAmountETH = derivedEthToken0.plus(derivedEthToken1) + } else { + derivedAmountETH = derivedEthToken0.plus(derivedEthToken1).div(BigDecimal.fromString('2')) + } + let derivedAmountUSD = derivedAmountETH.times(bundle.ethPrice) - // only accounts for volume through white listed tokens + // only accounts for volume through price tracking tokens let trackedAmountUSD = getTrackedVolumeUSD(amount0Total, token0 as Token, amount1Total, token1 as Token, pair as Pair) let trackedAmountETH: BigDecimal @@ -470,7 +492,10 @@ export function handleSwap(event: Swap): void { } let swaps = transaction.swaps let swap = new SwapEvent( - event.transaction.hash.toHexString().concat('-').concat(BigInt.fromI32(swaps.length).toString()), + event.transaction.hash + .toHexString() + .concat('-') + .concat(BigInt.fromI32(swaps.length).toString()) ) // update swap event @@ -527,7 +552,7 @@ export function handleSwap(event: Swap): void { token0DayData.dailyVolumeToken = token0DayData.dailyVolumeToken.plus(amount0Total) token0DayData.dailyVolumeETH = token0DayData.dailyVolumeETH.plus(amount0Total.times(token0.derivedETH as BigDecimal)) token0DayData.dailyVolumeUSD = token0DayData.dailyVolumeUSD.plus( - amount0Total.times(token0.derivedETH as BigDecimal).times(bundle.ethPrice), + amount0Total.times(token0.derivedETH as BigDecimal).times(bundle.ethPrice) ) token0DayData.save() @@ -535,7 +560,7 @@ export function handleSwap(event: Swap): void { token1DayData.dailyVolumeToken = token1DayData.dailyVolumeToken.plus(amount1Total) token1DayData.dailyVolumeETH = token1DayData.dailyVolumeETH.plus(amount1Total.times(token1.derivedETH as BigDecimal)) token1DayData.dailyVolumeUSD = token1DayData.dailyVolumeUSD.plus( - amount1Total.times(token1.derivedETH as BigDecimal).times(bundle.ethPrice), + amount1Total.times(token1.derivedETH as BigDecimal).times(bundle.ethPrice) ) token1DayData.save() } diff --git a/src/mappings/factory.ts b/src/mappings/factory.ts index 0321a2e5..82d5cc27 100644 --- a/src/mappings/factory.ts +++ b/src/mappings/factory.ts @@ -13,6 +13,7 @@ import { ZERO_BD, ZERO_BI, } from './helpers' +import { PRICE_TRACKING_TOKENS } from './pricing' export function handleNewPair(event: PairCreated): void { // load factory (create if first exchange) @@ -35,7 +36,7 @@ export function handleNewPair(event: PairCreated): void { factory.pairCount = factory.pairCount + 1 factory.save() - // create the tokens + let pair = new Pair(event.params.pair.toHexString()) as Pair let token0 = Token.load(event.params.token0.toHexString()) let token1 = Token.load(event.params.token1.toHexString()) @@ -59,8 +60,8 @@ export function handleNewPair(event: PairCreated): void { token0.tradeVolumeUSD = ZERO_BD token0.untrackedVolumeUSD = ZERO_BD token0.totalLiquidity = ZERO_BD - // token0.allPairs = [] token0.txCount = ZERO_BI + token0.priceTrackingPairs = [] } // fetch info if null @@ -81,11 +82,21 @@ export function handleNewPair(event: PairCreated): void { token1.tradeVolumeUSD = ZERO_BD token1.untrackedVolumeUSD = ZERO_BD token1.totalLiquidity = ZERO_BD - // token1.allPairs = [] token1.txCount = ZERO_BI + token1.priceTrackingPairs = [] + } + + if (PRICE_TRACKING_TOKENS.includes(token0.id)) { + const priceTrackingPairs = token1.priceTrackingPairs + priceTrackingPairs.push(pair.id) + token1.priceTrackingPairs = priceTrackingPairs + } + if (PRICE_TRACKING_TOKENS.includes(token1.id)) { + const priceTrackingPairs = token0.priceTrackingPairs + priceTrackingPairs.push(pair.id) + token0.priceTrackingPairs = priceTrackingPairs } - let pair = new Pair(event.params.pair.toHexString()) as Pair pair.token0 = token0.id pair.token1 = token1.id pair.liquidityProviderCount = ZERO_BI diff --git a/src/mappings/pricing.ts b/src/mappings/pricing.ts index c49b32f8..f2210540 100644 --- a/src/mappings/pricing.ts +++ b/src/mappings/pricing.ts @@ -1,8 +1,8 @@ /* eslint-disable prefer-const */ -import { Address, BigDecimal, BigInt } from '@graphprotocol/graph-ts/index' +import { BigDecimal, BigInt } from '@graphprotocol/graph-ts/index' import { Bundle, Pair, Token } from '../types/schema' -import { ADDRESS_ZERO, factoryContract, ONE_BD, UNTRACKED_PAIRS, ZERO_BD } from './helpers' +import { ONE_BD, UNTRACKED_PAIRS, ZERO_BD } from './helpers' const WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' const USDC_WETH_PAIR = '0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc' // created 10008355 @@ -40,7 +40,7 @@ export function getEthPriceInUSD(): BigDecimal { } // token where amounts should contribute to tracked volume and liquidity -let WHITELIST: string[] = [ +export const PRICE_TRACKING_TOKENS: string[] = [ '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC @@ -78,36 +78,36 @@ export function findEthPerToken(token: Token): BigDecimal { if (token.id == WETH_ADDRESS) { return ONE_BD } - // loop through whitelist and check if paired with any - for (let i = 0; i < WHITELIST.length; ++i) { - let pairAddress = factoryContract.getPair(Address.fromString(token.id), Address.fromString(WHITELIST[i])) - if (pairAddress.toHexString() != ADDRESS_ZERO) { - let pair = Pair.load(pairAddress.toHexString()) - if (pair === null) { + const priceTrackingPairs = token.priceTrackingPairs + for (let i = 0; i < priceTrackingPairs.length; i++) { + const pairAddress = priceTrackingPairs[i] + const pair = Pair.load(pairAddress) + + if (pair === null) { + continue + } + if (pair.token0 == token.id && pair.reserveETH.gt(MINIMUM_LIQUIDITY_THRESHOLD_ETH)) { + let token1 = Token.load(pair.token1) + if (token1 === null) { continue } - if (pair.token0 == token.id && pair.reserveETH.gt(MINIMUM_LIQUIDITY_THRESHOLD_ETH)) { - let token1 = Token.load(pair.token1) - if (token1 === null) { - continue - } - return pair.token1Price.times(token1.derivedETH as BigDecimal) // return token1 per our token * Eth per token 1 - } - if (pair.token1 == token.id && pair.reserveETH.gt(MINIMUM_LIQUIDITY_THRESHOLD_ETH)) { - let token0 = Token.load(pair.token0) - if (token0 === null) { - continue - } - return pair.token0Price.times(token0.derivedETH as BigDecimal) // return token0 per our token * ETH per token 0 + return pair.token1Price.times(token1.derivedETH as BigDecimal) // return token1 per our token * Eth per token 1 + } + if (pair.token1 == token.id && pair.reserveETH.gt(MINIMUM_LIQUIDITY_THRESHOLD_ETH)) { + let token0 = Token.load(pair.token0) + if (token0 === null) { + continue } + return pair.token0Price.times(token0.derivedETH as BigDecimal) // return token0 per our token * ETH per token 0 } } + return ZERO_BD // nothing was found return 0 } /** - * Accepts tokens and amounts, return tracked amount based on token whitelist - * If one token on whitelist, return amount in that token converted to USD. + * Accepts tokens and amounts, return tracked amount based on price tracking tokens + * If one token is a price tracking token, return amount in that token converted to USD. * If both are, return average of two amounts * If neither is, return 0 */ @@ -131,45 +131,45 @@ export function getTrackedVolumeUSD( if (pair.liquidityProviderCount.lt(BigInt.fromI32(5))) { let reserve0USD = pair.reserve0.times(price0) let reserve1USD = pair.reserve1.times(price1) - if (WHITELIST.includes(token0.id) && WHITELIST.includes(token1.id)) { + if (PRICE_TRACKING_TOKENS.includes(token0.id) && PRICE_TRACKING_TOKENS.includes(token1.id)) { if (reserve0USD.plus(reserve1USD).lt(MINIMUM_USD_THRESHOLD_NEW_PAIRS)) { return ZERO_BD } } - if (WHITELIST.includes(token0.id) && !WHITELIST.includes(token1.id)) { + if (PRICE_TRACKING_TOKENS.includes(token0.id) && !PRICE_TRACKING_TOKENS.includes(token1.id)) { if (reserve0USD.times(BigDecimal.fromString('2')).lt(MINIMUM_USD_THRESHOLD_NEW_PAIRS)) { return ZERO_BD } } - if (!WHITELIST.includes(token0.id) && WHITELIST.includes(token1.id)) { + if (!PRICE_TRACKING_TOKENS.includes(token0.id) && PRICE_TRACKING_TOKENS.includes(token1.id)) { if (reserve1USD.times(BigDecimal.fromString('2')).lt(MINIMUM_USD_THRESHOLD_NEW_PAIRS)) { return ZERO_BD } } } - // both are whitelist tokens, take average of both amounts - if (WHITELIST.includes(token0.id) && WHITELIST.includes(token1.id)) { + // both are price tracking tokens, take average of both amounts + if (PRICE_TRACKING_TOKENS.includes(token0.id) && PRICE_TRACKING_TOKENS.includes(token1.id)) { return tokenAmount0.times(price0).plus(tokenAmount1.times(price1)).div(BigDecimal.fromString('2')) } - // take full value of the whitelisted token amount - if (WHITELIST.includes(token0.id) && !WHITELIST.includes(token1.id)) { + // take full value of the price tracking token amount + if (PRICE_TRACKING_TOKENS.includes(token0.id) && !PRICE_TRACKING_TOKENS.includes(token1.id)) { return tokenAmount0.times(price0) } - // take full value of the whitelisted token amount - if (!WHITELIST.includes(token0.id) && WHITELIST.includes(token1.id)) { + // take full value of the price tracking token amount + if (!PRICE_TRACKING_TOKENS.includes(token0.id) && PRICE_TRACKING_TOKENS.includes(token1.id)) { return tokenAmount1.times(price1) } - // neither token is on white list, tracked volume is 0 + // neither token is a price tracking token, tracked volume is 0 return ZERO_BD } /** - * Accepts tokens and amounts, return tracked amount based on token whitelist - * If one token on whitelist, return amount in that token converted to USD * 2. + * Accepts tokens and amounts, return tracked amount based on price tracking token list + * If one token is a price tracking token, return amount in that token converted to USD * 2. * If both are, return sum of two amounts * If neither is, return 0 */ @@ -183,21 +183,21 @@ export function getTrackedLiquidityUSD( let price0 = token0.derivedETH.times(bundle.ethPrice) let price1 = token1.derivedETH.times(bundle.ethPrice) - // both are whitelist tokens, take average of both amounts - if (WHITELIST.includes(token0.id) && WHITELIST.includes(token1.id)) { + // both are price tracking tokens, take average of both amounts + if (PRICE_TRACKING_TOKENS.includes(token0.id) && PRICE_TRACKING_TOKENS.includes(token1.id)) { return tokenAmount0.times(price0).plus(tokenAmount1.times(price1)) } - // take double value of the whitelisted token amount - if (WHITELIST.includes(token0.id) && !WHITELIST.includes(token1.id)) { + // take double value of the price tracking token amount + if (PRICE_TRACKING_TOKENS.includes(token0.id) && !PRICE_TRACKING_TOKENS.includes(token1.id)) { return tokenAmount0.times(price0).times(BigDecimal.fromString('2')) } - // take double value of the whitelisted token amount - if (!WHITELIST.includes(token0.id) && WHITELIST.includes(token1.id)) { + // take double value of the price tracking token amount + if (!PRICE_TRACKING_TOKENS.includes(token0.id) && PRICE_TRACKING_TOKENS.includes(token1.id)) { return tokenAmount1.times(price1).times(BigDecimal.fromString('2')) } - // neither token is on white list, tracked volume is 0 + // neither token is a price tracking token, tracked volume is 0 return ZERO_BD }