'use client' import BigNumber from 'bignumber.js' import React, { useEffect, useMemo, useState } from 'react' import { Button } from 'components/Button' import { CircularProgress } from 'components/CircularProgress' import { ArrowsUpDown } from 'components/Icons' import { Slider } from 'components/Slider' import { useCalculateMaxTradeAmount } from 'hooks/data/useCalculateMaxTradeAmount' import { useTradeAsset } from 'hooks/mutations/useTradeAsset' import { useAllBalances } from 'hooks/queries/useAllBalances' import { useAllowedCoins } from 'hooks/queries/useAllowedCoins' import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions' import { useMarkets } from 'hooks/queries/useMarkets' import { useTokenPrices } from 'hooks/queries/useTokenPrices' import useStore from 'store' import { getMarketAssets } from 'utils/assets' import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' import Switch from 'components/Switch' enum FundingMode { Account = 'Account', WalletAndAccount = 'WalletAndAccount', } export const TradeActionModule = () => { const marketAssets = getMarketAssets() const [selectedTokenIn, setSelectedTokenIn] = useState('') const [selectedTokenOut, setSelectedTokenOut] = useState('') const [amountIn, setAmountIn] = useState(0) const [amountOut, setAmountOut] = useState(0) const [slippageTolerance, setSlippageTolerance] = useState(1) const [fundingMode, setFundingMode] = useState(FundingMode.WalletAndAccount) const [isMarginEnabled, setIsMarginEnabled] = React.useState(false) const selectedAccount = useStore((s) => s.selectedAccount) const { data: allowedCoinsData } = useAllowedCoins() const { data: balancesData } = useAllBalances() const { data: marketsData } = useMarkets() const { data: tokenPrices } = useTokenPrices() const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '') const resetAmounts = () => { setAmountIn(0) setAmountOut(0) } useEffect(() => { resetAmounts() }, [selectedAccount]) const accountAmount = useMemo(() => { return Number(positionsData?.coins?.find((coin) => coin.denom === selectedTokenIn)?.amount ?? 0) }, [positionsData, selectedTokenIn]) const walletAmount = useMemo(() => { return Number(balancesData?.find((balance) => balance.denom === selectedTokenIn)?.amount ?? 0) }, [balancesData, selectedTokenIn]) const { swapAmount, borrowAmount, depositAmount } = useMemo(() => { const swapAmount = amountIn let borrowAmount = 0 let depositAmount = 0 if (fundingMode === FundingMode.WalletAndAccount) { const walletAndAccountAmount = walletAmount + accountAmount borrowAmount = amountIn > walletAndAccountAmount ? BigNumber(amountIn).minus(walletAndAccountAmount).toNumber() : 0 depositAmount = amountIn > walletAmount ? walletAmount : amountIn } if (fundingMode === FundingMode.Account) { borrowAmount = amountIn > accountAmount ? BigNumber(amountIn).minus(accountAmount).toNumber() : 0 } return { swapAmount, borrowAmount, depositAmount } }, [accountAmount, amountIn, fundingMode, walletAmount]) const { mutate, isLoading } = useTradeAsset( swapAmount, borrowAmount, depositAmount, selectedTokenIn, selectedTokenOut, slippageTolerance / 100, { onSuccess: () => { useStore.setState({ toast: { message: `${amountIn} ${getTokenSymbol( selectedTokenIn, marketAssets, )} swapped for ${amountOut} ${getTokenSymbol(selectedTokenOut, marketAssets)}`, }, }) resetAmounts() }, }, ) useEffect(() => { if (allowedCoinsData && allowedCoinsData.length > 0) { // initialize selected token when allowedCoins fetch data is available setSelectedTokenIn(allowedCoinsData[0]) if (allowedCoinsData.length > 1) { setSelectedTokenOut(allowedCoinsData[1]) } else { setSelectedTokenOut(allowedCoinsData[0]) } } }, [allowedCoinsData]) const handleSelectedTokenInChange = (e: React.ChangeEvent) => { setSelectedTokenIn(e.target.value) resetAmounts() } const handleSelectedTokenOutChange = (e: React.ChangeEvent) => { setSelectedTokenOut(e.target.value) resetAmounts() } const handleFundingModeChange = (e: React.ChangeEvent) => { setFundingMode(e.target.value as FundingMode) resetAmounts() } // max amount that can be traded without considering wallet amount // wallet amount should just be directly added to this amount in case user wants to include wallet as funding source const maxTradeAmount = useCalculateMaxTradeAmount( selectedTokenIn, selectedTokenOut, isMarginEnabled, ) // if funding from wallet & account, add wallet amount to the max trade amount const maxAmount = useMemo(() => { if (fundingMode === FundingMode.WalletAndAccount) { return walletAmount + maxTradeAmount } return maxTradeAmount }, [fundingMode, maxTradeAmount, walletAmount]) const percentageValue = useMemo(() => { if (isNaN(amountIn) || amountIn === 0) return 0 return amountIn / maxAmount > 1 ? 100 : (amountIn / maxAmount) * 100 }, [amountIn, maxAmount]) const borrowRate = Number(marketsData?.[selectedTokenOut]?.borrow_rate) const handleAmountChange = (value: number, mode: 'in' | 'out') => { const tokenInPrice = tokenPrices?.[selectedTokenIn] ?? 1 const tokenOutPrice = tokenPrices?.[selectedTokenOut] ?? 1 const priceRatio = BigNumber(tokenInPrice).div(tokenOutPrice) if (mode === 'in') { setAmountIn(value) setAmountOut(BigNumber(value).times(priceRatio).decimalPlaces(0).toNumber()) } else { setAmountOut(value) setAmountIn(BigNumber(value).div(BigNumber(1).times(priceRatio)).decimalPlaces(0).toNumber()) } } const submitDisabled = selectedTokenIn === selectedTokenOut || !amountIn || amountIn > maxAmount return (
{isLoading && (
)}

From:

{ const valueAsNumber = e.target.valueAsNumber const valueWithDecimals = valueAsNumber * 10 ** getTokenDecimals(selectedTokenIn, marketAssets) handleAmountChange(valueWithDecimals, 'in') }} />
{ setSelectedTokenIn(selectedTokenOut) setSelectedTokenOut(selectedTokenIn) resetAmounts() }} >

To:

{ const valueAsNumber = e.target.valueAsNumber const valueWithDecimals = valueAsNumber * 10 ** getTokenDecimals(selectedTokenOut, marketAssets) handleAmountChange(valueWithDecimals, 'out') }} />
In Wallet:{' '} {BigNumber(walletAmount) .dividedBy(10 ** getTokenDecimals(selectedTokenIn, marketAssets)) .toNumber() .toLocaleString(undefined, { maximumFractionDigits: getTokenDecimals(selectedTokenIn, marketAssets), })}{' '} {getTokenSymbol(selectedTokenIn, marketAssets)}
In Account:{' '} {BigNumber(accountAmount) .dividedBy(10 ** getTokenDecimals(selectedTokenIn, marketAssets)) .toNumber() .toLocaleString(undefined, { maximumFractionDigits: getTokenDecimals(selectedTokenIn, marketAssets), })}{' '} {getTokenSymbol(selectedTokenIn, marketAssets)}
{ const decimal = value[0] / 100 const tokenDecimals = getTokenDecimals(selectedTokenIn, marketAssets) // limit decimal precision based on token contract decimals const newAmount = Number((decimal * maxAmount).toFixed(0)) handleAmountChange(newAmount, 'in') }} onMaxClick={() => handleAmountChange(maxAmount, 'in')} />

Margin

{ // reset amounts only if margin is turned off if (!value) resetAmounts() setIsMarginEnabled(value) }} />

Borrow

{isMarginEnabled ? BigNumber(borrowAmount) .dividedBy(10 ** getTokenDecimals(selectedTokenIn, marketAssets)) .toNumber() .toLocaleString(undefined, { maximumFractionDigits: getTokenDecimals(selectedTokenIn, marketAssets), }) : '-'}

Borrow Rate

{isMarginEnabled ? `${(borrowRate * 100).toFixed(2)}%` : '-'}

OTHER INFO PLACEHOLDER

Slippage Tolerance:

setSlippageTolerance(e.target.valueAsNumber)} value={slippageTolerance} />

Funded From

) }