diff --git a/adapters/beefy/hourly_blocks.csv b/adapters/beefy/hourly_blocks.csv index 4908bcda..94a6ea25 100644 --- a/adapters/beefy/hourly_blocks.csv +++ b/adapters/beefy/hourly_blocks.csv @@ -9,3 +9,5 @@ number,timestamp 4740000,1716263772 4730000,1716233771 4784763,1716398064 +5684890,1718771519 +5784890,1718972096 \ No newline at end of file diff --git a/adapters/beefy/src/abi/BeefyClmStrategy.ts b/adapters/beefy/src/abi/BeefyClmStrategy.ts new file mode 100644 index 00000000..76b4abac --- /dev/null +++ b/adapters/beefy/src/abi/BeefyClmStrategy.ts @@ -0,0 +1,19 @@ +export const BeefyClmStrategyAbi = [ + { + type: "function", + name: "price", + inputs: [], + outputs: [{ name: "_price", type: "uint256", internalType: "uint256" }], + stateMutability: "view", + }, + { + type: "function", + name: "range", + inputs: [], + outputs: [ + { name: "lowerPrice", type: "uint256", internalType: "uint256" }, + { name: "upperPrice", type: "uint256", internalType: "uint256" }, + ], + stateMutability: "view", + }, +] as const; diff --git a/adapters/beefy/src/abi/BeefyVaultConcLiq.ts b/adapters/beefy/src/abi/BeefyVaultConcLiq.ts new file mode 100644 index 00000000..a027d24b --- /dev/null +++ b/adapters/beefy/src/abi/BeefyVaultConcLiq.ts @@ -0,0 +1,29 @@ +export const BeefyVaultConcLiqAbi = [ + { + inputs: [], + name: "balances", + outputs: [ + { internalType: "uint256", name: "amount0", type: "uint256" }, + { internalType: "uint256", name: "amount1", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "wants", + outputs: [ + { internalType: "address", name: "token0", type: "address" }, + { internalType: "address", name: "token1", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/adapters/beefy/src/config.ts b/adapters/beefy/src/config.ts index 281e67e3..210b37bd 100644 --- a/adapters/beefy/src/config.ts +++ b/adapters/beefy/src/config.ts @@ -1,8 +1,8 @@ -export const BEEFY_VAULT_API = "https://api.beefy.finance/vaults"; +export const BEEFY_VAULT_API = "https://api.beefy.finance/harvestable-vaults"; // subgraph source: https://github.com/beefyfinance/l2-lxp-liquidity-subgraph export const BEEFY_SUBGRAPH_URL = - "https://api.goldsky.com/api/public/project_clu2walwem1qm01w40v3yhw1f/subgraphs/beefy-l2-lxp-liquidity-linea/latest/gn"; + "https://api.goldsky.com/api/public/project_clu2walwem1qm01w40v3yhw1f/subgraphs/beefy-balances-linea/latest/gn"; export const SUBGRAPH_PAGE_SIZE = 1000; diff --git a/adapters/beefy/src/index.ts b/adapters/beefy/src/index.ts index f8b44ec5..72d1667d 100644 --- a/adapters/beefy/src/index.ts +++ b/adapters/beefy/src/index.ts @@ -6,7 +6,7 @@ import { getVaultBreakdowns } from "./sdk/breakdown/getVaultBreakdown"; import { uniq } from "lodash"; import { BeefyVaultBreakdown } from "./sdk/breakdown/types"; import { Hex } from "viem"; -import { getVaultShareTokenBalances } from "./sdk/vault/getVaultShareTokenBalances"; +import { getTokenBalances } from "./sdk/vault/getTokenBalances"; interface BlockData { blockNumber: number; @@ -30,11 +30,11 @@ export const getUserTVLByBlock = async ( const [vaultConfigs, investorPositions] = await Promise.all([ getBeefyVaultConfig("linea"), - getVaultShareTokenBalances(BigInt(blockNumber)), + getTokenBalances(BigInt(blockNumber)), ]); const vaultAddressWithActivePosition = uniq( - investorPositions.map((pos) => pos.vault_address.toLowerCase()) + investorPositions.map((pos) => pos.token_address) ); const vaults = vaultConfigs.filter((vault) => vaultAddressWithActivePosition.includes(vault.vault_address) @@ -53,12 +53,17 @@ export const getUserTVLByBlock = async ( Record > = {}; for (const position of investorPositions) { - const breakdown = breakdownByVaultAddress[position.vault_address]; + const breakdown = breakdownByVaultAddress[position.token_address]; if (!breakdown) { // some test vaults were never available in the api continue; } + if (breakdown.isLiquidityEligible === false) { + // skip non-eligible vaults + continue; + } + if (!investorTokenBalances[position.user_address]) { investorTokenBalances[position.user_address] = {}; } @@ -70,7 +75,7 @@ export const getUserTVLByBlock = async ( } investorTokenBalances[position.user_address][balance.tokenAddress] += - (BigInt(position.shares_balance) * balance.vaultBalance) / + (BigInt(position.token_address) * balance.vaultBalance) / breakdown.vaultTotalSupply; } } diff --git a/adapters/beefy/src/sdk/breakdown/getVaultBreakdown.ts b/adapters/beefy/src/sdk/breakdown/getVaultBreakdown.ts index c0d53538..bbc55697 100644 --- a/adapters/beefy/src/sdk/breakdown/getVaultBreakdown.ts +++ b/adapters/beefy/src/sdk/breakdown/getVaultBreakdown.ts @@ -4,6 +4,7 @@ import { BeefyViemClient, getViemClient } from "../viemClient"; import { getSolidlyVaultBreakdown } from "./protocol_type/solidly"; import { getGammaVaultBreakdown } from "./protocol_type/gamma"; import { getMendiVaultBreakdown } from "./protocol_type/mendi"; +import { getBeefyClmVaultBreakdown } from "./protocol_type/beefy_clm"; type BreakdownMethod = ( client: BeefyViemClient, @@ -16,6 +17,7 @@ const breakdownMethods: Record = { mendi: getMendiVaultBreakdown, gamma: getGammaVaultBreakdown, ichi: getGammaVaultBreakdown, + beefy_clm: getBeefyClmVaultBreakdown, }; export const getVaultBreakdowns = async ( diff --git a/adapters/beefy/src/sdk/breakdown/protocol_type/beefy_clm.ts b/adapters/beefy/src/sdk/breakdown/protocol_type/beefy_clm.ts new file mode 100644 index 00000000..17323df7 --- /dev/null +++ b/adapters/beefy/src/sdk/breakdown/protocol_type/beefy_clm.ts @@ -0,0 +1,52 @@ +import { Hex, getContract } from "viem"; +import { BeefyVault } from "../../vault/getBeefyVaultConfig"; +import { BeefyViemClient } from "../../viemClient"; +import { BeefyVaultBreakdown } from "../types"; +import { BeefyVaultConcLiqAbi } from "../../../abi/BeefyVaultConcLiq"; +import { BeefyClmStrategyAbi } from "../../../abi/BeefyClmStrategy"; + +export const getBeefyClmVaultBreakdown = async ( + client: BeefyViemClient, + blockNumber: bigint, + vault: BeefyVault +): Promise => { + const managerContract = getContract({ + client, + address: vault.vault_address, + abi: BeefyVaultConcLiqAbi, + }); + + const strategyContract = getContract({ + client, + address: vault.strategy_address, + abi: BeefyClmStrategyAbi, + }); + + const [balances, vaultTotalSupply, wants, range, price] = await Promise.all([ + managerContract.read.balances({ blockNumber }), + managerContract.read.totalSupply({ blockNumber }), + managerContract.read.wants({ blockNumber }), + strategyContract.read.range({ blockNumber }), + strategyContract.read.price({ blockNumber }), + ]); + + // special rule to exclude out of range liquidity for concentrated liquidity vaults + const isLiquidityEligible = price >= range[0] && price <= range[1]; + + return { + vault, + blockNumber, + vaultTotalSupply, + isLiquidityEligible, + balances: [ + { + tokenAddress: wants[0].toLocaleLowerCase() as Hex, + vaultBalance: balances[0], + }, + { + tokenAddress: wants[1].toLocaleLowerCase() as Hex, + vaultBalance: balances[1], + }, + ], + }; +}; diff --git a/adapters/beefy/src/sdk/breakdown/protocol_type/gamma.ts b/adapters/beefy/src/sdk/breakdown/protocol_type/gamma.ts index 3bfb1879..2a021183 100644 --- a/adapters/beefy/src/sdk/breakdown/protocol_type/gamma.ts +++ b/adapters/beefy/src/sdk/breakdown/protocol_type/gamma.ts @@ -35,6 +35,7 @@ export const getGammaVaultBreakdown = async ( vault, blockNumber, vaultTotalSupply, + isLiquidityEligible: true, balances: [ { tokenAddress: token0.toLocaleLowerCase() as Hex, diff --git a/adapters/beefy/src/sdk/breakdown/protocol_type/ichi.ts b/adapters/beefy/src/sdk/breakdown/protocol_type/ichi.ts index 21764b48..516ed726 100644 --- a/adapters/beefy/src/sdk/breakdown/protocol_type/ichi.ts +++ b/adapters/beefy/src/sdk/breakdown/protocol_type/ichi.ts @@ -46,6 +46,7 @@ export const getGammaVaultBreakdown = async ( vault, blockNumber, vaultTotalSupply, + isLiquidityEligible: true, balances: [ { tokenAddress: token0.toLocaleLowerCase() as Hex, diff --git a/adapters/beefy/src/sdk/breakdown/protocol_type/mendi.ts b/adapters/beefy/src/sdk/breakdown/protocol_type/mendi.ts index 3a3f3724..a9c9b038 100644 --- a/adapters/beefy/src/sdk/breakdown/protocol_type/mendi.ts +++ b/adapters/beefy/src/sdk/breakdown/protocol_type/mendi.ts @@ -24,6 +24,7 @@ export const getMendiVaultBreakdown = async ( vault, blockNumber, vaultTotalSupply, + isLiquidityEligible: true, balances: [ { tokenAddress: vault.undelying_lp_address.toLocaleLowerCase() as Hex, diff --git a/adapters/beefy/src/sdk/breakdown/protocol_type/solidly.ts b/adapters/beefy/src/sdk/breakdown/protocol_type/solidly.ts index 4c4ebffb..00a548f8 100644 --- a/adapters/beefy/src/sdk/breakdown/protocol_type/solidly.ts +++ b/adapters/beefy/src/sdk/breakdown/protocol_type/solidly.ts @@ -38,6 +38,7 @@ export const getSolidlyVaultBreakdown = async ( vault, blockNumber, vaultTotalSupply, + isLiquidityEligible: true, balances: [ { tokenAddress: t0.toLocaleLowerCase() as Hex, diff --git a/adapters/beefy/src/sdk/breakdown/types.ts b/adapters/beefy/src/sdk/breakdown/types.ts index 1081d1ae..40949ade 100644 --- a/adapters/beefy/src/sdk/breakdown/types.ts +++ b/adapters/beefy/src/sdk/breakdown/types.ts @@ -5,6 +5,7 @@ export type BeefyVaultBreakdown = { vault: BeefyVault; blockNumber: bigint; vaultTotalSupply: bigint; + isLiquidityEligible: boolean; balances: { tokenAddress: Hex; vaultBalance: bigint; diff --git a/adapters/beefy/src/sdk/vault/getBeefyVaultConfig.ts b/adapters/beefy/src/sdk/vault/getBeefyVaultConfig.ts index 4059dc3e..b82f05f0 100644 --- a/adapters/beefy/src/sdk/vault/getBeefyVaultConfig.ts +++ b/adapters/beefy/src/sdk/vault/getBeefyVaultConfig.ts @@ -6,13 +6,19 @@ export type BeefyVault = { id: string; vault_address: Hex; undelying_lp_address: Hex; + strategy_address: Hex; chain: string; protocol_type: BeefyProtocolType; }; -export type BeefyProtocolType = "gamma" | "ichi" | "mendi" | "solidly"; +export type BeefyProtocolType = + | "gamma" + | "ichi" + | "mendi" + | "solidly" + | "beefy_clm"; -type ApiPlatformId = "gamma" | "ichi" | "lynex" | "mendi" | "nile"; // and more but we don't use those on linea +type ApiPlatformId = "gamma" | "ichi" | "lynex" | "mendi" | "nile" | "beefy"; // and more but we don't use those on linea type ApiVault = { id: string; @@ -21,6 +27,7 @@ type ApiVault = { chain: string; platformId: ApiPlatformId; tokenAddress: Hex; + strategy: Hex; }; const protocol_map: Record = { @@ -29,6 +36,7 @@ const protocol_map: Record = { lynex: "solidly", mendi: "mendi", nile: "solidly", + beefy: "beefy_clm", }; export const getBeefyVaultConfig = memoize( @@ -49,6 +57,7 @@ export const getBeefyVaultConfig = memoize( vault_address: vault.earnedTokenAddress.toLocaleLowerCase() as Hex, chain: vault.chain, protocol_type, + strategy_address: vault.strategy.toLocaleLowerCase() as Hex, undelying_lp_address: vault.tokenAddress.toLocaleLowerCase() as Hex, }; }); diff --git a/adapters/beefy/src/sdk/vault/getVaultShareTokenBalances.ts b/adapters/beefy/src/sdk/vault/getTokenBalances.ts similarity index 55% rename from adapters/beefy/src/sdk/vault/getVaultShareTokenBalances.ts rename to adapters/beefy/src/sdk/vault/getTokenBalances.ts index 96470ac5..47a88118 100644 --- a/adapters/beefy/src/sdk/vault/getVaultShareTokenBalances.ts +++ b/adapters/beefy/src/sdk/vault/getTokenBalances.ts @@ -1,56 +1,45 @@ import { Hex } from "viem"; import { BEEFY_SUBGRAPH_URL, SUBGRAPH_PAGE_SIZE } from "../../config"; -type ShareTokenBalance = { +type TokenBalance = { user_address: Hex; - vault_address: Hex; - shares_balance: bigint; + token_address: Hex; + balance: bigint; }; type QueryResult = { - investorPositions: { - investor: { + tokenBalances: { + account: { id: Hex; }; - vault: { - sharesToken: { - id: Hex; - }; - underlyingToken: { - id: Hex; - symbol: string; - }; + token: { + id: Hex; }; - rawSharesBalance: string; + amount: string; }[]; }; -export const getVaultShareTokenBalances = async ( +export const getTokenBalances = async ( blockNumber: bigint -): Promise => { - let allPositions: ShareTokenBalance[] = []; +): Promise => { + let allPositions: TokenBalance[] = []; let skip = 0; while (true) { const query = ` query LineaUser($blockNumber: Int!, $skip: Int!, $first: Int!) { - investorPositions( + tokenBalances( block: {number: $blockNumber} first: $first - where: { rawSharesBalance_gt: 0 } + where: { amount_gt: 0 } skip: $skip ) { - investor { + account { id } - vault { - sharesToken { - id - } - underlyingToken { - id - } + token { + id } - rawSharesBalance + amount } } `; @@ -80,17 +69,16 @@ export const getVaultShareTokenBalances = async ( } allPositions = allPositions.concat( - res.data.investorPositions.map( - (position): ShareTokenBalance => ({ - shares_balance: BigInt(position.rawSharesBalance), - user_address: position.investor.id.toLocaleLowerCase() as Hex, - vault_address: - position.vault.sharesToken.id.toLocaleLowerCase() as Hex, + res.data.tokenBalances.map( + (position): TokenBalance => ({ + balance: BigInt(position.amount), + user_address: position.account.id.toLocaleLowerCase() as Hex, + token_address: position.token.id.toLocaleLowerCase() as Hex, }) ) ); - if (res.data.investorPositions.length < SUBGRAPH_PAGE_SIZE) { + if (res.data.tokenBalances.length < SUBGRAPH_PAGE_SIZE) { break; }