Skip to content

Commit

Permalink
feat: slippage calculations for add/remove liquidity (closes #1)
Browse files Browse the repository at this point in the history
  • Loading branch information
Makeev Ivan committed Oct 7, 2021
1 parent 5715a15 commit b5d37cc
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 1 deletion.
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": "1.5.1",
"version": "1.6.0",
"description": "JavaScript library for curve.fi",
"main": "lib/index.js",
"scripts": {
Expand Down
74 changes: 74 additions & 0 deletions src/pools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,17 @@ export class Pool {
}

public addLiquidityExpected = async (amounts: string[]): Promise<string> => {
amounts = amounts.map((a, i) => Number(a).toFixed(this.underlyingDecimals[i]));
return await this.calcLpTokenAmount(amounts);
}

public addLiquiditySlippage = async (amounts: string[]): Promise<string> => {
const totalAmount = amounts.reduce((s, a) => s + Number(a), 0);
const expected = Number(await this.addLiquidityExpected(amounts));

return await this._addLiquiditySlippage(totalAmount, expected);
}

public addLiquidityIsApproved = async (amounts: string[]): Promise<boolean> => {
const spender = (['compound', 'usdt', 'y', 'busd', 'pax'].includes(this.name) || this.isMeta) ? this.zap as string : this.swap;
return await hasAllowance(this.underlyingCoinAddresses, amounts, curve.signerAddress, spender);
Expand Down Expand Up @@ -287,9 +295,17 @@ export class Pool {
}

public addLiquidityWrappedExpected = async (amounts: string[]): Promise<string> => {
amounts = amounts.map((a, i) => Number(a).toFixed(this.decimals[i]));
return await this.calcLpTokenAmountWrapped(amounts);
}

public addLiquidityWrappedSlippage = async (amounts: string[]): Promise<string> => {
const totalAmount = amounts.reduce((s, a) => s + Number(a), 0);
const expected = Number(await this.addLiquidityWrappedExpected(amounts));

return await this._addLiquiditySlippage(totalAmount, expected, false);
}

public addLiquidityWrappedIsApproved = async (amounts: string[]): Promise<boolean> => {
return await hasAllowance(this.coinAddresses, amounts, curve.signerAddress, this.swap);
}
Expand Down Expand Up @@ -460,9 +476,17 @@ export class Pool {
}

public removeLiquidityImbalanceExpected = async (amounts: string[]): Promise<string> => {
amounts = amounts.map((a, i) => Number(a).toFixed(this.underlyingDecimals[i]));
return await this.calcLpTokenAmount(amounts, false);
}

public removeLiquidityImbalanceSlippage = async (amounts: string[]): Promise<string> => {
const totalAmount = amounts.reduce((s, a) => s + Number(a), 0);
const expected = Number(await this.removeLiquidityImbalanceExpected(amounts));

return await this._removeLiquiditySlippage(totalAmount, expected);
}

public removeLiquidityImbalanceIsApproved = async (amounts: string[]): Promise<boolean> => {
const _amounts: ethers.BigNumber[] = amounts.map((amount: string, i: number) => ethers.utils.parseUnits(amount, this.underlyingDecimals[i]));

Expand Down Expand Up @@ -560,9 +584,17 @@ export class Pool {
}

public removeLiquidityImbalanceWrappedExpected = async (amounts: string[]): Promise<string> => {
amounts = amounts.map((a, i) => Number(a).toFixed(this.underlyingDecimals[i]));
return await this.calcLpTokenAmountWrapped(amounts, false);
}

public removeLiquidityImbalanceWrappedSlippage = async (amounts: string[]): Promise<string> => {
const totalAmount = amounts.reduce((s, a) => s + Number(a), 0);
const expected = Number(await this.removeLiquidityImbalanceWrappedExpected(amounts));

return await this._removeLiquiditySlippage(totalAmount, expected, false);
}

private removeLiquidityImbalanceWrappedEstimateGas = async (amounts: string[]): Promise<number> => {
const lpTokenAmount = await this.removeLiquidityImbalanceExpected(amounts);
const lpTokenBalance = (await this.lpTokenBalances())['lpToken'];
Expand Down Expand Up @@ -607,6 +639,12 @@ export class Pool {
return ethers.utils.formatUnits(_expected, this.underlyingDecimals[i]);
}

public removeLiquidityOneCoinSlippage = async (lpTokenAmount: string, coin: string | number): Promise<string> => {
const totalAmount = Number(await this.removeLiquidityOneCoinExpected(lpTokenAmount, coin));

return await this._removeLiquiditySlippage(totalAmount, Number(lpTokenAmount));
}

public removeLiquidityOneCoinIsApproved = async (lpTokenAmount: string): Promise<boolean> => {
if (!['compound', 'usdt', 'y', 'busd', 'pax'].includes(this.name) && !(this.name === 'susd') && !this.isMeta) return true

Expand Down Expand Up @@ -688,6 +726,12 @@ export class Pool {
return ethers.utils.formatUnits(_expected, this.decimals[i]);
}

public removeLiquidityOneCoinWrappedSlippage = async (lpTokenAmount: string, coin: string | number): Promise<string> => {
const totalAmount = Number(await this.removeLiquidityOneCoinWrappedExpected(lpTokenAmount, coin));

return await this._removeLiquiditySlippage(totalAmount, Number(lpTokenAmount), false);
}


private removeLiquidityOneCoinWrappedEstimateGas = async (lpTokenAmount: string, coin: string | number): Promise<number> => {
const lpTokenBalance = (await this.lpTokenBalances())['lpToken'];
Expand Down Expand Up @@ -1180,6 +1224,36 @@ export class Pool {
return addresses.length === 1 ? balances[addresses[0]] : balances
}

private _addLiquiditySlippage = async (totalAmount: number, expected: number, useUnderlying = true): Promise<string> => {
const poolBalances: number[] = useUnderlying ?
(await this.getPoolBalances()).map(Number) :
(await this.getPoolWrappedBalances()).map(Number);
const poolTotalBalance: number = poolBalances.reduce((a,b) => a + b);
const poolBalancesRatios: number[] = poolBalances.map((b) => b / poolTotalBalance);

const balancedAmounts: string[] = poolBalancesRatios.map((r) => String(r * totalAmount));
const balancedExpected = useUnderlying ?
Number(await this.addLiquidityExpected(balancedAmounts)) :
Number(await this.addLiquidityWrappedExpected(balancedAmounts));

return String((balancedExpected - expected) / balancedExpected)
}

private _removeLiquiditySlippage = async (totalAmount: number, expected: number, useUnderlying = true): Promise<string> => {
const poolBalances: number[] = useUnderlying ?
(await this.getPoolBalances()).map(Number) :
(await this.getPoolWrappedBalances()).map(Number);
const poolTotalBalance: number = poolBalances.reduce((a,b) => a + b);
const poolBalancesRatios: number[] = poolBalances.map((b) => b / poolTotalBalance);

const balancedAmounts: string[] = poolBalancesRatios.map((r) => String(r * totalAmount));
const balancedExpected = useUnderlying ?
Number(await this.removeLiquidityImbalanceExpected(balancedAmounts)) :
Number(await this.removeLiquidityImbalanceWrappedExpected(balancedAmounts));

return String((expected - balancedExpected) / balancedExpected)
}

private _balancedAmounts = (poolBalances: number[], walletBalances: number[], decimals: number[]): string[] => {
const poolBalancesRatios = poolBalances.map((b) => b / poolBalances.reduce((a,b) => a + b));
// Cross factors for each wallet balance used as reference to see the
Expand Down

0 comments on commit b5d37cc

Please sign in to comment.