Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,57 @@
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'
import type { FormValues, LoadMaxAmount } from '@/dex/components/PagePool/Deposit/types'
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,
Expand All @@ -17,7 +61,7 @@ const FieldsDeposit = ({
blockchainId,
poolData,
poolDataCacheOrApi,
routerParams,
routerParams: { rChainId, rPoolId },
tokensMapper,
userPoolBalances,
updateFormValues,
Expand All @@ -33,21 +77,30 @@ const FieldsDeposit = ({
updatedMaxSlippage: string | null,
) => void
} & Pick<TransferProps, 'poolData' | 'poolDataCacheOrApi' | 'routerParams' | 'tokensMapper' | 'userPoolBalances'>) => {
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
? {
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,
)
Expand Down Expand Up @@ -103,8 +156,10 @@ const FieldsDeposit = ({
<FieldsWrapper>
<Checkbox
isDisabled={isDisabled}
isSelected={formValues.isBalancedAmounts}
onChange={(isBalancedAmounts) => 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`}
</Checkbox>
Expand Down
8 changes: 7 additions & 1 deletion apps/main/src/dex/components/PagePool/Deposit/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion apps/main/src/dex/lib/curvejs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 3 additions & 3 deletions apps/main/src/dex/store/createPoolDepositSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ const createPoolDepositSlice = (set: SetState<State>, get: GetState<State>): 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)

Expand Down Expand Up @@ -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: '' })),
}
Expand Down
32 changes: 10 additions & 22 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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"
Expand Down