Skip to content

Commit

Permalink
Merge pull request #250 from curvefi/fix/fixed-volumes-api
Browse files Browse the repository at this point in the history
fix: fixed volumes and apys api
  • Loading branch information
fedorovdg authored Jan 9, 2024
2 parents 47cd668 + 12ea37b commit fe67f81
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 95 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@curvefi/api",
"version": "2.53.7",
"version": "2.53.8",
"description": "JavaScript library for curve.fi",
"main": "lib/index.js",
"author": "Macket",
Expand Down
13 changes: 13 additions & 0 deletions src/constants/volumeNetworks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {IChainId} from "../interfaces";

export interface IVolumeNetworks {
getVolumes: IChainId[];
getSubgraphData: IChainId[];
getFactoryAPYs: IChainId[];
}

export const volumeNetworks: IVolumeNetworks = {
getVolumes: [1,137,8453, 42161],
getSubgraphData: [10,100,250,1284,42220,43114,1313161554],
getFactoryAPYs: [56,324,2222],
}
97 changes: 70 additions & 27 deletions src/external-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import axios from "axios";
import memoize from "memoizee";
import {
IExtendedPoolDataFromApi,
ISubgraphPoolData,
IDict,
INetworkName,
IPoolType,
IGaugesDataFromApi,
IDaoProposal,
IDaoProposalListItem,
IVolumeAndAPYs,
} from "./interfaces";


Expand Down Expand Up @@ -38,11 +38,21 @@ export const _getAllPoolsFromApi = async (network: INetworkName): Promise<IExten
}

