Skip to content
This repository was archived by the owner on Feb 4, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions frontend/src/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export enum TxState {
// Staking tokens
export enum StakingToken {
UNISWAP_V2,
BALANCER_V1,
BALANCER_SMART_POOL_V1,
BALANCER_WEIGHTED_POOL_V2,

// for testing
MOCK,
WAMPL,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/utils/price.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const DEFAULT_PRICES: Record<string, number> = {
USDbC: 1,
}

const SYMBOL_TO_QUERY: Record<string, string> = {
export const SYMBOL_TO_QUERY: Record<string, string> = {
WETH: 'ethereum',

USDC: 'usd-coin',
Expand Down
160 changes: 157 additions & 3 deletions frontend/src/utils/stakingToken.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { BigNumber, Contract } from 'ethers'
import { BigNumber, Contract, constants } from 'ethers'
import { formatUnits } from 'ethers/lib/utils'
import { toChecksumAddress } from 'web3-utils'
import { StakingToken } from '../constants'
import { WRAPPED_ERC20_ABI } from './abis/WrappedERC20'
import { AAVEV2_DEPOSIT_TOKEN } from './abis/AaveV2DepositToken'
import { getCurrentPrice } from './price'
import { getCurrentPrice, SYMBOL_TO_QUERY } from './price'
import { defaultTokenInfo, getTokenInfo } from './token'
import { UNISWAP_V2_PAIR_ABI } from './abis/UniswapV2Pair'
import { SignerOrProvider, StakingTokenInfo, TokenComposition } from '../types'
import { ERC20Balance } from '../sdk'
import { BALANCER_BPOOL_V1_ABI } from './abis/BalancerBPoolV1'
import { BALANCER_CRP_V1_ABI } from './abis/BalancerCRPV1'
import { BALANCER_WEIGHTED_POOL_V2_ABI } from './abis/BalancerWeightedPoolV2'
import { BALANCER_VAULT_V2_ABI } from './abis/BalancerVaultV2'

export const defaultStakingTokenInfo = (): StakingTokenInfo => ({
...defaultTokenInfo(),
Expand All @@ -25,6 +29,12 @@ export const getStakingTokenInfo = async (
switch (token) {
case StakingToken.UNISWAP_V2:
return getUniswapV2(tokenAddress, signerOrProvider)
case StakingToken.BALANCER_V1:
return getBalancerV1(tokenAddress, signerOrProvider)
case StakingToken.BALANCER_SMART_POOL_V1:
return getBalancerSmartPoolV1(tokenAddress, signerOrProvider)
case StakingToken.BALANCER_WEIGHTED_POOL_V2:
return getBalancerWeightedPoolV2(tokenAddress, signerOrProvider)
case StakingToken.MOCK:
return getMockLPToken(tokenAddress)
case StakingToken.WAMPL:
Expand All @@ -47,7 +57,7 @@ const getTokenCompositions = async (
): Promise<TokenComposition[]> => {
const compositions = tokenAddresses.map(async (tokenAddress, index) => {
const { name, symbol, decimals } = await getTokenInfo(tokenAddress, signerOrProvider)
const price = await getCurrentPrice(symbol)
const price = await getTokenPrice(symbol, tokenAddress, signerOrProvider)
const balance = await ERC20Balance(tokenAddress, poolAddress, signerOrProvider)
const balanceNumber = parseInt(formatUnits(balance as BigNumber, decimals), 10)
return {
Expand Down Expand Up @@ -188,3 +198,147 @@ const getBasicToken = async (tokenAddress: string, signerOrProvider: SignerOrPro
wrappedToken: null,
}
}

const getBalancerTokenCompositions = async (
address: string,
signerOrProvider: SignerOrProvider,
): Promise<TokenComposition[]> => {
const contract = new Contract(address, BALANCER_BPOOL_V1_ABI, signerOrProvider)
const tokenAddresses: string[] = await contract.getCurrentTokens()
const totalDenormalizedWeight: number = await contract.getTotalDenormalizedWeight()
const tokenDenormalizedWeights: number[] = await Promise.all(
tokenAddresses.map((token) => contract.getDenormalizedWeight(token)),
)
const tokenWeights = tokenDenormalizedWeights.map((w) => w / totalDenormalizedWeight)

return getTokenCompositions(tokenAddresses, contract.address, signerOrProvider, tokenWeights)
}

const getBalancerV1 = async (tokenAddress: string, signerOrProvider: SignerOrProvider): Promise<StakingTokenInfo> => {
const address = toChecksumAddress(tokenAddress)
const contract = new Contract(address, BALANCER_BPOOL_V1_ABI, signerOrProvider)

const { name, symbol, decimals } = await getTokenInfo(address, signerOrProvider)

const totalSupply: BigNumber = await contract.totalSupply()
const totalSupplyNumber = parseFloat(formatUnits(totalSupply, decimals))

const tokenCompositions = await getBalancerTokenCompositions(address, signerOrProvider)
const marketCap = getMarketCap(tokenCompositions)

return {
address,
decimals,
name,
symbol,
price: marketCap / totalSupplyNumber,
composition: tokenCompositions,
wrappedToken: null,
}
}

const getBalancerSmartPoolV1 = async (
tokenAddress: string,
signerOrProvider: SignerOrProvider,
): Promise<StakingTokenInfo> => {
const address = toChecksumAddress(tokenAddress)
const contract = new Contract(address, BALANCER_CRP_V1_ABI, signerOrProvider)

const bPool: string = await contract.bPool()
const { name, symbol, decimals } = await getTokenInfo(address, signerOrProvider)

const totalSupply: BigNumber = await contract.totalSupply()
const totalSupplyNumber = parseFloat(formatUnits(totalSupply, decimals))

const tokenCompositions = await getBalancerTokenCompositions(bPool, signerOrProvider)
const marketCap = getMarketCap(tokenCompositions)

return {
address,
decimals,
name,
symbol,
// totalSupply: totalSupplyNumber,
// marketCap,
price: marketCap / totalSupplyNumber,
composition: tokenCompositions,
wrappedToken: null,
}
}

const getBalancerWeightedPoolV2 = async (
tokenAddress: string,
signerOrProvider: SignerOrProvider,
): Promise<StakingTokenInfo> => {
const address = toChecksumAddress(tokenAddress)
const contract = new Contract(address, BALANCER_WEIGHTED_POOL_V2_ABI, signerOrProvider)

const { name, symbol, decimals } = await getTokenInfo(address, signerOrProvider)

const totalSupply: BigNumber = await contract.totalSupply()
const totalSupplyNumber = parseFloat(formatUnits(totalSupply, decimals))

const tokenCompositions = await getBalancerV2TokenCompositions(address, signerOrProvider)
const marketCap = getMarketCap(tokenCompositions)

return {
address,
decimals,
name,
symbol,
price: marketCap / totalSupplyNumber,
composition: tokenCompositions,
wrappedToken: null,
}
}

const getBalancerV2TokenCompositions = async (
address: string,
signerOrProvider: SignerOrProvider,
): Promise<TokenComposition[]> => {
const contract = new Contract(address, BALANCER_WEIGHTED_POOL_V2_ABI, signerOrProvider)
const vault = new Contract(await contract.getVault(), BALANCER_VAULT_V2_ABI, signerOrProvider)

const r = await vault.getPoolTokens(await contract.getPoolId())
const tokenAddresses: string[] = r[0]
const tokenBalances: string[] = r[1]

const totalNormalizedWeight: BigNumber = BigNumber.from(constants.WeiPerEther)
const tokenNormalizedWeights: BigNumber[] = await contract.getNormalizedWeights()
const tokenWeights = tokenNormalizedWeights.map((w) => w.div(totalNormalizedWeight).toNumber())

return getTokenCompositionsWithBalances(tokenAddresses, tokenBalances, signerOrProvider, tokenWeights)
}

const getTokenCompositionsWithBalances = async (
tokenAddresses: string[],
balances: string[],
signerOrProvider: SignerOrProvider,
weights: number[],
): Promise<TokenComposition[]> => {
const compositions = tokenAddresses.map(async (tokenAddress, index) => {
const { name, symbol, decimals } = await getTokenInfo(tokenAddress, signerOrProvider)
const price = await getTokenPrice(symbol, tokenAddress, signerOrProvider)
const balanceNumber = parseInt(formatUnits(BigNumber.from(balances[index]), decimals), 10)
return {
address: tokenAddress,
name,
symbol,
balance: balanceNumber,
decimals,
value: price * balanceNumber,
weight: weights[index],
}
})
return Promise.all(compositions)
}

const getTokenPrice = async (symbol: string, tokenAddress: string, signerOrProvider: SignerOrProvider) => {
if (SYMBOL_TO_QUERY[symbol]) {
const price = await getCurrentPrice(symbol)
return price
} else {
const { price } = await uniswapV2Pair(tokenAddress, signerOrProvider, 'UniswapV2', 'UNI')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likely the LP token we would want to get the price of would be an aaveV3 LP token not Uniswap

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it. Assuming that aUSDC has the same price as USDC. Now we support both aave token and uni tokens. We could do it another way around by just mapping atoken to coingecko id of underlying asset. If you think that solutions is better i can correct it it is just a matter of minutes

return price
}
}