Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 && 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,
)
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