Skip to content

Commit

Permalink
Merge pull request #27 from curvefi/fix/leverage-priceImpact
Browse files Browse the repository at this point in the history
Fix: leverage price impact
  • Loading branch information
Macket committed Jul 12, 2024
2 parents 25b0868 + effbf3c commit 12627f6
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 111 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/lending-api",
"version": "2.1.0",
"version": "2.1.1",
"description": "JavaScript library for Curve Lending",
"main": "lib/index.js",
"author": "Macket",
Expand Down
119 changes: 114 additions & 5 deletions src/external-api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import axios from "axios";
import memoize from "memoizee";
import BigNumber from 'bignumber.js';
import { lending } from "./lending.js";
import { IExtendedPoolDataFromApi, INetworkName, IPoolFactory, I1inchRoute, I1inchSwapData } from "./interfaces";
import { IExtendedPoolDataFromApi, INetworkName, IPoolFactory, I1inchSwapData, IDict } from "./interfaces";


export const _getPoolsFromApi = memoize(
const _getPoolsFromApi = memoize(
async (network: INetworkName, poolFactory: IPoolFactory ): Promise<IExtendedPoolDataFromApi> => {
const url = `https://api.curve.fi/api/getPools/${network}/${poolFactory}`;
const response = await axios.get(url, { validateStatus: () => true });
Expand All @@ -16,7 +17,7 @@ export const _getPoolsFromApi = memoize(
}
)

export const _getAllPoolsFromApi = async (network: INetworkName): Promise<IExtendedPoolDataFromApi[]> => {
const _getAllPoolsFromApi = async (network: INetworkName): Promise<IExtendedPoolDataFromApi[]> => {
return await Promise.all([
_getPoolsFromApi(network, "main"),
_getPoolsFromApi(network, "crypto"),
Expand All @@ -29,6 +30,84 @@ export const _getAllPoolsFromApi = async (network: INetworkName): Promise<IExten
]);
}

export const _getUsdPricesFromApi = async (): Promise<IDict<number>> => {
const network = lending.constants.NETWORK_NAME;
const allTypesExtendedPoolData = await _getAllPoolsFromApi(network);
const priceDict: IDict<Record<string, number>[]> = {};
const priceDictByMaxTvl: IDict<number> = {};

for (const extendedPoolData of allTypesExtendedPoolData) {
for (const pool of extendedPoolData.poolData) {
const lpTokenAddress = pool.lpTokenAddress ?? pool.address;
const totalSupply = pool.totalSupply / (10 ** 18);
if(lpTokenAddress.toLowerCase() in priceDict) {
priceDict[lpTokenAddress.toLowerCase()].push({
price: pool.usdTotal && totalSupply ? pool.usdTotal / totalSupply : 0,
tvl: pool.usdTotal,
})
} else {
priceDict[lpTokenAddress.toLowerCase()] = []
priceDict[lpTokenAddress.toLowerCase()].push({
price: pool.usdTotal && totalSupply ? pool.usdTotal / totalSupply : 0,
tvl: pool.usdTotal,
})
}

for (const coin of pool.coins) {
if (typeof coin.usdPrice === "number") {
if(coin.address.toLowerCase() in priceDict) {
priceDict[coin.address.toLowerCase()].push({
price: coin.usdPrice,
tvl: pool.usdTotal,
})
} else {
priceDict[coin.address.toLowerCase()] = []
priceDict[coin.address.toLowerCase()].push({
price: coin.usdPrice,
tvl: pool.usdTotal,
})
}
}
}

for (const coin of pool.gaugeRewards ?? []) {
if (typeof coin.tokenPrice === "number") {
if(coin.tokenAddress.toLowerCase() in priceDict) {
priceDict[coin.tokenAddress.toLowerCase()].push({
price: coin.tokenPrice,
tvl: pool.usdTotal,
});
} else {
priceDict[coin.tokenAddress.toLowerCase()] = []
priceDict[coin.tokenAddress.toLowerCase()].push({
price: coin.tokenPrice,
tvl: pool.usdTotal,
});
}
}
}
}
}

for(const address in priceDict) {
if(priceDict[address].length > 0) {
const maxTvlItem = priceDict[address].reduce((prev, current) => {
if (+current.tvl > +prev.tvl) {
return current;
} else {
return prev;
}
});
priceDictByMaxTvl[address] = maxTvlItem.price
} else {
priceDictByMaxTvl[address] = 0
}

}

return priceDictByMaxTvl
}

export const _getUserCollateral = memoize(
async (network: INetworkName, controller: string, user: string, collateralDecimals = 18): Promise<string> => {
const url = `https://prices.curve.fi/v1/lending/collateral_events/${network}/${controller}/${user}`;
Expand All @@ -44,7 +123,7 @@ export const _getUserCollateral = memoize(
export const _getExpected1inch = memoize(
async (fromToken: string, toToken: string, _amount: bigint): Promise<string> => {
if (_amount === BigInt(0)) return "0.0";
const url = `https://prices.curve.fi/1inch/swap/v6.0/${lending.chainId}/quote?src=${fromToken}&dst=${toToken}&amount=${_amount}&protocols=${lending.constants.PROTOCOLS_1INCH}&includeTokensInfo=true&includeProtocols=true`;
const url = `https://prices.curve.fi/1inch/swap/v6.0/${lending.chainId}/quote?src=${fromToken}&dst=${toToken}&amount=${_amount}&excludedProtocols=${lending.constants.EXCLUDED_PROTOCOLS_1INCH}&includeTokensInfo=true&includeProtocols=true`;
const response = await axios.get(
url,
{
Expand All @@ -66,7 +145,7 @@ export const _getExpected1inch = memoize(
export const _getSwapData1inch = memoize(
async (fromToken: string, toToken: string, _amount: bigint, slippage: number): Promise<I1inchSwapData> => {
if (_amount === BigInt(0)) throw Error("Amount must be > 0");
const url = `https://prices.curve.fi/1inch/swap/v6.0/${lending.chainId}/swap?src=${fromToken}&dst=${toToken}&amount=${_amount}&from_=${lending.constants.ALIASES.leverage_zap}&slippage=${slippage}&protocols=${lending.constants.PROTOCOLS_1INCH}&includeTokensInfo=true&includeProtocols=true&disableEstimate=true`;
const url = `https://prices.curve.fi/1inch/swap/v6.0/${lending.chainId}/swap?src=${fromToken}&dst=${toToken}&amount=${_amount}&from_=${lending.constants.ALIASES.leverage_zap}&slippage=${slippage}&excludedProtocols=${lending.constants.EXCLUDED_PROTOCOLS_1INCH}&includeTokensInfo=true&includeProtocols=true&disableEstimate=true`;
const response = await axios.get(
url,
{
Expand All @@ -84,3 +163,33 @@ export const _getSwapData1inch = memoize(
maxAge: 10 * 1000, // 10s
}
)

export const _getSpotPrice1inch = memoize(
async (fromToken: string, toToken: string): Promise<string | undefined> => {
const url = `https://prices.curve.fi/1inch/price/v1.1/${lending.chainId}?tokens=${fromToken},${toToken}&currency=USD`;
console.log(url)
const response = await axios.get(
url,
{
headers: {"accept": "application/json"},
validateStatus: () => true,
});
if (response.status !== 200) {
throw Error(`1inch error: ${response.status} ${response.statusText}`);
}
const pricesFromApi: IDict<string> = {};
for (const coin in response.data) {
if (response.data[coin] !== "0") continue;
const _pricesFromApi = await _getUsdPricesFromApi();
pricesFromApi[coin] = String(_pricesFromApi[coin] || 0);
}
const prices = { ...response.data, ...pricesFromApi };
if (prices[fromToken] === '0' || prices[toToken] === '0') return undefined;

return (new BigNumber(prices[toToken])).div(prices[fromToken]).toString()
},
{
promise: true,
maxAge: 10 * 1000, // 10s
}
)
16 changes: 8 additions & 8 deletions src/lending.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ethers, Contract, Networkish, BigNumberish, Numeric } from "ethers";
import { Provider as MulticallProvider, Contract as MulticallContract, Call } from 'ethcall';
import { IChainId, ILending, IDict, INetworkName, ICurveContract, IOneWayMarket, ICoin } from "./interfaces";
import { IChainId, ILending, IDict, INetworkName, ICurveContract, IOneWayMarket, ICoin } from "./interfaces.js";
import OneWayLendingFactoryABI from "./constants/abis/OneWayLendingFactoryABI.json" assert { type: 'json' };
import ERC20ABI from './constants/abis/ERC20.json' assert { type: 'json' };
import LlammaABI from './constants/abis/Llamma.json' assert { type: 'json' };
Expand Down Expand Up @@ -47,14 +47,14 @@ import {
COINS_BSC,
} from "./constants/coins.js";
import { createCall, handleMultiCallResponse} from "./utils.js";
import {cacheKey, cacheStats} from "./cache";
import {cacheKey, cacheStats} from "./cache/index.js";

export const NETWORK_CONSTANTS: { [index: number]: any } = {
1: {
NAME: 'ethereum',
ALIASES: ALIASES_ETHEREUM,
COINS: COINS_ETHEREUM,
PROTOCOLS_1INCH: "UNISWAP_V1,UNISWAP_V2,SUSHI,MOONISWAP,BALANCER,COMPOUND,CURVE,CURVE_V2_SPELL_2_ASSET,CURVE_V2_SGT_2_ASSET,CURVE_V2_THRESHOLDNETWORK_2_ASSET,CHAI,OASIS,KYBER,AAVE,IEARN,BANCOR,SWERVE,BLACKHOLESWAP,DODO,DODO_V2,VALUELIQUID,SHELL,DEFISWAP,SAKESWAP,LUASWAP,MINISWAP,MSTABLE,SYNTHETIX,AAVE_V2,ST_ETH,ONE_INCH_LP,ONE_INCH_LP_1_1,LINKSWAP,S_FINANCE,PSM,POWERINDEX,XSIGMA,SMOOTHY_FINANCE,SADDLE,KYBER_DMM,BALANCER_V2,UNISWAP_V3,SETH_WRAPPER,CURVE_V2,CURVE_V2_EURS_2_ASSET,CURVE_V2_ETH_CRV,CURVE_V2_ETH_CVX,CONVERGENCE_X,ONE_INCH_LIMIT_ORDER,ONE_INCH_LIMIT_ORDER_V2,ONE_INCH_LIMIT_ORDER_V3,ONE_INCH_LIMIT_ORDER_V4,DFX_FINANCE,FIXED_FEE_SWAP,DXSWAP,SHIBASWAP,UNIFI,PSM_PAX,WSTETH,DEFI_PLAZA,FIXED_FEE_SWAP_V3,SYNTHETIX_WRAPPER,SYNAPSE,CURVE_V2_YFI_2_ASSET,CURVE_V2_ETH_PAL,POOLTOGETHER,ETH_BANCOR_V3,ELASTICSWAP,BALANCER_V2_WRAPPER,FRAXSWAP,RADIOSHACK,KYBERSWAP_ELASTIC,CURVE_V2_TWO_CRYPTO,STABLE_PLAZA,ZEROX_LIMIT_ORDER,CURVE_3CRV,KYBER_DMM_STATIC,ANGLE,ROCKET_POOL,ETHEREUM_ELK,ETHEREUM_PANCAKESWAP_V2,SYNTHETIX_ATOMIC_SIP288,PSM_GUSD,INTEGRAL,MAINNET_SOLIDLY,NOMISWAP_STABLE,CURVE_V2_TWOCRYPTO_META,MAVERICK_V1,VERSE,DFX_FINANCE_V3,ZK_BOB,PANCAKESWAP_V3,NOMISWAPEPCS,XFAI,CURVE_V2_TRICRYPTO_NG,SUSHISWAP_V3,SFRX_ETH,SDAI,ETHEREUM_WOMBATSWAP,CARBON,COMPOUND_V3,DODO_V3,SMARDEX,TRADERJOE_V2_1,PMM15,SOLIDLY_V3,RAFT_PSM,CLAYSTACK,CURVE_STABLE_NG,LIF3,BLUEPRINT,AAVE_V3,ORIGIN,BGD_AAVE_STATIC",
EXCLUDED_PROTOCOLS_1INCH: "CURVE_V2_LLAMMA",
},
10: {
NAME: 'optimism',
Expand Down Expand Up @@ -105,7 +105,7 @@ export const NETWORK_CONSTANTS: { [index: number]: any } = {
NAME: 'arbitrum',
ALIASES: ALIASES_ARBITRUM,
COINS: COINS_ARBITRUM,
PROTOCOLS_1INCH: "ARBITRUM_BALANCER_V2,ARBITRUM_ONE_INCH_LIMIT_ORDER,ARBITRUM_ONE_INCH_LIMIT_ORDER_V2,ARBITRUM_ONE_INCH_LIMIT_ORDER_V3,ARBITRUM_ONE_INCH_LIMIT_ORDER_V4,ARBITRUM_DODO,ARBITRUM_DODO_V2,ARBITRUM_SUSHISWAP,ARBITRUM_DXSWAP,ARBITRUM_UNISWAP_V3,ARBITRUM_CURVE,ARBITRUM_CURVE_V2,ARBITRUM_GMX,ARBITRUM_SYNAPSE,ARBITRUM_SADDLE,ARBITRUM_KYBERSWAP_ELASTIC,ARBITRUM_KYBER_DMM_STATIC,ARBITRUM_AAVE_V3,ARBITRUM_ELK,ARBITRUM_WOOFI_V2,ARBITRUM_CAMELOT,ARBITRUM_TRADERJOE,ARBITRUM_TRADERJOE_V2,ARBITRUM_SWAPFISH,ARBITRUM_ZYBER,ARBITRUM_ZYBER_STABLE,ARBITRUM_SOLIDLIZARD,ARBITRUM_ZYBER_V3,ARBITRUM_MYCELIUM,ARBITRUM_TRIDENT,ARBITRUM_SHELL_OCEAN,ARBITRUM_RAMSES,ARBITRUM_TRADERJOE_V2_1,ARBITRUM_NOMISWAPEPCS,ARBITRUM_CAMELOT_V3,ARBITRUM_WOMBATSWAP,ARBITRUM_CHRONOS,ARBITRUM_LIGHTER,ARBITRUM_ARBIDEX,ARBITRUM_ARBIDEX_V3,ARBSWAP,ARBSWAP_STABLE,ARBITRUM_SUSHISWAP_V3,ARBITRUM_RAMSES_V2,ARBITRUM_LEVEL_FINANCE,ARBITRUM_CHRONOS_V3,ARBITRUM_PANCAKESWAP_V3,ARBITRUM_PMM11,ARBITRUM_DODO_V3,ARBITRUM_SMARDEX,ARBITRUM_INTEGRAL,ARBITRUM_DFX_FINANCE_V3,ARBITRUM_CURVE_STABLE_NG",
EXCLUDED_PROTOCOLS_1INCH: "",
},
42220: {
NAME: 'celo',
Expand Down Expand Up @@ -143,7 +143,7 @@ class Lending implements ILending {
ALIASES: Record<string, string>;
COINS: Record<string, string>;
ZERO_ADDRESS: string,
PROTOCOLS_1INCH: string,
EXCLUDED_PROTOCOLS_1INCH: string,
};

constructor() {
Expand All @@ -166,7 +166,7 @@ class Lending implements ILending {
NETWORK_NAME: 'ethereum',
ALIASES: {},
ZERO_ADDRESS: ethers.ZeroAddress,
PROTOCOLS_1INCH: "",
EXCLUDED_PROTOCOLS_1INCH: "",
};
}

Expand Down Expand Up @@ -194,7 +194,7 @@ class Lending implements ILending {
NETWORK_NAME: 'ethereum',
ALIASES: {},
ZERO_ADDRESS: ethers.ZeroAddress,
PROTOCOLS_1INCH: "",
EXCLUDED_PROTOCOLS_1INCH: "",
};

// JsonRpc provider
Expand Down Expand Up @@ -249,7 +249,7 @@ class Lending implements ILending {
this.constants.NETWORK_NAME = NETWORK_CONSTANTS[this.chainId].NAME;
this.constants.ALIASES = NETWORK_CONSTANTS[this.chainId].ALIASES;
this.constants.COINS = NETWORK_CONSTANTS[this.chainId].COINS;
this.constants.PROTOCOLS_1INCH = NETWORK_CONSTANTS[this.chainId].PROTOCOLS_1INCH;
this.constants.EXCLUDED_PROTOCOLS_1INCH = NETWORK_CONSTANTS[this.chainId].EXCLUDED_PROTOCOLS_1INCH;
this.setContract(this.constants.ALIASES.crv, ERC20ABI);

this.multicallProvider = new MulticallProvider(this.chainId, this.provider);
Expand Down
Loading

0 comments on commit 12627f6

Please sign in to comment.