diff --git a/src/adapters/aura/common/balance.ts b/src/adapters/aura/common/balance.ts index 66302c00a..4aa0e531e 100644 --- a/src/adapters/aura/common/balance.ts +++ b/src/adapters/aura/common/balance.ts @@ -1,7 +1,9 @@ +import { getExtraRewardsBalances } from '@adapters/aura/common/extraReward' import type { Balance, BalancesContext, Contract } from '@lib/adapter' import { mapSuccessFilter } from '@lib/array' +import { getUnderlyingsBalancesFromBalancer, type IBalancerBalance } from '@lib/balancer/underlying' import { call } from '@lib/call' -import { abi as erc20Abi } from '@lib/erc20' +import { getBalancesOf } from '@lib/erc20' import { multicall } from '@lib/multicall' import { parseEther } from 'viem' @@ -13,25 +15,6 @@ const abi = { stateMutability: 'view', type: 'function', }, - extraEarned: { - inputs: [ - { - internalType: 'address', - name: 'account', - type: 'address', - }, - ], - name: 'earned', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, getPoolTokens: { inputs: [{ internalType: 'bytes32', name: 'poolId', type: 'bytes32' }], name: 'getPoolTokens', @@ -103,13 +86,7 @@ const AURA: { [key: string]: `0x${string}`[] } = { export async function getAuraBalStakerBalances(ctx: BalancesContext, staker: Contract): Promise { const balanceOfRes = await call({ ctx, target: staker.address, params: [ctx.address], abi: abi.balanceOfUnderlying }) - return { - ...staker, - amount: balanceOfRes, - underlyings: undefined, - rewards: undefined, - category: 'farm', - } + return { ...staker, amount: balanceOfRes, underlyings: undefined, rewards: undefined, category: 'farm' } } export async function getAuraFarmBalances( @@ -117,133 +94,32 @@ export async function getAuraFarmBalances( pools: Contract[], vault: Contract, ): Promise { - const balanceWithStandardRewards: Balance[] = [] - const balanceWithExtraRewards: Balance[] = [] - const balances = await getAuraBalancesInternal(ctx, pools, vault, 'gauge') - - const earnedsRes = await multicall({ - ctx, - calls: balances.map((balance: Contract) => ({ target: balance.gauge, params: [ctx.address] }) as const), - abi: abi.earned, - }) - - mapSuccessFilter(earnedsRes, (res, idx) => { - const balance = balances[idx] - const rewards = balance.rewards - - const poolBalance: Balance = { + const balances: Balance[] = (await getBalancesOf(ctx, pools, { getAddress: (contract) => contract.gauge })).map( + (balance) => ({ ...balance, - rewards: [{ ...rewards![0], amount: res.output }, ...rewards!.slice(1)], - } - - if (poolBalance.rewards && poolBalance.rewards.length > 1) { - balanceWithExtraRewards.push(poolBalance) - } else { - balanceWithStandardRewards.push(poolBalance) - } - }) - - const balanceWithExtraRewardsBalances = await getExtraRewardsBalances(ctx, balanceWithExtraRewards) - - return getAuraMintAmount(ctx, [...balanceWithStandardRewards, ...balanceWithExtraRewardsBalances]) -} - -export async function getAuraBalancesInternal( - ctx: BalancesContext, - inputPools: Contract[], - vault: Contract, - targetProp: 'address' | 'gauge', -): Promise { - const balances: Balance[] = [] - - const fmtPools: Contract[] = [] - inputPools.forEach((pool) => { - if (targetProp === 'gauge' && Array.isArray(pool.gauge)) { - pool.gauge.forEach((gauge) => { - fmtPools.push({ ...pool, gauge: gauge }) - }) - } else { - fmtPools.push(pool) - } - }) - - const [poolBalancesRes, uBalancesRes, totalSuppliesRes] = await Promise.all([ - multicall({ - ctx, - calls: fmtPools.map((pool) => ({ target: pool[targetProp], params: [ctx.address] }) as const), - abi: erc20Abi.balanceOf, - }), - multicall({ - ctx, - calls: fmtPools.map((pool) => ({ target: vault.address, params: [pool.poolId] }) as const), - abi: abi.getPoolTokens, + category: 'farm', }), - multicall({ - ctx, - calls: fmtPools.map((pool) => ({ target: pool.address }) as const), - abi: erc20Abi.totalSupply, - }), - ]) - - for (const [index, pool] of fmtPools.entries()) { - const underlyings = pool.underlyings as Contract[] - const rewards = pool.rewards as Balance[] - const poolBalanceRes = poolBalancesRes[index] - const uBalanceRes = uBalancesRes[index] - const totalSupplyRes = totalSuppliesRes[index] - - if ( - !underlyings || - !poolBalanceRes.success || - !uBalanceRes.success || - !totalSupplyRes.success || - totalSupplyRes.output === 0n - ) { - continue - } + ) - const [_tokens, underlyingsBalances] = uBalanceRes.output - - underlyings.forEach((underlying, idx) => { - const amount = underlyingsBalances[idx] - underlying.amount = amount - }) - - const lpTokenBalance = underlyings.find( - (underlying) => underlying.address.toLowerCase() === pool.address.toLowerCase(), - ) - - const fmtUnderlyings = underlyings - .map((underlying) => { - const realSupply = lpTokenBalance ? totalSupplyRes.output - lpTokenBalance.amount : totalSupplyRes.output - const amount = (underlying.amount * poolBalanceRes.output) / realSupply - - return { - ...underlying, - amount, - } - }) - .filter((underlying) => underlying.address.toLowerCase() !== pool.address.toLowerCase()) - - balances.push({ ...pool, amount: poolBalanceRes.output, underlyings: fmtUnderlyings, rewards, category: 'farm' }) - } - - return balances -} + const poolBalances = await getUnderlyingsBalancesFromBalancer(ctx, balances as IBalancerBalance[], vault, { + getAddress: (balance: Balance) => balance.address, + getCategory: (balance: Balance) => balance.category, + }) -const getExtraRewardsBalances = async (ctx: BalancesContext, poolBalance: Balance[]): Promise => { - const extraRewardsBalancesRes = await multicall({ + const earnedsRes = await multicall({ ctx, - calls: poolBalance.map((pool: Contract) => ({ target: pool.rewarder, params: [ctx.address] }) as const), - abi: abi.extraEarned, + calls: poolBalances.map((balance: Contract) => ({ target: balance.gauge, params: [ctx.address] }) as const), + abi: abi.earned, }) - poolBalance.forEach((pool, idx) => { - const extraRewardsBalances = extraRewardsBalancesRes[idx].success ? extraRewardsBalancesRes[idx].output : 0n - pool.rewards = [pool.rewards![0], { ...pool.rewards![1], amount: extraRewardsBalances! }] - }) + const fmtBalances = mapSuccessFilter(earnedsRes, (res, idx) => { + const poolBalance = poolBalances[idx] + const rewards = poolBalance.rewards as Contract[] + + return { ...poolBalance, rewards: [{ ...rewards![0], amount: res.output }, ...rewards!.slice(1)] } + }) as Balance[] - return poolBalance + return getAuraMintAmount(ctx, await getExtraRewardsBalances(ctx, fmtBalances)) } const getAuraMintAmount = async (ctx: BalancesContext, balances: Balance[]): Promise => { diff --git a/src/adapters/aura/common/extraReward.ts b/src/adapters/aura/common/extraReward.ts new file mode 100644 index 000000000..1e765e5fa --- /dev/null +++ b/src/adapters/aura/common/extraReward.ts @@ -0,0 +1,35 @@ +import type { Balance, BalancesContext, Contract } from '@lib/adapter' +import { multicall } from '@lib/multicall' + +const abi = { + extraEarned: { + inputs: [{ internalType: 'address', name: 'account', type: 'address' }], + name: 'earned', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +} as const + +export const getExtraRewardsBalances = async (ctx: BalancesContext, poolBalance: Balance[]): Promise => { + const balanceWithStandardRewards: Balance[] = poolBalance.filter((poolBalance) => { + return !poolBalance.rewards || poolBalance.rewards.length === 0 + }) + + const balanceWithExtraRewards: Balance[] = poolBalance.filter((poolBalance) => { + return poolBalance.rewards && poolBalance.rewards.length > 0 + }) + + const extraRewardsBalancesRes = await multicall({ + ctx, + calls: balanceWithExtraRewards.map((pool: Contract) => ({ target: pool.rewarder, params: [ctx.address] }) as const), + abi: abi.extraEarned, + }) + + balanceWithExtraRewards.forEach((pool, idx) => { + const extraRewardsBalances = extraRewardsBalancesRes[idx].success ? extraRewardsBalancesRes[idx].output : 0n + pool.rewards = [pool.rewards![0], { ...pool.rewards![1], amount: extraRewardsBalances! }] + }) + + return [...balanceWithExtraRewards, ...balanceWithStandardRewards] +} diff --git a/src/adapters/aura/common/pool.ts b/src/adapters/aura/common/pool.ts index 571871ba5..dda9739bb 100644 --- a/src/adapters/aura/common/pool.ts +++ b/src/adapters/aura/common/pool.ts @@ -128,7 +128,7 @@ export async function getAuraPools(ctx: BaseContext, booster: Contract, vault: C address: lptoken, pool: lptoken, lpToken: lptoken, - gauge: [crvRewards], + gauge: crvRewards, rewards: [BAL], } }) @@ -161,10 +161,7 @@ const getAuraPoolsUnderlyings = async (ctx: BaseContext, pools: Contract[], vaul const poolsWithUnderlyings: Contract[] = mapSuccessFilter(underlyingsRes, (res, idx) => { const [tokens]: any = res.output - return { - ...pools[idx], - underlyings: tokens, - } + return { ...pools[idx], underlyings: tokens } }) return unwrapPoolsAsUnderlyings(poolsWithUnderlyings) @@ -177,16 +174,11 @@ const unwrapPoolsAsUnderlyings = (pools: Contract[]) => { for (const pool of pools) { const underlyings = pool.underlyings as Contract[] - if (!underlyings) { - continue - } + if (!underlyings) continue const unwrappedUnderlyings = underlyings.map((address) => poolByAddress[address.toLowerCase()] || address) - unwrappedPools.push({ - ...pool, - underlyings: unwrappedUnderlyings, - }) + unwrappedPools.push({ ...pool, underlyings: unwrappedUnderlyings }) } return unwrappedPools @@ -198,7 +190,7 @@ const getAuraExtraRewards = async (ctx: BaseContext, pools: Contract[]): Promise const extraRewardsLengthRes = await multicall({ ctx, - calls: pools.map((pool) => ({ target: pool.gauge[0] }) as const), + calls: pools.map((pool) => ({ target: pool.gauge }) as const), abi: abi.extraRewardsLength, }) @@ -237,9 +229,7 @@ const getAuraExtraRewards = async (ctx: BaseContext, pools: Contract[]): Promise extraRewardsPools.forEach((pool, idx) => { const baseTokenRes: any = baseTokensRes[idx] - if (!baseTokenRes) { - return - } + if (!baseTokenRes) return pool.rewards?.push(baseTokenRes.output) }) diff --git a/src/adapters/aura/ethereum/balance.ts b/src/adapters/aura/ethereum/balance.ts index 10d4a21b0..5585205d7 100644 --- a/src/adapters/aura/ethereum/balance.ts +++ b/src/adapters/aura/ethereum/balance.ts @@ -1,8 +1,9 @@ -import { getBalancerBalancesInternal } from '@adapters/balancer/common/balance' +import { getExtraRewardsBalances } from '@adapters/aura/common/extraReward' import type { Balance, BalancesContext, Contract } from '@lib/adapter' import { mapSuccessFilter } from '@lib/array' +import { getUnderlyingsBalancesFromBalancer, type IBalancerBalance } from '@lib/balancer/underlying' import { call } from '@lib/call' -import { abi as erc20Abi } from '@lib/erc20' +import { abi as erc20Abi, getBalancesOf } from '@lib/erc20' import { multicall } from '@lib/multicall' import type { Token } from '@lib/token' @@ -14,25 +15,6 @@ const abi = { stateMutability: 'view', type: 'function', }, - extraEarned: { - inputs: [ - { - internalType: 'address', - name: 'account', - type: 'address', - }, - ], - name: 'earned', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, getPoolTokens: { inputs: [{ internalType: 'bytes32', name: 'poolId', type: 'bytes32' }], name: 'getPoolTokens', @@ -85,6 +67,13 @@ const abi = { stateMutability: 'view', type: 'function', }, + balanceOfUnderlying: { + inputs: [{ internalType: 'address', name: 'user', type: 'address' }], + name: 'balanceOfUnderlying', + outputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, } as const const BAL: Token = { @@ -108,7 +97,7 @@ const AURA: Contract = { symbol: 'AURA', } -export async function getAuraBalStakerBalances(ctx: BalancesContext, staker: Contract): Promise { +export async function getAuraBalStakerBalance(ctx: BalancesContext, staker: Contract): Promise { const [balanceOfRes, earnedRes] = await Promise.all([ call({ ctx, target: staker.address, params: [ctx.address], abi: erc20Abi.balanceOf }), call({ ctx, target: staker.address, params: [ctx.address], abi: abi.earned }), @@ -122,40 +111,51 @@ export async function getAuraBalStakerBalances(ctx: BalancesContext, staker: Con } } +export async function getAuraYieldBalance(ctx: BalancesContext, staker: Contract): Promise { + const [userBalance, userUnderlyingBalance, earned] = await Promise.all([ + call({ ctx, target: staker.address, params: [ctx.address], abi: erc20Abi.balanceOf }), + call({ ctx, target: staker.address, params: [ctx.address], abi: abi.balanceOfUnderlying }), + call({ ctx, target: staker.rewarder, params: [ctx.address], abi: abi.earned }), + ]) + + return { + ...staker, + amount: userBalance, + underlyings: [{ ...(staker.underlyings?.[0] as Contract), amount: userUnderlyingBalance }], + rewards: [{ ...(staker.rewards?.[0] as Contract), amount: earned }], + category: 'farm', + } +} + export async function getAuraFarmBalances( ctx: BalancesContext, pools: Contract[], vault: Contract, ): Promise { - const balanceWithStandardRewards: Balance[] = [] - const balanceWithExtraRewards: Balance[] = [] - const balances: Balance[] = await getBalancerBalancesInternal(ctx, pools, vault, 'farm', 'gauge') + const balances = (await getBalancesOf(ctx, pools, { getAddress: (contract) => contract.gauge })).map((balance) => ({ + ...balance, + category: 'farm', + })) + + const poolBalances = await getUnderlyingsBalancesFromBalancer(ctx, balances as IBalancerBalance[], vault, { + getAddress: (balance: Balance) => balance.address, + getCategory: (balance: Balance) => balance.category, + }) const earnedsRes = await multicall({ ctx, - calls: balances.map((balance: Contract) => ({ target: balance.gauge, params: [ctx.address] }) as const), + calls: poolBalances.map((balance: Contract) => ({ target: balance.gauge, params: [ctx.address] }) as const), abi: abi.earned, }) - mapSuccessFilter(earnedsRes, (res, idx) => { - const balance = balances[idx] - const rewards = balance.rewards - - const poolBalance: Balance = { - ...balance, - rewards: [{ ...rewards![0], amount: res.output }, ...rewards!.slice(1)], - } - - if (poolBalance.rewards && poolBalance.rewards.length > 1) { - balanceWithExtraRewards.push(poolBalance) - } else { - balanceWithStandardRewards.push(poolBalance) - } - }) + const fmtBalances = mapSuccessFilter(earnedsRes, (res, idx) => { + const poolBalance = poolBalances[idx] + const rewards = poolBalance.rewards as Contract[] - const balanceWithExtraRewardsBalances = await getExtraRewardsBalances(ctx, balanceWithExtraRewards) + return { ...poolBalance, rewards: [{ ...rewards![0], amount: res.output }, ...rewards!.slice(1)] } + }) as Balance[] - return getAuraMintAmount(ctx, [...balanceWithStandardRewards, ...balanceWithExtraRewardsBalances]) + return getAuraMintAmount(ctx, await getExtraRewardsBalances(ctx, fmtBalances)) } export const getAuraMintAmount = async (ctx: BalancesContext, balances: Balance[]): Promise => { @@ -211,18 +211,3 @@ export const getAuraMintAmount = async (ctx: BalancesContext, balances: Balance[ return balancesWithExtraRewards } - -const getExtraRewardsBalances = async (ctx: BalancesContext, poolBalance: Balance[]): Promise => { - const extraRewardsBalancesRes = await multicall({ - ctx, - calls: poolBalance.map((pool: Contract) => ({ target: pool.rewarder, params: [ctx.address] }) as const), - abi: abi.extraEarned, - }) - - poolBalance.forEach((pool, idx) => { - const extraRewardsBalances = extraRewardsBalancesRes[idx].success ? extraRewardsBalancesRes[idx].output : 0n - pool.rewards = [pool.rewards![0], { ...pool.rewards![1], amount: extraRewardsBalances! }] - }) - - return poolBalance -} diff --git a/src/adapters/aura/ethereum/index.ts b/src/adapters/aura/ethereum/index.ts index 18120d2e6..f091ae006 100644 --- a/src/adapters/aura/ethereum/index.ts +++ b/src/adapters/aura/ethereum/index.ts @@ -1,4 +1,4 @@ -import { getAuraBalStakerBalances, getAuraFarmBalances } from '@adapters/aura/ethereum/balance' +import { getAuraBalStakerBalance, getAuraFarmBalances, getAuraYieldBalance } from '@adapters/aura/ethereum/balance' import type { BaseContext, Contract, GetBalancesHandler } from '@lib/adapter' import { resolveBalances } from '@lib/balance' import { getMultipleLockerBalances } from '@lib/lock' @@ -35,6 +35,14 @@ const auraStaker: Contract = { underlyings: [auraBal], } +const stkAura: Contract = { + chain: 'ethereum', + address: '0xfaa2ed111b4f580fcb85c48e6dc6782dc5fcd7a6', + underlyings: ['0x616e8BfA43F920657B3497DBf40D6b1A02D4608d'], + rewards: ['0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF'], + rewarder: '0xAc16927429c5c7Af63dD75BC9d8a58c63FfD0147', +} + const booster: Contract = { chain: 'ethereum', address: '0xA57b8d98dAE62B26Ec3bcC4a365338157060B234', @@ -49,13 +57,14 @@ export const getContracts = async (ctx: BaseContext) => { const pools = await getAuraPools(ctx, booster, vaultBAL) return { - contracts: { booster, pools, auraStaker, auraLocker }, + contracts: { booster, pools, auraStaker, auraLocker, stkAura }, } } export const getBalances: GetBalancesHandler = async (ctx, contracts) => { const balances = await resolveBalances(ctx, contracts, { - auraStaker: getAuraBalStakerBalances, + auraStaker: getAuraBalStakerBalance, + stkAura: getAuraYieldBalance, auraLocker: (...args) => getMultipleLockerBalances(...args, AURA, [auraBal], false), pools: (...args) => getAuraFarmBalances(...args, vaultBAL), }) diff --git a/src/adapters/aura/optimism/index.ts b/src/adapters/aura/optimism/index.ts index bf3a5aa3a..5ba1f088f 100644 --- a/src/adapters/aura/optimism/index.ts +++ b/src/adapters/aura/optimism/index.ts @@ -26,6 +26,12 @@ export const getBalances: GetBalancesHandler = async (ctx, pools: (...args) => getAuraFarmBalances(...args, vaultBAL), }) + for (const balance of balances) { + if (balance.amount !== 0n) { + console.log(balance) + } + } + return { groups: [{ balances }], } diff --git a/src/adapters/balancer/common/balance.ts b/src/adapters/balancer/common/balance.ts index 569cc9894..4834f0a1a 100644 --- a/src/adapters/balancer/common/balance.ts +++ b/src/adapters/balancer/common/balance.ts @@ -1,118 +1,21 @@ import type { Balance, BalancesContext, Contract } from '@lib/adapter' -import { ADDRESS_ZERO } from '@lib/contract' -import { abi as erc20Abi } from '@lib/erc20' -import { multicall } from '@lib/multicall' - -const abi = { - getPoolTokens: { - inputs: [{ internalType: 'bytes32', name: 'poolId', type: 'bytes32' }], - name: 'getPoolTokens', - outputs: [ - { internalType: 'contract IERC20[]', name: 'tokens', type: 'address[]' }, - { internalType: 'uint256[]', name: 'balances', type: 'uint256[]' }, - { internalType: 'uint256', name: 'lastChangeBlock', type: 'uint256' }, - ], - stateMutability: 'view', - type: 'function', - }, -} as const - -export async function getBalancesBalances( - ctx: BalancesContext, - pools: Contract[], - vault: Contract, -): Promise { - return Promise.all([ - getBalancerBalancesInternal(ctx, pools, vault, 'lp', 'address'), - getBalancerBalancesInternal(ctx, pools, vault, 'farm', 'gauge'), +import { getUnderlyingsBalancesFromBalancer, type IBalancerBalance } from '@lib/balancer/underlying' +import { getBalancesOf } from '@lib/erc20' + +export async function getBalancesBalances(ctx: BalancesContext, pools: Contract[], vault: Contract) { + const [lpBalances, farmBalances]: Balance[][] = await Promise.all([ + (await getBalancesOf(ctx, pools, { getAddress: (contract) => contract.address })).map((poolBalance) => ({ + ...poolBalance, + category: 'lp', + })), + (await getBalancesOf(ctx, pools, { getAddress: (contract) => contract.gauge })).map((poolBalance) => ({ + ...poolBalance, + category: 'farm', + })), ]) -} - -export async function getBalancerBalancesInternal( - ctx: BalancesContext, - inputPools: Contract[], - vault: Contract, - category: 'lp' | 'farm', - targetProp: 'address' | 'gauge', -): Promise { - const balances: Balance[] = [] - // Duplicate pools based on gauges - let pools: Contract[] = [] - inputPools.forEach((pool) => { - if (targetProp === 'gauge' && Array.isArray(pool.gauge)) { - pool.gauge.forEach((gauge) => { - pools.push({ ...pool, gauge: gauge }) - }) - } else { - pools.push(pool) - } + return getUnderlyingsBalancesFromBalancer(ctx, [...lpBalances, ...farmBalances] as IBalancerBalance[], vault, { + getAddress: (balance: Balance) => balance.address, + getCategory: (balance: Balance) => balance.category, }) - - if (category === 'farm') { - pools = pools.filter((pool) => pool.gauge !== undefined && pool.gauge !== ADDRESS_ZERO) - } - - const [poolBalancesRes, uBalancesRes, totalSuppliesRes] = await Promise.all([ - multicall({ - ctx, - calls: pools.map((pool) => ({ target: pool[targetProp], params: [ctx.address] }) as const), - abi: erc20Abi.balanceOf, - }), - multicall({ - ctx, - calls: pools.map((pool) => ({ target: vault.address, params: [pool.poolId] }) as const), - abi: abi.getPoolTokens, - }), - multicall({ - ctx, - calls: pools.map((pool) => ({ target: pool.address }) as const), - abi: erc20Abi.totalSupply, - }), - ]) - - for (const [index, pool] of pools.entries()) { - const underlyings = pool.underlyings as Contract[] - const rewards = pool.rewards as Balance[] - const poolBalanceRes = poolBalancesRes[index] - const uBalanceRes = uBalancesRes[index] - const totalSupplyRes = totalSuppliesRes[index] - - if ( - !underlyings || - !poolBalanceRes.success || - !uBalanceRes.success || - !totalSupplyRes.success || - totalSupplyRes.output === 0n - ) { - continue - } - - const [_tokens, underlyingsBalances] = uBalanceRes.output - - underlyings.forEach((underlying, idx) => { - const amount = underlyingsBalances[idx] - underlying.amount = amount - }) - - const lpTokenBalance = underlyings.find( - (underlying) => underlying.address.toLowerCase() === pool.address.toLowerCase(), - ) - - const fmtUnderlyings = underlyings - .map((underlying) => { - const realSupply = lpTokenBalance ? totalSupplyRes.output - lpTokenBalance.amount : totalSupplyRes.output - const amount = (underlying.amount * poolBalanceRes.output) / realSupply - - return { - ...underlying, - amount, - } - }) - .filter((underlying) => underlying.address.toLowerCase() !== pool.address.toLowerCase()) - - balances.push({ ...pool, amount: poolBalanceRes.output, underlyings: fmtUnderlyings, rewards, category }) - } - - return balances } diff --git a/src/adapters/balancer/common/pool.ts b/src/adapters/balancer/common/pool.ts index 05f1a8184..3b749b99a 100644 --- a/src/adapters/balancer/common/pool.ts +++ b/src/adapters/balancer/common/pool.ts @@ -119,8 +119,6 @@ async function getBalancerEthGauges( } async function getBalancerChildGauges(ctx: BaseContext, pools: Contract[]): Promise { - const gauges: any = [] - const fmtCtx = ctx.chain === 'gnosis' ? 'gnosis-chain' : ctx.chain const URL = `https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-gauges-${fmtCtx}` @@ -135,24 +133,28 @@ async function getBalancerChildGauges(ctx: BaseContext, pools: Contract[]): Prom } } ` + const res: any = await request(URL, query) - for (const pool of res.pools) { - if (!pool.address || !pool.gauges) { - continue + const gauges = res.pools.map((pool: any) => { + return { + address: pool.address.toLowerCase(), + gauges: pool.gauges.map((gauge: any) => gauge.id), } + }) - gauges.push({ address: pool.address, gauges: pool.gauges }) - } + const mergedPools: Contract[] = [] - const mergedPools: any[] = pools.map((pool) => { + pools.forEach((pool: Contract) => { const poolAddressLower = pool.address.toLowerCase() - const matchingLpToken = gauges.find((gauge: any) => gauge.address.toLowerCase() === poolAddressLower) + const matchingGauge = gauges.find((gauge: any) => gauge.address === poolAddressLower) - if (matchingLpToken) { - return { ...pool, gauge: matchingLpToken.gauges.map((gauge: any) => gauge.id) } + if (matchingGauge) { + matchingGauge.gauges.forEach((gaugeId: string, id: number) => { + mergedPools.push({ ...pool, gauge: gaugeId, id }) + }) } else { - return { ...pool, gauge: undefined } + mergedPools.push({ ...pool, gauge: undefined }) } }) diff --git a/src/lib/balancer/underlying.ts b/src/lib/balancer/underlying.ts new file mode 100644 index 000000000..dcc4cd8f7 --- /dev/null +++ b/src/lib/balancer/underlying.ts @@ -0,0 +1,95 @@ +import type { Balance, BalancesContext, Contract } from '@lib/adapter' +import { mapMultiSuccessFilter } from '@lib/array' +import type { Category } from '@lib/category' +import { abi as erc20Abi } from '@lib/erc20' +import { multicall } from '@lib/multicall' +import { isNotNullish } from '@lib/type' + +const abi = { + getPoolTokens: { + inputs: [{ internalType: 'bytes32', name: 'poolId', type: 'bytes32' }], + name: 'getPoolTokens', + outputs: [ + { internalType: 'contract IERC20[]', name: 'tokens', type: 'address[]' }, + { internalType: 'uint256[]', name: 'balances', type: 'uint256[]' }, + { internalType: 'uint256', name: 'lastChangeBlock', type: 'uint256' }, + ], + stateMutability: 'view', + type: 'function', + }, +} as const + +export type IBalancerBalance = Balance & { + poolId: `0x${string}` +} + +interface Params { + getAddress: (poolbalance: Balance) => `0x${string}` + getCategory: (poolbalance: Balance) => Category +} + +export async function getUnderlyingsBalancesFromBalancer( + ctx: BalancesContext, + poolbalances: IBalancerBalance[], + vault: Contract, + params: Params = { + getAddress: (poolbalance: Balance) => poolbalance.address, + getCategory: (poolbalance: Balance) => poolbalance.category, + }, +): Promise { + const [underlyingsBalancesRes, totalSuppliesRes] = await Promise.all([ + multicall({ + ctx, + calls: poolbalances.map((balance) => ({ target: vault.address, params: [balance.poolId] }) as const), + abi: abi.getPoolTokens, + }), + multicall({ + ctx, + calls: poolbalances.map((balance) => ({ target: params.getAddress(balance) }) as const), + abi: erc20Abi.totalSupply, + }), + ]) + + const formattedBalances = mapMultiSuccessFilter( + underlyingsBalancesRes.map((_, i) => [underlyingsBalancesRes[i], totalSuppliesRes[i]]), + + (res, index) => { + const poolBalance = poolbalances[index] + const { underlyings, rewards } = poolBalance as { underlyings: Contract[]; rewards: Contract[] } + const [{ output: underlyingsBalances }, { output: totalSupply }] = res.inputOutputPairs + + if (totalSupply === 0n) return null + + // Update underlying amounts + underlyings.forEach((underlying, idx) => { + underlying.amount = underlyingsBalances[1][idx] + }) + + // Find the LP token balance + const lpTokenBalance = underlyings.find( + (underlying) => underlying.address.toLowerCase() === poolBalance.address.toLowerCase(), + ) + + // Calculate formatted underlyings + const fmtUnderlyings = underlyings + .map((underlying) => { + const realSupply = lpTokenBalance ? totalSupply - lpTokenBalance.amount : totalSupply + + return { + ...underlying, + amount: (underlying.amount * poolBalance.amount) / realSupply, + } + }) + .filter((underlying) => underlying.address.toLowerCase() !== poolBalance.address.toLowerCase()) + + return { + ...(poolBalance as Contract), + underlyings: fmtUnderlyings, + rewards, + category: params.getCategory(poolBalance), + } + }, + ) + + return formattedBalances.filter(isNotNullish) as Balance[] +} diff --git a/src/lib/erc20.ts b/src/lib/erc20.ts index fd2ba77f6..29eec9743 100644 --- a/src/lib/erc20.ts +++ b/src/lib/erc20.ts @@ -1,6 +1,6 @@ import type { Balance, BalancesContext, BaseContext, Contract } from '@lib/adapter' -import { sliceIntoChunks } from '@lib/array' -import { type Chain, chainById } from '@lib/chains' +import { mapSuccessFilter, sliceIntoChunks } from '@lib/array' +import { chainById, type Chain } from '@lib/chains' import type { Call } from '@lib/multicall' import { multicall } from '@lib/multicall' import { sleep } from '@lib/promise' @@ -422,22 +422,20 @@ export async function userBalances({ /** * @description Returns given contracts with their ERC20 token balances */ -export async function getBalancesOf(ctx: BalancesContext, contracts: T[]): Promise { +export async function getBalancesOf( + ctx: BalancesContext, + contracts: Contract[], + params = { getAddress: (contract: Contract) => contract.token }, +): Promise { const balancesOf = await multicall({ ctx, calls: contracts.map( - (contract) => ({ target: contract.token || contract.address, params: [ctx.address] }) as const, + (contract) => ({ target: params.getAddress(contract) || contract.address, params: [ctx.address] }) as const, ), abi: abi.balanceOf, }) - for (let contractIdx = 0; contractIdx < contracts.length; contractIdx++) { - const balanceOfRes = balancesOf[contractIdx] - - ;(contracts[contractIdx] as Balance).amount = balanceOfRes.success ? balanceOfRes.output : 0n - } - - return contracts as Balance[] + return mapSuccessFilter(balancesOf, (res, idx) => ({ ...contracts[idx], amount: res.output })) as Balance[] } export async function getERC20Details(ctx: BaseContext, tokens: readonly `0x${string}`[]): Promise {