Skip to content

Commit

Permalink
feat: include routers in lps
Browse files Browse the repository at this point in the history
  • Loading branch information
LayneHaber committed May 23, 2024
1 parent 66f9416 commit fae6984
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 88 deletions.
84 changes: 84 additions & 0 deletions adapters/connext/src/utils/assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { createPublicClient, http, parseUnits } from "viem";
import { linea } from "viem/chains";
import { PoolInformation, getPoolInformationFromLpToken } from "./cartographer";
import { LINEA_CHAIN_ID, CONNEXT_LINEA_ADDRESS } from "./subgraph";
import { LpAccountBalanceHourly, RouterEventResponse } from "./types";

type CompositeBalanceHourly = LpAccountBalanceHourly & {
underlyingTokens: string[];
underlyingBalances: string[];
}

const CONNEXT_ABI = [
{
"inputs": [
{
"internalType": "bytes32",
"name": "key",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "calculateRemoveSwapLiquidity",
"outputs": [
{
"internalType": "uint256[]",
"name": "",
"type": "uint256[]"
}
],
"stateMutability": "view",
"type": "function"
}
]

export const getCompositeBalances = async (amms: LpAccountBalanceHourly[]): Promise<CompositeBalanceHourly[]> => {
// get lp token balances
const poolInfo = new Map<string, PoolInformation>();

// get pool info
await Promise.all(amms.map(async d => {
const poolId = d.token.id.toLowerCase();
if (poolInfo.has(poolId)) {
return;
}
const pool = await getPoolInformationFromLpToken(d.token.id, LINEA_CHAIN_ID);
poolInfo.set(poolId, pool);
}));

// get contract interface
const client = createPublicClient({ chain: linea, transport: http() });

// get composite balances for amms (underlying tokens and balances)
const balances = await Promise.all(amms.map(async ({ token, amount, block }) => {
const poolId = token.id.toLowerCase();
const pool = poolInfo.get(poolId);
if (!pool) {
throw new Error(`Pool info not found for token: ${token.id}`);
}
// calculate the swap if you remove equal
const withdrawn = await client.readContract({
address: CONNEXT_LINEA_ADDRESS,
functionName: "calculateRemoveSwapLiquidity",
args: [pool.key, parseUnits(amount, 18)],
abi: CONNEXT_ABI,
blockNumber: BigInt(block)
}) as [bigint, bigint];
return withdrawn.map(w => w.toString());
}));

// return composite balance object
const ret = amms.map((d, idx) => {
const { pooledTokens } = poolInfo.get(d.token.id.toLowerCase())!;
return {
...d,
underlyingTokens: pooledTokens,
underlyingBalances: balances[idx] as [string, string]
}
})
return ret;
}
99 changes: 98 additions & 1 deletion adapters/connext/src/utils/cartographer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { chainIdToDomain, domainToChainId } from "@connext/nxtp-utils";
import { BlockData, OutputDataSchemaRow, RouterEventResponse } from "./types";
import { parseUnits } from "viem";

export type PoolInformation = {
lpToken: string;
Expand Down Expand Up @@ -34,4 +36,99 @@ export const getPoolInformationFromLpToken = async (lpToken: string, chainId: nu
pooledTokenDecimals: pool_token_decimals,
chainId: domainToChainId(+domain)
}
};
};


export const getRouterBalanceAtBlock = async (block: number, interval = 1000, account?: string) => {
let hasMore = true;
let offset = 0;
const balances = new Map<string, RouterEventResponse[]>()

while (hasMore) {
const liquidityEvents = await getRouterLiquidityEvents(interval, block, account)
appendCartographerData(liquidityEvents, balances);
hasMore = liquidityEvents.length === interval;
offset += interval;
}
return [...balances.values()].flat();
};

const appendCartographerData = (toAppend: RouterEventResponse[], existing: Map<string, RouterEventResponse[]>) => {
// get the latest record for each account. map should be keyed on router address,
// and store an array of locked router balances
toAppend.forEach((entry) => {
// no tally for account, set and continue
if (!existing.has(entry.router.toLowerCase())) {
existing.set(entry.router.toLowerCase(), [entry]);
return;
}

// get the existing record for the router
const prev = existing.get(entry.router.toLowerCase())!;
// get the asset idx for this event
const idx = prev.findIndex((r) => r.asset.toLowerCase() === entry.asset.toLowerCase());
if (idx < 0) {
// no record for this asset. append entry to existing list
existing.set(entry.router.toLowerCase(), [...prev, entry]);
return;
}

// if the existing record is more recent, exit without updating
if (prev[idx].block_number >= entry.block_number) {
return;
}
prev[idx] = entry;
existing.set(entry.router.toLowerCase(), prev.filter((_, i) => idx !== i).concat([entry]));
});
}

export const getRouterLiquidityEvents = async (
limit: number,
blockNumber: number,
router?: string
): Promise<RouterEventResponse[]> => {
const url = `${MAINNET_CARTOGRAPHER_URL}/router_liquidity_events?block_number=lte.${blockNumber}&limit=eq.${limit}${router ? `&router=eq.${router}` : ''}`;
const response = await fetch(url);
const data: RouterEventResponse[] = await response.json();
return data;
}

type AssetConfiguration = {
local: string;
adopted: string;
canonical_id: string;
canonical_domain: string;
domain: string;
key: string;
id: string;
decimal: number;
adopted_decimal: number
};

export const getAssets = async (): Promise<AssetConfiguration[]> => {
const url = `${MAINNET_CARTOGRAPHER_URL}/assets?domain=eq.1818848877`;
const response = await fetch(url);
return await response.json() as AssetConfiguration[];
}

export const formatRouterLiquidityEvents = async (block: BlockData, data: RouterEventResponse[]): Promise<OutputDataSchemaRow[]> => {
// Get the asset information
const assets = await getAssets();

// Format the data
return data.map(d => {
const config = assets.find(a => a.local.toLowerCase() === d.asset.toLowerCase());
const decimals = config ? config.decimal : 18;
const toParse = d.balance < 0 ? 0 : d.balance;
const balance = toParse.toString().includes('e') ? BigInt(toParse * 10 ** 18) : parseUnits(toParse.toString(), decimals)
return {
block_number: block.blockNumber,
timestamp: block.blockTimestamp,
user_address: d.router,
token_address: config ? config.adopted : d.asset.toLowerCase(),
token_balance: balance,
token_symbol: '',
usd_price: 0
}
})
}
23 changes: 14 additions & 9 deletions adapters/connext/src/utils/getUserTvlByBlock.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { getBlock, getCompositeBalances, getLpAccountBalanceAtBlock } from "./subgraph";
import { formatRouterLiquidityEvents, getRouterBalanceAtBlock } from "./cartographer";
import { getLpAccountBalanceAtBlock } from "./subgraph";
import { BlockData, OutputDataSchemaRow } from "./types";
import { getCompositeBalances } from "./assets";

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

const data = await getLpAccountBalanceAtBlock(blockNumber);
const amms = await getLpAccountBalanceAtBlock(blockNumber);

// get the composite balances
const composite = await getCompositeBalances(data);

// get block info
const { timestamp } = await getBlock(blockNumber);
// get the composite balances
const composite = await getCompositeBalances(amms);

// format into output
const results: OutputDataSchemaRow[] = [];
// format amm lps
composite.forEach(({ account, underlyingBalances, underlyingTokens }) => {
results.push(...underlyingBalances.map((b, i) => {
const formatted: OutputDataSchemaRow = {
timestamp: +timestamp.toString(),
timestamp: blockTimestamp,
block_number: blockNumber,
user_address: account.id,
token_address: underlyingTokens[i],
Expand All @@ -29,6 +30,10 @@ export const getUserTVLByBlock = async (blocks: BlockData): Promise<OutputDataSc
}));
})

return results;
// get the router balances
const routers = await getRouterBalanceAtBlock(blockNumber);
const formatted = await formatRouterLiquidityEvents(blocks, routers);

return results.concat(formatted);
};

77 changes: 0 additions & 77 deletions adapters/connext/src/utils/subgraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,6 @@ export const CONNEXT_SUBGRAPH_QUERY_URL = "https://api.goldsky.com/api/public/pr
export const LINEA_CHAIN_ID = 59144;
export const CONNEXT_LINEA_ADDRESS = "0xa05eF29e9aC8C75c530c2795Fa6A800e188dE0a9";

const CONNEXT_ABI = [
{
"inputs": [
{
"internalType": "bytes32",
"name": "key",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "calculateRemoveSwapLiquidity",
"outputs": [
{
"internalType": "uint256[]",
"name": "",
"type": "uint256[]"
}
],
"stateMutability": "view",
"type": "function"
}
]

const LP_HOURLY_QUERY_BY_BLOCK = (
first: number,
Expand Down Expand Up @@ -138,57 +112,6 @@ export const getLpAccountBalanceAtTimestamp = async (timestamp: number, interval
return [...balances.values()].flat();
}

type CompositeBalanceHourly = LpAccountBalanceHourly & {
underlyingTokens: [string, string];
underlyingBalances: [string, string];
}

export const getCompositeBalances = async (data: LpAccountBalanceHourly[]): Promise<CompositeBalanceHourly[]> => {
// get lp token balances
const poolInfo = new Map<string, PoolInformation>();

// get pool info
await Promise.all(data.map(async d => {
const poolId = d.token.id.toLowerCase();
if (poolInfo.has(poolId)) {
return;
}
const pool = await getPoolInformationFromLpToken(d.token.id, LINEA_CHAIN_ID);
poolInfo.set(poolId, pool);
}));

// get contract interface
const client = createPublicClient({ chain: linea, transport: http() });

// get composite balances
const balances = await Promise.all(data.map(async ({ token, amount, block }) => {
const poolId = token.id.toLowerCase();
const pool = poolInfo.get(poolId);
if (!pool) {
throw new Error(`Pool info not found for token: ${token.id}`);
}
// calculate the swap if you remove equal
const withdrawn = await client.readContract({
address: CONNEXT_LINEA_ADDRESS,
functionName: "calculateRemoveSwapLiquidity",
args: [pool.key, parseUnits(amount, 18)],
abi: CONNEXT_ABI,
blockNumber: BigInt(block)
}) as [bigint, bigint];
return withdrawn.map(w => w.toString());
}));

// return composite balance object
return data.map((d, idx) => {
const { pooledTokens } = poolInfo.get(d.token.id.toLowerCase())!;
return {
...d,
underlyingTokens: pooledTokens,
underlyingBalances: balances[idx] as [string, string]
}
})
}

const appendSubgraphData = (data: LpAccountBalanceHourly[], existing: Map<string, LpAccountBalanceHourly[]>) => {
// looking for latest record of account balance
data.forEach(d => {
Expand Down
17 changes: 16 additions & 1 deletion adapters/connext/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,19 @@ export type LpAccountBalanceHourly = {
// Subgraph query result
export type SubgraphResult = {
lpAccountBalanceHourlies: LpAccountBalanceHourly[];
}
}

// Router liquidity event response
export type RouterEventResponse = {
id: string;
domain: string;
router: string;
event: 'Add' | 'Remove';
asset: string;
amount: number;
balance: number;
block_number: number;
transaction_hash: string;
timestamp: number;
nonce: number;
}

0 comments on commit fae6984

Please sign in to comment.