Repay modal (#146)

* enable repay

* update formatters

* remove suffix from numberInput

* implement repay
This commit is contained in:
Bob van der Helm 2023-04-03 13:31:00 +02:00 committed by GitHub
parent 8e0bb97839
commit a747a585af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 124 additions and 41 deletions

View File

@ -30,7 +30,10 @@ export default function AssetExpanded(props: AssetRowProps) {
}
function repayHandler() {
useStore.setState({ repayModal: true })
if (!asset) return null
useStore.setState({
borrowModal: { asset: asset, marketData: props.row.original, isRepay: true },
})
}
return (
@ -51,7 +54,7 @@ export default function AssetExpanded(props: AssetRowProps) {
color='primary'
text={isActive ? 'Borrow more' : 'Borrow'}
/>
{isActive && <Button color='primary' text='Repay' />}
{isActive && <Button color='primary' text='Repay' onClick={repayHandler} />}
</div>
</td>
</tr>

View File

@ -24,6 +24,7 @@ export default function BorrowModal() {
const [selectedAccount, setSelectedAccount] = useState(params.account)
const modal = useStore((s) => s.borrowModal)
const borrow = useStore((s) => s.borrow)
const repay = useStore((s) => s.repay)
const creditAccounts = useStore((s) => s.creditAccounts)
function onAccountSelect(accountId: string) {
@ -32,13 +33,25 @@ export default function BorrowModal() {
function setOpen(isOpen: boolean) {
useStore.setState({ borrowModal: null })
setValue(0)
setPercentage(0)
}
function onBorrowClick() {
function onConfirmClick() {
if (!modal?.asset) return
const amount = new BigNumber(value).shiftedBy(modal.asset.decimals)
if (modal.isRepay) {
repay({
fee: hardcodedFee,
accountId: selectedAccount,
coin: { denom: modal.asset.denom, amount: amount.toString() },
accountBalance: percentage === 100,
})
return
}
borrow({
fee: hardcodedFee,
accountId: selectedAccount,
@ -47,15 +60,22 @@ export default function BorrowModal() {
}
const onSliderChange = useCallback(
(percentage: number, liquidityAmount: number) =>
setValue(new BigNumber(percentage).div(100).times(liquidityAmount).toNumber()),
[],
(percentage: number, maxAmount: number) => {
const amount = new BigNumber(percentage).div(100).times(maxAmount).toNumber()
setValue(amount)
setPercentage(percentage)
},
[modal?.asset.decimals],
)
const onInputChange = useCallback((value: number, liquidityAmount: number) => {
setValue(value)
setPercentage(new BigNumber(value).div(liquidityAmount).times(100).toNumber())
}, [])
const onInputChange = useCallback(
(value: number, maxAmount: number) => {
setValue(value)
setPercentage(new BigNumber(value).div(maxAmount).times(100).toNumber())
},
[modal?.asset.decimals],
)
if (!modal) return null
@ -71,6 +91,15 @@ export default function BorrowModal() {
decimals: 6,
})
let debtAmount = 0
if ((modal.marketData as BorrowAssetActive)?.debt)
debtAmount = Number((modal.marketData as BorrowAssetActive).debt)
const maxAmount = new BigNumber(modal.isRepay ? debtAmount : liquidityAmount)
.shiftedBy(-modal.asset.decimals)
.toNumber()
return (
<Modal
open={true}
@ -78,7 +107,9 @@ export default function BorrowModal() {
header={
<span className='flex items-center gap-4 px-4'>
<Image src={modal?.asset.logo} alt='token' width={24} height={24} />
<Text>Borrow {modal.asset.symbol}</Text>
<Text>
{modal.isRepay ? 'Repay' : 'Borrow'} {modal.asset.symbol}
</Text>
</span>
}
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
@ -90,7 +121,10 @@ export default function BorrowModal() {
sub={'Borrow rate'}
/>
<div className='h-100 w-[1px] bg-white/10'></div>
<TitleAndSubCell title={'$0'} sub={'Borrowed'} />
<TitleAndSubCell
title={formatValue(debtAmount, { abbreviated: true, decimals: modal.asset.decimals })}
sub={'Borrowed'}
/>
<div className='h-100 w-[1px] bg-white/10'></div>
<TitleAndSubCell
title={`${liquidityAmountString} (${liquidityValueString})`}
@ -104,13 +138,13 @@ export default function BorrowModal() {
>
<TokenInput
asset={modal.asset}
onChange={(value) => onInputChange(value, liquidityAmount)}
onChange={(value) => onInputChange(value, maxAmount)}
value={value}
max={liquidityAmount}
max={maxAmount}
/>
<Slider value={percentage} onChange={(value) => onSliderChange(value, liquidityAmount)} />
<Slider value={percentage} onChange={(value) => onSliderChange(value, maxAmount)} />
<Divider />
<Text size='lg'>Borrow to</Text>
<Text size='lg'>{modal.isRepay ? 'Repay for' : 'Borrow to'}</Text>
<select
name='creditAccount'
value={selectedAccount}
@ -124,9 +158,9 @@ export default function BorrowModal() {
))}
</select>
<Button
onClick={onBorrowClick}
onClick={onConfirmClick}
className='w-full'
text='Borrow'
text={modal.isRepay ? 'Repay' : 'Borrow'}
rightIcon={<ArrowRight />}
/>
</Card>

View File

@ -11,7 +11,6 @@ interface Props {
max?: number
maxLength?: number
allowNegative?: boolean
suffix?: string
style?: {}
onChange: (value: number) => void
onBlur?: () => void
@ -73,9 +72,6 @@ export default function NumberInput(props: Props) {
}, [inputValue, inputRef])
const onInputChange = (value: string) => {
if (props.suffix) {
value = value.replace(props.suffix, '')
}
const numberCount = value.match(/[0-9]/g)?.length || 0
const decimals = value.split('.')[1]?.length || 0
const lastChar = value.charAt(value.length - 1)
@ -136,7 +132,7 @@ export default function NumberInput(props: Props) {
<input
ref={inputRef}
type='text'
value={`${inputValue.formatted}${props.suffix ? props.suffix : ''}`}
value={inputValue.formatted === '0' ? '' : inputValue.formatted}
onFocus={onInputFocus}
onChange={(e) => onInputChange(e.target.value)}
onBlur={props.onBlur}

View File

@ -7,8 +7,7 @@ import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
import { Store } from 'store'
import { getMarketAssets } from 'utils/assets'
import { getSingleValueFromBroadcastResult } from 'utils/broadcast'
import { convertFromGwei } from 'utils/formatters'
import { getTokenSymbol } from 'utils/tokens'
import { formatAmountWithSymbol } from 'utils/formatters'
interface BroadcastResult {
result?: TxBroadcastResult
@ -26,6 +25,12 @@ export interface BroadcastSlice {
createCreditAccount: (options: { fee: StdFee }) => Promise<string | null>
deleteCreditAccount: (options: { fee: StdFee; accountId: string }) => Promise<boolean>
deposit: (options: { fee: StdFee; accountId: string; coin: Coin }) => Promise<boolean>
repay: (options: {
fee: StdFee
accountId: string
coin: Coin
accountBalance?: boolean
}) => Promise<boolean>
}
export function createBroadcastSlice(set: SetState<Store>, get: GetState<Store>): BroadcastSlice {
@ -45,7 +50,9 @@ export function createBroadcastSlice(set: SetState<Store>, get: GetState<Store>)
if (response.result?.response.code === 0) {
set({
toast: {
message: `Borrowed ${options.coin.amount} ${options.coin.denom} to Account ${options.accountId}`,
message: `Borrowed ${formatAmountWithSymbol(options.coin)} to Account ${
options.accountId
}`,
},
})
} else {
@ -119,7 +126,9 @@ export function createBroadcastSlice(set: SetState<Store>, get: GetState<Store>)
if (response.result) {
set({
toast: {
message: `Deposited ${options.coin} to Account ${options.accountId}`,
message: `Deposited ${formatAmountWithSymbol(options.coin)} to Account ${
options.accountId
}`,
},
})
} else {
@ -171,5 +180,44 @@ export function createBroadcastSlice(set: SetState<Store>, get: GetState<Store>)
return { result: undefined, error }
}
},
repay: async (options: {
fee: StdFee
accountId: string
coin: Coin
accountBalance?: boolean
}) => {
const msg = {
update_credit_account: {
account_id: options.accountId,
actions: [
{
repay: {
denom: options.coin.denom,
amount: options.accountBalance ? 'account_balance' : { exact: options.coin.amount },
},
},
],
},
}
const response = await get().executeMsg({ msg, fee: options.fee, funds: [] })
if (response.result?.response.code === 0) {
set({
toast: {
message: `Repayed ${formatAmountWithSymbol(options.coin)} to Account ${
options.accountId
}`,
},
})
} else {
set({
toast: {
message: response.error ?? `Transaction failed: ${response.error}`,
isError: true,
},
})
}
return !!response.result
},
}
}

View File

@ -16,6 +16,7 @@ export interface CommonSlice {
borrowModal: {
asset: Asset
marketData: BorrowAsset | BorrowAssetActive
isRepay?: boolean
} | null
client?: WalletClient
clients: {
@ -30,7 +31,6 @@ export interface CommonSlice {
fundAccountModal: boolean
isOpen: boolean
prices: Coin[]
repayModal: boolean
selectedAccount: string | null
signingClient?: SigningCosmWasmClient
status: WalletConnectionStatus

View File

@ -1,6 +1,7 @@
import BigNumber from 'bignumber.js'
import { getTokenDecimals } from 'utils/tokens'
import { getMarketAssets } from './assets'
import { Coin } from '@cosmjs/stargate'
export function truncate(text = '', [h, t]: [number, number] = [6, 6]): string {
const head = text.slice(0, h)
@ -10,18 +11,6 @@ export function truncate(text = '', [h, t]: [number, number] = [6, 6]): string {
return text.length > h + t ? [head, tail].join('...') : text
}
export const convertFromGwei = (amount: string | number, denom: string, marketAssets: Asset[]) => {
return BigNumber(amount)
.div(10 ** getTokenDecimals(denom, marketAssets))
.toNumber()
}
export const convertToGwei = (amount: string | number, denom: string, marketAssets: Asset[]) => {
return BigNumber(amount)
.times(10 ** getTokenDecimals(denom, marketAssets))
.toNumber()
}
export interface FormatOptions {
decimals?: number
minDecimals?: number
@ -135,3 +124,16 @@ export function formatPercent(percent: number | string) {
suffix: '%',
})
}
export function formatAmountWithSymbol(coin: Coin) {
const marketAssets = getMarketAssets()
const asset = marketAssets.find((asset) => asset.denom === coin.denom)
return formatValue(coin.amount, {
decimals: asset?.decimals,
suffix: ` ${asset?.symbol}`,
abbreviated: true,
rounded: true,
})
}