export const _getSubgraphData = memoize(
async (network: INetworkName): Promise<{ poolsData: ISubgraphPoolData[], totalVolume: number, cryptoVolume: number, cryptoShare: number }> => {
async (network: INetworkName): Promise<IVolumeAndAPYs> => {
const url = `https://api.curve.fi/api/getSubgraphData/${network}`;
const response = await axios.get(url, { validateStatus: () => true });

const poolsData = response.data.data.poolList.map((item: any) => {
return {
address: item.address,
volumeUSD: item.volumeUSD,
day: item.latestDailyApy,
week: item.latestWeeklyApy,
}
})

return {
poolsData: response.data.data.poolList ?? [],
poolsData: poolsData ?? [],
totalVolume: response.data.data.totalVolume ?? 0,
cryptoVolume: response.data.data.cryptoVolume ?? 0,
cryptoShare: response.data.data.cryptoShare ?? 0,
Expand All @@ -54,49 +64,82 @@ export const _getSubgraphData = memoize(
}
)

// Moonbeam and Aurora only
export const _getLegacyAPYsAndVolumes = memoize(
async (network: string): Promise<IDict<{ apy: { day: number, week: number }, volume: number }>> => {
if (["kava", "celo", "zksync", "base", "bsc"].includes(network)) return {}; // Exclude Kava, Celo, ZkSync, Base and Bsc
const url = "https://api.curve.fi/api/getMainPoolsAPYs/" + network;
const data = (await axios.get(url, { validateStatus: () => true })).data;
const result: IDict<{ apy: { day: number, week: number }, volume: number }> = {};
Object.keys(data.apy.day).forEach((poolId) => {
result[poolId] = { apy: { day: 0, week: 0 }, volume: 0};
result[poolId].apy.day = data.apy.day[poolId] * 100;
result[poolId].apy.week = data.apy.week[poolId] * 100;
result[poolId].volume = data.volume[poolId];
export const _getVolumes = memoize(
async (network: string): Promise<IVolumeAndAPYs> => {

const url = `https://api.curve.fi/api/getVolumes/${network}`;
const response = await axios.get(url, { validateStatus: () => true });

const poolsData = response.data.data.pools.map((item: any) => {
return {
address: item.address,
volumeUSD: item.volumeUSD,
day: item.latestDailyApyPcent,
week: item.latestWeeklyApyPcent,
}
})

return result;
return {
poolsData: poolsData ?? [],
totalVolume: response.data.data.totalVolumes.totalVolume ?? 0,
cryptoVolume: response.data.data.totalVolumes.totalCryptoVolume ?? 0,
cryptoShare: response.data.data.totalVolumes.cryptoVolumeSharePcent ?? 0,
};
},
{
promise: true,
maxAge: 5 * 60 * 1000, // 5m
}
)

// Base, Bsc, ZkSync, Moonbeam, Kava and Celo only
export const _getFactoryAPYsAndVolumes = memoize(
async (network: string, mode: 'stable' | 'crypto' = 'stable'): Promise<{ poolAddress: string, apy: number, volume: number }[]> => {
if (network === "aurora") return []; // Exclude Aurora

const url = `https://api.curve.fi/api/getFactoryAPYs/${network}/${mode}`;
const response = await axios.get(url, { validateStatus: () => true });
export const _getFactoryAPYs = memoize(
async (network: string): Promise<IVolumeAndAPYs> => {
const urlStable = `getFactoryAPYs/${network}/stable}`;
const urlCrypto = `getFactoryAPYs/${network}/crypto}`;
const response = await Promise.all([
axios.get(urlStable, { validateStatus: () => true }),
axios.get(urlCrypto, { validateStatus: () => true }),
]);

const stableVolume = response[0].data.data.totalVolumeUsd || response[0].data.data.totalVolume;
const cryptoVolume = response[1].data.data.totalVolumeUsd || response[1].data.data.totalVolume;

const poolsData = [...response[0].data.data.pools, ...response[1].data.data.pools].map((item) => {
return {
address: item.poolAddress,
volumeUSD: item.totalVolumeUsd,
day: item.apy,
week: item.apy*7, //Because api does not return week apy
}
})

return response.data.data.poolDetails ?? [];
return {
poolsData: poolsData ?? [],
totalVolume: stableVolume + cryptoVolume ?? 0,
cryptoVolume: cryptoVolume ?? 0,
cryptoShare: 100*cryptoVolume/(stableVolume + cryptoVolume) ?? 0,
};
},
{
promise: true,
maxAge: 5 * 60 * 1000, // 5m
}
)

//4
export const _getTotalVolumes = memoize(
async (network: string, mode: 'stable' | 'crypto' = 'stable'): Promise<{ totalVolumeUsd: number}> => {
if (network === "aurora") return {totalVolumeUsd: 0}; // Exclude Aurora
async (network: string): Promise<{
totalVolume: number;
cryptoVolume: number;
cryptoShare: number;
}> => {
if (network === "aurora") return {
totalVolume: 0,
cryptoVolume: 0,
cryptoShare: 0,
}; // Exclude Aurora

const url = `https://api.curve.fi/api/getFactoryAPYs/${network}/${mode}`;
const url = `https://api.curve.fi/api/getSubgraphData/${network}`;
const response = await axios.get(url, { validateStatus: () => true });

return response.data.data;
Expand Down
14 changes: 14 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,18 @@ export interface IDaoProposal extends IDaoProposalListItem{
creatorVotingPower: number,
script: string,
votes: IDaoProposalVote[],
}

export interface IVolumeAndAPYsPoolData {
address: string,
volumeUSD: number,
day: number,
week: number,
}

export interface IVolumeAndAPYs {
totalVolume: number,
cryptoVolume: number,
cryptoShare: number,
poolsData: IVolumeAndAPYsPoolData[],
}
62 changes: 15 additions & 47 deletions src/pools/PoolTemplate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import BigNumber from 'bignumber.js';
import memoize from "memoizee";
import { _getPoolsFromApi, _getSubgraphData, _getFactoryAPYsAndVolumes, _getLegacyAPYsAndVolumes } from '../external-api.js';
import { _getPoolsFromApi } from '../external-api.js';
import {
_getCoinAddresses,
_getBalances,
Expand Down Expand Up @@ -28,6 +28,7 @@ import {
DIGas,
_getAddress,
isMethodExist,
getVolumeApiController,
} from '../utils.js';
import { IDict, IReward, IProfit, IPoolType } from '../interfaces';
import { curve } from "../curve.js";
Expand Down Expand Up @@ -417,62 +418,29 @@ export class PoolTemplate {
}

private statsVolume = async (): Promise<string> => {
if ([56, 324, 1284, 2222, 8453, 42220, 1313161554].includes(curve.chainId)) { // Bsc || ZkSync || Moonbeam || Kava || Base || Celo || Aurora || Bsc
const _response = await Promise.all([
_getLegacyAPYsAndVolumes(curve.constants.NETWORK_NAME),
_getFactoryAPYsAndVolumes(curve.constants.NETWORK_NAME, 'stable'),
_getFactoryAPYsAndVolumes(curve.constants.NETWORK_NAME, 'crypto'),
]);
const [mainPoolsData, factoryPoolsData] = [_response[0], [..._response[1], ..._response[2]]];
if (this.id in mainPoolsData) {
return (mainPoolsData[this.id].volume ?? 0).toString();
}
const poolData = factoryPoolsData.find((d) => d.poolAddress.toLowerCase() === this.address);
if (!poolData) throw Error(`Can't get Volume for ${this.name} (id: ${this.id})`)
const lpPrice = await _getUsdRate(this.lpToken);

return (poolData.volume * lpPrice).toString()
}
const network = curve.constants.NETWORK_NAME;
const poolsData = (await _getSubgraphData(network)).poolsData;
const {poolsData} = await getVolumeApiController(network);
const poolData = poolsData.find((d) => d.address.toLowerCase() === this.address);
if (!poolData) throw Error(`Can't get Volume for ${this.name} (id: ${this.id})`)

return poolData.volumeUSD.toString()
if(poolData) {
return poolData.volumeUSD.toString()
}

throw Error(`Can't get Volume for ${this.name} (id: ${this.id})`)
}

private statsBaseApy = async (): Promise<{ day: string, week: string }> => {
if ([56, 324, 1284, 2222, 8453, 42220, 1313161554].includes(curve.chainId)) { // Bsc || ZkSync || Moonbeam || Kava || Base || Celo || Aurora
const _response = await Promise.all([
_getLegacyAPYsAndVolumes(curve.constants.NETWORK_NAME),
_getFactoryAPYsAndVolumes(curve.constants.NETWORK_NAME, 'stable'),
_getFactoryAPYsAndVolumes(curve.constants.NETWORK_NAME, 'crypto'),
]);
const [mainPoolsData, factoryPoolsData] = [_response[0], [..._response[1], ..._response[2]]];
if (this.id in mainPoolsData) {
return {
day: mainPoolsData[this.id].apy.day.toString(),
week: mainPoolsData[this.id].apy.week.toString(),
}
}
const poolData = factoryPoolsData.find((d) => d.poolAddress.toLowerCase() === this.address);
if (!poolData) throw Error(`Can't get base APY for ${this.name} (id: ${this.id})`)

return {
day: poolData.apy.toString(),
week: poolData.apy.toString(),
}
}
const network = curve.constants.NETWORK_NAME;
const poolsData = (await _getSubgraphData(network)).poolsData;
const {poolsData} = await getVolumeApiController(network);
const poolData = poolsData.find((d) => d.address.toLowerCase() === this.address);

if (!poolData) throw Error(`Can't get base APY for ${this.name} (id: ${this.id})`)

return {
day: poolData.latestDailyApy.toString(),
week: poolData.latestWeeklyApy.toString(),
if(poolData) {
return {
day: poolData.day.toString(),
week: poolData.week.toString(),
}
}
throw Error(`Can't get base APY for ${this.name} (id: ${this.id})`)
}

private _calcTokenApy = async (futureWorkingSupplyBN: BigNumber | null = null): Promise<[baseApy: number, boostedApy: number]> => {
Expand Down
39 changes: 19 additions & 20 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import axios from 'axios';
import { Contract } from 'ethers';
import { Contract as MulticallContract } from "ethcall";
import BigNumber from 'bignumber.js';
import {IChainId, IDict, INetworkName, IRewardFromApi, REFERENCE_ASSET} from './interfaces';
import {IChainId, IDict, INetworkName, IRewardFromApi, IVolumeAndAPYs, REFERENCE_ASSET} from './interfaces';
import { curve, NETWORK_CONSTANTS } from "./curve.js";
import {
_getFactoryAPYsAndVolumes,
_getLegacyAPYsAndVolumes,
_getAllPoolsFromApi,
_getFactoryAPYs,
_getSubgraphData,
_getTotalVolumes,
_getVolumes,
} from "./external-api.js";
import ERC20Abi from './constants/abis/ERC20.json' assert { type: 'json' };
import { L2Networks } from './constants/L2Networks.js';
import {volumeNetworks} from "./constants/volumeNetworks";


export const ETH_ADDRESS = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
Expand Down Expand Up @@ -558,24 +558,23 @@ export const getTVL = async (network: INetworkName | IChainId = curve.chainId):
return allTypesExtendedPoolData.reduce((sum, data) => sum + (data.tvl ?? data.tvlAll ?? 0), 0)
}

export const getVolume = async (network: INetworkName | IChainId = curve.chainId): Promise<{ totalVolume: number, cryptoVolume: number, cryptoShare: number }> => {
network = _getNetworkName(network);
if (["zksync", "moonbeam", "kava", "base", "celo", "aurora", "bsc"].includes(network)) {
const chainId = _getChainId(network);
if (curve.chainId !== chainId) throw Error("To get volume for ZkSync, Moonbeam, Kava, Base, Celo, Aurora or Bsc connect to the network first");


const [factoryPoolsData, cryptoPoolsData] = await Promise.all([
_getTotalVolumes(network, 'stable'),
_getTotalVolumes(network, 'crypto'),
]);
const stableVolume = factoryPoolsData.totalVolumeUsd;
const cryptoVolume = cryptoPoolsData.totalVolumeUsd;

return { totalVolume: stableVolume + cryptoVolume, cryptoVolume: cryptoVolume, cryptoShare: cryptoVolume/(stableVolume + cryptoVolume) }
export const getVolumeApiController = async (network: INetworkName): Promise<IVolumeAndAPYs> => {
if(volumeNetworks.getVolumes.includes(curve.chainId)) {
return await _getVolumes(network);
}
if(volumeNetworks.getFactoryAPYs.includes(curve.chainId)) {
return await _getFactoryAPYs(network);
}
if(volumeNetworks.getSubgraphData.includes(curve.chainId)) {
return await _getSubgraphData(network);
}

throw Error(`Can't get volume for network: ${network}`);
}

const { totalVolume, cryptoVolume, cryptoShare } = await _getSubgraphData(network);
export const getVolume = async (network: INetworkName | IChainId = curve.chainId): Promise<{ totalVolume: number, cryptoVolume: number, cryptoShare: number }> => {
network = _getNetworkName(network);
const { totalVolume, cryptoVolume, cryptoShare } = await getVolumeApiController(network);
return { totalVolume, cryptoVolume, cryptoShare }
}

Expand Down

0 comments on commit fe67f81

Please sign in to comment.