Skip to content

Commit 0bd9772

Browse files
Merge pull request #1735 from curvefi/feat/queries
feat: missing repay queries
2 parents 8fdf87a + c5e008f commit 0bd9772

File tree

11 files changed

+215
-69
lines changed

11 files changed

+215
-69
lines changed

apps/main/src/llamalend/features/manage-loan/components/RepayForm.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import Button from '@mui/material/Button'
1111
import Stack from '@mui/material/Stack'
1212
import { useSwitch } from '@ui-kit/hooks/useSwitch'
1313
import { t } from '@ui-kit/lib/i18n'
14-
import type { Decimal } from '@ui-kit/utils'
1514
import { InputDivider } from '../../../widgets/InputDivider'
1615
import { useRepayForm } from '../hooks/useRepayForm'
1716

@@ -46,14 +45,17 @@ export const RepayForm = <ChainId extends IChainId>({
4645
health,
4746
prices,
4847
gas,
49-
isAvailable,
48+
isDisabled,
5049
isFull,
5150
formErrors,
5251
collateralToken,
5352
borrowToken,
5453
params,
5554
values,
5655
txHash,
56+
expectedBorrowed,
57+
routeImage,
58+
priceImpact,
5759
} = useRepayForm({
5860
market,
5961
network,
@@ -64,8 +66,6 @@ export const RepayForm = <ChainId extends IChainId>({
6466

6567
const marketRates = useMarketRates(params, isOpen)
6668

67-
const isDisabled = formErrors.length > 0 || isAvailable.data === false
68-
6969
return (
7070
<LoanFormWrapper // todo: prevHealth, prevRates, debt, prevDebt
7171
{...form}
@@ -85,7 +85,7 @@ export const RepayForm = <ChainId extends IChainId>({
8585
collateralToken,
8686
borrowToken,
8787
enabled: isOpen,
88-
debtDelta: values.userBorrowed == null ? undefined : (`-${values.userBorrowed}` as Decimal),
88+
expectedBorrowed: expectedBorrowed.data?.totalBorrowed,
8989
})}
9090
gas={gas}
9191
/>

apps/main/src/llamalend/features/manage-loan/hooks/useLoanToValueFromUserState.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ type Params<ChainId extends IChainId> = {
1313
collateralToken: Token | undefined
1414
borrowToken: Token | undefined
1515
enabled: boolean
16-
/** Net change applied to on-chain collateral (positive = adding, negative = removing). */
16+
/**
17+
* Net change applied to on-chain collateral (positive = adding, negative = removing).
18+
* TODO: use expectedCollateral from llamalend-js, currently being implemented by @0xPearce
19+
* */
1720
collateralDelta?: Decimal | null
18-
/** Net change applied to on-chain debt (positive = increasing, negative = repaying). */
19-
debtDelta?: Decimal | null
21+
/** Expected new borrowed amount after the loan is updated. */
22+
expectedBorrowed?: Decimal | null
2023
}
2124

2225
/**
@@ -34,7 +37,7 @@ export const useLoanToValueFromUserState = <ChainId extends IChainId>({
3437
borrowToken,
3538
enabled,
3639
collateralDelta,
37-
debtDelta,
40+
expectedBorrowed,
3841
}: Params<ChainId>) => {
3942
const {
4043
data: userState,
@@ -54,11 +57,9 @@ export const useLoanToValueFromUserState = <ChainId extends IChainId>({
5457
error: borrowUsdRateError,
5558
} = useTokenUsdRate({ chainId, tokenAddress: borrowToken?.address }, enabled && !!borrowToken?.address)
5659

57-
const baseDebt = userState?.debt
5860
const baseCollateral = userState?.collateral
5961

60-
const debt = baseDebt == null ? null : new BigNumber(baseDebt).plus(debtDelta ?? '0')
61-
62+
const debt = expectedBorrowed == null ? null : new BigNumber(expectedBorrowed)
6263
const collateral = baseCollateral == null ? null : new BigNumber(baseCollateral).plus(collateralDelta ?? '0')
6364

6465
return {

apps/main/src/llamalend/features/manage-loan/hooks/useRepayForm.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import { useRepayIsFull } from '@/llamalend/queries/repay/repay-is-full.query'
1515
import { useRepayPriceImpact } from '@/llamalend/queries/repay/repay-price-impact.query'
1616
import { useRepayPrices } from '@/llamalend/queries/repay/repay-prices.query'
1717
import { useRepayRouteImage } from '@/llamalend/queries/repay/repay-route-image.query'
18-
import type { RepayFromCollateralParams } from '@/llamalend/queries/validation/manage-loan.types'
19-
import { repayFormValidationSuite, type RepayForm } from '@/llamalend/queries/validation/manage-loan.validation'
18+
import type { RepayFromCollateralIsFullParams } from '@/llamalend/queries/validation/manage-loan.types'
19+
import { type RepayForm, repayFormValidationSuite } from '@/llamalend/queries/validation/manage-loan.validation'
2020
import type { IChainId as LlamaChainId, INetworkName as LlamaNetworkId } from '@curvefi/llamalend-api/lib/interfaces'
2121
import { vestResolver } from '@hookform/resolvers/vest'
2222
import { useDebouncedValue } from '@ui-kit/hooks/useDebounce'
@@ -54,23 +54,32 @@ export const useRepayForm = <ChainId extends LlamaChainId, NetworkName extends L
5454
stateCollateral: undefined,
5555
userCollateral: undefined,
5656
userBorrowed: undefined,
57+
isFull: false,
5758
},
5859
})
5960

6061
const values = form.watch()
6162

6263
const params = useDebouncedValue(
6364
useMemo(
64-
() =>
65-
({
66-
chainId,
67-
marketId,
68-
userAddress,
69-
stateCollateral: values.stateCollateral,
70-
userCollateral: values.userCollateral,
71-
userBorrowed: values.userBorrowed,
72-
}) as RepayFromCollateralParams<ChainId>,
73-
[chainId, marketId, userAddress, values.stateCollateral, values.userCollateral, values.userBorrowed],
65+
(): RepayFromCollateralIsFullParams<ChainId> => ({
66+
chainId,
67+
marketId,
68+
userAddress,
69+
stateCollateral: values.stateCollateral,
70+
userCollateral: values.userCollateral,
71+
userBorrowed: values.userBorrowed,
72+
isFull: values.isFull,
73+
}),
74+
[
75+
chainId,
76+
marketId,
77+
userAddress,
78+
values.stateCollateral,
79+
values.userCollateral,
80+
values.userBorrowed,
81+
values.isFull,
82+
],
7483
),
7584
)
7685

@@ -90,9 +99,9 @@ export const useRepayForm = <ChainId extends LlamaChainId, NetworkName extends L
9099

91100
const formErrors = useFormErrors(form.formState)
92101

102+
useEffect(() => form.setValue('isFull', isFull.data, { shouldValidate: true }), [form, isFull.data])
103+
93104
return {
94-
chainId,
95-
marketId,
96105
form,
97106
values,
98107
params,
@@ -102,7 +111,7 @@ export const useRepayForm = <ChainId extends LlamaChainId, NetworkName extends L
102111
bands,
103112
expectedBorrowed,
104113
health,
105-
isAvailable,
114+
isDisabled: !isAvailable.data || formErrors.length > 0,
106115
isFull,
107116
priceImpact,
108117
prices,

apps/main/src/llamalend/features/manage-soft-liquidation/hooks/useImproveHealthTab.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export function useImproveHealthTab(params: MarketParams): ImproveHealthProps {
2020
stateCollateral: '0' as Decimal,
2121
userCollateral: '0' as Decimal,
2222
userBorrowed: debt,
23+
isFull: false, // todo: implement full repays
2324
})
2425
},
2526
[mutate],

apps/main/src/llamalend/mutations/repay.mutation.ts

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import { useCallback } from 'react'
22
import type { Address, Hex } from 'viem'
3+
import { useConfig } from 'wagmi'
34
import { formatTokenAmounts } from '@/llamalend/llama.utils'
5+
import { LlamaMarketTemplate } from '@/llamalend/llamalend.types'
46
import { type LlammaMutationOptions, useLlammaMutation } from '@/llamalend/mutations/useLlammaMutation'
7+
import { fetchRepayIsApproved } from '@/llamalend/queries/repay/repay-is-approved.query'
58
import {
69
type RepayForm,
7-
repayFromCollateralValidationSuite,
10+
repayFromCollateralIsFullValidationSuite,
811
} from '@/llamalend/queries/validation/manage-loan.validation'
912
import type { IChainId as LlamaChainId, INetworkName as LlamaNetworkId } from '@curvefi/llamalend-api/lib/interfaces'
1013
import { LendMarketTemplate } from '@curvefi/llamalend-api/lib/lendMarkets'
1114
import { t } from '@ui-kit/lib/i18n'
1215
import { rootKeys } from '@ui-kit/lib/model'
13-
import type { Decimal } from '@ui-kit/utils'
16+
import { type Decimal, waitForApproval } from '@ui-kit/utils'
1417

15-
type RepayMutation = { stateCollateral: Decimal; userCollateral: Decimal; userBorrowed: Decimal }
18+
type RepayMutation = { stateCollateral: Decimal; userCollateral: Decimal; userBorrowed: Decimal; isFull: boolean }
1619

1720
export type RepayOptions = {
1821
marketId: string | undefined
@@ -22,6 +25,41 @@ export type RepayOptions = {
2225
userAddress: Address | undefined
2326
}
2427

28+
const approveRepay = async (market: LlamaMarketTemplate, { userCollateral, userBorrowed, isFull }: RepayMutation) => {
29+
if (isFull) {
30+
return (await market.fullRepayApprove()) as Hex[]
31+
}
32+
if (market instanceof LendMarketTemplate) {
33+
return (await market.leverage.repayApprove(userCollateral, userBorrowed)) as Hex[]
34+
}
35+
if (market.leverageV2.hasLeverage()) {
36+
return (await market.leverageV2.repayApprove(userCollateral, userBorrowed)) as Hex[]
37+
}
38+
console.assert(!+userCollateral, `userCollateral should be 0 for non-leverage repay`)
39+
return (await market.repayApprove(userBorrowed)) as Hex[]
40+
}
41+
42+
const repay = async (market: LlamaMarketTemplate, mutation: RepayMutation): Promise<Hex> => {
43+
const { stateCollateral, userCollateral, userBorrowed, isFull } = mutation
44+
if (isFull) {
45+
return (await market.fullRepay()) as Hex
46+
}
47+
48+
if (market instanceof LendMarketTemplate) {
49+
await market.leverage.repayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed)
50+
return (await market.leverage.repay(stateCollateral, userCollateral, userBorrowed)) as Hex
51+
}
52+
53+
if (market.leverageV2.hasLeverage()) {
54+
await market.leverageV2.repayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed)
55+
return (await market.leverageV2.repay(stateCollateral, userCollateral, userBorrowed)) as Hex
56+
}
57+
58+
console.assert(!+stateCollateral, `stateCollateral should be 0 for non-leverage repay`)
59+
console.assert(!+userBorrowed, `userBorrowed should be 0 for non-leverage repay`)
60+
return (await market.deleverage.repay(userCollateral)) as Hex
61+
}
62+
2563
export const useRepayMutation = ({
2664
network,
2765
network: { chainId },
@@ -30,19 +68,33 @@ export const useRepayMutation = ({
3068
onReset,
3169
userAddress,
3270
}: RepayOptions) => {
71+
const config = useConfig()
3372
const { mutate, mutateAsync, error, data, isPending, isSuccess, reset } = useLlammaMutation<RepayMutation>({
3473
network,
3574
marketId,
3675
mutationKey: [...rootKeys.userMarket({ chainId, marketId, userAddress }), 'repay'] as const,
37-
mutationFn: async ({ userCollateral, userBorrowed, stateCollateral }, { market }) => ({
38-
hash:
39-
market instanceof LendMarketTemplate
40-
? ((await market.leverage.repay(stateCollateral, userCollateral, userBorrowed)) as Hex)
41-
: market.leverageV2.hasLeverage()
42-
? ((await market.leverageV2.repay(stateCollateral, userCollateral, userBorrowed)) as Hex)
43-
: ((await market.deleverage.repay(userCollateral)) as Hex),
44-
}),
45-
validationSuite: repayFromCollateralValidationSuite,
76+
mutationFn: async (mutation, { market }) => {
77+
const { stateCollateral, userBorrowed, userCollateral, isFull } = mutation
78+
79+
await waitForApproval({
80+
isApproved: () =>
81+
fetchRepayIsApproved({
82+
chainId,
83+
marketId,
84+
userAddress,
85+
stateCollateral,
86+
userCollateral,
87+
userBorrowed,
88+
isFull,
89+
}),
90+
onApprove: () => approveRepay(market, mutation),
91+
message: t`Approved repayment`,
92+
config,
93+
})
94+
95+
return { hash: await repay(market, mutation) }
96+
},
97+
validationSuite: repayFromCollateralIsFullValidationSuite,
4698
pendingMessage: (mutation, { market }) => t`Repaying loan... ${formatTokenAmounts(market, mutation)}`,
4799
successMessage: (mutation, { market }) => t`Loan repaid! ${formatTokenAmounts(market, mutation)}`,
48100
onSuccess: onRepaid,

apps/main/src/llamalend/queries/repay/repay-expected-borrowed.query.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import { repayFromCollateralValidationSuite } from '../validation/manage-loan.va
77

88
type RepayExpectedBorrowedResult = {
99
totalBorrowed: Decimal
10-
borrowedFromStateCollateral: Decimal
11-
borrowedFromUserCollateral: Decimal
12-
userBorrowed: Decimal
13-
avgPrice: Decimal
10+
borrowedFromStateCollateral?: Decimal
11+
borrowedFromUserCollateral?: Decimal
12+
userBorrowed?: Decimal
13+
avgPrice?: Decimal
1414
}
1515

1616
export const { useQuery: useRepayExpectedBorrowed } = queryFactory({
@@ -30,20 +30,21 @@ export const { useQuery: useRepayExpectedBorrowed } = queryFactory({
3030
{ userBorrowed },
3131
] as const,
3232
queryFn: async ({ marketId, stateCollateral, userCollateral, userBorrowed }: RepayFromCollateralQuery) => {
33+
// todo: investigate if this is OK when the user's position is not leveraged
3334
const market = getLlamaMarket(marketId)
34-
return market instanceof LendMarketTemplate
35-
? ((await market.leverage.repayExpectedBorrowed(
36-
stateCollateral,
37-
userCollateral,
38-
userBorrowed,
39-
)) as RepayExpectedBorrowedResult)
40-
: market.leverageV2.hasLeverage()
41-
? ((await market.leverageV2.repayExpectedBorrowed(
42-
stateCollateral,
43-
userCollateral,
44-
userBorrowed,
45-
)) as RepayExpectedBorrowedResult)
46-
: null
35+
if (market instanceof LendMarketTemplate) {
36+
const result = await market.leverage.repayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed)
37+
return result as RepayExpectedBorrowedResult
38+
}
39+
if (market.leverageV2.hasLeverage()) {
40+
const result = await market.leverageV2.repayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed)
41+
return result as RepayExpectedBorrowedResult
42+
}
43+
44+
console.assert(!+stateCollateral, `Expected 0 stateCollateral for non-leverage market, got ${stateCollateral}`)
45+
console.assert(!+userBorrowed, `Expected 0 userBorrowed for non-leverage market, got ${userBorrowed}`)
46+
const { stablecoins, routeIdx } = await market.deleverage.repayStablecoins(userCollateral)
47+
return { totalBorrowed: stablecoins[routeIdx] as Decimal }
4748
},
4849
staleTime: '1m',
4950
validationSuite: repayFromCollateralValidationSuite,

apps/main/src/llamalend/queries/repay/repay-gas-estimate.query.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import type { IChainId } from '@curvefi/llamalend-api/lib/interfaces'
55
import { LendMarketTemplate } from '@curvefi/llamalend-api/lib/lendMarkets'
66
import { type FieldsOf } from '@ui-kit/lib'
77
import { queryFactory, rootKeys } from '@ui-kit/lib/model'
8-
import type { RepayFromCollateralQuery } from '../validation/manage-loan.types'
9-
import { repayFromCollateralValidationSuite } from '../validation/manage-loan.validation'
8+
import type { RepayFromCollateralHealthQuery } from '../validation/manage-loan.types'
9+
import { repayFromCollateralIsFullValidationSuite } from '../validation/manage-loan.validation'
10+
import { repayIsFullQueryKey } from './repay-is-full.query'
1011

11-
type RepayFromCollateralGasQuery<T = IChainId> = RepayFromCollateralQuery<T>
12+
type RepayFromCollateralGasQuery<T = IChainId> = RepayFromCollateralHealthQuery<T>
1213
type RepayFromCollateralGasParams<T = IChainId> = FieldsOf<RepayFromCollateralGasQuery<T>>
1314

1415
const { useQuery: useRepayGasEstimate } = queryFactory({
@@ -19,23 +20,47 @@ const { useQuery: useRepayGasEstimate } = queryFactory({
1920
userCollateral = '0',
2021
userBorrowed = '0',
2122
userAddress,
23+
isFull,
2224
}: RepayFromCollateralGasParams) =>
2325
[
2426
...rootKeys.userMarket({ chainId, marketId, userAddress }),
2527
'repay-from-collateral-gas-estimation',
2628
{ stateCollateral },
2729
{ userCollateral },
2830
{ userBorrowed },
31+
{ isFull },
2932
] as const,
30-
queryFn: async ({ marketId, stateCollateral, userCollateral, userBorrowed }: RepayFromCollateralGasQuery) => {
33+
queryFn: async ({
34+
marketId,
35+
stateCollateral,
36+
userCollateral,
37+
userBorrowed,
38+
isFull,
39+
userAddress,
40+
}: RepayFromCollateralGasQuery) => {
3141
const market = getLlamaMarket(marketId)
32-
return market instanceof LendMarketTemplate
33-
? await market.leverage.estimateGas.repay(stateCollateral, userCollateral, userBorrowed)
34-
: market.leverageV2.hasLeverage()
35-
? await market.leverageV2.estimateGas.repay(stateCollateral, userCollateral, userBorrowed)
36-
: await market.deleverage.estimateGas.repay(userCollateral)
42+
if (isFull) {
43+
return market instanceof LendMarketTemplate
44+
? await market.estimateGas.fullRepay(userAddress)
45+
: await market.fullRepayEstimateGas(userAddress)
46+
}
47+
if (market instanceof LendMarketTemplate) {
48+
return await market.leverage.estimateGas.repay(stateCollateral, userCollateral, userBorrowed)
49+
}
50+
if (market.leverageV2.hasLeverage()) {
51+
return await market.leverageV2.estimateGas.repay(stateCollateral, userCollateral, userBorrowed)
52+
}
53+
return await market.deleverage.estimateGas.repay(userCollateral)
3754
},
38-
validationSuite: repayFromCollateralValidationSuite,
55+
validationSuite: repayFromCollateralIsFullValidationSuite,
56+
dependencies: ({
57+
chainId,
58+
marketId,
59+
stateCollateral = '0',
60+
userCollateral = '0',
61+
userBorrowed = '0',
62+
userAddress,
63+
}) => [repayIsFullQueryKey({ chainId, marketId, stateCollateral, userCollateral, userBorrowed, userAddress })],
3964
})
4065

4166
export const useRepayEstimateGas = <ChainId extends IChainId>(

0 commit comments

Comments
 (0)