Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix withdraw batch #2535

Merged
merged 3 commits into from
Nov 8, 2024
Merged
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
291 changes: 147 additions & 144 deletions centrifuge-app/src/pages/Loan/FinanceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,160 +197,164 @@ function InternalFinanceForm({
<>
{!maturityDatePassed && (
<FormikProvider value={financeForm}>
<Box
px={3}
py={2}
backgroundColor={theme.colors.backgroundSecondary}
borderRadius={10}
border={`1px solid ${theme.colors.borderPrimary}`}
>
<Stack as={Form} gap={2} noValidate ref={financeFormRef}>
<SourceSelect loan={loan} value={source} onChange={onChange} action="finance" />
<Field
name="principal"
validate={combine(positiveNumber(), (val) => {
const principalValue = typeof val === 'number' ? Dec(val) : (val as Decimal)
if (maxAvailable !== UNLIMITED && principalValue.gt(maxAvailable)) {
return `Principal exceeds available financing`
}
return ''
})}
>
{({ field, form }: FieldProps) => {
return (
<CurrencyInput
{...field}
value={field.value instanceof Decimal ? field.value.toNumber() : field.value}
label={isCashLoan(loan) ? 'Amount' : 'Principal'}
currency={displayCurrency}
onChange={(value) => form.setFieldValue('principal', value)}
onSetMax={
maxAvailable !== UNLIMITED ? () => form.setFieldValue('principal', maxAvailable) : undefined
}
/>
)
}}
</Field>
{source === 'other' && (
<Field name="category">
{({ field }: FieldProps) => {
<Form noValidate ref={financeFormRef}>
<Box
px={3}
py={2}
backgroundColor={theme.colors.backgroundSecondary}
borderRadius={10}
border={`1px solid ${theme.colors.borderPrimary}`}
>
<Stack gap={2}>
<SourceSelect loan={loan} value={source} onChange={onChange} action="finance" />
<Field
name="principal"
validate={combine(positiveNumber(), (val) => {
const principalValue = typeof val === 'number' ? Dec(val) : (val as Decimal)
if (maxAvailable !== UNLIMITED && principalValue.gt(maxAvailable)) {
return `Principal exceeds available financing`
}
return ''
})}
>
{({ field, form }: FieldProps) => {
return (
<Select
options={[
{ label: 'Interest', value: 'interest' },
{ label: 'Correction', value: 'correction' },
{ label: 'Miscellaneous', value: 'miscellaneous' },
]}
label="Category"
<CurrencyInput
{...field}
value={field.value instanceof Decimal ? field.value.toNumber() : field.value}
label={isCashLoan(loan) ? 'Amount' : 'Principal'}
currency={displayCurrency}
onChange={(value) => form.setFieldValue('principal', value)}
onSetMax={
maxAvailable !== UNLIMITED ? () => form.setFieldValue('principal', maxAvailable) : undefined
}
/>
)
}}
</Field>
)}
{source === 'reserve' && withdraw.render()}

{poolFees.render()}

<ErrorMessage
type="critical"
condition={totalFinance.gt(0) && maxAvailable !== UNLIMITED && totalFinance.gt(maxAvailable)}
>
{isCashLoan(loan) ? 'Deposit amount' : 'Financing amount'} (
{formatBalance(totalFinance, displayCurrency, 2)}) is greater than the available balance (
{formatBalance(maxAvailable, displayCurrency, 2)}).
</ErrorMessage>

<ErrorMessage
type="default"
condition={
source === 'reserve' && totalFinance.gt(maxAvailable) && pool.reserve.total.gt(pool.reserve.available)
}
>
There is an additional{' '}
{formatBalance(
new CurrencyBalance(pool.reserve.total.sub(pool.reserve.available), pool.currency.decimals),
displayCurrency
)}{' '}
available from repayments or deposits. This requires first executing the orders on the{' '}
<AnchorTextLink href={`#/pools/${pool.id}/liquidity`}>Liquidity tab</AnchorTextLink>.
</ErrorMessage>
</Stack>
</Box>

<Stack gap={2} mt={2} border={`1px solid ${theme.colors.borderPrimary}`} px={3} py={2} borderRadius={10}>
<Text variant="heading4">Transaction summary</Text>
<Box padding={2}>
<Stack gap={1} mb={3}>
<Shelf justifyContent="space-between">
<Tooltip
body={
maxAvailable === UNLIMITED
? 'Unlimited because this is a virtual accounting process.'
: `Balance of the ${source === 'reserve' ? 'onchain reserve' : 'source asset'}.`
}
style={{ pointerEvents: 'auto' }}
>
<Text variant="body2" color="textSecondary">
Available balance
</Text>
</Tooltip>
<Text variant="heading4">
{maxAvailable === UNLIMITED ? 'No limit' : formatBalance(maxAvailable, displayCurrency, 2)}
</Text>
</Shelf>
{source === 'other' && (
<Field name="category">
{({ field }: FieldProps) => {
return (
<Select
options={[
{ label: 'Interest', value: 'interest' },
{ label: 'Correction', value: 'correction' },
{ label: 'Miscellaneous', value: 'miscellaneous' },
]}
label="Category"
{...field}
/>
)
}}
</Field>
)}
{source === 'reserve' && withdraw.render()}

{poolFees.render()}

<ErrorMessage
type="critical"
condition={totalFinance.gt(0) && maxAvailable !== UNLIMITED && totalFinance.gt(maxAvailable)}
>
{isCashLoan(loan) ? 'Deposit amount' : 'Financing amount'} (
{formatBalance(totalFinance, displayCurrency, 2)}) is greater than the available balance (
{formatBalance(maxAvailable, displayCurrency, 2)}).
</ErrorMessage>

<ErrorMessage
type="default"
condition={
source === 'reserve' &&
totalFinance.gt(maxAvailable) &&
pool.reserve.total.gt(pool.reserve.available)
}
>
There is an additional{' '}
{formatBalance(
new CurrencyBalance(pool.reserve.total.sub(pool.reserve.available), pool.currency.decimals),
displayCurrency
)}{' '}
available from repayments or deposits. This requires first executing the orders on the{' '}
<AnchorTextLink href={`#/pools/${pool.id}/liquidity`}>Liquidity tab</AnchorTextLink>.
</ErrorMessage>
</Stack>
</Box>

<Stack gap={1}>
<Stack gap={2} mt={2} border={`1px solid ${theme.colors.borderPrimary}`} px={3} py={2} borderRadius={10}>
<Text variant="heading4">Transaction summary</Text>
<Box padding={2}>
<Stack gap={1} mb={3}>
<Shelf justifyContent="space-between">
<Text variant="body2" color="textSecondary">
{isCashLoan(loan) ? 'Deposit amount' : 'Financing amount'}
<Tooltip
body={
maxAvailable === UNLIMITED
? 'Unlimited because this is a virtual accounting process.'
: `Balance of the ${source === 'reserve' ? 'onchain reserve' : 'source asset'}.`
}
style={{ pointerEvents: 'auto' }}
>
<Text variant="body2" color="textSecondary">
Available balance
</Text>
</Tooltip>
<Text variant="heading4">
{maxAvailable === UNLIMITED ? 'No limit' : formatBalance(maxAvailable, displayCurrency, 2)}
</Text>
<Text variant="heading4">{formatBalance(totalFinance, displayCurrency, 2)}</Text>
</Shelf>
</Stack>

{poolFees.renderSummary()}
</Stack>
<Stack gap={1}>
<Shelf justifyContent="space-between">
<Text variant="body2" color="textSecondary">
{isCashLoan(loan) ? 'Deposit amount' : 'Financing amount'}
</Text>
<Text variant="heading4">{formatBalance(totalFinance, displayCurrency, 2)}</Text>
</Shelf>
</Stack>

{source === 'reserve' ? (
<InlineFeedback status="default">
<Text variant="body2" color="statusDefault">
Stablecoins will be transferred to the designated withdrawal addresses on the specified networks. A
delay may occur before the transfer is completed.
</Text>
</InlineFeedback>
) : source === 'other' ? (
<InlineFeedback status="default">
<Text variant="body2" color="statusDefault">
Virtual accounting process. No onchain stablecoin transfers are expected. This action will lead to
an increase in the NAV of the pool.
</Text>
</InlineFeedback>
) : (
<InlineFeedback status="default">
<Text variant="body2" color="statusDefault">
Virtual accounting process. No onchain stablecoin transfers are expected.
</Text>
</InlineFeedback>
)}
</Box>
</Stack>
{poolFees.renderSummary()}
</Stack>

<Stack>
<Button
type="submit"
loading={isFinanceLoading}
disabled={
!financeForm.values.principal ||
!withdraw.isValid(financeForm) ||
!poolFees.isValid(financeForm) ||
!financeForm.isValid ||
maxAvailable.eq(0)
}
>
{isCashLoan(loan) ? 'Deposit' : 'Finance'}
</Button>
</Stack>
{source === 'reserve' ? (
<InlineFeedback status="default">
<Text variant="body2" color="statusDefault">
Stablecoins will be transferred to the designated withdrawal addresses on the specified networks.
A delay may occur before the transfer is completed.
</Text>
</InlineFeedback>
) : source === 'other' ? (
<InlineFeedback status="default">
<Text variant="body2" color="statusDefault">
Virtual accounting process. No onchain stablecoin transfers are expected. This action will lead to
an increase in the NAV of the pool.
</Text>
</InlineFeedback>
) : (
<InlineFeedback status="default">
<Text variant="body2" color="statusDefault">
Virtual accounting process. No onchain stablecoin transfers are expected.
</Text>
</InlineFeedback>
)}
</Box>
</Stack>

<Stack>
<Button
type="submit"
loading={isFinanceLoading}
disabled={
!financeForm.values.principal ||
!withdraw.isValid(financeForm) ||
!poolFees.isValid(financeForm) ||
!financeForm.isValid ||
maxAvailable.eq(0)
}
>
{isCashLoan(loan) ? 'Deposit' : 'Finance'}
</Button>
</Stack>
</Form>
</FormikProvider>
)}
</>
Expand Down Expand Up @@ -577,12 +581,11 @@ export function useWithdraw(poolId: string, borrower: CombinedSubstrateAccount,
amount={amount}
/>
),
isValid: (_: { values: Pick<FinanceValues, 'withdraw'> }) => {
isValid: () => {
const withdrawalAddresses = Object.values(selectedAddressIndexByCurrency).filter((index) => index !== -1)
return source === 'reserve' ? amount.lte(totalAvailable) && !!withdrawalAddresses.length : true
},
getBatch: ({ values }: { values: Pick<FinanceValues, 'withdraw'> }) => {
if (!values.withdraw) return of([])
getBatch: () => {
const withdrawalAddresses = Object.values(selectedAddressIndexByCurrency).filter((index) => index !== -1)
if (!withdrawalAddresses.length) return of([])
return combineLatest(
Expand Down
Loading