diff --git a/.gitignore b/.gitignore index 0da354ab..7827f6e8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ build/ node_modules/ src/types/ .DS_STORE -yarn-error.log \ No newline at end of file +yarn-error.log +.vscode/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 44aef9f9..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.formatting.provider": "black" -} \ No newline at end of file diff --git a/abis/ERC20NameBytes.json b/abis/ERC20NameBytes.json deleted file mode 100644 index 2d3c877a..00000000 --- a/abis/ERC20NameBytes.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } -] diff --git a/abis/ERC20SymbolBytes.json b/abis/ERC20SymbolBytes.json deleted file mode 100644 index a76d6163..00000000 --- a/abis/ERC20SymbolBytes.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } -] diff --git a/package.json b/package.json index 407eaa84..400ef95a 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,7 @@ "build": "run-s codegen && graph build", "buildonly": "graph build", "deploy:alchemy": "graph deploy --node https://subgraphs.alchemy.com/api/subgraphs/deploy --ipfs https://ipfs.satsuma.xyz", - "codegen": "graph codegen --output-dir src/types/", - "create-local": "graph create davekaj/uniswap --node http://127.0.0.1:8020", - "deploy-local": "graph deploy davekaj/uniswap --debug --ipfs http://localhost:5001 --node http://127.0.0.1:8020", - "deploy": "graph deploy ianlapham/uniswap-v2-dev --ipfs https://api.thegraph.com/ipfs/ --node https://api.thegraph.com/deploy/ --debug", - "deploy-staging": "graph deploy $THE_GRAPH_GITHUB_USER/$THE_GRAPH_SUBGRAPH_NAME /Uniswap --ipfs https://api.staging.thegraph.com/ipfs/ --node https://api.staging.thegraph.com/deploy/", - "watch-local": "graph deploy graphprotocol/Uniswap2 --watch --debug --node http://127.0.0.1:8020/ --ipfs http://localhost:5001" + "codegen": "graph codegen --output-dir src/types/" }, "devDependencies": { "@graphprotocol/graph-cli": "^0.64.1", diff --git a/schema.graphql b/schema.graphql index 54f6dc4e..ae86b774 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1,274 +1,75 @@ type UniswapFactory @entity { - # factory address - id: ID! - - # pair info - pairCount: Int! - - # total volume - totalVolumeUSD: BigDecimal! - totalVolumeETH: BigDecimal! - - # untracked values - less confident USD scores - untrackedVolumeUSD: BigDecimal! - - # total liquidity - totalLiquidityUSD: BigDecimal! - totalLiquidityETH: BigDecimal! - - # transactions - txCount: BigInt! + id: ID! # factory address, checksummed } type Token @entity { - # token address - id: ID! - - # mirrored from the smart contract - symbol: String! - name: String! + id: ID! # token address, lowercased decimals: BigInt! - - # used for other stats like marketcap - totalSupply: BigInt! - - # token specific volume - tradeVolume: BigDecimal! - tradeVolumeUSD: BigDecimal! - untrackedVolumeUSD: BigDecimal! - - # transactions across all pairs - txCount: BigInt! - - # liquidity across all pairs + totalLiquidity: BigDecimal! - - # derived prices - derivedETH: BigDecimal! - - # derived fields - tokenDayData: [TokenDayData!]! @derivedFrom(field: "token") - pairDayDataBase: [PairDayData!]! @derivedFrom(field: "token0") - pairDayDataQuote: [PairDayData!]! @derivedFrom(field: "token1") - pairBase: [Pair!]! @derivedFrom(field: "token0") - pairQuote: [Pair!]! @derivedFrom(field: "token1") + + pairsAsToken0: [Pair!]! @derivedFrom(field: "token0") + pairsAsToken1: [Pair!]! @derivedFrom(field: "token1") + mintsAsToken0: [Mint!]! @derivedFrom(field: "token0") + mintsAsToken1: [Mint!]! @derivedFrom(field: "token1") + burnsAsToken0: [Burn!]! @derivedFrom(field: "token0") + burnsAsToken1: [Burn!]! @derivedFrom(field: "token1") + swapsAsToken0: [Swap!]! @derivedFrom(field: "token0") + swapsAsToken1: [Swap!]! @derivedFrom(field: "token1") } type Pair @entity { - # pair address - id: ID! - - # mirrored from the smart contract + id: ID! # pair address, lowercased token0: Token! token1: Token! - reserve0: BigDecimal! - reserve1: BigDecimal! - totalSupply: BigDecimal! - - # derived liquidity - reserveETH: BigDecimal! - reserveUSD: BigDecimal! - # used for separating per pair reserves and global - trackedReserveETH: BigDecimal! - - # Price in terms of the asset pair - token0Price: BigDecimal! - token1Price: BigDecimal! - - # lifetime volume stats - volumeToken0: BigDecimal! - volumeToken1: BigDecimal! - volumeUSD: BigDecimal! - untrackedVolumeUSD: BigDecimal! - txCount: BigInt! - - # creation stats createdAtTimestamp: BigInt! createdAtBlockNumber: BigInt! - - # Fields used to help derived relationship - liquidityProviderCount: BigInt! # used to detect new exchanges - # derived fields - pairHourData: [PairHourData!]! @derivedFrom(field: "pair") + + reserve0: BigDecimal! + reserve1: BigDecimal! + mints: [Mint!]! @derivedFrom(field: "pair") burns: [Burn!]! @derivedFrom(field: "pair") swaps: [Swap!]! @derivedFrom(field: "pair") } -type User @entity { - id: ID! - usdSwapped: BigDecimal! -} - -type Transaction @entity { - id: ID! # txn hash - blockNumber: BigInt! - timestamp: BigInt! - # This is not the reverse of Mint.transaction; it is only used to - # track incomplete mints (similar for burns and swaps) - mints: [Mint!]! - burns: [Burn!]! - swaps: [Swap!]! -} - type Mint @entity { - # transaction hash + "-" + index in mints Transaction array - id: ID! - transaction: Transaction! - timestamp: BigInt! # need this to pull recent txns for specific token or pair - pair: Pair! - - # populated from the primary Transfer event + id: ID! # transactionhash#logIndex + timestamp: BigInt! + blockNumber: BigInt! to: Bytes! - liquidity: BigDecimal! - - # populated from the Mint event - sender: Bytes - amount0: BigDecimal - amount1: BigDecimal - logIndex: BigInt - # derived amount based on available prices of tokens - amountUSD: BigDecimal - - # optional fee fields, if a Transfer event is fired in _mintFee - feeTo: Bytes - feeLiquidity: BigDecimal + pair: Pair! + token0: Token! + token1: Token! + sender: Bytes! + amount0: BigDecimal! + amount1: BigDecimal! } type Burn @entity { - # transaction hash + "-" + index in mints Transaction array - id: ID! - transaction: Transaction! - timestamp: BigInt! # need this to pull recent txns for specific token or pair + id: ID! # transactionhash#logIndex + timestamp: BigInt! + blockNumber: BigInt! pair: Pair! - - # populated from the primary Transfer event - liquidity: BigDecimal! - - # populated from the Burn event - sender: Bytes - amount0: BigDecimal - amount1: BigDecimal - to: Bytes - logIndex: BigInt - # derived amount based on available prices of tokens - amountUSD: BigDecimal - - # mark uncomplete in ETH case - needsComplete: Boolean! - - # optional fee fields, if a Transfer event is fired in _mintFee - feeTo: Bytes - feeLiquidity: BigDecimal + token0: Token! + token1: Token! + sender: Bytes! + amount0: BigDecimal! + amount1: BigDecimal! + to: Bytes! } type Swap @entity { - # transaction hash + "-" + index in swaps Transaction array - id: ID! - transaction: Transaction! - timestamp: BigInt! # need this to pull recent txns for specific token or pair + id: ID! # transactionhash#logIndex + timestamp: BigInt! + blockNumber: BigInt! pair: Pair! - - # populated from the Swap event + token0: Token! + token1: Token! sender: Bytes! - from: Bytes! # the EOA that initiated the txn amount0In: BigDecimal! amount1In: BigDecimal! amount0Out: BigDecimal! amount1Out: BigDecimal! to: Bytes! - logIndex: BigInt - - # derived info - amountUSD: BigDecimal! -} - -# stores for USD calculations -type Bundle @entity { - id: ID! - ethPrice: BigDecimal! # price of ETH usd -} - -# Data accumulated and condensed into day stats for all of Uniswap -type UniswapDayData @entity { - id: ID! # timestamp rounded to current day by dividing by 86400 - date: Int! - - dailyVolumeETH: BigDecimal! - dailyVolumeUSD: BigDecimal! - dailyVolumeUntracked: BigDecimal! - - totalVolumeETH: BigDecimal! - totalLiquidityETH: BigDecimal! - totalVolumeUSD: BigDecimal! # Accumulate at each trade, not just calculated off whatever totalVolume is. making it more accurate as it is a live conversion - totalLiquidityUSD: BigDecimal! - - txCount: BigInt! -} - -type PairHourData @entity { - id: ID! - hourStartUnix: Int! # unix timestamp for start of hour - pair: Pair! - - # reserves - reserve0: BigDecimal! - reserve1: BigDecimal! - - # total supply for LP historical returns - totalSupply: BigDecimal - - # derived liquidity - reserveUSD: BigDecimal! - - # volume stats - hourlyVolumeToken0: BigDecimal! - hourlyVolumeToken1: BigDecimal! - hourlyVolumeUSD: BigDecimal! - hourlyTxns: BigInt! -} - -# Data accumulated and condensed into day stats for each exchange -type PairDayData @entity { - id: ID! - date: Int! - pairAddress: Bytes! - token0: Token! - token1: Token! - - # reserves - reserve0: BigDecimal! - reserve1: BigDecimal! - - # total supply for LP historical returns - totalSupply: BigDecimal - - # derived liquidity - reserveUSD: BigDecimal! - - # volume stats - dailyVolumeToken0: BigDecimal! - dailyVolumeToken1: BigDecimal! - dailyVolumeUSD: BigDecimal! - dailyTxns: BigInt! -} - -type TokenDayData @entity { - id: ID! - date: Int! - token: Token! - - # volume stats - dailyVolumeToken: BigDecimal! - dailyVolumeETH: BigDecimal! - dailyVolumeUSD: BigDecimal! - dailyTxns: BigInt! - - # liquidity stats - totalLiquidityToken: BigDecimal! - totalLiquidityETH: BigDecimal! - totalLiquidityUSD: BigDecimal! - - # price stats - priceUSD: BigDecimal! } diff --git a/src/mappings/core.ts b/src/mappings/core.ts deleted file mode 100644 index 2862c3dc..00000000 --- a/src/mappings/core.ts +++ /dev/null @@ -1,541 +0,0 @@ -/* eslint-disable prefer-const */ -import { BigDecimal, BigInt, store } from '@graphprotocol/graph-ts' - -import { - Bundle, - Burn as BurnEvent, - Mint as MintEvent, - Pair, - Swap as SwapEvent, - Token, - Transaction, - UniswapFactory, -} from '../types/schema' -import { Burn, Mint, Swap, Sync, Transfer } from '../types/templates/Pair/Pair' -import { updatePairDayData, updatePairHourData, updateTokenDayData, updateUniswapDayData } from './dayUpdates' -import { ADDRESS_ZERO, BI_18, convertTokenToDecimal, createUser, FACTORY_ADDRESS, ONE_BI, ZERO_BD } from './helpers' -import { findEthPerToken, getEthPriceInUSD, getTrackedLiquidityUSD, getTrackedVolumeUSD } from './pricing' - -function isCompleteMint(mintId: string): boolean { - return MintEvent.load(mintId)!.sender !== null // sufficient checks -} - -export function handleTransfer(event: Transfer): void { - // ignore initial transfers for first adds - if (event.params.to.toHexString() == ADDRESS_ZERO && event.params.value.equals(BigInt.fromI32(1000))) { - return - } - - let factory = UniswapFactory.load(FACTORY_ADDRESS)! - let transactionHash = event.transaction.hash.toHexString() - - // user stats - let from = event.params.from - createUser(from) - let to = event.params.to - createUser(to) - - // get pair and load contract - let pair = Pair.load(event.address.toHexString())! - - // liquidity token amount being transfered - let value = convertTokenToDecimal(event.params.value, BI_18) - - // get or create transaction - let transaction = Transaction.load(transactionHash) - if (transaction === null) { - transaction = new Transaction(transactionHash) - transaction.blockNumber = event.block.number - transaction.timestamp = event.block.timestamp - transaction.mints = [] - transaction.burns = [] - transaction.swaps = [] - } - - // mints - let mints = transaction.mints - // part of the erc-20 standard (which is also the pool), whenever you mint new tokens, the from address is 0x0..0 - // the pool is also the erc-20 that gets minted and transferred around - if (from.toHexString() == ADDRESS_ZERO) { - // update total supply - pair.totalSupply = pair.totalSupply.plus(value) - pair.save() - - // create new mint if no mints so far or if last one is done already - // transfers and mints come in pairs, but there could be a case where that doesn't happen and it might break - // 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()), - ) - mint.transaction = transaction.id - mint.pair = pair.id - mint.to = to - mint.liquidity = value - mint.timestamp = transaction.timestamp - mint.transaction = transaction.id - mint.save() - - // update mints in transaction - transaction.mints = mints.concat([mint.id]) - - // save entities - transaction.save() - factory.save() - } - } - - // case where direct send first on ETH withdrawls - // for every burn event, there is a transfer first from the LP to the pool (erc-20) - // when you LP, you get an ERC-20 token which is the accounting token of the LP position - // the thing that's actually getting transfered is the LP account token - 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()), - ) - burn.transaction = transaction.id - burn.pair = pair.id - burn.liquidity = value - burn.timestamp = transaction.timestamp - burn.to = event.params.to - burn.sender = event.params.from - burn.needsComplete = true - burn.transaction = transaction.id - burn.save() - - // TODO: Consider using .concat() for handling array updates to protect - // against unintended side effects for other code paths. - burns.push(burn.id) - transaction.burns = burns - transaction.save() - } - - // burn - // there's two transfers for the LP token, - // first its going to move from the LP back to the pool, and then it will go from the pool to the zero address - if (event.params.to.toHexString() == ADDRESS_ZERO && event.params.from.toHexString() == pair.id) { - pair.totalSupply = pair.totalSupply.minus(value) - pair.save() - - // this is a new instance of a logical burn - let burns = transaction.burns - let burn: BurnEvent - // this block creates the burn or gets the reference to it if it already exists - if (burns.length > 0) { - let currentBurn = BurnEvent.load(burns[burns.length - 1])! - if (currentBurn.needsComplete) { - burn = currentBurn as BurnEvent - } else { - burn = new BurnEvent( - event.transaction.hash.toHexString().concat('-').concat(BigInt.fromI32(burns.length).toString()), - ) - burn.transaction = transaction.id - burn.needsComplete = false - burn.pair = pair.id - burn.liquidity = value - burn.transaction = transaction.id - burn.timestamp = transaction.timestamp - } - } else { - burn = new BurnEvent( - event.transaction.hash.toHexString().concat('-').concat(BigInt.fromI32(burns.length).toString()), - ) - burn.transaction = transaction.id - burn.needsComplete = false - burn.pair = pair.id - burn.liquidity = value - burn.transaction = transaction.id - burn.timestamp = transaction.timestamp - } - - // if this logical burn included a fee mint, account for this - // what is a fee mint? - // how are fees collected on v2? - // when you're an LP in v2, you're earning fees in terms of LP tokens, so when you go to burn your position, burn and collect fees at the same time - // protocol is sending the LP something and we think it's a mint when it's not and it's really fees - if (mints.length !== 0 && !isCompleteMint(mints[mints.length - 1])) { - let mint = MintEvent.load(mints[mints.length - 1])! - burn.feeTo = mint.to - burn.feeLiquidity = mint.liquidity - // remove the logical mint - store.remove('Mint', mints[mints.length - 1]) - // update the transaction - - // TODO: Consider using .slice().pop() to protect against unintended - // side effects for other code paths. - mints.pop() - transaction.mints = mints - transaction.save() - } - // when you collect fees or burn liquidity what are the events that get triggered - // not sure why this replaced the last one instead of updating - burn.save() - // if accessing last one, replace it - if (burn.needsComplete) { - // TODO: Consider using .slice(0, -1).concat() to protect against - // unintended side effects for other code paths. - burns[burns.length - 1] = burn.id - } - // else add new one - else { - // TODO: Consider using .concat() for handling array updates to protect - // against unintended side effects for other code paths. - burns.push(burn.id) - } - transaction.burns = burns - transaction.save() - } - - transaction.save() -} - -export function handleSync(event: Sync): void { - let pair = Pair.load(event.address.toHex())! - let token0 = Token.load(pair.token0) - let token1 = Token.load(pair.token1) - if (token0 === null || token1 === null) { - return - } - let uniswap = UniswapFactory.load(FACTORY_ADDRESS)! - - // reset factory liquidity by subtracting onluy tarcked liquidity - uniswap.totalLiquidityETH = uniswap.totalLiquidityETH.minus(pair.trackedReserveETH as BigDecimal) - - // reset token total liquidity amounts - token0.totalLiquidity = token0.totalLiquidity.minus(pair.reserve0) - token1.totalLiquidity = token1.totalLiquidity.minus(pair.reserve1) - - pair.reserve0 = convertTokenToDecimal(event.params.reserve0, token0.decimals) - pair.reserve1 = convertTokenToDecimal(event.params.reserve1, token1.decimals) - - if (pair.reserve1.notEqual(ZERO_BD)) pair.token0Price = pair.reserve0.div(pair.reserve1) - else pair.token0Price = ZERO_BD - if (pair.reserve0.notEqual(ZERO_BD)) pair.token1Price = pair.reserve1.div(pair.reserve0) - else pair.token1Price = ZERO_BD - - pair.save() - - // update ETH price now that reserves could have changed - let bundle = Bundle.load('1')! - bundle.ethPrice = getEthPriceInUSD() - bundle.save() - - token0.derivedETH = findEthPerToken(token0 as Token) - token1.derivedETH = findEthPerToken(token1 as Token) - token0.save() - token1.save() - - // get tracked liquidity - will be 0 if neither is in whitelist - let trackedLiquidityETH: BigDecimal - if (bundle.ethPrice.notEqual(ZERO_BD)) { - trackedLiquidityETH = getTrackedLiquidityUSD(pair.reserve0, token0 as Token, pair.reserve1, token1 as Token).div( - bundle.ethPrice, - ) - } else { - trackedLiquidityETH = ZERO_BD - } - - // use derived amounts within pair - pair.trackedReserveETH = trackedLiquidityETH - pair.reserveETH = pair.reserve0 - .times(token0.derivedETH as BigDecimal) - .plus(pair.reserve1.times(token1.derivedETH as BigDecimal)) - pair.reserveUSD = pair.reserveETH.times(bundle.ethPrice) - - // use tracked amounts globally - uniswap.totalLiquidityETH = uniswap.totalLiquidityETH.plus(trackedLiquidityETH) - uniswap.totalLiquidityUSD = uniswap.totalLiquidityETH.times(bundle.ethPrice) - - // now correctly set liquidity amounts for each token - token0.totalLiquidity = token0.totalLiquidity.plus(pair.reserve0) - token1.totalLiquidity = token1.totalLiquidity.plus(pair.reserve1) - - // save entities - pair.save() - uniswap.save() - token0.save() - token1.save() -} - -export function handleMint(event: Mint): void { - // loaded from a previous handler creating this transaction - // transfer event is emitted first and mint event is emitted afterwards, good to confirm with a protocol eng - let transaction = Transaction.load(event.transaction.hash.toHexString()) - if (transaction === null) { - return - } - - let mints = transaction.mints - let mint = MintEvent.load(mints[mints.length - 1]) - - if (mint === null) { - return - } - - let pair = Pair.load(event.address.toHex())! - let uniswap = UniswapFactory.load(FACTORY_ADDRESS)! - - let token0 = Token.load(pair.token0) - let token1 = Token.load(pair.token1) - if (token0 === null || token1 === null) { - return - } - - // update exchange info (except balances, sync will cover that) - let token0Amount = convertTokenToDecimal(event.params.amount0, token0.decimals) - let token1Amount = convertTokenToDecimal(event.params.amount1, token1.decimals) - - // update txn counts - token0.txCount = token0.txCount.plus(ONE_BI) - token1.txCount = token1.txCount.plus(ONE_BI) - - // get new amounts of USD and ETH for tracking - let bundle = Bundle.load('1')! - let amountTotalUSD = token1.derivedETH - .times(token1Amount) - .plus(token0.derivedETH.times(token0Amount)) - .times(bundle.ethPrice) - - // update txn counts - pair.txCount = pair.txCount.plus(ONE_BI) - uniswap.txCount = uniswap.txCount.plus(ONE_BI) - - // save entities - token0.save() - token1.save() - pair.save() - uniswap.save() - - mint.sender = event.params.sender - mint.amount0 = token0Amount as BigDecimal - mint.amount1 = token1Amount as BigDecimal - mint.logIndex = event.logIndex - mint.amountUSD = amountTotalUSD as BigDecimal - mint.save() - - // update day entities - updatePairDayData(event) - updatePairHourData(event) - updateUniswapDayData(event) - updateTokenDayData(token0 as Token, event) - updateTokenDayData(token1 as Token, event) -} - -export function handleBurn(event: Burn): void { - let transaction = Transaction.load(event.transaction.hash.toHexString()) - - // safety check - if (transaction === null) { - return - } - - let burns = transaction.burns - let burn = BurnEvent.load(burns[burns.length - 1]) - - if (burn === null) { - return - } - - let pair = Pair.load(event.address.toHex())! - let uniswap = UniswapFactory.load(FACTORY_ADDRESS)! - - //update token info - let token0 = Token.load(pair.token0) - let token1 = Token.load(pair.token1) - if (token0 === null || token1 === null) { - return - } - - let token0Amount = convertTokenToDecimal(event.params.amount0, token0.decimals) - let token1Amount = convertTokenToDecimal(event.params.amount1, token1.decimals) - - // update txn counts - token0.txCount = token0.txCount.plus(ONE_BI) - token1.txCount = token1.txCount.plus(ONE_BI) - - // get new amounts of USD and ETH for tracking - let bundle = Bundle.load('1')! - let amountTotalUSD = token1.derivedETH - .times(token1Amount) - .plus(token0.derivedETH.times(token0Amount)) - .times(bundle.ethPrice) - - // update txn counts - uniswap.txCount = uniswap.txCount.plus(ONE_BI) - pair.txCount = pair.txCount.plus(ONE_BI) - - // update global counter and save - token0.save() - token1.save() - pair.save() - uniswap.save() - - // update burn - // burn.sender = event.params.sender - burn.amount0 = token0Amount as BigDecimal - burn.amount1 = token1Amount as BigDecimal - // burn.to = event.params.to - burn.logIndex = event.logIndex - burn.amountUSD = amountTotalUSD as BigDecimal - burn.save() - - // update day entities - updatePairDayData(event) - updatePairHourData(event) - updateUniswapDayData(event) - updateTokenDayData(token0 as Token, event) - updateTokenDayData(token1 as Token, event) -} - -export function handleSwap(event: Swap): void { - let pair = Pair.load(event.address.toHexString())! - let token0 = Token.load(pair.token0) - let token1 = Token.load(pair.token1) - if (token0 === null || token1 === null) { - return - } - let amount0In = convertTokenToDecimal(event.params.amount0In, token0.decimals) - let amount1In = convertTokenToDecimal(event.params.amount1In, token1.decimals) - let amount0Out = convertTokenToDecimal(event.params.amount0Out, token0.decimals) - let amount1Out = convertTokenToDecimal(event.params.amount1Out, token1.decimals) - - // totals for volume updates - let amount0Total = amount0Out.plus(amount0In) - let amount1Total = amount1Out.plus(amount1In) - - // 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')) - let derivedAmountUSD = derivedAmountETH.times(bundle.ethPrice) - - // only accounts for volume through white listed tokens - let trackedAmountUSD = getTrackedVolumeUSD(amount0Total, token0 as Token, amount1Total, token1 as Token, pair as Pair) - - let trackedAmountETH: BigDecimal - if (bundle.ethPrice.equals(ZERO_BD)) { - trackedAmountETH = ZERO_BD - } else { - trackedAmountETH = trackedAmountUSD.div(bundle.ethPrice) - } - - // update token0 global volume and token liquidity stats - token0.tradeVolume = token0.tradeVolume.plus(amount0In.plus(amount0Out)) - token0.tradeVolumeUSD = token0.tradeVolumeUSD.plus(trackedAmountUSD) - token0.untrackedVolumeUSD = token0.untrackedVolumeUSD.plus(derivedAmountUSD) - - // update token1 global volume and token liquidity stats - token1.tradeVolume = token1.tradeVolume.plus(amount1In.plus(amount1Out)) - token1.tradeVolumeUSD = token1.tradeVolumeUSD.plus(trackedAmountUSD) - token1.untrackedVolumeUSD = token1.untrackedVolumeUSD.plus(derivedAmountUSD) - - // update txn counts - token0.txCount = token0.txCount.plus(ONE_BI) - token1.txCount = token1.txCount.plus(ONE_BI) - - // update pair volume data, use tracked amount if we have it as its probably more accurate - pair.volumeUSD = pair.volumeUSD.plus(trackedAmountUSD) - pair.volumeToken0 = pair.volumeToken0.plus(amount0Total) - pair.volumeToken1 = pair.volumeToken1.plus(amount1Total) - pair.untrackedVolumeUSD = pair.untrackedVolumeUSD.plus(derivedAmountUSD) - pair.txCount = pair.txCount.plus(ONE_BI) - pair.save() - - // update global values, only used tracked amounts for volume - let uniswap = UniswapFactory.load(FACTORY_ADDRESS)! - uniswap.totalVolumeUSD = uniswap.totalVolumeUSD.plus(trackedAmountUSD) - uniswap.totalVolumeETH = uniswap.totalVolumeETH.plus(trackedAmountETH) - uniswap.untrackedVolumeUSD = uniswap.untrackedVolumeUSD.plus(derivedAmountUSD) - uniswap.txCount = uniswap.txCount.plus(ONE_BI) - - // save entities - pair.save() - token0.save() - token1.save() - uniswap.save() - - let transaction = Transaction.load(event.transaction.hash.toHexString()) - if (transaction === null) { - transaction = new Transaction(event.transaction.hash.toHexString()) - transaction.blockNumber = event.block.number - transaction.timestamp = event.block.timestamp - transaction.mints = [] - transaction.swaps = [] - transaction.burns = [] - } - let swaps = transaction.swaps - let swap = new SwapEvent( - event.transaction.hash.toHexString().concat('-').concat(BigInt.fromI32(swaps.length).toString()), - ) - - // update swap event - swap.transaction = transaction.id - swap.pair = pair.id - swap.timestamp = transaction.timestamp - swap.transaction = transaction.id - swap.sender = event.params.sender - swap.amount0In = amount0In - swap.amount1In = amount1In - swap.amount0Out = amount0Out - swap.amount1Out = amount1Out - swap.to = event.params.to - swap.from = event.transaction.from - swap.logIndex = event.logIndex - // use the tracked amount if we have it - swap.amountUSD = trackedAmountUSD === ZERO_BD ? derivedAmountUSD : trackedAmountUSD - swap.save() - - // update the transaction - - // TODO: Consider using .concat() for handling array updates to protect - // against unintended side effects for other code paths. - swaps.push(swap.id) - transaction.swaps = swaps - transaction.save() - - // update day entities - let pairDayData = updatePairDayData(event) - let pairHourData = updatePairHourData(event) - let uniswapDayData = updateUniswapDayData(event) - let token0DayData = updateTokenDayData(token0 as Token, event) - let token1DayData = updateTokenDayData(token1 as Token, event) - - // swap specific updating - uniswapDayData.dailyVolumeUSD = uniswapDayData.dailyVolumeUSD.plus(trackedAmountUSD) - uniswapDayData.dailyVolumeETH = uniswapDayData.dailyVolumeETH.plus(trackedAmountETH) - uniswapDayData.dailyVolumeUntracked = uniswapDayData.dailyVolumeUntracked.plus(derivedAmountUSD) - uniswapDayData.save() - - // swap specific updating for pair - pairDayData.dailyVolumeToken0 = pairDayData.dailyVolumeToken0.plus(amount0Total) - pairDayData.dailyVolumeToken1 = pairDayData.dailyVolumeToken1.plus(amount1Total) - pairDayData.dailyVolumeUSD = pairDayData.dailyVolumeUSD.plus(trackedAmountUSD) - pairDayData.save() - - // update hourly pair data - pairHourData.hourlyVolumeToken0 = pairHourData.hourlyVolumeToken0.plus(amount0Total) - pairHourData.hourlyVolumeToken1 = pairHourData.hourlyVolumeToken1.plus(amount1Total) - pairHourData.hourlyVolumeUSD = pairHourData.hourlyVolumeUSD.plus(trackedAmountUSD) - pairHourData.save() - - // swap specific updating for token0 - 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), - ) - token0DayData.save() - - // swap specific updating - 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), - ) - token1DayData.save() -} diff --git a/src/mappings/dayUpdates.ts b/src/mappings/dayUpdates.ts deleted file mode 100644 index 15326e9a..00000000 --- a/src/mappings/dayUpdates.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* eslint-disable prefer-const */ -import { BigDecimal, BigInt, ethereum } from '@graphprotocol/graph-ts' - -import { Bundle, Pair, PairDayData, Token, TokenDayData, UniswapDayData, UniswapFactory } from '../types/schema' -import { PairHourData } from './../types/schema' -import { FACTORY_ADDRESS, ONE_BI, ZERO_BD, ZERO_BI } from './helpers' - -export function updateUniswapDayData(event: ethereum.Event): UniswapDayData { - let uniswap = UniswapFactory.load(FACTORY_ADDRESS)! - let timestamp = event.block.timestamp.toI32() - let dayID = timestamp / 86400 - let dayStartTimestamp = dayID * 86400 - let uniswapDayData = UniswapDayData.load(dayID.toString()) - if (uniswapDayData === null) { - uniswapDayData = new UniswapDayData(dayID.toString()) - uniswapDayData.date = dayStartTimestamp - uniswapDayData.dailyVolumeUSD = ZERO_BD - uniswapDayData.dailyVolumeETH = ZERO_BD - uniswapDayData.totalVolumeUSD = ZERO_BD - uniswapDayData.totalVolumeETH = ZERO_BD - uniswapDayData.dailyVolumeUntracked = ZERO_BD - } - - uniswapDayData.totalLiquidityUSD = uniswap.totalLiquidityUSD - uniswapDayData.totalLiquidityETH = uniswap.totalLiquidityETH - uniswapDayData.txCount = uniswap.txCount - uniswapDayData.save() - - return uniswapDayData as UniswapDayData -} - -export function updatePairDayData(event: ethereum.Event): PairDayData { - let timestamp = event.block.timestamp.toI32() - let dayID = timestamp / 86400 - let dayStartTimestamp = dayID * 86400 - let dayPairID = event.address.toHexString().concat('-').concat(BigInt.fromI32(dayID).toString()) - let pair = Pair.load(event.address.toHexString())! - let pairDayData = PairDayData.load(dayPairID) - if (pairDayData === null) { - pairDayData = new PairDayData(dayPairID) - pairDayData.date = dayStartTimestamp - pairDayData.token0 = pair.token0 - pairDayData.token1 = pair.token1 - pairDayData.pairAddress = event.address - pairDayData.dailyVolumeToken0 = ZERO_BD - pairDayData.dailyVolumeToken1 = ZERO_BD - pairDayData.dailyVolumeUSD = ZERO_BD - pairDayData.dailyTxns = ZERO_BI - } - - pairDayData.totalSupply = pair.totalSupply - pairDayData.reserve0 = pair.reserve0 - pairDayData.reserve1 = pair.reserve1 - pairDayData.reserveUSD = pair.reserveUSD - pairDayData.dailyTxns = pairDayData.dailyTxns.plus(ONE_BI) - pairDayData.save() - - return pairDayData as PairDayData -} - -export function updatePairHourData(event: ethereum.Event): PairHourData { - let timestamp = event.block.timestamp.toI32() - let hourIndex = timestamp / 3600 // get unique hour within unix history - let hourStartUnix = hourIndex * 3600 // want the rounded effect - let hourPairID = event.address.toHexString().concat('-').concat(BigInt.fromI32(hourIndex).toString()) - let pair = Pair.load(event.address.toHexString())! - let pairHourData = PairHourData.load(hourPairID) - if (pairHourData === null) { - pairHourData = new PairHourData(hourPairID) - pairHourData.hourStartUnix = hourStartUnix - pairHourData.pair = event.address.toHexString() - pairHourData.hourlyVolumeToken0 = ZERO_BD - pairHourData.hourlyVolumeToken1 = ZERO_BD - pairHourData.hourlyVolumeUSD = ZERO_BD - pairHourData.hourlyTxns = ZERO_BI - } - - pairHourData.totalSupply = pair.totalSupply - pairHourData.reserve0 = pair.reserve0 - pairHourData.reserve1 = pair.reserve1 - pairHourData.reserveUSD = pair.reserveUSD - pairHourData.hourlyTxns = pairHourData.hourlyTxns.plus(ONE_BI) - pairHourData.save() - - return pairHourData as PairHourData -} - -export function updateTokenDayData(token: Token, event: ethereum.Event): TokenDayData { - let bundle = Bundle.load('1')! - let timestamp = event.block.timestamp.toI32() - let dayID = timestamp / 86400 - let dayStartTimestamp = dayID * 86400 - let tokenDayID = token.id.toString().concat('-').concat(BigInt.fromI32(dayID).toString()) - - let tokenDayData = TokenDayData.load(tokenDayID) - if (tokenDayData === null) { - tokenDayData = new TokenDayData(tokenDayID) - tokenDayData.date = dayStartTimestamp - tokenDayData.token = token.id - tokenDayData.priceUSD = token.derivedETH.times(bundle.ethPrice) - tokenDayData.dailyVolumeToken = ZERO_BD - tokenDayData.dailyVolumeETH = ZERO_BD - tokenDayData.dailyVolumeUSD = ZERO_BD - tokenDayData.dailyTxns = ZERO_BI - tokenDayData.totalLiquidityUSD = ZERO_BD - } - tokenDayData.priceUSD = token.derivedETH.times(bundle.ethPrice) - tokenDayData.totalLiquidityToken = token.totalLiquidity - tokenDayData.totalLiquidityETH = token.totalLiquidity.times(token.derivedETH as BigDecimal) - tokenDayData.totalLiquidityUSD = tokenDayData.totalLiquidityETH.times(bundle.ethPrice) - tokenDayData.dailyTxns = tokenDayData.dailyTxns.plus(ONE_BI) - tokenDayData.save() - - /** - * @todo test if this speeds up sync - */ - // updateStoredTokens(tokenDayData as TokenDayData, dayID) - // updateStoredPairs(tokenDayData as TokenDayData, dayPairID) - - return tokenDayData as TokenDayData -} diff --git a/src/mappings/factory.ts b/src/mappings/factory.ts index 0321a2e5..2d05129a 100644 --- a/src/mappings/factory.ts +++ b/src/mappings/factory.ts @@ -2,115 +2,56 @@ import { log } from '@graphprotocol/graph-ts' import { PairCreated } from '../types/Factory/Factory' -import { Bundle, Pair, Token, UniswapFactory } from '../types/schema' +import { Pair, Token, UniswapFactory } from '../types/schema' import { Pair as PairTemplate } from '../types/templates' -import { - FACTORY_ADDRESS, - fetchTokenDecimals, - fetchTokenName, - fetchTokenSymbol, - fetchTokenTotalSupply, - ZERO_BD, - ZERO_BI, -} from './helpers' +import { FACTORY_ADDRESS, fetchTokenDecimals, ONE_BI, ZERO_BD } from './helpers' export function handleNewPair(event: PairCreated): void { - // load factory (create if first exchange) - let factory = UniswapFactory.load(FACTORY_ADDRESS) - if (factory === null) { - factory = new UniswapFactory(FACTORY_ADDRESS) - factory.pairCount = 0 - factory.totalVolumeETH = ZERO_BD - factory.totalLiquidityETH = ZERO_BD - factory.totalVolumeUSD = ZERO_BD - factory.untrackedVolumeUSD = ZERO_BD - factory.totalLiquidityUSD = ZERO_BD - factory.txCount = ZERO_BI - - // create new bundle - let bundle = new Bundle('1') - bundle.ethPrice = ZERO_BD - bundle.save() + if (event.params.param3.equals(ONE_BI)) { + const factory = new UniswapFactory(FACTORY_ADDRESS) + factory.save() } - factory.pairCount = factory.pairCount + 1 - factory.save() - - // create the tokens - let token0 = Token.load(event.params.token0.toHexString()) - let token1 = Token.load(event.params.token1.toHexString()) - // fetch info if null - if (token0 === null) { - token0 = new Token(event.params.token0.toHexString()) - token0.symbol = fetchTokenSymbol(event.params.token0) - token0.name = fetchTokenName(event.params.token0) - token0.totalSupply = fetchTokenTotalSupply(event.params.token0) - let decimals = fetchTokenDecimals(event.params.token0) - - // bail if we couldn't figure out the decimals + if (Token.load(event.params.token0.toHexString()) === null) { + const decimals = fetchTokenDecimals(event.params.token0) if (decimals === null) { - log.debug('mybug the decimal on token 0 was null', []) + log.warning('Could not fetch decimals for token {}, skipping creation of pair {}.', [ + event.params.token0.toHexString(), + event.params.pair.toHexString(), + ]) return } + const token0 = new Token(event.params.token0.toHexString()) token0.decimals = decimals - token0.derivedETH = ZERO_BD - token0.tradeVolume = ZERO_BD - token0.tradeVolumeUSD = ZERO_BD - token0.untrackedVolumeUSD = ZERO_BD token0.totalLiquidity = ZERO_BD - // token0.allPairs = [] - token0.txCount = ZERO_BI + token0.save() } - // fetch info if null - if (token1 === null) { - token1 = new Token(event.params.token1.toHexString()) - token1.symbol = fetchTokenSymbol(event.params.token1) - token1.name = fetchTokenName(event.params.token1) - token1.totalSupply = fetchTokenTotalSupply(event.params.token1) - let decimals = fetchTokenDecimals(event.params.token1) - - // bail if we couldn't figure out the decimals + if (Token.load(event.params.token1.toHexString()) === null) { + const decimals = fetchTokenDecimals(event.params.token1) if (decimals === null) { + log.warning('Could not fetch decimals for token {}, skipping creation of pair {}.', [ + event.params.token1.toHexString(), + event.params.pair.toHexString(), + ]) return } + + const token1 = new Token(event.params.token1.toHexString()) token1.decimals = decimals - token1.derivedETH = ZERO_BD - token1.tradeVolume = ZERO_BD - token1.tradeVolumeUSD = ZERO_BD - token1.untrackedVolumeUSD = ZERO_BD token1.totalLiquidity = ZERO_BD - // token1.allPairs = [] - token1.txCount = ZERO_BI + token1.save() } - let pair = new Pair(event.params.pair.toHexString()) as Pair - pair.token0 = token0.id - pair.token1 = token1.id - pair.liquidityProviderCount = ZERO_BI + const pair = new Pair(event.params.pair.toHexString()) + pair.token0 = event.params.token0.toHexString() + pair.token1 = event.params.token1.toHexString() pair.createdAtTimestamp = event.block.timestamp pair.createdAtBlockNumber = event.block.number - pair.txCount = ZERO_BI pair.reserve0 = ZERO_BD pair.reserve1 = ZERO_BD - pair.trackedReserveETH = ZERO_BD - pair.reserveETH = ZERO_BD - pair.reserveUSD = ZERO_BD - pair.totalSupply = ZERO_BD - pair.volumeToken0 = ZERO_BD - pair.volumeToken1 = ZERO_BD - pair.volumeUSD = ZERO_BD - pair.untrackedVolumeUSD = ZERO_BD - pair.token0Price = ZERO_BD - pair.token1Price = ZERO_BD + pair.save() - // create the tracked contract based on the template PairTemplate.create(event.params.pair) - - // save updated values - token0.save() - token1.save() - pair.save() - factory.save() } diff --git a/src/mappings/helpers.ts b/src/mappings/helpers.ts index adda9eb6..d2f91a42 100644 --- a/src/mappings/helpers.ts +++ b/src/mappings/helpers.ts @@ -2,142 +2,15 @@ import { Address, BigDecimal, BigInt } from '@graphprotocol/graph-ts' import { ERC20 } from '../types/Factory/ERC20' -import { ERC20NameBytes } from '../types/Factory/ERC20NameBytes' -import { ERC20SymbolBytes } from '../types/Factory/ERC20SymbolBytes' -import { User } from '../types/schema' -import { Factory as FactoryContract } from '../types/templates/Pair/Factory' -import { TokenDefinition } from './tokenDefinition' -export const ADDRESS_ZERO = '0x0000000000000000000000000000000000000000' export const FACTORY_ADDRESS = '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f' -export let ZERO_BI = BigInt.fromI32(0) -export let ONE_BI = BigInt.fromI32(1) -export let ZERO_BD = BigDecimal.fromString('0') -export let ONE_BD = BigDecimal.fromString('1') -export let BI_18 = BigInt.fromI32(18) - -export let factoryContract = FactoryContract.bind(Address.fromString(FACTORY_ADDRESS)) - -// rebass tokens, dont count in tracked volume -export let UNTRACKED_PAIRS: string[] = ['0x9ea3b5b4ec044b70375236a281986106457b20ef'] - -export function exponentToBigDecimal(decimals: BigInt): BigDecimal { - let bd = BigDecimal.fromString('1') - for (let i = ZERO_BI; i.lt(decimals as BigInt); i = i.plus(ONE_BI)) { - bd = bd.times(BigDecimal.fromString('10')) - } - return bd -} - -export function bigDecimalExp18(): BigDecimal { - return BigDecimal.fromString('1000000000000000000') -} - -export function convertEthToDecimal(eth: BigInt): BigDecimal { - return eth.toBigDecimal().div(exponentToBigDecimal(18)) -} - -export function convertTokenToDecimal(tokenAmount: BigInt, exchangeDecimals: BigInt): BigDecimal { - if (exchangeDecimals == ZERO_BI) { - return tokenAmount.toBigDecimal() - } - return tokenAmount.toBigDecimal().div(exponentToBigDecimal(exchangeDecimals)) -} - -export function equalToZero(value: BigDecimal): boolean { - const formattedVal = parseFloat(value.toString()) - const zero = parseFloat(ZERO_BD.toString()) - if (zero == formattedVal) { - return true - } - return false -} - -export function isNullEthValue(value: string): boolean { - return value == '0x0000000000000000000000000000000000000000000000000000000000000001' -} - -export function fetchTokenSymbol(tokenAddress: Address): string { - // static definitions overrides - let staticDefinition = TokenDefinition.fromAddress(tokenAddress) - if (staticDefinition != null) { - return (staticDefinition as TokenDefinition).symbol - } - - let contract = ERC20.bind(tokenAddress) - let contractSymbolBytes = ERC20SymbolBytes.bind(tokenAddress) - - // try types string and bytes32 for symbol - let symbolValue = 'unknown' - let symbolResult = contract.try_symbol() - if (symbolResult.reverted) { - let symbolResultBytes = contractSymbolBytes.try_symbol() - if (!symbolResultBytes.reverted) { - // for broken pairs that have no symbol function exposed - if (!isNullEthValue(symbolResultBytes.value.toHexString())) { - symbolValue = symbolResultBytes.value.toString() - } - } - } else { - symbolValue = symbolResult.value - } - - return symbolValue -} - -export function fetchTokenName(tokenAddress: Address): string { - // static definitions overrides - let staticDefinition = TokenDefinition.fromAddress(tokenAddress) - if (staticDefinition != null) { - return (staticDefinition as TokenDefinition).name - } - - let contract = ERC20.bind(tokenAddress) - let contractNameBytes = ERC20NameBytes.bind(tokenAddress) - - // try types string and bytes32 for name - let nameValue = 'unknown' - let nameResult = contract.try_name() - if (nameResult.reverted) { - let nameResultBytes = contractNameBytes.try_name() - if (!nameResultBytes.reverted) { - // for broken exchanges that have no name function exposed - if (!isNullEthValue(nameResultBytes.value.toHexString())) { - nameValue = nameResultBytes.value.toString() - } - } - } else { - nameValue = nameResult.value - } - - return nameValue -} - -// HOT FIX: we cant implement try catch for overflow catching so skip total supply parsing on these tokens that overflow -// TODO: find better way to handle overflow -let SKIP_TOTAL_SUPPLY: string[] = ['0x0000000000bf2686748e1c0255036e7617e7e8a5'] - -export function fetchTokenTotalSupply(tokenAddress: Address): BigInt { - if (SKIP_TOTAL_SUPPLY.includes(tokenAddress.toHexString())) { - return BigInt.fromI32(0) - } - const contract = ERC20.bind(tokenAddress) - let totalSupplyValue = BigInt.zero() - const totalSupplyResult = contract.try_totalSupply() - if (!totalSupplyResult.reverted) { - totalSupplyValue = totalSupplyResult.value - } - return totalSupplyValue -} +export const ZERO_BD = BigDecimal.fromString('0') +export const ZERO_BI = BigInt.fromI32(0) +export const ONE_BD = BigDecimal.fromString('1') +export const ONE_BI = BigInt.fromI32(1) export function fetchTokenDecimals(tokenAddress: Address): BigInt | null { - // static definitions overrides - let staticDefinition = TokenDefinition.fromAddress(tokenAddress) - if (staticDefinition != null) { - return (staticDefinition as TokenDefinition).decimals - } - let contract = ERC20.bind(tokenAddress) let decimalResult = contract.try_decimals() if (!decimalResult.reverted) { @@ -148,11 +21,15 @@ export function fetchTokenDecimals(tokenAddress: Address): BigInt | null { return null } -export function createUser(address: Address): void { - let user = User.load(address.toHexString()) - if (user === null) { - user = new User(address.toHexString()) - user.usdSwapped = ZERO_BD - user.save() +export function exponentToBigDecimal(decimals: BigInt): BigDecimal { + const zeros = '0'.repeat(decimals.toI32()) + const bigDecimalString = `1${zeros}` + return BigDecimal.fromString(bigDecimalString) +} + +export function convertTokenToDecimal(tokenAmount: BigInt, exchangeDecimals: BigInt): BigDecimal { + if (exchangeDecimals == ZERO_BI) { + return tokenAmount.toBigDecimal() } + return tokenAmount.toBigDecimal().div(exponentToBigDecimal(exchangeDecimals)) } diff --git a/src/mappings/pair.ts b/src/mappings/pair.ts new file mode 100644 index 00000000..dc072702 --- /dev/null +++ b/src/mappings/pair.ts @@ -0,0 +1,113 @@ +import { BigDecimal } from '@graphprotocol/graph-ts' +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, Sync } from '../types/templates/Pair/Pair' +import { convertTokenToDecimal } from './helpers' + +export function handleMint(event: Mint): void { + const block = event.block + const transaction = event.transaction + const mintId = transaction.hash.toHex() + '#' + event.logIndex.toString() + + const transactionTo = transaction.to + + if (transactionTo === null) { + log.warning('Could not fetch transaction to address, skipping mint {}.', [mintId]) + return + } + + const pair = Pair.load(event.address.toHex())! + const token0 = Token.load(pair.token0)! + const token1 = Token.load(pair.token1)! + + const token0Amount = convertTokenToDecimal(event.params.amount0, token0.decimals) + const token1Amount = convertTokenToDecimal(event.params.amount1, token1.decimals) + + const mint = new MintEntity(mintId) + mint.timestamp = block.timestamp + mint.blockNumber = block.number + mint.to = transactionTo + mint.pair = pair.id + mint.token0 = token0.id + mint.token1 = token1.id + mint.sender = event.params.sender + mint.amount0 = token0Amount as BigDecimal + mint.amount1 = token1Amount as BigDecimal + mint.save() +} + +export function handleBurn(event: Burn): void { + const block = event.block + const transaction = event.transaction + const burnId = transaction.hash.toHex() + '#' + event.logIndex.toString() + + const pair = Pair.load(event.address.toHex())! + const token0 = Token.load(pair.token0)! + const token1 = Token.load(pair.token1)! + + const token0Amount = convertTokenToDecimal(event.params.amount0, token0.decimals) + const token1Amount = convertTokenToDecimal(event.params.amount1, token1.decimals) + + const burn = new BurnEntity(burnId) + burn.timestamp = block.timestamp + burn.blockNumber = block.number + burn.pair = pair.id + burn.token0 = token0.id + burn.token1 = token1.id + burn.sender = event.params.sender + burn.amount0 = token0Amount as BigDecimal + burn.amount1 = token1Amount as BigDecimal + burn.to = event.params.to + burn.save() +} + +export function handleSwap(event: Swap): void { + const block = event.block + const transaction = event.transaction + const swapId = transaction.hash.toHex() + '#' + event.logIndex.toString() + + const pair = Pair.load(event.address.toHex())! + const token0 = Token.load(pair.token0)! + const token1 = Token.load(pair.token1)! + + const token0AmountIn = convertTokenToDecimal(event.params.amount0In, token0.decimals) + const token1AmountIn = convertTokenToDecimal(event.params.amount1In, token1.decimals) + const token0AmountOut = convertTokenToDecimal(event.params.amount0Out, token0.decimals) + const token1AmountOut = convertTokenToDecimal(event.params.amount1Out, token1.decimals) + + const swap = new SwapEntity(swapId) + swap.timestamp = block.timestamp + swap.blockNumber = block.number + swap.pair = pair.id + swap.token0 = token0.id + swap.token1 = token1.id + swap.sender = event.params.sender + swap.amount0In = token0AmountIn + swap.amount1In = token1AmountIn + swap.amount0Out = token0AmountOut + swap.amount1Out = token1AmountOut + swap.to = event.params.to + swap.save() +} + +export function handleSync(event: Sync): void { + const pair = Pair.load(event.address.toHex())! + const token0 = Token.load(pair.token0)! + const token1 = Token.load(pair.token1)! + + const reserve0 = convertTokenToDecimal(event.params.reserve0, token0.decimals) + const reserve1 = convertTokenToDecimal(event.params.reserve1, token1.decimals) + + const oldReserve0 = pair.reserve0 + const oldReserve1 = pair.reserve1 + + pair.reserve0 = reserve0 + pair.reserve1 = reserve1 + pair.save() + + token0.totalLiquidity = token0.totalLiquidity.plus(reserve0.minus(oldReserve0)) + token1.totalLiquidity = token1.totalLiquidity.plus(reserve1.minus(oldReserve1)) + token0.save() + token1.save() +} diff --git a/src/mappings/pricing.ts b/src/mappings/pricing.ts deleted file mode 100644 index c49b32f8..00000000 --- a/src/mappings/pricing.ts +++ /dev/null @@ -1,203 +0,0 @@ -/* eslint-disable prefer-const */ -import { Address, 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' - -const WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' -const USDC_WETH_PAIR = '0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc' // created 10008355 -const DAI_WETH_PAIR = '0xa478c2975ab1ea89e8196811f51a7b7ade33eb11' // created block 10042267 -const USDT_WETH_PAIR = '0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852' // created block 10093341 - -export function getEthPriceInUSD(): BigDecimal { - // fetch eth prices for each stablecoin - let daiPair = Pair.load(DAI_WETH_PAIR) // dai is token0 - let usdcPair = Pair.load(USDC_WETH_PAIR) // usdc is token0 - let usdtPair = Pair.load(USDT_WETH_PAIR) // usdt is token1 - - // all 3 have been created - if (daiPair !== null && usdcPair !== null && usdtPair !== null) { - let totalLiquidityETH = daiPair.reserve1.plus(usdcPair.reserve1).plus(usdtPair.reserve0) - let daiWeight = daiPair.reserve1.div(totalLiquidityETH) - let usdcWeight = usdcPair.reserve1.div(totalLiquidityETH) - let usdtWeight = usdtPair.reserve0.div(totalLiquidityETH) - return daiPair.token0Price - .times(daiWeight) - .plus(usdcPair.token0Price.times(usdcWeight)) - .plus(usdtPair.token1Price.times(usdtWeight)) - // dai and USDC have been created - } else if (daiPair !== null && usdcPair !== null) { - let totalLiquidityETH = daiPair.reserve1.plus(usdcPair.reserve1) - let daiWeight = daiPair.reserve1.div(totalLiquidityETH) - let usdcWeight = usdcPair.reserve1.div(totalLiquidityETH) - return daiPair.token0Price.times(daiWeight).plus(usdcPair.token0Price.times(usdcWeight)) - // USDC is the only pair so far - } else if (usdcPair !== null) { - return usdcPair.token0Price - } else { - return ZERO_BD - } -} - -// token where amounts should contribute to tracked volume and liquidity -let WHITELIST: string[] = [ - '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH - '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC - '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT - '0x0000000000085d4780b73119b644ae5ecd22b376', // TUSD - '0x5d3a536e4d6dbd6114cc1ead35777bab948e3643', // cDAI - '0x39aa39c021dfbae8fac545936693ac917d5e7563', // cUSDC - '0x86fadb80d8d2cff3c3680819e4da99c10232ba0f', // EBASE - '0x57ab1ec28d129707052df4df418d58a2d46d5f51', // sUSD - '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', // MKR - '0xc00e94cb662c3520282e6f5717214004a7f26888', // COMP - '0x514910771af9ca656af840dff83e8264ecf986ca', //LINK - '0x960b236a07cf122663c4303350609a66a7b288c0', //ANT - '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', //SNX - '0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e', //YFI - '0xdf5e0e81dff6faf3a7e52ba697820c5e32d806a8', // yCurv - '0x853d955acef822db058eb8505911ed77f175b99e', // FRAX - '0xa47c8bf37f92abed4a126bda807a7b7498661acd', // WUST - '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', // UNI - '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', // WBTC - '0x956f47f50a910163d8bf957cf5846d573e7f87ca', // FEI -] - -// minimum liquidity required to count towards tracked volume for pairs with small # of Lps -let MINIMUM_USD_THRESHOLD_NEW_PAIRS = BigDecimal.fromString('400000') - -// minimum liquidity for price to get tracked -let MINIMUM_LIQUIDITY_THRESHOLD_ETH = BigDecimal.fromString('2') - -/** - * Search through graph to find derived Eth per token. - * @todo update to be derived ETH (add stablecoin estimates) - **/ -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) { - 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 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. - * If both are, return average of two amounts - * If neither is, return 0 - */ -export function getTrackedVolumeUSD( - tokenAmount0: BigDecimal, - token0: Token, - tokenAmount1: BigDecimal, - token1: Token, - pair: Pair, -): BigDecimal { - let bundle = Bundle.load('1')! - let price0 = token0.derivedETH.times(bundle.ethPrice) - let price1 = token1.derivedETH.times(bundle.ethPrice) - - // dont count tracked volume on these pairs - usually rebass tokens - if (UNTRACKED_PAIRS.includes(pair.id)) { - return ZERO_BD - } - - // if less than 5 LPs, require high minimum reserve amount amount or return 0 - 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 (reserve0USD.plus(reserve1USD).lt(MINIMUM_USD_THRESHOLD_NEW_PAIRS)) { - return ZERO_BD - } - } - if (WHITELIST.includes(token0.id) && !WHITELIST.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 (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)) { - 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)) { - return tokenAmount0.times(price0) - } - - // take full value of the whitelisted token amount - if (!WHITELIST.includes(token0.id) && WHITELIST.includes(token1.id)) { - return tokenAmount1.times(price1) - } - - // neither token is on white list, 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. - * If both are, return sum of two amounts - * If neither is, return 0 - */ -export function getTrackedLiquidityUSD( - tokenAmount0: BigDecimal, - token0: Token, - tokenAmount1: BigDecimal, - token1: Token, -): BigDecimal { - let bundle = Bundle.load('1')! - 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)) { - 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)) { - 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)) { - return tokenAmount1.times(price1).times(BigDecimal.fromString('2')) - } - - // neither token is on white list, tracked volume is 0 - return ZERO_BD -} diff --git a/src/mappings/tokenDefinition.ts b/src/mappings/tokenDefinition.ts deleted file mode 100644 index 86cea581..00000000 --- a/src/mappings/tokenDefinition.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Address, BigInt } from '@graphprotocol/graph-ts' - -// Initialize a Token Definition with the attributes -export class TokenDefinition { - address: Address - symbol: string - name: string - decimals: BigInt - - // Get all tokens with a static defintion - static getStaticDefinitions(): Array { - const staticDefinitions: Array = [ - { - address: Address.fromString('0xe0b7927c4af23765cb51314a0e0521a9645f0e2a'), - symbol: 'DGD', - name: 'DGD', - decimals: BigInt.fromI32(9), - }, - { - address: Address.fromString('0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9'), - symbol: 'AAVE', - name: 'Aave Token', - decimals: BigInt.fromI32(18), - }, - { - address: Address.fromString('0xeb9951021698b42e4399f9cbb6267aa35f82d59d'), - symbol: 'LIF', - name: 'Lif', - decimals: BigInt.fromI32(18), - }, - { - address: Address.fromString('0xbdeb4b83251fb146687fa19d1c660f99411eefe3'), - symbol: 'SVD', - name: 'savedroid', - decimals: BigInt.fromI32(18), - }, - { - address: Address.fromString('0xbb9bc244d798123fde783fcc1c72d3bb8c189413'), - symbol: 'TheDAO', - name: 'TheDAO', - decimals: BigInt.fromI32(16), - }, - { - address: Address.fromString('0x38c6a68304cdefb9bec48bbfaaba5c5b47818bb2'), - symbol: 'HPB', - name: 'HPBCoin', - decimals: BigInt.fromI32(18), - }, - ] - return staticDefinitions - } - - // Helper for hardcoded tokens - static fromAddress(tokenAddress: Address): TokenDefinition | null { - const staticDefinitions = this.getStaticDefinitions() - const tokenAddressHex = tokenAddress.toHexString() - - // Search the definition using the address - for (let i = 0; i < staticDefinitions.length; i++) { - const staticDefinition = staticDefinitions[i] - if (staticDefinition.address.toHexString() == tokenAddressHex) { - return staticDefinition - } - } - - // If not found, return null - return null - } -} diff --git a/subgraph.yaml b/subgraph.yaml index fa437318..83365a97 100644 --- a/subgraph.yaml +++ b/subgraph.yaml @@ -1,4 +1,4 @@ -specVersion: 0.0.4 +specVersion: 1.2.0 description: Uniswap is a decentralized protocol for automated token exchange on Ethereum. repository: https://github.com/Uniswap/uniswap-v2-subgraph schema: @@ -13,10 +13,11 @@ dataSources: startBlock: 10000834 mapping: kind: ethereum/events - apiVersion: 0.0.7 + apiVersion: 0.0.9 language: wasm/assemblyscript file: ./src/mappings/factory.ts entities: + - UniswapFactory - Pair - Token abis: @@ -24,10 +25,6 @@ dataSources: file: ./abis/factory.json - name: ERC20 file: ./abis/ERC20.json - - name: ERC20SymbolBytes - file: ./abis/ERC20SymbolBytes.json - - name: ERC20NameBytes - file: ./abis/ERC20NameBytes.json eventHandlers: - event: PairCreated(indexed address,indexed address,address,uint256) handler: handleNewPair @@ -39,17 +36,17 @@ templates: abi: Pair mapping: kind: ethereum/events - apiVersion: 0.0.7 + apiVersion: 0.0.9 language: wasm/assemblyscript - file: ./src/mappings/core.ts + file: ./src/mappings/pair.ts entities: - Pair - Token + - Mint + - Burn abis: - name: Pair file: ./abis/pair.json - - name: Factory - file: ./abis/factory.json eventHandlers: - event: Mint(indexed address,uint256,uint256) handler: handleMint @@ -57,7 +54,5 @@ templates: handler: handleBurn - event: Swap(indexed address,uint256,uint256,uint256,uint256,indexed address) handler: handleSwap - - event: Transfer(indexed address,indexed address,uint256) - handler: handleTransfer - event: Sync(uint112,uint112) - handler: handleSync + handler: handleSync \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 65664612..00000000 --- a/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "./node_modules/@graphprotocol/graph-ts/tsconfig.json", - "compilerOptions": { - "types": ["@graphprotocol/graph-ts"] - } -}