diff --git a/package.json b/package.json index 9d6ad6e7..8299ad8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@curvefi/api", - "version": "2.4.1", + "version": "2.4.2", "description": "JavaScript library for curve.fi", "main": "lib/index.js", "author": "Macket", diff --git a/src/pools/PoolTemplate.ts b/src/pools/PoolTemplate.ts index 51483499..f61b4734 100644 --- a/src/pools/PoolTemplate.ts +++ b/src/pools/PoolTemplate.ts @@ -1274,13 +1274,9 @@ export class PoolTemplate { return await this.calcLpTokenAmount(amounts, false); } + // OVERRIDE public async withdrawImbalanceBonus(amounts: (number | string)[]): Promise { - if (this.isCrypto) throw Error(`withdrawImbalanceBonus method doesn't exist for pool ${this.name} (id: ${this.name})`); - - const totalAmount = amounts.map(checkNumber).map(Number).reduce((a, b) => a + b); - const expected = Number(await this.withdrawImbalanceExpected(amounts)); - - return await this._withdrawBonus(totalAmount, expected); + throw Error(`withdrawImbalanceBonus method doesn't exist for pool ${this.name} (id: ${this.name})`); } public async withdrawImbalanceIsApproved(amounts: (number | string)[]): Promise { @@ -1337,13 +1333,9 @@ export class PoolTemplate { return await this.calcLpTokenAmountWrapped(amounts, false); } + // OVERRIDE public async withdrawImbalanceWrappedBonus(amounts: (number | string)[]): Promise { - if (this.isCrypto) throw Error(`withdrawImbalanceWrappedBonus method doesn't exist for pool ${this.name} (id: ${this.name})`); - - const totalAmount = amounts.map(checkNumber).map(Number).reduce((a, b) => a + b); - const expected = Number(await this.withdrawImbalanceWrappedExpected(amounts)); - - return await this._withdrawBonus(totalAmount, expected, false); + throw Error(`withdrawImbalanceWrappedBonus method doesn't exist for pool ${this.name} (id: ${this.name})`); } // OVERRIDE @@ -1371,15 +1363,9 @@ export class PoolTemplate { return ethers.utils.formatUnits(_expected, this.underlyingDecimals[i]); } + // OVERRIDE public async withdrawOneCoinBonus(lpTokenAmount: number | string, coin: string | number): Promise { - const totalAmount = Number(await this.withdrawOneCoinExpected(lpTokenAmount, coin)); - - if (this.isCrypto) { - const coinPrice = (await this._underlyingPrices())[this._getCoinIdx(coin)]; - return await this._withdrawCryptoBonus(totalAmount * coinPrice, Number(lpTokenAmount)); - } - - return await this._withdrawBonus(totalAmount, Number(lpTokenAmount)); + throw Error(`withdrawOneCoinBonus method doesn't exist for pool ${this.name} (id: ${this.name})`); } public async withdrawOneCoinIsApproved(lpTokenAmount: number | string): Promise { @@ -1423,19 +1409,9 @@ export class PoolTemplate { return ethers.utils.formatUnits(_expected, this.wrappedDecimals[i]); } + // OVERRIDE public async withdrawOneCoinWrappedBonus(lpTokenAmount: number | string, coin: string | number): Promise { - if (this.isFake || this.isPlain) { - throw Error(`${this.name} pool doesn't have this method`); - } - - const totalAmount = Number(await this.withdrawOneCoinWrappedExpected(lpTokenAmount, coin)); - - if (this.isCrypto) { - const coinPrice = (await this._underlyingPrices())[this._getCoinIdx(coin, false)]; - return await this._withdrawCryptoBonus(totalAmount * coinPrice, Number(lpTokenAmount)); - } - - return await this._withdrawBonus(totalAmount, Number(lpTokenAmount), false); + throw Error(`withdrawOneCoinWrappedBonus method doesn't exist for pool ${this.name} (id: ${this.name})`); } // OVERRIDE @@ -1878,6 +1854,7 @@ export class PoolTemplate { return addresses.length === 1 ? balances[addresses[0]] : balances } + // Used by mixin. Don't delete it!!! private _underlyingPrices = async (): Promise => { const promises = []; for (const addr of this.underlyingCoinAddresses) { @@ -1887,6 +1864,7 @@ export class PoolTemplate { return await Promise.all(promises) } + // Used by mixin. Don't delete it!!! // NOTE! It may crash! private _wrappedPrices = async (): Promise => { const promises = []; @@ -1896,31 +1874,4 @@ export class PoolTemplate { return await Promise.all(promises) } - - private _withdrawCryptoBonus = async (totalAmountUSD: number, lpTokenAmount: number, useUnderlying = true): Promise => { - const prices: number[] = useUnderlying ? await this._underlyingPrices() : await this._wrappedPrices(); - - const balancedAmounts = useUnderlying ? - await this.withdrawExpected(String(lpTokenAmount)) : - await this.withdrawWrappedExpected(String(lpTokenAmount)); - const balancedTotalAmountsUSD = balancedAmounts.reduce((s, b, i) => s + (Number(b) * prices[i]), 0); - - return String((totalAmountUSD - balancedTotalAmountsUSD) / totalAmountUSD * 100) - } - - // TODO make the same as _withdrawCryptoBonus - private _withdrawBonus = async (totalAmount: number, expected: number, useUnderlying = true): Promise => { - const poolBalances: number[] = useUnderlying ? - (await this.stats.underlyingBalances()).map(Number) : - (await this.stats.wrappedBalances()).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.withdrawImbalanceExpected(balancedAmounts)) : - Number(await this.withdrawImbalanceWrappedExpected(balancedAmounts)); - - return String((balancedExpected - expected) / balancedExpected * 100) - } } \ No newline at end of file diff --git a/src/pools/mixins/depositBonusMixins.ts b/src/pools/mixins/depositBonusMixins.ts index c416409d..ab9adf0a 100644 --- a/src/pools/mixins/depositBonusMixins.ts +++ b/src/pools/mixins/depositBonusMixins.ts @@ -1,3 +1,5 @@ +import { ethers} from "ethers"; +import { curve } from "../../curve"; import { PoolTemplate } from "../PoolTemplate"; import { checkNumber } from "../../utils"; @@ -23,15 +25,23 @@ export const depositBonusMixin: PoolTemplate = { // @ts-ignore export const depositWrappedBonusMixin: PoolTemplate = { async depositWrappedBonus(amounts: (number | string)[]): Promise { - const totalAmount = amounts.map(checkNumber).map(Number).reduce((a, b) => a + b); + let vp = 1; + if (this.isMeta) { + const basePoolAddress = curve.constants.POOLS_DATA[this.basePool].swap_address; + vp = Number(ethers.utils.formatUnits(await curve.contracts[basePoolAddress].contract.get_virtual_price(curve.constantOptions))); + } + const prices = this.wrappedCoins.map((_, i, arr) => i === arr.length - 1 ? vp : 1); + const totalValue = amounts.map(checkNumber).map(Number).reduce((s, a, i) => s + (a * prices[i]), 0); const expected = Number(await this.depositWrappedExpected(amounts)); // @ts-ignore const poolBalances: number[] = (await this.stats.wrappedBalances()).map(Number); - const poolTotalBalance: number = poolBalances.reduce((a,b) => a + b); - const poolBalancesRatios: number[] = poolBalances.map((b) => b / poolTotalBalance); + const poolValues = poolBalances.map((b, i) => b * prices[i]); + const poolTotalValue = poolValues.reduce((a,b) => a + b); + const poolRatios = poolValues.map((b) => b / poolTotalValue); - const balancedAmounts = poolBalancesRatios.map((r) => r * totalAmount); + const balancedValues = poolRatios.map((r) => r * totalValue); + const balancedAmounts = balancedValues.map((a, i) => a / prices[i]); const balancedExpected = Number(await this.depositWrappedExpected(balancedAmounts)); return String((expected - balancedExpected) / expected * 100) @@ -65,7 +75,14 @@ export const depositBonusCryptoMixin: PoolTemplate = { export const depositWrappedBonusCryptoMixin: PoolTemplate = { async depositWrappedBonus(amounts: string[]): Promise { // @ts-ignore - const prices = await this._wrappedPrices(); + let prices = await this._wrappedPrices(); + let vp = 1; + if (this.isMeta) { + const basePoolAddress = curve.constants.POOLS_DATA[this.basePool].swap_address; + vp = Number(ethers.utils.formatUnits(await curve.contracts[basePoolAddress].contract.get_virtual_price(curve.constantOptions))); + } + prices = prices.map((p, i) => i === prices.length - 1 ? p * vp : p); + const totalAmountUSD = amounts.map(checkNumber).map(Number).reduce((s, a, i) => s + (a * prices[i]), 0); const expected = Number(await this.depositWrappedExpected(amounts)); diff --git a/src/pools/mixins/withdrawBonusMixins.ts b/src/pools/mixins/withdrawBonusMixins.ts new file mode 100644 index 00000000..09a7ba0f --- /dev/null +++ b/src/pools/mixins/withdrawBonusMixins.ts @@ -0,0 +1,112 @@ +import { ethers} from "ethers"; +import { curve } from "../../curve"; +import { PoolTemplate } from "../PoolTemplate"; +import { checkNumber } from "../../utils"; + + +// @ts-ignore +export const withdrawImbalanceBonusMixin: PoolTemplate = { + async withdrawImbalanceBonus(amounts: (number | string)[]): Promise { + const totalAmount = amounts.map(checkNumber).map(Number).reduce((a, b) => a + b); + const lpTokenAmount = await this.withdrawImbalanceExpected(amounts); + + const balancedAmounts = await this.withdrawExpected(lpTokenAmount); + const balancedTotalAmount = balancedAmounts.map(Number).reduce((a, b) => a + b); + + return String((totalAmount - balancedTotalAmount) / Math.max(totalAmount, balancedTotalAmount) * 100); + }, +} + +// @ts-ignore +export const withdrawImbalanceWrappedBonusMixin: PoolTemplate = { + async withdrawImbalanceWrappedBonus(amounts: (number | string)[]): Promise { + let vp = 1; + if (this.isMeta) { + const basePoolAddress = curve.constants.POOLS_DATA[this.basePool].swap_address; + vp = Number(ethers.utils.formatUnits(await curve.contracts[basePoolAddress].contract.get_virtual_price(curve.constantOptions))); + } + const prices = this.wrappedCoins.map((_, i, arr) => i === arr.length - 1 ? vp : 1); + + const totalValue = amounts.map(checkNumber).map(Number).reduce((s, a, i) => s + (a * prices[i]), 0); + const lpTokenAmount = Number(await this.withdrawImbalanceWrappedExpected(amounts)); + + const balancedAmounts = await this.withdrawWrappedExpected(lpTokenAmount); + const balancedTotalValue = balancedAmounts.map(Number).reduce((s, a, i) => s + (a * prices[i]), 0); + + return String((totalValue - balancedTotalValue) / Math.max(totalValue, balancedTotalValue) * 100); + }, +} + +// @ts-ignore +export const withdrawOneCoinBonusMixin: PoolTemplate = { + async withdrawOneCoinBonus(lpTokenAmount: number | string, coin: string | number): Promise { + const totalAmount = Number(await this.withdrawOneCoinExpected(lpTokenAmount, coin)); + const balancedAmounts = await this.withdrawExpected(lpTokenAmount); + const balancedTotalAmount = balancedAmounts.map(Number).reduce((a, b) => a + b); + + return String((totalAmount - balancedTotalAmount) / Math.max(totalAmount, balancedTotalAmount) * 100); + }, +} + +// @ts-ignore +export const withdrawOneCoinWrappedBonusMixin: PoolTemplate = { + async withdrawOneCoinWrappedBonus(lpTokenAmount: number | string, coin: string | number): Promise { + let vp = 1; + if (this.isMeta) { + const basePoolAddress = curve.constants.POOLS_DATA[this.basePool].swap_address; + vp = Number(ethers.utils.formatUnits(await curve.contracts[basePoolAddress].contract.get_virtual_price(curve.constantOptions))); + } + const prices = this.wrappedCoins.map((_, i, arr) => i === arr.length - 1 ? vp : 1); + + const coinAmount = Number(await this.withdrawOneCoinWrappedExpected(lpTokenAmount, coin)); + // @ts-ignore + const totalValue = coinAmount * prices[this._getCoinIdx(coin)]; + + const balancedAmounts = await this.withdrawWrappedExpected(lpTokenAmount); + const balancedTotalValue = balancedAmounts.map(Number).reduce((s, a, i) => s + (a * prices[i]), 0); + + return String((totalValue - balancedTotalValue) / Math.max(totalValue, balancedTotalValue) * 100); + }, +} + +// @ts-ignore +export const withdrawOneCoinCryptoBonusMixin: PoolTemplate = { + async withdrawOneCoinBonus(lpTokenAmount: number | string, coin: string | number): Promise { + // @ts-ignore + const prices: number[] = await this._underlyingPrices(); + // @ts-ignore + const coinPrice = prices[this._getCoinIdx(coin)]; + + const totalAmount = Number(await this.withdrawOneCoinExpected(lpTokenAmount, coin)); + const totalAmountUSD = totalAmount * coinPrice; + + const balancedAmounts = await this.withdrawExpected(lpTokenAmount); + const balancedTotalAmountsUSD = balancedAmounts.reduce((s, b, i) => s + (Number(b) * prices[i]), 0); + + return String((totalAmountUSD - balancedTotalAmountsUSD) / Math.max(totalAmountUSD, balancedTotalAmountsUSD) * 100) + }, +} + +// @ts-ignore +export const withdrawOneCoinWrappedCryptoBonusMixin: PoolTemplate = { + async withdrawOneCoinWrappedBonus(lpTokenAmount: number | string, coin: string | number): Promise { + let vp = 1; + if (this.isMeta) { + const basePoolAddress = curve.constants.POOLS_DATA[this.basePool].swap_address; + vp = Number(ethers.utils.formatUnits(await curve.contracts[basePoolAddress].contract.get_virtual_price(curve.constantOptions))); + } + + // @ts-ignore + const prices: number[] = (await this._wrappedPrices()).map((p, i) => i === prices.length - 1 ? p * vp : p); + // @ts-ignore + const coinPrice = prices[this._getCoinIdx(coin)]; + + const totalAmount = Number(await this.withdrawOneCoinWrappedExpected(lpTokenAmount, coin)); + const totalAmountUSD = totalAmount * coinPrice; + + const balancedAmounts = await this.withdrawWrappedExpected(lpTokenAmount); + const balancedTotalAmountsUSD = balancedAmounts.reduce((s, b, i) => s + (Number(b) * prices[i]), 0); + + return String((totalAmountUSD - balancedTotalAmountsUSD) / Math.max(totalAmountUSD, balancedTotalAmountsUSD) * 100) + }, +} diff --git a/src/pools/poolConstructor.ts b/src/pools/poolConstructor.ts index 94e974a7..38e3ad6e 100644 --- a/src/pools/poolConstructor.ts +++ b/src/pools/poolConstructor.ts @@ -8,6 +8,7 @@ import { depositWrapped2argsMixin, depositWrapped3argsMixin } from "./mixins/dep import { withdrawExpectedMixin, withdrawExpectedLendingOrCryptoMixin, withdrawExpectedMetaMixin, withdrawExpectedAtricrypto3Mixin, withdrawWrappedExpectedMixin } from "./mixins/withdrawExpectedMixins"; import { withdrawMetaFactoryMixin, withdrawZapMixin, withdrawLendingOrCryptoMixin, withdrawPlainMixin } from "./mixins/withdrawMixins"; import { withdrawWrapped2argsMixin, withdrawWrapped3argsMixin } from "./mixins/withdrawWrappedMixins"; +import { withdrawImbalanceBonusMixin, withdrawOneCoinBonusMixin, withdrawOneCoinCryptoBonusMixin, withdrawImbalanceWrappedBonusMixin, withdrawOneCoinWrappedBonusMixin, withdrawOneCoinWrappedCryptoBonusMixin } from "./mixins/withdrawBonusMixins"; import { withdrawImbalanceMetaFactoryMixin, withdrawImbalanceZapMixin, withdrawImbalanceLendingMixin, withdrawImbalancePlainMixin } from "./mixins/withdrawImbalanceMixins"; import { withdrawImbalanceWrapped2argsMixin, withdrawImbalanceWrapped3argsMixin } from "./mixins/withdrawImbalanceWrappedMixins"; import { withdrawOneCoinExpectedMetaFactoryMixin, withdrawOneCoinExpectedZapMixin, withdrawOneCoinExpected3argsMixin, withdrawOneCoinExpected2argsMixin } from "./mixins/withdrawOneCoinExpectedMixins"; @@ -98,6 +99,24 @@ export const getPool = (poolId: string): PoolTemplate => { Object.assign(Pool.prototype, withdrawPlainMixin); } + // withdrawImbalanceBonus and withdrawOneCoinBonus + if (!poolDummy.isCrypto) { + Object.assign(Pool.prototype, withdrawImbalanceBonusMixin); + Object.assign(Pool.prototype, withdrawOneCoinBonusMixin); + } else { + Object.assign(Pool.prototype, withdrawOneCoinCryptoBonusMixin); + } + + // withdrawImbalanceWrappedBonus and withdrawOneCoinWrappedBonus + if (!poolDummy.isPlain && !poolDummy.isFake) { + if (!poolDummy.isCrypto) { + Object.assign(Pool.prototype, withdrawImbalanceWrappedBonusMixin); + Object.assign(Pool.prototype, withdrawOneCoinWrappedBonusMixin); + } else { + Object.assign(Pool.prototype, withdrawOneCoinWrappedCryptoBonusMixin); + } + } + // withdrawWrapped and withdrawWrappedEstimateGas if (!poolDummy.isPlain && !poolDummy.isFake) { if ((poolDummy.isLending || poolDummy.isCrypto) && !poolDummy.zap) {