diff --git a/package.json b/package.json index 02cd04a..bb01638 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@clober/v2-sdk", - "version": "0.0.75.dev", + "version": "0.0.76.dev", "description": "🛠 An SDK for building applications on top of Clober V2", "files": [ "dist" diff --git a/src/apis/pool.ts b/src/apis/pool.ts index 38e19b6..949ee87 100644 --- a/src/apis/pool.ts +++ b/src/apis/pool.ts @@ -1,7 +1,12 @@ import { PublicClient } from 'viem' import { CHAIN_IDS } from '../constants/chain' -import { Pool, PoolSnapshotDto, PoolVolumeDto } from '../model/pool' +import { + Pool, + PoolSnapshotDto, + PoolSpreadProfitDto, + PoolVolumeDto, +} from '../model/pool' import { CONTRACT_ADDRESSES } from '../constants/addresses' import { toPoolKey } from '../utils/pool-key' import { REBALANCER_ABI } from '../abis/rebalancer/rebalancer-abi' @@ -15,20 +20,23 @@ export const fetchPoolPerformance = async ( poolKey: `0x${string}`, volumeFromTimestamp: number, snapshotFromTimestamp: number, + spreadProfitFromTimestamp: number, ) => { return Subgraph.get<{ data: { poolVolumes: PoolVolumeDto[] poolSnapshots: PoolSnapshotDto[] + poolSpreadProfits: PoolSpreadProfitDto[] } }>( chainId, 'getPoolPerformanceData', - 'query getPoolPerformanceData($poolKey: String!, $volumeFrom: BigInt!, $snapshotFrom: BigInt!) { poolVolumes(where: { poolKey: $poolKey, intervalType: "1d", timestamp_gte: $volumeFrom, }) { id poolKey intervalType timestamp currencyAVolume currencyBVolume bookACurrencyAVolume bookACurrencyBVolume bookBCurrencyAVolume bookBCurrencyBVolume } poolSnapshots( where: { poolKey: $poolKey, intervalType: "1h", timestamp_gte: $snapshotFrom, } ) { id poolKey intervalType timestamp price liquidityA liquidityB totalSupply } }', + 'query getPoolPerformanceData($poolKey: String!, $volumeFrom: BigInt!, $snapshotFrom: BigInt!, $spreadProfitFrom: BigInt!) { poolVolumes(where: { poolKey: $poolKey, intervalType: "1d", timestamp_gte: $volumeFrom, }) { id poolKey intervalType timestamp currencyAVolume currencyBVolume bookACurrencyAVolume bookACurrencyBVolume bookBCurrencyAVolume bookBCurrencyBVolume } poolSnapshots( where: { poolKey: $poolKey, intervalType: "1h", timestamp_gte: $snapshotFrom, } ) { id poolKey intervalType timestamp price liquidityA liquidityB totalSupply } poolSpreadProfits( where: { intervalType: "1h", timestamp_gte: $spreadProfitFrom, } ) { id intervalType timestamp accumulatedProfitInUsd } }', { poolKey, volumeFrom: BigInt(volumeFromTimestamp), snapshotFrom: BigInt(snapshotFromTimestamp), + spreadProfitFrom: BigInt(spreadProfitFromTimestamp), }, ) } diff --git a/src/model/pool.ts b/src/model/pool.ts index 3ca14cc..54af55d 100644 --- a/src/model/pool.ts +++ b/src/model/pool.ts @@ -199,3 +199,10 @@ export type PoolSnapshotDto = { liquidityB: bigint totalSupply: bigint } + +export type PoolSpreadProfitDto = { + id: string + intervalType: '1h' + timestamp: bigint + accumulatedProfitInUsd: string +} diff --git a/src/type.ts b/src/type.ts index 190d110..83afdd5 100644 --- a/src/type.ts +++ b/src/type.ts @@ -156,7 +156,14 @@ export type PoolSnapshotDto = { totalSupply: Currency6909Amount } +export type PoolSpreadProfitDto = { + intervalType: '1h' + timestamp: number + accumulatedProfitInUsd: string +} + export type PoolPerformanceData = { poolVolumes: PoolVolumeDto[] poolSnapshots: PoolSnapshotDto[] + poolSpreadProfits: PoolSpreadProfitDto[] } diff --git a/src/utils/time-series.ts b/src/utils/time-series.ts new file mode 100644 index 0000000..fce7fe2 --- /dev/null +++ b/src/utils/time-series.ts @@ -0,0 +1,31 @@ +export function fillAndSortByTimestamp< + T extends { timestamp: number | bigint }, +>( + data: T[], + granularity: number, + emptyObjectGenerator: (timestamp: number, prev: T) => T, +): T[] { + const sortedData = data.sort( + (a, b) => Number(a.timestamp) - Number(b.timestamp), + ) + + const result: T[] = [] + + for (let i = 0; i < sortedData.length; i++) { + result.push(sortedData[i]) + + if (i < sortedData.length - 1) { + const currentTimestamp = sortedData[i].timestamp + const nextTimestamp = sortedData[i + 1].timestamp + for ( + let ts = Number(currentTimestamp) + granularity; + ts < Number(nextTimestamp); + ts += granularity + ) { + result.push(emptyObjectGenerator(ts, sortedData[i])) + } + } + } + + return result +} diff --git a/src/view.ts b/src/view.ts index 9c0dd26..3f2df77 100644 --- a/src/view.ts +++ b/src/view.ts @@ -30,6 +30,12 @@ import { MAX_TICK, MIN_TICK } from './constants/tick' import { fetchPool, fetchPoolPerformance } from './apis/pool' import { fetchStrategyPrice } from './apis/strategy' import { Subgraph } from './constants/subgraph' +import { fillAndSortByTimestamp } from './utils/time-series' +import { + PoolSnapshotDto as ModelPoolSnapshot, + PoolSpreadProfitDto as ModelPoolSpreadProfit, + PoolVolumeDto as ModelPoolVolume, +} from './model/pool' /** * Get contract addresses by chain id @@ -194,6 +200,7 @@ export const getPoolPerformance = async ({ salt, volumeFromTimestamp, snapshotFromTimestamp, + spreadProfitFromTimestamp, options, }: { chainId: CHAIN_IDS @@ -202,6 +209,7 @@ export const getPoolPerformance = async ({ salt: `0x${string}` volumeFromTimestamp: number snapshotFromTimestamp: number + spreadProfitFromTimestamp: number options?: { pool?: Pool useSubgraph?: boolean @@ -237,9 +245,59 @@ export const getPoolPerformance = async ({ pool.key, volumeFromTimestamp, snapshotFromTimestamp, + spreadProfitFromTimestamp, + ) + const poolVolumes = fillAndSortByTimestamp( + poolPerformance.data.poolVolumes, + 24 * 60 * 60, + (timestamp: number) => { + const emptyPoolVolume: ModelPoolVolume = { + id: '', + poolKey: pool.key, + intervalType: '1d', + timestamp: BigInt(timestamp), + currencyAVolume: 0n, + currencyBVolume: 0n, + bookACurrencyAVolume: 0n, + bookACurrencyBVolume: 0n, + bookBCurrencyAVolume: 0n, + bookBCurrencyBVolume: 0n, + } + return emptyPoolVolume + }, + ) + const poolSnapshots = fillAndSortByTimestamp( + poolPerformance.data.poolSnapshots, + 60 * 60, + (timestamp: number, prev: ModelPoolSnapshot) => { + const emptyPoolSnapshot: ModelPoolSnapshot = { + id: '', + poolKey: pool.key, + intervalType: '1h', + timestamp: BigInt(timestamp), + price: prev.price, + liquidityA: prev.liquidityA, + liquidityB: prev.liquidityB, + totalSupply: prev.totalSupply, + } + return emptyPoolSnapshot + }, + ) + const poolSpreadProfits = fillAndSortByTimestamp( + poolPerformance.data.poolSpreadProfits, + 60 * 60, + (timestamp: number) => { + const emptyPoolSpreadProfit: ModelPoolSpreadProfit = { + id: '', + intervalType: '1h', + timestamp: BigInt(timestamp), + accumulatedProfitInUsd: '0', + } + return emptyPoolSpreadProfit + }, ) return { - poolVolumes: poolPerformance.data.poolVolumes.map((poolVolume) => ({ + poolVolumes: poolVolumes.map((poolVolume) => ({ poolKey: poolVolume.poolKey, intervalType: poolVolume.intervalType, timestamp: Number(poolVolume.timestamp), @@ -252,7 +310,7 @@ export const getPoolPerformance = async ({ value: formatUnits(poolVolume.currencyBVolume, pool.currencyB.decimals), }, })), - poolSnapshots: poolPerformance.data.poolSnapshots.map((poolSnapshot) => ({ + poolSnapshots: poolSnapshots.map((poolSnapshot) => ({ poolKey: poolSnapshot.poolKey, intervalType: poolSnapshot.intervalType, timestamp: Number(poolSnapshot.timestamp), @@ -270,6 +328,11 @@ export const getPoolPerformance = async ({ value: formatUnits(poolSnapshot.totalSupply, pool.currencyLp.decimals), }, })), + poolSpreadProfits: poolSpreadProfits.map((poolSpreadProfit) => ({ + intervalType: poolSpreadProfit.intervalType, + timestamp: Number(poolSpreadProfit.timestamp), + accumulatedProfitInUsd: poolSpreadProfit.accumulatedProfitInUsd, + })), } }