Skip to content

Commit

Permalink
feat: Add user active unstaked liquidity TVL
Browse files Browse the repository at this point in the history
  • Loading branch information
Apegurus committed Apr 6, 2024
1 parent 3fea99f commit 05ed186
Show file tree
Hide file tree
Showing 6 changed files with 498 additions and 114 deletions.
245 changes: 168 additions & 77 deletions adapters/lynex/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,102 +7,193 @@ import {
fetchUserVotes,
} from "./sdk/lensDetails";
import BigNumber from "bignumber.js";

interface CSVRow {
block_number: string;
timestamp: string;
user_address: string;
token_address: string;
token_balance: string;
}
import { BlockData, OutputSchemaRow } from "./sdk/types";
import {
getV2UserPositionsAtBlock,
getV3UserPositionsAtBlock,
} from "./sdk/pools";

const getData = async () => {
const snapshotBlocks = [2999728];
const snapshotBlocks = [3460121];

const csvRows: CSVRow[] = [];
const csvRows: OutputSchemaRow[] = [];

for (let block of snapshotBlocks) {
const [userAddresses] = await Promise.all([getUserAddresses(block)]);
console.log(`Block: ${block}`);
console.log("UserAddresses: ", userAddresses.length);
const timestamp = await getTimestampAtBlock(block);
csvRows.push(
...(await getUserTVLByBlock({
blockNumber: block,
blockTimestamp: timestamp,
}))
);
}

const tokenBalanceMap = {} as {
[userAddress: string]: { [tokenAddress: string]: BigNumber };
};
const ws = fs.createWriteStream("outputData.csv");
write(csvRows, { headers: true })
.pipe(ws)
.on("finish", () => {
console.log("CSV file has been written.");
});
};

