Skip to content

Commit f41bcae

Browse files
committed
wip
1 parent 9f517ac commit f41bcae

File tree

20 files changed

+730
-27
lines changed

20 files changed

+730
-27
lines changed

apps/main/src/llamalend/features/manage-soft-liquidation/ManageSoftLiquidation.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ const collateralToRecover: ClosePositionProps['collateralToRecover'] = [
1717
symbol: 'ETH',
1818
address: ethAddress,
1919
amount: '26539422',
20-
usd: '638000',
20+
usd: 638000,
2121
},
2222
{
2323
symbol: 'crvUSD',
2424
address: CRVUSD_ADDRESS,
2525
amount: '12450',
26-
usd: '12450',
26+
usd: 12450,
2727
},
2828
]
2929

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { decimal } from '@ui-kit/utils'
2+
import type { ClosePositionProps } from '..'
3+
import type { UserState, UserBalances } from '../types'
4+
5+
type Props = {
6+
userState: Pick<UserState, 'debt' | 'stablecoin'> | undefined
7+
userBalances: UserBalances | undefined
8+
}
9+
10+
/**
11+
* Determines if a user can close their position and how much additional
12+
* stablecoin is required. Applies a 0.01% safety buffer to account for
13+
* potential contract execution edge cases where exact balance matching
14+
* might fail due to rounding or state changes between transaction
15+
* submission and execution.
16+
*
17+
* @returns Object containing required amount to close and missing amount
18+
* @example
19+
* ```typescript
20+
* const result = canClose({
21+
* userState: { debt: '100', stablecoin: '50' },
22+
* userBalances: { stablecoin: '60' }
23+
* })
24+
* // result: { requiredToClose: 50.005, missing: 9.995 }
25+
* ```
26+
*/
27+
export function checkCanClose({ userState, userBalances }: Props): ClosePositionProps['canClose'] {
28+
const { debt = '0', stablecoin = '0' } = userState ?? {}
29+
const { borrowed = '0' } = userBalances ?? {}
30+
31+
const requiredToClose = (+debt - +stablecoin) * 1.0001
32+
const missing = Math.max(0, requiredToClose - +borrowed)
33+
34+
return { requiredToClose: decimal(requiredToClose), missing: decimal(missing) }
35+
}
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: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { decimal, 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+
if (collateral && collateralToken && +collateral > 0) {
33+
result.push({
34+
symbol: collateralToken.symbol,
35+
address: collateralToken.address,
36+
amount: collateral,
37+
usd: +collateral * (collateralToken.usdRate ?? 0),
38+
})
39+
}
40+
41+
// Add excess stablecoin (stablecoin balance minus outstanding debt) if positive
42+
const stablecoinBalance = (stablecoin && debt && +stablecoin - +debt) || 0
43+
if (stablecoin && stablecoinToken && stablecoinBalance > 0) {
44+
result.push({
45+
symbol: stablecoinToken.symbol,
46+
address: stablecoinToken.address,
47+
amount: decimal(stablecoinBalance),
48+
usd: stablecoinBalance * (stablecoinToken.usdRate ?? 0),
49+
})
50+
}
51+
52+
return result
53+
}
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: 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'

0 commit comments

Comments
 (0)