Auto repay on trade (#631)

This commit is contained in:
Linkie Link 2023-11-13 17:55:03 +01:00 committed by GitHub
parent 28f27c3882
commit 8e976a5d70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 113 additions and 9 deletions

View File

@ -32,6 +32,7 @@ export default function Size(props: Props) {
const color = getAmountChangeColor(type, amountChange)
const className = classNames('text-xs text-right', color)
const allowZero = !amountChange.isZero()
if (size >= 1)
return (
@ -44,11 +45,12 @@ export default function Size(props: Props) {
)
const formattedAmount = formatAmountToPrecision(size, MAX_AMOUNT_DECIMALS)
const lowAmount = formattedAmount === 0 ? MIN_AMOUNT : Math.max(formattedAmount, MIN_AMOUNT)
const minimumAmount = allowZero ? 0 : MIN_AMOUNT
const lowAmount = formattedAmount === 0 ? minimumAmount : Math.max(formattedAmount, MIN_AMOUNT)
return (
<FormattedNumber
className={className}
smallerThanThreshold={formattedAmount < MIN_AMOUNT}
smallerThanThreshold={!allowZero && formattedAmount < MIN_AMOUNT}
amount={lowAmount}
options={{
maxDecimals: MAX_AMOUNT_DECIMALS,

View File

@ -24,12 +24,17 @@ export const valueSortingFn = (a: Row<AccountBalanceRow>, b: Row<AccountBalanceR
export default function Value(props: Props) {
const { amountChange, type, value } = props
const color = getAmountChangeColor(type, amountChange)
const allowZero = !amountChange.isZero()
const coin = new BNCoin({
denom: ORACLE_DENOM,
amount: value,
})
return (
<DisplayCurrency coin={coin} className={classNames('text-xs text-right', color)} showZero />
<DisplayCurrency
coin={coin}
className={classNames('text-xs text-right', color)}
showZero={!allowZero}
/>
)
}

View File

@ -0,0 +1,32 @@
import Switch from 'components/Switch'
import Text from 'components/Text'
import { Tooltip } from 'components/Tooltip'
interface Props {
checked: boolean
onChange: (value: boolean) => void
buyAssetSymbol: string
}
export default function AutoRepayToggle(props: Props) {
return (
<div className='flex flex-row justify-between flex-1 px-4 py-2 bg-white/5'>
<div className='flex items-center gap-1'>
<Text size='sm'>Auto Repay Debt</Text>
<Tooltip
type='info'
content={
<Text size='sm'>
Use the bought {props.buyAssetSymbol} directly to repay your {props.buyAssetSymbol}{' '}
debt.
</Text>
}
/>
</div>
<div className='flex flex-row'>
<Switch {...props} name='repay' />
</div>
</div>
)
}

View File

@ -7,6 +7,7 @@ import DepositCapMessage from 'components/DepositCapMessage'
import Divider from 'components/Divider'
import RangeInput from 'components/RangeInput'
import AssetAmountInput from 'components/Trade/TradeModule/SwapForm/AssetAmountInput'
import AutoRepayToggle from 'components/Trade/TradeModule/SwapForm/AutoRepayToggle'
import MarginToggle from 'components/Trade/TradeModule/SwapForm/MarginToggle'
import OrderTypeSelector from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector'
import { AvailableOrderType } from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector/types'
@ -38,6 +39,7 @@ interface Props {
export default function SwapForm(props: Props) {
const { buyAsset, sellAsset } = props
const useMargin = useStore((s) => s.useMargin)
const useAutoRepay = useStore((s) => s.useAutoRepay)
const account = useCurrentAccount()
const swap = useStore((s) => s.swap)
const [slippage] = useLocalStorage(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
@ -46,7 +48,9 @@ export default function SwapForm(props: Props) {
const { data: marketAssets } = useMarketAssets()
const { data: route, isLoading: isRouteLoading } = useSwapRoute(sellAsset.denom, buyAsset.denom)
const isBorrowEnabled = !!marketAssets.find(byDenom(sellAsset.denom))?.borrowEnabled
const isRepayable = account?.debts.find(byDenom(buyAsset.denom))
const [isMarginChecked, setMarginChecked] = useToggle(isBorrowEnabled ? useMargin : false)
const [isAutoRepayChecked, setAutoRepayChecked] = useToggle(isRepayable ? useAutoRepay : false)
const [buyAssetAmount, setBuyAssetAmount] = useState(BN_ZERO)
const [sellAssetAmount, setSellAssetAmount] = useState(BN_ZERO)
const [maxBuyableAmountEstimation, setMaxBuyableAmountEstimation] = useState(BN_ZERO)
@ -157,6 +161,7 @@ export default function SwapForm(props: Props) {
denomOut: buyAsset.denom,
slippage,
isMax: sellAssetAmount.isEqualTo(maxSellAmount),
repay: isAutoRepayChecked,
})
}, [
removedLends,
@ -173,9 +178,15 @@ export default function SwapForm(props: Props) {
const debouncedUpdateAccount = useMemo(
() =>
debounce((removeCoin: BNCoin, addCoin: BNCoin, debtCoin: BNCoin) => {
simulateTrade(removeCoin, addCoin, debtCoin, isAutoLendEnabled ? 'lend' : 'deposit')
simulateTrade(
removeCoin,
addCoin,
debtCoin,
isAutoLendEnabled ? 'lend' : 'deposit',
isAutoRepayChecked,
)
}, 100),
[simulateTrade, isAutoLendEnabled],
[simulateTrade, isAutoLendEnabled, isAutoRepayChecked],
)
const handleMarginToggleChange = useCallback(
@ -185,6 +196,13 @@ export default function SwapForm(props: Props) {
},
[isBorrowEnabled, setMarginChecked],
)
const handleAutoRepayToggleChange = useCallback(
(isAutoRepay: boolean) => {
useStore.setState({ useAutoRepay: isAutoRepay })
setAutoRepayChecked(isAutoRepay)
},
[setAutoRepayChecked],
)
useEffect(() => {
setBuyAssetAmount(BN_ZERO)
@ -195,6 +213,7 @@ export default function SwapForm(props: Props) {
BNCoin.fromDenomAndBigNumber(sellAsset.denom, BN_ZERO),
BNCoin.fromDenomAndBigNumber(sellAsset.denom, BN_ZERO),
isAutoLendEnabled ? 'lend' : 'deposit',
isAutoRepayChecked,
)
}, [
isBorrowEnabled,
@ -202,6 +221,7 @@ export default function SwapForm(props: Props) {
buyAsset.denom,
sellAsset.denom,
isAutoLendEnabled,
isAutoRepayChecked,
simulateTrade,
setMarginChecked,
])
@ -286,6 +306,15 @@ export default function SwapForm(props: Props) {
borrowAssetSymbol={sellAsset.symbol}
/>
<Divider />
{isRepayable && (
<AutoRepayToggle
checked={isAutoRepayChecked}
onChange={handleAutoRepayToggleChange}
buyAssetSymbol={buyAsset.symbol}
/>
)}
<Divider />
<OrderTypeSelector selected={selectedOrderType} onChange={setSelectedOrderType} />
<div className='flex flex-col gap-6 px-3 mt-6'>
<AssetAmountInput
@ -318,7 +347,6 @@ export default function SwapForm(props: Props) {
asset={borrowAsset}
/>
)}
<AssetAmountInput
label='Sell'
max={maxSellAmount}

View File

@ -135,7 +135,13 @@ export function useUpdatedAccount(account?: Account) {
)
const simulateTrade = useCallback(
(removeCoin: BNCoin, addCoin: BNCoin, debtCoin: BNCoin, target: 'deposit' | 'lend') => {
(
removeCoin: BNCoin,
addCoin: BNCoin,
debtCoin: BNCoin,
target: 'deposit' | 'lend',
repay: boolean,
) => {
removeDeposits([])
removeLends([])
addDebts([])
@ -143,13 +149,29 @@ export function useUpdatedAccount(account?: Account) {
addLends([])
const { deposit, lend } = getDepositAndLendCoinsToSpend(removeCoin, account)
const currentDebtCoin = account?.debts.find(byDenom(addCoin.denom))
let usedAmountForDebt = BN_ZERO
if (!deposit.amount.isZero()) removeDeposits([deposit])
if (!lend.amount.isZero()) removeLends([lend])
if (target === 'deposit') addDeposits([addCoin])
if (target === 'lend') addLends([addCoin])
if (repay && currentDebtCoin) {
if (currentDebtCoin.amount.isGreaterThanOrEqualTo(addCoin.amount)) {
removeDebts([addCoin])
usedAmountForDebt = addCoin.amount
} else {
removeDebts([currentDebtCoin])
usedAmountForDebt = currentDebtCoin.amount
}
}
const remainingAddCoin = BNCoin.fromDenomAndBigNumber(
addCoin.denom,
addCoin.amount.minus(usedAmountForDebt),
)
if (target === 'deposit') addDeposits(repay ? [remainingAddCoin] : [addCoin])
if (target === 'lend') addLends(repay ? [remainingAddCoin] : [addCoin])
if (debtCoin.amount.isGreaterThan(BN_ZERO)) addDebts([debtCoin])
},
[account, addDebts, addDeposits, addLends, removeDeposits, removeLends],

View File

@ -689,6 +689,7 @@ export default function createBroadcastSlice(
denomOut: string
slippage: number
isMax?: boolean
repay: boolean
}) => {
const msg: CreditManagerExecuteMsg = {
update_credit_account: {
@ -703,6 +704,17 @@ export default function createBroadcastSlice(
slippage: options.slippage.toString(),
},
},
...(options.repay
? [
{
repay: {
coin: BNCoin.fromDenomAndBigNumber(options.denomOut, BN_ZERO).toActionCoin(
true,
),
},
},
]
: []),
],
},
}

View File

@ -13,6 +13,7 @@ export default function createCommonSlice(set: SetState<CommonSlice>, get: GetSt
migrationBanner: true,
tutorial: true,
useMargin: true,
useAutoRepay: true,
isOracleStale: false,
}
}

View File

@ -126,6 +126,7 @@ interface BroadcastSlice {
denomOut: string
slippage: number
isMax?: boolean
repay: boolean
}) => ExecutableTx
toast: ToastResponse | ToastPending | null
unlock: (options: {

View File

@ -12,6 +12,7 @@ interface CommonSlice {
migrationBanner: boolean
tutorial: boolean
useMargin: boolean
useAutoRepay: boolean
isOracleStale: boolean
}