import { Switch } from '@headlessui/react' import BigNumber from 'bignumber.js' import React, { useEffect, useMemo, useState } from 'react' import { toast } from 'react-toastify' import Button from 'components/Button' import CircularProgress from 'components/CircularProgress' import ArrowsUpDownIcon from 'components/Icons/arrows-up-down.svg' import Slider from 'components/Slider' import useTradeAsset from 'hooks/mutations/useTradeAsset' import useAllBalances from 'hooks/useAllBalances' import useAllowedCoins from 'hooks/useAllowedCoins' import useCalculateMaxTradeAmount from 'hooks/useCalculateMaxTradeAmount' import useCreditAccountPositions from 'hooks/useCreditAccountPositions' import useMarkets from 'hooks/useMarkets' import useTokenPrices from 'hooks/useTokenPrices' import { useAccountDetailsStore } from 'stores' import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' enum FundingMode { Account = 'Account', WalletAndAccount = 'WalletAndAccount', } const TradeActionModule = () => { 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 = useAccountDetailsStore((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: () => { toast.success( `${amountIn} ${getTokenSymbol(selectedTokenIn)} swapped for ${amountOut} ${getTokenSymbol( selectedTokenOut, )}`, ) 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) handleAmountChange(valueWithDecimals, 'in') }} />
{ setSelectedTokenIn(selectedTokenOut) setSelectedTokenOut(selectedTokenIn) resetAmounts() }} >

To:

{ const valueAsNumber = e.target.valueAsNumber const valueWithDecimals = valueAsNumber * 10 ** getTokenDecimals(selectedTokenOut) handleAmountChange(valueWithDecimals, 'out') }} />
In Wallet:{' '} {BigNumber(walletAmount) .dividedBy(10 ** getTokenDecimals(selectedTokenIn)) .toNumber() .toLocaleString(undefined, { maximumFractionDigits: getTokenDecimals(selectedTokenIn), })}{' '} {getTokenSymbol(selectedTokenIn)}
In Account:{' '} {BigNumber(accountAmount) .dividedBy(10 ** getTokenDecimals(selectedTokenIn)) .toNumber() .toLocaleString(undefined, { maximumFractionDigits: getTokenDecimals(selectedTokenIn), })}{' '} {getTokenSymbol(selectedTokenIn)}
{ const decimal = value[0] / 100 const tokenDecimals = getTokenDecimals(selectedTokenIn) // 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) }} className={`${ isMarginEnabled ? 'bg-[#524BB1]' : 'bg-gray-400' } relative inline-flex h-4 w-8 items-center rounded-full`} >

Borrow

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

Borrow Rate

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

OTHER INFO PLACEHOLDER

Slippage Tolerance:

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

Funded From

) } export default TradeActionModule