Skip to content

Commit 13a82c0

Browse files
committed
wip
1 parent 0169dfb commit 13a82c0

File tree

15 files changed

+593
-2
lines changed

15 files changed

+593
-2
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { ClosePositionProps } from '..'
2+
import type { UserState, UserBalances } from '../types'
3+
4+
type Props = {
5+
userState: Pick<UserState, 'debt' | 'stablecoin'> | undefined
6+
userBalances: UserBalances | undefined
7+
}
8+
9+
/**
10+
* Determines if a user can close their position and how much additional
11+
* stablecoin is required. Applies a 0.01% safety buffer to account for
12+
* potential contract execution edge cases where exact balance matching
13+
* might fail due to rounding or state changes between transaction
14+
* submission and execution.
15+
*
16+
* @returns Object containing required amount to close and missing amount
17+
* @example
18+
* ```typescript
19+
* const result = canClose({
20+
* userState: { debt: '100', stablecoin: '50' },
21+
* userBalances: { stablecoin: '60' }
22+
* })
23+
* // result: { requiredToClose: 50.005, missing: 9.995 }
24+
* ```
25+
*/
26+
export function checkCanClose({ userState, userBalances }: Props): ClosePositionProps['canClose'] {
27+
const { debt = '0', stablecoin = '0' } = userState ?? {}
28+
const { borrowed = 0 } = userBalances ?? {}
29+
30+
const requiredToClose = (parseFloat(debt) - parseFloat(stablecoin)) * 1.0001
31+
const missing = Math.max(0, requiredToClose - borrowed)
32+
33+
return { requiredToClose, missing }
34+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { getTokens } from '@/llamalend/llama.utils'
2+
import type { ActionInfosProps } from '..'
3+
import type { UserState, Market } from '../types'
4+
import { parseFloatOptional } from './float'
5+
6+
type Props = {
7+
market: Market | undefined
8+
userState: Pick<UserState, 'collateral' | 'stablecoin' | 'debt'> | undefined
9+
}
10+
11+
export function getCollateralInfo({ market, userState }: Props): ActionInfosProps['collateral'] {
12+
const { collateral } = userState ?? {}
13+
const amount = parseFloatOptional(collateral)
14+
const { symbol } = (market && getTokens(market))?.collateralToken || {}
15+
const borrowed = (amount && symbol && { symbol, amount }) || undefined
16+
17+
return {
18+
borrowed,
19+
leverage: undefined, // I don't know yet how to determine it so it's not available for now
20+
assetsToWithdraw: undefined, // Not sure what the point is atm, same as 'collateral' action info in loan group?
21+
}
22+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { Address } from '@ui-kit/utils'
2+
import type { ClosePositionProps } from '..'
3+
import type { UserState } from '../types'
4+
5+
type Token = { symbol: string; address: Address; usdRate: number | undefined }
6+
7+
type Props = {
8+
stablecoinToken: Token | undefined
9+
collateralToken: Token | undefined
10+
userState: Pick<UserState, 'collateral' | 'stablecoin' | 'debt'> | undefined
11+
}
12+
13+
/**
14+
* Calculates the recoverable collateral when closing a position
15+
*
16+
* This function determines what assets a user can recover when closing their position:
17+
* 1. Any remaining collateral tokens after position closure
18+
* 2. Excess stablecoin (if stablecoin balance exceeds outstanding debt)
19+
*
20+
* @returns Array of recoverable token objects
21+
*/
22+
export function getCollateralToRecover({
23+
stablecoinToken,
24+
collateralToken,
25+
userState,
26+
}: Props): ClosePositionProps['collateralToRecover'] {
27+
const { collateral, debt, stablecoin } = userState ?? {}
28+
29+
const result: ReturnType<typeof getCollateralToRecover> = []
30+
31+
// Add collateral tokens if user has any remaining after position closure
32+
const collateralBalance = (collateral && parseFloat(collateral)) || 0
33+
if (collateral && collateralToken && collateralBalance > 0) {
34+
result.push({
35+
symbol: collateralToken.symbol,
36+
address: collateralToken.address,
37+
amount: collateralBalance,
38+
usd: collateralBalance * (collateralToken.usdRate ?? 0),
39+
})
40+
}
41+
42+
// Add excess stablecoin (stablecoin balance minus outstanding debt) if positive
43+
const stablecoinBalance = (stablecoin && debt && parseFloat(stablecoin) - parseFloat(debt)) || 0
44+
if (stablecoin && stablecoinToken && stablecoinBalance > 0) {
45+
result.push({
46+
symbol: stablecoinToken.symbol,
47+
address: stablecoinToken.address,
48+
amount: stablecoinBalance,
49+
usd: stablecoinBalance * (stablecoinToken.usdRate ?? 0),
50+
})
51+
}
52+
53+
return result
54+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { getTokens } from '@/llamalend/llama.utils'
2+
import type { ClosePositionProps } from '..'
3+
import type { UserState, Market } from '../types'
4+
5+
type Props = {
6+
market: Market | undefined
7+
userState: Pick<UserState, 'debt'> | undefined
8+
}
9+
10+
/**
11+
* Extracts debt token information for a user's position in a Llamma market.
12+
*
13+
* @returns Debt token object.
14+
*/
15+
export function getDebtToken({ market, userState }: Props): ClosePositionProps['debtToken'] {
16+
const { debt } = userState ?? {}
17+
const { borrowToken } = (market && getTokens(market)) || {}
18+
19+
if (!borrowToken || !borrowToken.address || debt == null) {
20+
return undefined
21+
}
22+
23+
return {
24+
symbol: borrowToken.symbol,
25+
address: borrowToken.address,
26+
amount: parseFloat(debt),
27+
}
28+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Parses a string to a float, returning undefined if the input is null/undefined,
3+
* or 0 if the parsing fails.
4+
*
5+
* @param x - The string to parse as a float
6+
* @returns The parsed float value, undefined if input is null/undefined, or 0 if parsing fails
7+
*/
8+
export const parseFloatOptional = (x?: string) => (x == null ? undefined : parseFloat(x) || 0)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { ActionInfosProps } from '..'
2+
import type { UserLoanDetails } from '../types'
3+
4+
type Props = {
5+
userLoanDetails: Pick<UserLoanDetails, 'healthFull'> | undefined
6+
}
7+
8+
/** Calculates the current health of a user's loan position */
9+
export function getHealthInfo({ userLoanDetails }: Props): ActionInfosProps['health'] {
10+
const { healthFull: healthFullRaw } = userLoanDetails ?? {}
11+
const healthFull = isNaN(parseFloat(healthFullRaw ?? '')) ? 0 : parseFloat(healthFullRaw ?? '')
12+
13+
return { current: healthFull }
14+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export { getDebtToken } from './debt-token'
2+
export { getCollateralToRecover } from './collateral-to-recover'
3+
export { checkCanClose } from './can-close'
4+
export { getHealthInfo } from './health-info'
5+
export { getLoanInfo } from './loan-info'
6+
export { getCollateralInfo } from './collateral-info'
7+
export * from './float'
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { getTokens } from '@/llamalend/llama.utils'
2+
import type { ActionInfosProps } from '..'
3+
import type { LoanParameters, Market, UserState } from '../types'
4+
import type { getCollateralToRecover } from './collateral-to-recover'
5+
import { parseFloatOptional } from './float'
6+
7+
type Props = {
8+
market: Market | undefined
9+
loanParameters: LoanParameters | undefined
10+
userState: Pick<UserState, 'collateral' | 'stablecoin' | 'debt'> | undefined
11+
collateralToRecover: ReturnType<typeof getCollateralToRecover> | undefined
12+
}
13+
14+
export function getLoanInfo({
15+
market,
16+
loanParameters,
17+
userState,
18+
collateralToRecover,
19+
}: Props): ActionInfosProps['loan'] {
20+
const { stablecoin } = userState ?? {}
21+
const amount = parseFloatOptional(stablecoin)
22+
const { symbol } = (market && getTokens(market))?.borrowToken || {}
23+
const debt = (amount && symbol && { symbol, amount }) || undefined
24+
25+
const borrowRate = loanParameters
26+
? {
27+
current: parseFloat(loanParameters.rate),
28+
next: parseFloatOptional(loanParameters.future_rate),
29+
}
30+
: undefined
31+
32+
return {
33+
borrowRate,
34+
debt,
35+
ltv: undefined, // I don't know yet how to determine it so it's not available for now
36+
collateral: (collateralToRecover ?? []).filter(
37+
(item): item is typeof item & { amount: number } => item.amount != null && item.amount > 0,
38+
),
39+
}
40+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { ManageSoftLiquidation, type Props } from './ui/ManageSoftLiquidation'
22
export type { Props as ImproveHealthProps } from './ui/tabs/ImproveHealth'
33
export type { Props as ClosePositionProps } from './ui/tabs/ClosePosition'
4+
export type { Props as ActionInfosProps } from './ui/ActionInfos'
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { Address } from 'viem'
2+
import { useConfig } from 'wagmi'
3+
import type { LlamaMarketTemplate } from '@/llamalend/llamalend.types'
4+
import { useLlammaMutation } from '@/llamalend/mutations/useLlammaMutation'
5+
import { notify } from '@ui-kit/features/connect-wallet'
6+
import { useUserProfileStore } from '@ui-kit/features/user-profile'
7+
import { waitForTransactionReceipt } from '@wagmi/core'
8+
9+
type ClosePositionOptions = {
10+
market: LlamaMarketTemplate | undefined
11+
}
12+
13+
/**
14+
* Hook for closing a market position by self liquidating the user's position
15+
* @param market - The llama market template to close the position for
16+
* @returns Mutation object for closing the position
17+
*/
18+
export function useClosePosition({ market }: ClosePositionOptions) {
19+
const maxSlippage = useUserProfileStore((state) => state.maxSlippage.crypto)
20+
const config = useConfig()
21+
22+
return useLlammaMutation({
23+
mutationKey: ['close-position', { marketId: market?.id }] as const,
24+
market,
25+
mutationFn: async (_: void, { market }) => {
26+
const hash = (await market.selfLiquidate(+maxSlippage)) as Address
27+
await waitForTransactionReceipt(config, { hash })
28+
return { hash }
29+
},
30+
pendingMessage: 'Closing position',
31+
onSuccess: ({ hash }) => {
32+
notify('Position closed', 'success')
33+
// TODO: invalidate specific queries to update the values in close position tab
34+
},
35+
onError: (error: Error) => notify(error.message, 'error'),
36+
})
37+
}

0 commit comments

Comments
 (0)