const userPoolFetch = [];
const userVotesFetch = [];
export const getUserTVLByBlock = async ({
blockNumber,
blockTimestamp,
}: BlockData): Promise<OutputSchemaRow[]> => {
const result: OutputSchemaRow[] = [];

const [stakedTvl, liquidityTvl] = await Promise.all([
getUserStakedTVLByBlock({ blockNumber, blockTimestamp }),
getUserLiquidityTVLByBlock({ blockNumber, blockTimestamp }),
]);

// combine staked & unstaked
const combinedPositions = [...stakedTvl, ...liquidityTvl];
const balances: Record<string, Record<string, bigint>> = {};
for (const position of combinedPositions) {
balances[position.user_address] = balances[position.user_address] || {};

if (position.token_balance > 0n)
balances[position.user_address][position.token_address] =
(balances?.[position.user_address]?.[position.token_address] ?? 0n) +
position.token_balance;
}

for (const user of userAddresses) {
userPoolFetch.push(fetchUserPools(BigInt(block), user.id, user.pools));
userVotesFetch.push(fetchUserVotes(BigInt(block), user.id));
for (const [user, tokenBalances] of Object.entries(balances)) {
for (const [token, balance] of Object.entries(tokenBalances)) {
result.push({
block_number: blockNumber,
timestamp: blockTimestamp,
user_address: user,
token_address: token,
token_balance: balance,
});
}
}

const userFetchResult = await Promise.all(userPoolFetch);
const userVotesResult = await Promise.all(userVotesFetch);
const block_number = block.toString();
const timestamp = new Date(await getTimestampAtBlock(block)).toISOString();

for (const userFetchedPools of userFetchResult) {
for (const userPool of userFetchedPools) {
const user_address = userPool.result.userAddress.toLowerCase();
const totalLPBalance =
userPool.result.account_lp_balance +
userPool.result.account_gauge_balance;
const total0 =
(totalLPBalance * userPool.result.reserve0) /
userPool.result.total_supply;
const total1 =
(totalLPBalance * userPool.result.reserve1) /
userPool.result.total_supply;
const token0Address = userPool.result.token0.toLowerCase();
const token1Address = userPool.result.token1.toLowerCase();

// Aggregate tokens
tokenBalanceMap[user_address] = tokenBalanceMap[user_address] ?? {};
tokenBalanceMap[user_address][token0Address] = BigNumber(
tokenBalanceMap[user_address][token0Address] ?? 0
).plus(total0.toString());
tokenBalanceMap[user_address] = tokenBalanceMap[user_address] ?? {};
tokenBalanceMap[user_address][token1Address] = BigNumber(
tokenBalanceMap[user_address][token1Address] ?? 0
).plus(total1.toString());
}
return result;
};

export const getUserStakedTVLByBlock = async ({
blockNumber,
blockTimestamp,
}: BlockData): Promise<OutputSchemaRow[]> => {
const result: OutputSchemaRow[] = [];
const [userAddresses] = await Promise.all([getUserAddresses(blockNumber)]);
console.log(`Block: ${blockNumber}`);
console.log("UserAddresses: ", userAddresses.length);

const tokenBalanceMap = {} as {
[userAddress: string]: { [tokenAddress: string]: BigNumber };
};

const userPoolFetch = [];
const userVotesFetch = [];

for (const user of userAddresses) {
userPoolFetch.push(
fetchUserPools(BigInt(blockNumber), user.id, user.pools)
);
userVotesFetch.push(fetchUserVotes(BigInt(blockNumber), user.id));
}

const userFetchResult = await Promise.all(userPoolFetch);
const userVotesResult = await Promise.all(userVotesFetch);

for (const userFetchedPools of userFetchResult) {
for (const userPool of userFetchedPools) {
const user_address = userPool.result.userAddress.toLowerCase();
const totalLPBalance = userPool.result.account_gauge_balance;
const total0 =
(totalLPBalance * userPool.result.reserve0) /
userPool.result.total_supply;
const total1 =
(totalLPBalance * userPool.result.reserve1) /
userPool.result.total_supply;
const token0Address = userPool.result.token0.toLowerCase();
const token1Address = userPool.result.token1.toLowerCase();

// Aggregate tokens
tokenBalanceMap[user_address] = tokenBalanceMap[user_address] ?? {};
tokenBalanceMap[user_address][token0Address] = BigNumber(
tokenBalanceMap[user_address][token0Address] ?? 0
).plus(total0.toString());
tokenBalanceMap[user_address] = tokenBalanceMap[user_address] ?? {};
tokenBalanceMap[user_address][token1Address] = BigNumber(
tokenBalanceMap[user_address][token1Address] ?? 0
).plus(total1.toString());
}
}

for (const userFecthedVotes of userVotesResult) {
for (const userVote of userFecthedVotes) {
const user_address = userVote.result.userAddress.toLowerCase();
const token0Address = VE_LYNX_ADDRESS.toLowerCase();
tokenBalanceMap[user_address] = tokenBalanceMap[user_address] ?? {};
tokenBalanceMap[user_address][token0Address] = BigNumber(
tokenBalanceMap[user_address][token0Address] ?? 0
).plus(userVote.result.amount.toString());
}
for (const userFecthedVotes of userVotesResult) {
for (const userVote of userFecthedVotes) {
const user_address = userVote.result.userAddress.toLowerCase();
const token0Address = VE_LYNX_ADDRESS.toLowerCase();
tokenBalanceMap[user_address] = tokenBalanceMap[user_address] ?? {};
tokenBalanceMap[user_address][token0Address] = BigNumber(
tokenBalanceMap[user_address][token0Address] ?? 0
).plus(userVote.result.amount.toString());
}
}

Object.entries(tokenBalanceMap).forEach(([user_address, balances]) => {
Object.entries(balances).forEach(([token_address, token_balance]) => {
if (token_balance.dp(0).lte(0)) {
return;
}
csvRows.push({
block_number,
timestamp,
user_address,
token_address,
token_balance: token_balance.dp(0).toString(),
});
Object.entries(tokenBalanceMap).forEach(([user_address, balances]) => {
Object.entries(balances).forEach(([token_address, token_balance]) => {
if (token_balance.dp(0).lte(0)) {
return;
}
result.push({
block_number: blockNumber,
timestamp: blockTimestamp,
user_address,
token_address,
token_balance: BigInt(token_balance.dp(0).toNumber()),
});
});
});
return result;
};

const ws = fs.createWriteStream("outputData.csv");
write(csvRows, { headers: true })
.pipe(ws)
.on("finish", () => {
console.log("CSV file has been written.");
export const getUserLiquidityTVLByBlock = async ({
blockNumber,
blockTimestamp,
}: BlockData): Promise<OutputSchemaRow[]> => {
const result: OutputSchemaRow[] = [];

const [v2Positions, v3Positions] = await Promise.all([
getV2UserPositionsAtBlock(blockNumber),
getV3UserPositionsAtBlock(blockNumber),
]);

// combine v2 & v3
const combinedPositions = [...v2Positions, ...v3Positions];
const balances: Record<string, Record<string, bigint>> = {};
for (const position of combinedPositions) {
balances[position.user] = balances[position.user] || {};

if (position.token0.balance > 0n)
balances[position.user][position.token0.address] =
(balances?.[position.user]?.[position.token0.address] ?? 0n) +
position.token0.balance;

if (position.token1.balance > 0n)
balances[position.user][position.token1.address] =
(balances?.[position.user]?.[position.token1.address] ?? 0n) +
position.token1.balance;
}

for (const [user, tokenBalances] of Object.entries(balances)) {
for (const [token, balance] of Object.entries(tokenBalances)) {
result.push({
block_number: blockNumber,
timestamp: blockTimestamp,
user_address: user,
token_address: token,
token_balance: balance,
});
}
}

return result;
};

getData().then(() => {
Expand Down
32 changes: 12 additions & 20 deletions adapters/lynex/src/sdk/config.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
export const enum CHAINS {
L2_CHAIN_ID = 59144,
}
export const enum PROTOCOLS {
LYNEX = 0,
}
import { createPublicClient, http } from "viem";
import { linea } from "viem/chains";

export const enum AMM_TYPES {
LYNEX = 0,
}
export const V2_SUBGRAPH_URL =
"https://api.studio.thegraph.com/query/59052/lynex-v1/version/latest";
export const GAUGE_SUBGRAPH_URL =
"https://api.goldsky.com/api/public/project_cltyhthusbmxp01s95k9l8a1u/subgraphs/lynex-gauges/1.1.0/gn";
export const V3_SUBGRAPH_URL =
"https://api.studio.thegraph.com/query/59052/lynex-cl/v0.0.1";

export const SUBGRAPH_URLS = {
[CHAINS.L2_CHAIN_ID]: {
[PROTOCOLS.LYNEX]: {
[AMM_TYPES.LYNEX]:
"https://api.goldsky.com/api/public/project_cltyhthusbmxp01s95k9l8a1u/subgraphs/lynex-gauges/1.1.0/gn",
},
},
};
export const RPC_URLS = {
[CHAINS.L2_CHAIN_ID]: "https://rpc.linea.build",
};
export const client = createPublicClient({
chain: linea,
transport: http("https://rpc.linea.build"),
});
13 changes: 3 additions & 10 deletions adapters/lynex/src/sdk/lensDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import {
MulticallParameters,
PublicClient,
} from "viem";
import { linea } from "viem/chains";
import { CHAINS, RPC_URLS } from "./config";
import { client } from "./config";
import lensABI from "./abis/PairAPIABI.json";
import veLYNXAbi from "./abis/veLYNX.json";

Expand Down Expand Up @@ -67,10 +66,7 @@ export const fetchUserPools = async (
userAddress: string,
userPools: string[]
): Promise<LensResponseWithBlock[]> => {
const publicClient = createPublicClient({
chain: extractChain({ chains: [linea], id: CHAINS.L2_CHAIN_ID }),
transport: http(RPC_URLS[CHAINS.L2_CHAIN_ID]),
});
const publicClient = client;

const calls = userPools.map((pool: string) => {
return {
Expand All @@ -95,10 +91,7 @@ export const fetchUserVotes = async (
blockNumber: bigint,
userAddress: string
): Promise<VoteResponse[]> => {
const publicClient = createPublicClient({
chain: extractChain({ chains: [linea], id: CHAINS.L2_CHAIN_ID }),
transport: http(RPC_URLS[CHAINS.L2_CHAIN_ID]),
});
const publicClient = client;

const userBalanceCall = await multicall(
publicClient,
Expand Down
Loading

0 comments on commit 05ed186

Please sign in to comment.