-
Notifications
You must be signed in to change notification settings - Fork 0
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
feat: calculate L1 TVL using batch request #37
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,22 @@ | ||
import assert from "assert"; | ||
import { Inject, Injectable, LoggerService } from "@nestjs/common"; | ||
import { WINSTON_MODULE_NEST_PROVIDER } from "nest-winston"; | ||
import { Address, ContractConstructorArgs, parseAbiParameters } from "viem"; | ||
import { | ||
Address, | ||
ContractConstructorArgs, | ||
formatUnits, | ||
parseAbiParameters, | ||
parseUnits, | ||
} from "viem"; | ||
|
||
import { bridgeHubAbi, sharedBridgeAbi } from "@zkchainhub/metrics/l1/abis"; | ||
import { tokenBalancesAbi } from "@zkchainhub/metrics/l1/abis/tokenBalances.abi"; | ||
import { tokenBalancesBytecode } from "@zkchainhub/metrics/l1/bytecode"; | ||
import { Tvl } from "@zkchainhub/metrics/types"; | ||
import { AssetTvl } from "@zkchainhub/metrics/types"; | ||
import { IPricingService, PRICING_PROVIDER } from "@zkchainhub/pricing"; | ||
import { EvmProviderService } from "@zkchainhub/providers"; | ||
import { AbiWithAddress, ChainId, L1_CONTRACTS } from "@zkchainhub/shared"; | ||
import { tokens } from "@zkchainhub/shared/tokens/tokens"; | ||
import { parseUnits } from "@zkchainhub/shared/utils"; | ||
import { erc20Tokens, isNativeToken, tokens } from "@zkchainhub/shared/tokens/tokens"; | ||
|
||
/** | ||
* Acts as a wrapper around Viem library to provide methods to interact with an EVM-based blockchain. | ||
|
@@ -36,61 +41,78 @@ export class L1MetricsService { | |
|
||
/** | ||
* Retrieves the Total Value Locked by token on L1 Shared Bridge contract | ||
* @returns A Promise that resolves to an object representing the TVL. | ||
* @returns A Promise that resolves to an array of AssetTvl objects representing the TVL for each asset. | ||
*/ | ||
async l1Tvl(): Promise<Tvl> { | ||
const addresses = tokens | ||
.filter((token) => !!token.contractAddress) | ||
.map((token) => token.contractAddress) as Address[]; | ||
async l1Tvl(): Promise<AssetTvl[]> { | ||
const erc20Addresses = erc20Tokens.map((token) => token.contractAddress); | ||
|
||
const balances = await this.fetchTokenBalances(addresses); | ||
const balances = await this.fetchTokenBalances(erc20Addresses); | ||
const pricesRecord = await this.pricingService.getTokenPrices( | ||
tokens.map((token) => token.coingeckoId), | ||
); | ||
|
||
assert(balances.length === addresses.length + 1, "Invalid balances length"); | ||
assert(Object.keys(pricesRecord).length === tokens.length, "Invalid prices length"); | ||
|
||
return this.calculateTvl(balances, addresses, pricesRecord); | ||
return this.calculateTvl(balances, erc20Addresses, pricesRecord); | ||
} | ||
|
||
/** | ||
* Calculates the Total Value Locked (TVL) for each token based on the provided balances, addresses, and prices. | ||
* @param balances - The balances object containing the ETH balance and an array of erc20 token addresses balance. | ||
* @param addresses - The array of erc20 addresses. | ||
* @param prices - The object containing the prices of tokens. | ||
* @returns An array of AssetTvl objects representing the TVL for each token in descending order. | ||
*/ | ||
private calculateTvl( | ||
balances: bigint[], | ||
balances: { ethBalance: bigint; addressesBalance: bigint[] }, | ||
addresses: Address[], | ||
prices: Record<string, number>, | ||
): Tvl { | ||
const tvl: Tvl = {}; | ||
): AssetTvl[] { | ||
const tvl: AssetTvl[] = []; | ||
|
||
for (const token of tokens) { | ||
const balance = | ||
token.type === "native" | ||
? balances[addresses.length] | ||
: balances[addresses.indexOf(token.contractAddress as Address)]; | ||
const { coingeckoId, ...tokenInfo } = token; | ||
|
||
assert(balance !== undefined, `Balance for ${token.symbol} not found`); | ||
const balance = isNativeToken(token) | ||
? balances.ethBalance | ||
: balances.addressesBalance[ | ||
addresses.indexOf(tokenInfo.contractAddress as Address) | ||
]; | ||
|
||
const price = prices[token.coingeckoId] as number; | ||
const parsedBalance = parseUnits(balance, token.decimals); | ||
const tvlValue = parsedBalance * price; | ||
assert(balance !== undefined, `Balance for ${tokenInfo.symbol} not found`); | ||
|
||
tvl[token.symbol] = { | ||
amount: parsedBalance, | ||
const price = prices[coingeckoId] as number; | ||
// math is done with bigints for better precision | ||
const tvlValue = formatUnits( | ||
balance * parseUnits(price.toString(), tokenInfo.decimals), | ||
tokenInfo.decimals * 2, | ||
); | ||
|
||
const assetTvl: AssetTvl = { | ||
amount: formatUnits(balance, tokenInfo.decimals), | ||
amountUsd: tvlValue, | ||
name: token.name, | ||
imageUrl: token.imageUrl, | ||
price: price.toString(), | ||
...tokenInfo, | ||
}; | ||
|
||
tvl.push(assetTvl); | ||
} | ||
|
||
// we assume the rounding error is negligible for sorting purposes | ||
tvl.sort((a, b) => Number(b.amountUsd) - Number(a.amountUsd)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good clarifying comment there 💯 , just one extremely nitpick question but figured is worth asking: this might cause Eg, this is potentially possible:
It seems that the JavaScript engines now generally tend to implement the To wrap up, is the order of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, this is something we might not need to care about for now, but it's a good observation. Let's keep it as it is for now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think that the chances of two tokens to have the same tvl is really low on the most relevant tokens, this is more likely the situation on token with low balances or 0ish TVL There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hahahahaha , nigiri is right. However we should be aware of this if we need to change it in the future. :) |
||
|
||
return tvl; | ||
} | ||
|
||
/** | ||
* Fetches the token balances of Shared Bridgefor the given addresses. | ||
* Note: The last balance in the returned array is the ETH balance. | ||
* @param addresses The addresses for which to fetch the token balances. | ||
* @returns A promise that resolves to an array of token balances as bigints. | ||
* Fetches the token balances for the given addresses and ETH balance. | ||
* Note: The last balance in the returned array is the ETH balance, so the fetch length should be addresses.length + 1. | ||
* @param addresses - An array of addresses for which to fetch the token balances. | ||
* @returns A promise that resolves to an object containing the ETH balance and an array of address balances. | ||
*/ | ||
private async fetchTokenBalances(addresses: Address[]): Promise<bigint[]> { | ||
private async fetchTokenBalances( | ||
addresses: Address[], | ||
): Promise<{ ethBalance: bigint; addressesBalance: bigint[] }> { | ||
const returnAbiParams = parseAbiParameters("uint256[]"); | ||
const args: ContractConstructorArgs<typeof tokenBalancesAbi> = [ | ||
L1_CONTRACTS.SHARED_BRIDGE, | ||
|
@@ -104,7 +126,9 @@ export class L1MetricsService { | |
returnAbiParams, | ||
); | ||
|
||
return balances as bigint[]; | ||
assert(balances.length === addresses.length + 1, "Invalid balances length"); | ||
|
||
return { ethBalance: balances[addresses.length]!, addressesBalance: balances.slice(0, -1) }; | ||
} | ||
|
||
//TODO: Implement getBatchesInfo. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,7 @@ | ||
export type TokenTvl = { | ||
amount: number; | ||
amountUsd: number; | ||
name: string; | ||
imageUrl?: string; | ||
}; | ||
import { TokenUnion } from "@zkchainhub/shared/tokens/tokens"; | ||
|
||
export type Tvl = { | ||
[asset: string]: TokenTvl; | ||
export type AssetTvl = Omit<TokenUnion, "coingeckoId"> & { | ||
amount: string; | ||
amountUsd: string; | ||
price: string; | ||
}; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lmk if suggestions is what we want |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +0,0 @@ | ||
export * from "./parseUnits"; | ||
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💎