diff --git a/apps/main/package.json b/apps/main/package.json index 526d96225d..5f0a55c7a6 100644 --- a/apps/main/package.json +++ b/apps/main/package.json @@ -18,7 +18,7 @@ "analyze": "ANALYZE=true next build" }, "dependencies": { - "@curvefi/api": "2.66.26", + "@curvefi/api": "2.66.28", "@curvefi/llamalend-api": "^1.0.20", "@ethersproject/abi": "^5.8.0", "@hookform/error-message": "^2.0.1", diff --git a/apps/main/src/dex/components/PagePool/Deposit/components/FieldsDeposit.tsx b/apps/main/src/dex/components/PagePool/Deposit/components/FieldsDeposit.tsx index 9453804bd8..9e708d7ef0 100644 --- a/apps/main/src/dex/components/PagePool/Deposit/components/FieldsDeposit.tsx +++ b/apps/main/src/dex/components/PagePool/Deposit/components/FieldsDeposit.tsx @@ -1,3 +1,5 @@ +import { BigNumber } from 'bignumber.js' +import { zip } from 'lodash' import cloneDeep from 'lodash/cloneDeep' import { useMemo } from 'react' import FieldToken from '@/dex/components/PagePool/components/FieldToken' @@ -5,9 +7,51 @@ import type { FormValues, LoadMaxAmount } from '@/dex/components/PagePool/Deposi import { FieldsWrapper } from '@/dex/components/PagePool/styles' import type { TransferProps } from '@/dex/components/PagePool/types' import useStore from '@/dex/store/useStore' +import type { CurrencyReserves } from '@/dex/types/main.types' +import { getChainPoolIdActiveKey } from '@/dex/utils' import Checkbox from '@ui/Checkbox' import { formatNumber } from '@ui/utils' import { t } from '@ui-kit/lib/i18n' +import { Amount } from '../../utils' + +/** + * Format the precision of the balanced value based on the USD price. + * The amount of decimals is determined by the USD price, so it's accurate to around $1. + */ +function formatPrecision(balancedValue: BigNumber, usdPrice: number) { + const decimals = Math.ceil(Math.log10(usdPrice)) + return balancedValue.toFormat(decimals, BigNumber.ROUND_HALF_DOWN, { groupSeparator: '', decimalSeparator: '.' }) +} + +/** + * Calculate new balanced form values based on the changed index and value, keeping the ratios of the other tokens. + * This function is used to update the amounts in the form when a user changes the value of one of the inputs. + */ +function calculateBalancedValues( + [value, changedIndex]: [string, number], + oldAmounts: FormValues['amounts'], + tokenAddresses: string[], + { tokens, totalUsd }: CurrencyReserves, +): Amount[] { + const reserves = Object.fromEntries( + tokens.map((t) => [t.tokenAddress, { usdPrice: t.usdRate, reserveRatio: t.balanceUsd / Number(totalUsd) }]), + ) + const { reserveRatio: changedRatio, usdPrice: changedUsdPrice } = reserves[tokenAddresses[changedIndex]] ?? {} + return zip(oldAmounts, tokenAddresses).map((tuple, index) => { + const [amount, tokenAddress] = tuple as [Amount, string] + if (changedIndex === index) { + return { ...amount, value } + } + const { usdPrice, reserveRatio } = reserves[tokenAddress] ?? {} + if (usdPrice && reserveRatio && changedRatio) { + const valueUsd = BigNumber(value).times(changedUsdPrice) + const balancedValueUsd = BigNumber(valueUsd).times(reserveRatio).div(changedRatio) + const balancedValue = balancedValueUsd.div(usdPrice) + return { ...amount, value: formatPrecision(balancedValue, usdPrice) } + } + return amount + }) +} const FieldsDeposit = ({ formProcessing, @@ -17,7 +61,7 @@ const FieldsDeposit = ({ blockchainId, poolData, poolDataCacheOrApi, - routerParams, + routerParams: { rChainId, rPoolId }, tokensMapper, userPoolBalances, updateFormValues, @@ -33,21 +77,30 @@ const FieldsDeposit = ({ updatedMaxSlippage: string | null, ) => void } & Pick) => { - const { rChainId } = routerParams const network = useStore((state) => state.networks.networks[rChainId]) const balancesLoading = useStore((state) => state.user.walletBalancesLoading) const maxLoading = useStore((state) => state.poolDeposit.maxLoading) const setPoolIsWrapped = useStore((state) => state.pools.setPoolIsWrapped) + const reserves = useStore((state) => state.pools.currencyReserves[getChainPoolIdActiveKey(rChainId, rPoolId)]) + const isBalancedAmounts = formValues.isBalancedAmounts - const handleFormAmountChange = (value: string, idx: number) => { - const clonedFrmAmounts = cloneDeep(formValues.amounts) - clonedFrmAmounts[idx].value = value - + const handleFormAmountChange = (value: string, changedIndex: number) => { updateFormValues( - { - amounts: clonedFrmAmounts, - isBalancedAmounts: false, - }, + isBalancedAmounts && reserves + ? { + amounts: calculateBalancedValues( + [value, changedIndex], + formValues.amounts, + poolDataCacheOrApi.tokenAddresses, + reserves, + ), + isBalancedAmounts: 'by-form', + } + : { + amounts: formValues.amounts.map((amount, index) => + index === changedIndex ? { ...amount, value } : amount, + ), + }, null, null, ) @@ -103,8 +156,10 @@ const FieldsDeposit = ({ updateFormValues({ isBalancedAmounts }, null, null)} + isSelected={!!formValues.isBalancedAmounts} + onChange={(isBalancedAmounts) => + updateFormValues({ isBalancedAmounts: isBalancedAmounts ? 'by-wallet' : false }, null, null) + } > {t`Add all coins in a balanced proportion`} diff --git a/apps/main/src/dex/components/PagePool/Deposit/types.ts b/apps/main/src/dex/components/PagePool/Deposit/types.ts index 93ca18ced1..1823086135 100644 --- a/apps/main/src/dex/components/PagePool/Deposit/types.ts +++ b/apps/main/src/dex/components/PagePool/Deposit/types.ts @@ -14,7 +14,13 @@ export type FormStatus = { export type FormValues = { amounts: Amount[] - isBalancedAmounts: boolean + /** + * The balanced amounts switch works as follows: + * - when selected, we calculate balanced amounts based on the wallet balances ('by-wallet') + * - this happens in curvejs, and tokens with zero balance are ignored + * - when form values change, we recalculate the other amounts based on the changed value, ignoring the wallet ('by-form') + */ + isBalancedAmounts: 'by-wallet' | 'by-form' | false isWrapped: boolean lpToken: string } diff --git a/apps/main/src/dex/lib/curvejs.ts b/apps/main/src/dex/lib/curvejs.ts index 437da55b0a..e5c2c92e06 100644 --- a/apps/main/src/dex/lib/curvejs.ts +++ b/apps/main/src/dex/lib/curvejs.ts @@ -473,7 +473,7 @@ const router = { const poolDeposit = { depositBalancedAmounts: async (activeKey: string, p: Pool, isWrapped: boolean) => { - log('depositBalancedAmounts', p.name, isWrapped) + log('depositBalancedAmounts', p.name, { isWrapped }) const resp = { activeKey, amounts: [] as string[], error: '' } try { resp.amounts = isWrapped ? await p.depositWrappedBalancedAmounts() : await p.depositBalancedAmounts() diff --git a/apps/main/src/dex/store/createPoolDepositSlice.ts b/apps/main/src/dex/store/createPoolDepositSlice.ts index f5691ffc45..107c8c0d0c 100644 --- a/apps/main/src/dex/store/createPoolDepositSlice.ts +++ b/apps/main/src/dex/store/createPoolDepositSlice.ts @@ -237,7 +237,7 @@ const createPoolDepositSlice = (set: SetState, get: GetState): Poo formValues: cloneDeep(cFormValues), maxLoading: null, }) - } else if (cFormValues.isBalancedAmounts) { + } else if (cFormValues.isBalancedAmounts === 'by-wallet') { // get balanced amounts const resp = await curvejsApi.poolDeposit.depositBalancedAmounts(activeKey, pool, cFormValues.isWrapped) @@ -607,10 +607,10 @@ export function getActiveKey( return activeKey } -function resetFormValues(formValues: FormValues) { +function resetFormValues(formValues: FormValues): FormValues { return { ...cloneDeep(formValues), - isBalancedAmounts: false, + isBalancedAmounts: false as const, lpToken: '', amounts: formValues.amounts.map((a) => ({ ...a, value: '' })), } diff --git a/yarn.lock b/yarn.lock index 0a5e733ed6..efcf8bf928 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1709,31 +1709,19 @@ __metadata: languageName: node linkType: hard -"@curvefi/api@npm:2.66.26": - version: 2.66.26 - resolution: "@curvefi/api@npm:2.66.26" +"@curvefi/api@npm:2.66.28": + version: 2.66.28 + resolution: "@curvefi/api@npm:2.66.28" dependencies: - "@curvefi/ethcall": "npm:6.0.12" - bignumber.js: "npm:^9.1.2" - ethers: "npm:^6.13.4" + "@curvefi/ethcall": "npm:^6.0.13" + bignumber.js: "npm:^9.3.0" + ethers: "npm:^6.14.1" memoizee: "npm:^0.4.17" - checksum: 10c0/a801da8491b7420396c879670aacc80f46570bd6b2c76abfd733e5a6fea01f2a69cb707df2ad41cb6f5d089750e83d70825052eedac883e35a5296fc06a1406d - languageName: node - linkType: hard - -"@curvefi/ethcall@npm:6.0.12": - version: 6.0.12 - resolution: "@curvefi/ethcall@npm:6.0.12" - dependencies: - "@types/node": "npm:^22.12.0" - abi-coder: "npm:^5.0.0" - peerDependencies: - ethers: ^6.0.0 - checksum: 10c0/84c7795eac718ffe322f60d848c71189acea9b98d90ee401563ab058eddebe1e37eda135d74986f63fb52cccd828e600a314347cfc3ebcf41cd9d28669737a32 + checksum: 10c0/852e86d6161021934b31920d88c780d7e91becd2085ce305f7cb0eb9088629ccc75a20e88451a163e494ee57e8288736c37911d30f2c2471a1a97441e8b9599c languageName: node linkType: hard -"@curvefi/ethcall@npm:6.0.13": +"@curvefi/ethcall@npm:6.0.13, @curvefi/ethcall@npm:^6.0.13": version: 6.0.13 resolution: "@curvefi/ethcall@npm:6.0.13" dependencies: @@ -12298,7 +12286,7 @@ __metadata: languageName: node linkType: hard -"ethers@npm:^6.13.4, ethers@npm:^6.13.5": +"ethers@npm:^6.13.5": version: 6.13.6 resolution: "ethers@npm:6.13.6" dependencies: @@ -15025,7 +15013,7 @@ __metadata: version: 0.0.0-use.local resolution: "main@workspace:apps/main" dependencies: - "@curvefi/api": "npm:2.66.26" + "@curvefi/api": "npm:2.66.28" "@curvefi/llamalend-api": "npm:^1.0.20" "@ethersproject/abi": "npm:^5.8.0" "@hookform/error-message": "npm:^2.0.1"