diff --git a/README.md b/README.md index 94caf070..db8fb9e7 100644 --- a/README.md +++ b/README.md @@ -1607,7 +1607,9 @@ import curve from "@curvefi/api"; // factory-v2-221 const pool = curve.getPool(poolId); - await pool.depositAndStake([10, 10, 10]); // Initial amounts for stable pool must be equal + const amounts = await pool.getSeedAmounts(10); + // [ '10.0', '10.0', '10.0' ] + await pool.depositAndStake(amounts); const balances = await pool.stats.underlyingBalances(); // [ '10.0', '10.0', '10.0' ] })() @@ -1748,17 +1750,19 @@ import curve from "@curvefi/api"; const pool = curve.getPool(poolId); // Deposit & Stake Wrapped - - await pool.depositAndStakeWrapped([10, 10]); // Initial wrapped amounts for stable metapool must be equal + + const amounts = await pool.getSeedAmounts(10); + // [ '10', '9.666800376685890985' ] + await pool.depositAndStakeWrapped(amounts); const balances = await pool.stats.wrappedBalances(); - // [ '10.0', '10.0' ] + // [ '10', '9.666800376685890985' ] // Or deposit & Stake Underlying - // const amounts = pool.metaUnderlyingSeedAmounts(30); - // [ '30', '10.000000000000000000', '10.000000', '10.000000' ] + // const amounts = pool.getSeedAmounts(10, true); // useUnderlying = true + // [ '10', '3.690404151768511181', '3.713621', '2.595975' ] // await pool.depositAndStake(amounts); - // [ '30.0', '9.272021785560442569', '8.927595', '11.800485' ] + // [ '10', '3.690404151768511181', '3.713621', '2.595975' ] })() ``` @@ -1891,7 +1895,7 @@ import curve from "@curvefi/api"; // factory-crypto-155 const pool = curve.getPool(poolId); - const amounts = await pool.cryptoSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice + const amounts = await pool.getSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice // [ '30', '0.02' ] await pool.depositAndStake(amounts); const underlyingBalances = await pool.stats.underlyingBalances(); @@ -1943,7 +1947,7 @@ import curve from "@curvefi/api"; // factory-twocrypto-155 const pool = curve.getPool(poolId); - const amounts = await pool.cryptoSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice + const amounts = await pool.getSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice // [ '30', '0.02' ] await pool.depositAndStake(amounts); const underlyingBalances = await pool.stats.underlyingBalances(); @@ -2030,7 +2034,7 @@ import curve from "@curvefi/api"; // factory-tricrypto-2 const pool = curve.getPool(poolId); - const amounts = await pool.cryptoSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice + const amounts = await pool.getSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice // [ '30', '0.017647058823529412', '0.00111111' ] await pool.depositAndStake(amounts); const underlyingBalances = await pool.stats.underlyingBalances(); diff --git a/package.json b/package.json index 781ed77c..15c28873 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@curvefi/api", - "version": "2.61.15", + "version": "2.62.0", "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 c08040ad..a93719a9 100644 --- a/src/pools/PoolTemplate.ts +++ b/src/pools/PoolTemplate.ts @@ -584,7 +584,7 @@ export class PoolTemplate { const decimals = useUnderlying ? this.underlyingDecimals : this.wrappedDecimals; const amounts = _amounts.map((_a, i) => curve.formatUnits(_a, decimals[i])); - const seedAmounts = await this.cryptoSeedAmounts(amounts[0]); // Checks N coins is 2 or 3 and amounts > 0 + const seedAmounts = await this.getSeedAmounts(amounts[0]); // Checks N coins is 2 or 3 and amounts > 0 amounts.forEach((a, i) => { if (!BN(a).eq(BN(seedAmounts[i]))) throw Error(`Amounts must be = ${seedAmounts}`); }); @@ -630,17 +630,10 @@ export class PoolTemplate { const decimals = useUnderlying ? this.underlyingDecimals : this.wrappedDecimals; const amounts = _amounts.map((_a, i) => curve.formatUnits(_a, decimals[i])); - if (this.isMeta && useUnderlying) { - const seedAmounts = this.metaUnderlyingSeedAmounts(amounts[0]); // Checks N coins == 2 and amounts > 0 - amounts.forEach((a, i) => { - if (!BN(a).eq(BN(seedAmounts[i]))) throw Error(`Amounts must be = ${seedAmounts}`); - }); - } else { - if (_amounts[0] <= curve.parseUnits("0")) throw Error("Initial deposit amounts must be > 0"); - amounts.forEach((a) => { - if (a !== amounts[0]) throw Error("Initial deposit amounts must be equal"); - }); - } + const seedAmounts = await this.getSeedAmounts(amounts[0]); // Checks N coins == 2 and amounts > 0 + amounts.forEach((a, i) => { + if (!BN(a).eq(BN(seedAmounts[i]))) throw Error(`Amounts must be = ${seedAmounts}`); + }); const _amounts18Decimals: bigint[] = amounts.map((a) => parseUnits(a)); return _amounts18Decimals.reduce((_a, _b) => _a + _b); @@ -682,43 +675,50 @@ export class PoolTemplate { // ---------------- DEPOSIT ---------------- - public metaUnderlyingSeedAmounts(amount1: number | string): string[] { - if (this.isCrypto) throw Error(`Use cryptoSeedAmounts method for ${this.name} pool`); - if (!this.isMeta) throw Error("metaUnderlyingSeedAmounts method exists only for meta stable pools"); - + public async getSeedAmounts(amount1: number | string, useUnderlying = false): Promise { const amount1BN = BN(amount1); if (amount1BN.lte(0)) throw Error("Initial deposit amounts must be > 0"); - const amounts = [_cutZeros(amount1BN.toFixed(this.underlyingDecimals[0]))]; - for (let i = 1; i < this.underlyingDecimals.length; i++) { - amounts.push(amount1BN.div(this.underlyingDecimals.length - 1).toFixed(this.underlyingDecimals[i])); - } + if (this.isCrypto) { + const decimals = this.isMeta ? this.wrappedDecimals : this.underlyingDecimals; + + if (decimals.length === 2) { + const priceScaleBN = toBN(await curve.contracts[this.address].contract.price_scale(curve.constantOptions)); + return [_cutZeros(amount1BN.toFixed(decimals[0])), _cutZeros(amount1BN.div(priceScaleBN).toFixed(decimals[1]))]; + } else if (decimals.length === 3) { + const priceScaleBN = (await curve.multicallProvider.all([ + curve.contracts[this.address].multicallContract.price_scale(0), + curve.contracts[this.address].multicallContract.price_scale(1), + ]) as bigint[]).map((_p) => toBN(_p)); + return [ + _cutZeros(amount1BN.toFixed(decimals[0])), + _cutZeros(amount1BN.div(priceScaleBN[0]).toFixed(decimals[1])), + _cutZeros(amount1BN.div(priceScaleBN[1]).toFixed(decimals[2])), + ]; + } - return amounts - } + throw Error("getSeedAmounts method doesn't exist for crypto pools with N coins > 3"); + } else { + const amounts = [_cutZeros(amount1BN.toFixed(this.wrappedDecimals[0]))]; - public async cryptoSeedAmounts(amount1: number | string): Promise { - if (!this.isCrypto) throw Error("cryptoSeedAmounts method doesn't exist for stable pools"); - const decimals = this.isMeta ? this.wrappedDecimals : this.underlyingDecimals; - const amount1BN = BN(amount1); - if (amount1BN.lte(0)) throw Error("Initial deposit amounts must be > 0"); + if (this.isMeta && useUnderlying) { + const basePool = new PoolTemplate(this.basePool); + const basePoolBalancesBN = (await basePool.stats.underlyingBalances()).map(BN); + const totalBN = basePoolBalancesBN.reduce((a, b) => a.plus(b)); + for (let i = 1; i < this.underlyingDecimals.length; i++) { + amounts.push(amount1BN.times(basePoolBalancesBN[i - 1]).div(totalBN).toFixed(this.underlyingDecimals[i])); + } - if (decimals.length === 2) { - const priceScaleBN = toBN(await curve.contracts[this.address].contract.price_scale(curve.constantOptions)); - return [_cutZeros(amount1BN.toFixed(decimals[0])), _cutZeros(amount1BN.div(priceScaleBN).toFixed(decimals[1]))]; - } else if (decimals.length === 3) { - const priceScaleBN = (await curve.multicallProvider.all([ - curve.contracts[this.address].multicallContract.price_scale(0), - curve.contracts[this.address].multicallContract.price_scale(1), - ]) as bigint[]).map((_p) => toBN(_p)); - return [ - _cutZeros(amount1BN.toFixed(decimals[0])), - _cutZeros(amount1BN.div(priceScaleBN[0]).toFixed(decimals[1])), - _cutZeros(amount1BN.div(priceScaleBN[1]).toFixed(decimals[2])), - ]; - } + return amounts.map(_cutZeros) + } + + const storedRatesBN = await this._storedRatesBN(false); + for (let i = 1; i < this.wrappedDecimals.length; i++) { + amounts.push(amount1BN.times(storedRatesBN[0]).div(storedRatesBN[i]).toFixed(this.wrappedDecimals[i])); + } - throw Error("cryptoSeedAmounts method doesn't exist for pools with N coins > 3"); + return amounts.map(_cutZeros) + } } // OVERRIDE @@ -757,31 +757,21 @@ export class PoolTemplate { public async depositBonus(amounts: (number | string)[]): Promise { const amountsBN = amounts.map(BN); - let prices: number[] = []; - - - //for crvusd and stable-ng implementations - const isUseStoredRates = isMethodExist(curve.contracts[this.address].contract, 'stored_rates') && this.isPlain; + let pricesBN: BigNumber[] = []; if(this.isCrypto || this.id === 'wsteth') { - prices = await this._underlyingPrices(); - } else if (isUseStoredRates) { - const result = await this._stored_rates(); - result.forEach((item, index) => { - prices.push(Number(item)/(10 ** (36 - this.underlyingDecimals[index]))) - }) + pricesBN = (await this._underlyingPrices()).map(BN); } else { - prices = this.underlyingCoins.map(() => 1); + pricesBN = await this._storedRatesBN(true); } - const pricesBN = prices.map(BN); const balancesBN = (await this.stats.underlyingBalances()).map(BN); const balancedAmounts = this._balancedAmountsWithSameValue(amountsBN, pricesBN, balancesBN); const expectedBN = BN(await this.depositExpected(amounts)); const balancedExpectedBN = BN(await this.depositExpected(balancedAmounts)); - return String(expectedBN.minus(balancedExpectedBN).div(balancedExpectedBN).times(100)) + return expectedBN.minus(balancedExpectedBN).div(balancedExpectedBN).times(100).toString() } public async depositIsApproved(amounts: (number | string)[]): Promise { @@ -1666,29 +1656,21 @@ export class PoolTemplate { } public async withdrawImbalanceBonus(amounts: (number | string)[]): Promise { - let prices: number[] = []; - - //for crvusd and stable-ng implementations - const isUseStoredRates = isMethodExist(curve.contracts[this.address].contract, 'stored_rates') && this.isPlain; + let pricesBN: BigNumber[] = []; if(this.isCrypto || this.id === 'wsteth') { - prices = await this._underlyingPrices(); - } else if (isUseStoredRates) { - const result = await this._stored_rates(); - result.forEach((item, index) => { - prices.push(Number(item)/(10 ** (36 - this.underlyingDecimals[index]))) - }) + pricesBN = (await this._underlyingPrices()).map(BN); } else { - prices = this.underlyingCoins.map(() => 1); + pricesBN = await this._storedRatesBN(true); } - const value = amounts.map(checkNumber).map(Number).reduce((s, a, i) => s + (a * prices[i]), 0); + const valueBN = amounts.map(BN).reduce((sBN, aBN, i) => pricesBN[i].times(aBN).plus(sBN), BN(0)); const lpTokenAmount = await this.withdrawImbalanceExpected(amounts); const balancedAmounts = await this.withdrawExpected(lpTokenAmount); - const balancedValue = balancedAmounts.map(Number).reduce((s, a, i) => s + (a * prices[i]), 0); + const balancedValueBN = balancedAmounts.map(BN).reduce((sBN, aBN, i) => pricesBN[i].times(aBN).plus(sBN), BN(0)); - return String((value - balancedValue) / balancedValue * 100); + return valueBN.minus(balancedValueBN).div(balancedValueBN).times(100).toString(); } public async withdrawImbalanceIsApproved(amounts: (number | string)[]): Promise { @@ -1783,30 +1765,23 @@ export class PoolTemplate { } public async withdrawOneCoinBonus(lpTokenAmount: number | string, coin: string | number): Promise { - let prices: number[] = []; - - //for crvusd and stable-ng implementations - const isUseStoredRates = isMethodExist(curve.contracts[this.address].contract, 'stored_rates') && this.isPlain; + let pricesBN: BigNumber[] = []; if(this.isCrypto || this.id === 'wsteth') { - prices = await this._underlyingPrices(); - } else if (isUseStoredRates) { - const result = await this._stored_rates(); - result.forEach((item, index) => { - prices.push(Number(item)/(10 ** (36 - this.underlyingDecimals[index]))) - }) + pricesBN = (await this._underlyingPrices()).map(BN); } else { - prices = this.underlyingCoins.map(() => 1); + pricesBN = await this._storedRatesBN(true); } - const coinPrice = prices[this._getCoinIdx(coin)]; - const amount = Number(await this.withdrawOneCoinExpected(lpTokenAmount, coin)); - const value = amount * coinPrice; + const coinPriceBN = pricesBN[this._getCoinIdx(coin)]; + + const amountBN = BN(await this.withdrawOneCoinExpected(lpTokenAmount, coin)); + const valueBN = amountBN.times(coinPriceBN); const balancedAmounts = await this.withdrawExpected(lpTokenAmount); - const balancedValue = balancedAmounts.map(Number).reduce((s, a, i) => s + (a * prices[i]), 0); + const balancedValueBN = balancedAmounts.map(BN).reduce((sBN, aBN, i) => pricesBN[i].times(aBN).plus(sBN), BN(0)); - return String((value - balancedValue) / balancedValue * 100); + return valueBN.minus(balancedValueBN).div(balancedValueBN).times(100).toString(); } public async withdrawOneCoinIsApproved(lpTokenAmount: number | string): Promise { @@ -2339,8 +2314,22 @@ export class PoolTemplate { return addresses.length === 1 ? balances[addresses[0]] : balances } - private _stored_rates = async (): Promise => { - return await curve.contracts[this.address].contract.stored_rates(); + private _storedRatesBN = async (useUnderlying: boolean): Promise => { + if (this.isMeta) { + if (useUnderlying) return this.underlyingCoins.map(() => BN(1)); + + const _vp = await curve.contracts[curve.getPoolsData()[this.basePool].swap_address].contract.get_virtual_price(); + return [BN(1), toBN(_vp)] + } + + //for crvusd and stable-ng implementations + const isUseStoredRates = isMethodExist(curve.contracts[this.address].contract, 'stored_rates') && this.isPlain; + if (isUseStoredRates) { + const _stored_rates: bigint[] = await curve.contracts[this.address].contract.stored_rates(); + return _stored_rates.map((_r, i) => toBN(_r, 36 - this.wrappedDecimals[i])); + } + + return this.wrappedCoins.map(() => BN(1)) } private _underlyingPrices = async (): Promise => { diff --git a/src/utils.ts b/src/utils.ts index d56e050e..faacab0c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -32,7 +32,7 @@ export const MAX_ALLOWANCE = BigInt("1157920892373161954235709850086879078532699 // Formatting numbers export const _cutZeros = (strn: string): string => { - return strn.replace(/0+$/gi, '').replace(/\.$/gi, ''); + return strn.replace(/(\.\d*[1-9])0+$/gi, '$1').replace(/\.0+$/gi, ''); } export const checkNumber = (n: number | string): number | string => { diff --git a/test/readme.test.ts b/test/readme.test.ts index 797661aa..dbbb71e8 100644 --- a/test/readme.test.ts +++ b/test/readme.test.ts @@ -622,7 +622,9 @@ const deployPlainPoolTest = async () => { console.log(poolId); const pool = curve.getPool(poolId); - await pool.depositAndStake([10, 10, 10]); // Initial amounts for stable pool must be equal + const amounts = await pool.getSeedAmounts(10); + console.log(amounts); + await pool.depositAndStake(amounts); // Initial amounts for stable pool must be equal const balances = await pool.stats.underlyingBalances(); console.log(balances); } @@ -660,13 +662,15 @@ const deployMetaPoolTest = async () => { // Deposit & Stake Wrapped - await pool.depositAndStakeWrapped([10, 10]); // Initial wrapped amounts for stable metapool must be equal + const amounts = await pool.getSeedAmounts(10); + console.log(amounts) + await pool.depositAndStakeWrapped(amounts); const balances = await pool.stats.wrappedBalances(); console.log(balances); // Or deposit & Stake Underlying - // const amounts = pool.metaUnderlyingSeedAmounts(30); + // const amounts = pool.getSeedAmounts(10, true); // useUnderlying = true // console.log(amounts); // await pool.depositAndStake(amounts); // console.log(await pool.stats.underlyingBalances()); @@ -731,7 +735,7 @@ const deployCryptoPoolTest = async () => { console.log(poolId); const pool = curve.getPool(poolId); - const amounts = await pool.cryptoSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice + const amounts = await pool.getSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice console.log(amounts); await pool.depositAndStake(amounts); const underlyingBalances = await pool.stats.underlyingBalances(); @@ -798,7 +802,7 @@ const deployTricryptoPoolTest = async () => { console.log(poolId); const pool = curve.getPool(poolId); - const amounts = await pool.cryptoSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice + const amounts = await pool.getSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice console.log(amounts); await pool.depositAndStake(amounts); const underlyingBalances = await pool.stats.underlyingBalances();