Skip to content

Commit

Permalink
Merge pull request #166 from prevostc/beefy-adapter-client-side-break…
Browse files Browse the repository at this point in the history
…down

beefy : yield : tvl by user
  • Loading branch information
0xroll authored May 24, 2024
2 parents 718b25d + 00d9dfd commit 4fc8dc4
Show file tree
Hide file tree
Showing 21 changed files with 3,269 additions and 0 deletions.
11 changes: 11 additions & 0 deletions adapters/beefy/hourly_blocks.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
number,timestamp
4811392,1716477967
4801392,1716447957
4790000,1716413778
4780000,1716383775
4770000,1716353775
4760000,1716323774
4750000,1716293772
4740000,1716263772
4730000,1716233771
4784763,1716398064
27 changes: 27 additions & 0 deletions adapters/beefy/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "beefy-adapter",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node dist/index.js",
"compile": "tsc",
"watch": "tsc -w",
"clear": "rm -rf dist"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@types/lodash": "^4.17.4",
"csv-parser": "^3.0.0",
"fast-csv": "^5.0.1",
"lodash": "^4.17.21",
"viem": "^2.12.0"
},
"devDependencies": {
"@types/node": "^20.11.17",
"typescript": "^5.3.3"
}
}
22 changes: 22 additions & 0 deletions adapters/beefy/src/abi/BeefyVaultV7Abi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const BeefyVaultV7Abi = [
{
inputs: [],
name: "totalSupply",
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "balance",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
] as const;
34 changes: 34 additions & 0 deletions adapters/beefy/src/abi/GammaHypervisorAbi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const GammaHypervisorAbi = [
{
inputs: [],
name: "totalSupply",
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},

{
inputs: [],
name: "getTotalAmounts",
outputs: [
{ internalType: "uint256", name: "total0", type: "uint256" },
{ internalType: "uint256", name: "total1", type: "uint256" },
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "token0",
outputs: [{ internalType: "contract IERC20", name: "", type: "address" }],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "token1",
outputs: [{ internalType: "contract IERC20", name: "", type: "address" }],
stateMutability: "view",
type: "function",
},
] as const;
45 changes: 45 additions & 0 deletions adapters/beefy/src/abi/IchiAlmAbi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
export const IchiAlmAbi = [
{
inputs: [],
name: "totalSupply",
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "getBasePosition",
outputs: [
{ internalType: "uint128", name: "liquidity", type: "uint128" },
{ internalType: "uint256", name: "amount0", type: "uint256" },
{ internalType: "uint256", name: "amount1", type: "uint256" },
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "getLimitPosition",
outputs: [
{ internalType: "uint128", name: "liquidity", type: "uint128" },
{ internalType: "uint256", name: "amount0", type: "uint256" },
{ internalType: "uint256", name: "amount1", type: "uint256" },
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "token0",
outputs: [{ internalType: "address", name: "", type: "address" }],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "token1",
outputs: [{ internalType: "address", name: "", type: "address" }],
stateMutability: "view",
type: "function",
},
] as const;
24 changes: 24 additions & 0 deletions adapters/beefy/src/abi/SolidlyPoolAbi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const SolidlyPoolAbi = [
{
inputs: [],
name: "metadata",
outputs: [
{ internalType: "uint256", name: "dec0", type: "uint256" },
{ internalType: "uint256", name: "dec1", type: "uint256" },
{ internalType: "uint256", name: "r0", type: "uint256" },
{ internalType: "uint256", name: "r1", type: "uint256" },
{ internalType: "bool", name: "st", type: "bool" },
{ internalType: "address", name: "t0", type: "address" },
{ internalType: "address", name: "t1", type: "address" },
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "totalSupply",
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
] as const;
13 changes: 13 additions & 0 deletions adapters/beefy/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const BEEFY_VAULT_API = "https://api.beefy.finance/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/beefyfinance/l2-lxp-liquidity-linea/gn";
export const BEEFY_SUBGRAPH_URL =
"https://api.0xgraph.xyz/subgraphs/name/beefyfinance/l2-lxp-liquidity-linea";

export const SUBGRAPH_PAGE_SIZE = 1000;

export const RPC_URLS = (
process.env.RPC_URLS ?? "https://rpc.linea.build,https://rpc.linea.build"
).split(",");
148 changes: 148 additions & 0 deletions adapters/beefy/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import fs from "fs";
import { write } from "fast-csv";
import csv from "csv-parser";
import { getBeefyVaultConfig } from "./sdk/vault/getBeefyVaultConfig";
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";

interface BlockData {
blockNumber: number;
blockTimestamp: number;
}

type OutputDataSchemaRow = {
block_number: number;
timestamp: number;
user_address: string;
token_address: string;
token_balance: bigint;
token_symbol: string; //token symbol should be empty string if it is not available
usd_price: number; //assign 0 if not available
};

export const getUserTVLByBlock = async (
blocks: BlockData
): Promise<OutputDataSchemaRow[]> => {
const { blockNumber, blockTimestamp } = blocks;

const [vaultConfigs, investorPositions] = await Promise.all([
getBeefyVaultConfig("linea"),
getVaultShareTokenBalances(BigInt(blockNumber)),
]);

const vaultAddressWithActivePosition = uniq(
investorPositions.map((pos) => pos.vault_address.toLowerCase())
);
const vaults = vaultConfigs.filter((vault) =>
vaultAddressWithActivePosition.includes(vault.vault_address)
);
// get breakdowns for all vaults
const breakdowns = await getVaultBreakdowns(BigInt(blockNumber), vaults);

const breakdownByVaultAddress = breakdowns.reduce((acc, breakdown) => {
acc[breakdown.vault.vault_address.toLowerCase() as Hex] = breakdown;
return acc;
}, {} as Record<Hex, BeefyVaultBreakdown>);

// merge by investor address and token address
const investorTokenBalances: Record<
Hex /* investor */,
Record<Hex /* token */, bigint /* amount */>
> = {};
for (const position of investorPositions) {
const breakdown = breakdownByVaultAddress[position.vault_address];
if (!breakdown) {
// some test vaults were never available in the api
continue;
}

if (!investorTokenBalances[position.user_address]) {
investorTokenBalances[position.user_address] = {};
}

for (const balance of breakdown.balances) {
if (!investorTokenBalances[position.user_address][balance.tokenAddress]) {
investorTokenBalances[position.user_address][balance.tokenAddress] =
BigInt(0);
}

investorTokenBalances[position.user_address][balance.tokenAddress] +=
(BigInt(position.shares_balance) * balance.vaultBalance) /
breakdown.vaultTotalSupply;
}
}

// format output
return Object.entries(investorTokenBalances)
.map(([investor, balances]) =>
Object.entries(balances).map(([token, balance]) => ({
block_number: blockNumber,
timestamp: blockTimestamp,
user_address: investor,
token_address: token,
token_balance: balance,
token_symbol: "", //token symbol should be empty string if it is not available
usd_price: 0, //assign 0 if not available
}))
)
.flat();
};

const readBlocksFromCSV = async (filePath: string): Promise<BlockData[]> => {
const blocks: BlockData[] = [];

await new Promise<void>((resolve, reject) => {
fs.createReadStream(filePath)
.pipe(csv()) // Specify the separator as '\t' for TSV files
.on("data", (row) => {
const blockNumber = parseInt(row.number, 10);
const blockTimestamp = parseInt(row.timestamp, 10);
if (!isNaN(blockNumber) && blockTimestamp) {
blocks.push({ blockNumber: blockNumber, blockTimestamp });
}
})
.on("end", () => {
resolve();
})
.on("error", (err) => {
reject(err);
});
});

return blocks;
};

readBlocksFromCSV("hourly_blocks.csv")
.then(async (blocks: any[]) => {
console.log(blocks);
const allCsvRows: any[] = []; // Array to accumulate CSV rows for all blocks

for (const block of blocks) {
try {
const result = await getUserTVLByBlock(block);
for (let i = 0; i < result.length; i++) {
allCsvRows.push(result[i]);
}
} catch (error) {
console.error(
`An error occurred for block ${JSON.stringify(block)}:`,
error
);
}
}
await new Promise((resolve, reject) => {
const ws = fs.createWriteStream(`outputData.csv`, { flags: "w" });
write(allCsvRows, { headers: true })
.pipe(ws)
.on("finish", () => {
console.log(`CSV file has been written.`);
resolve;
});
});
})
.catch((err) => {
console.error("Error reading CSV file:", err);
});
49 changes: 49 additions & 0 deletions adapters/beefy/src/sdk/breakdown/getVaultBreakdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { BeefyProtocolType, BeefyVault } from "../vault/getBeefyVaultConfig";
import { BeefyVaultBreakdown } from "./types";
import { BeefyViemClient, getViemClient } from "../viemClient";
import { getSolidlyVaultBreakdown } from "./protocol_type/solidly";
import { getGammaVaultBreakdown } from "./protocol_type/gamma";
import { getMendiVaultBreakdown } from "./protocol_type/mendi";

type BreakdownMethod = (
client: BeefyViemClient,
blockNumber: bigint,
vault: BeefyVault
) => Promise<BeefyVaultBreakdown>;

const breakdownMethods: Record<BeefyProtocolType, BreakdownMethod> = {
solidly: getSolidlyVaultBreakdown,
mendi: getMendiVaultBreakdown,
gamma: getGammaVaultBreakdown,
ichi: getGammaVaultBreakdown,
};

export const getVaultBreakdowns = async (
blockNumber: bigint,
vaults: BeefyVault[]
): Promise<BeefyVaultBreakdown[]> => {
// group by protocol type
const vaultsPerProtocol: Record<BeefyProtocolType, BeefyVault[]> =
vaults.reduce((acc, vault) => {
if (!acc[vault.protocol_type]) {
acc[vault.protocol_type] = [];
}
acc[vault.protocol_type].push(vault);
return acc;
}, {} as Record<BeefyProtocolType, BeefyVault[]>);

return (
await Promise.all(
(Object.keys(vaultsPerProtocol) as BeefyProtocolType[]).map(
async (protocolType) => {
const client = getViemClient();
const vaults = vaultsPerProtocol[protocolType];
const getBreakdown = breakdownMethods[protocolType];
return await Promise.all(
vaults.map((vault) => getBreakdown(client, blockNumber, vault))
);
}
)
)
).flat();
};
Loading

0 comments on commit 4fc8dc4

Please sign in to comment.