@babylonlabs-io/ts-sdk 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BTCVaultRegistry.abi-DBW0dyqH.js → BTCVaultRegistry.abi-BmHpMUWD.js} +1 -52
- package/dist/BTCVaultRegistry.abi-BmHpMUWD.js.map +1 -0
- package/dist/BTCVaultRegistry.abi-DffWFdpA.cjs +2 -0
- package/dist/BTCVaultRegistry.abi-DffWFdpA.cjs.map +1 -0
- package/dist/BTCVaultRegistryKeyEpochs.abi-Btg0nBz1.js +262 -0
- package/dist/BTCVaultRegistryKeyEpochs.abi-Btg0nBz1.js.map +1 -0
- package/dist/BTCVaultRegistryKeyEpochs.abi-CReWFJdu.cjs +2 -0
- package/dist/BTCVaultRegistryKeyEpochs.abi-CReWFJdu.cjs.map +1 -0
- package/dist/{PeginManager-QRwMUxZg.js → PeginManager-Dfr5Qe1g.js} +2 -2
- package/dist/{PeginManager-QRwMUxZg.js.map → PeginManager-Dfr5Qe1g.js.map} +1 -1
- package/dist/{PeginManager-8EUlm10F.cjs → PeginManager-PyKbfy5q.cjs} +2 -2
- package/dist/{PeginManager-8EUlm10F.cjs.map → PeginManager-PyKbfy5q.cjs.map} +1 -1
- package/dist/VaultErrors.abi-Boz4w97c.js +5 -0
- package/dist/VaultErrors.abi-Boz4w97c.js.map +1 -0
- package/dist/VaultErrors.abi-DQ18kM0L.cjs +2 -0
- package/dist/VaultErrors.abi-DQ18kM0L.cjs.map +1 -0
- package/dist/{buildAndBroadcastReclaim-DjX7wVb2.cjs → buildAndBroadcastReclaim-Da8rD5Z7.cjs} +2 -2
- package/dist/{buildAndBroadcastReclaim-DjX7wVb2.cjs.map → buildAndBroadcastReclaim-Da8rD5Z7.cjs.map} +1 -1
- package/dist/{buildAndBroadcastReclaim-DoprHX7o.js → buildAndBroadcastReclaim-R0HNpLMD.js} +2 -2
- package/dist/{buildAndBroadcastReclaim-DoprHX7o.js.map → buildAndBroadcastReclaim-R0HNpLMD.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +222 -219
- package/dist/index.js.map +1 -1
- package/dist/{pegin-registration-client-D4EguWaz.cjs → pegin-registration-client-C-abxMWq.cjs} +2 -2
- package/dist/{pegin-registration-client-D4EguWaz.cjs.map → pegin-registration-client-C-abxMWq.cjs.map} +1 -1
- package/dist/{pegin-registration-client-BWMwxtr8.js → pegin-registration-client-kdz4fT5_.js} +3 -3
- package/dist/{pegin-registration-client-BWMwxtr8.js.map → pegin-registration-client-kdz4fT5_.js.map} +1 -1
- package/dist/{signer-set-reader-C1VmC9_U.js → signer-set-reader-BCtmSKUM.js} +2 -2
- package/dist/{signer-set-reader-C1VmC9_U.js.map → signer-set-reader-BCtmSKUM.js.map} +1 -1
- package/dist/{signer-set-reader-C6g47hdk.cjs → signer-set-reader-DFAu6I-v.cjs} +2 -2
- package/dist/{signer-set-reader-C6g47hdk.cjs.map → signer-set-reader-DFAu6I-v.cjs.map} +1 -1
- package/dist/tbv/core/clients/eth/index.cjs +1 -1
- package/dist/tbv/core/clients/eth/index.js +2 -2
- package/dist/tbv/core/clients/index.cjs +1 -1
- package/dist/tbv/core/clients/index.js +2 -2
- package/dist/tbv/core/contracts/__tests__/vaultErrors.abi.test.d.ts +2 -0
- package/dist/tbv/core/contracts/__tests__/vaultErrors.abi.test.d.ts.map +1 -0
- package/dist/tbv/core/contracts/abis/BTCVaultRegistry.abi.d.ts +0 -40
- package/dist/tbv/core/contracts/abis/BTCVaultRegistry.abi.d.ts.map +1 -1
- package/dist/tbv/core/contracts/abis/VaultErrors.abi.d.ts +3 -0
- package/dist/tbv/core/contracts/abis/VaultErrors.abi.d.ts.map +1 -0
- package/dist/tbv/core/contracts/abis/vaultErrors.abi.json.d.ts +4 -0
- package/dist/tbv/core/contracts/errors.d.ts +7 -2
- package/dist/tbv/core/contracts/errors.d.ts.map +1 -1
- package/dist/tbv/core/contracts/index.cjs +1 -1
- package/dist/tbv/core/contracts/index.d.ts +2 -1
- package/dist/tbv/core/contracts/index.d.ts.map +1 -1
- package/dist/tbv/core/contracts/index.js +13 -10
- package/dist/tbv/core/contracts/index.js.map +1 -1
- package/dist/tbv/core/index.cjs +1 -1
- package/dist/tbv/core/index.js +210 -207
- package/dist/tbv/core/index.js.map +1 -1
- package/dist/tbv/core/managers/index.cjs +1 -1
- package/dist/tbv/core/managers/index.js +1 -1
- package/dist/tbv/core/services/index.cjs +1 -1
- package/dist/tbv/core/services/index.js +1 -1
- package/dist/tbv/core/utils/eth/index.cjs +1 -1
- package/dist/tbv/core/utils/eth/index.cjs.map +1 -1
- package/dist/tbv/core/utils/eth/index.js +20 -20
- package/dist/tbv/core/utils/eth/index.js.map +1 -1
- package/dist/tbv/core/utils/eth/waitForTransactionReceiptSmartAware.d.ts.map +1 -1
- package/dist/tbv/index.cjs +1 -1
- package/dist/tbv/index.js +210 -207
- package/dist/tbv/index.js.map +1 -1
- package/dist/tbv/integrations/aave/clients/abis/AaveIntegrationAdapter.abi.json.d.ts +7 -701
- package/dist/tbv/integrations/aave/clients/query.d.ts +2 -2
- package/dist/tbv/integrations/aave/index.cjs +1 -1
- package/dist/tbv/integrations/aave/index.cjs.map +1 -1
- package/dist/tbv/integrations/aave/index.js +119 -119
- package/dist/tbv/integrations/aave/index.js.map +1 -1
- package/dist/{toRefundInputs-BZXvuOMY.cjs → toRefundInputs-BbAOOUg6.cjs} +2 -2
- package/dist/{toRefundInputs-BZXvuOMY.cjs.map → toRefundInputs-BbAOOUg6.cjs.map} +1 -1
- package/dist/{toRefundInputs-dKDSvpVI.js → toRefundInputs-DW14g6wH.js} +2 -2
- package/dist/{toRefundInputs-dKDSvpVI.js.map → toRefundInputs-DW14g6wH.js.map} +1 -1
- package/package.json +2 -2
- package/dist/BTCVaultRegistry.abi-CohlJX7c.cjs +0 -2
- package/dist/BTCVaultRegistry.abi-CohlJX7c.cjs.map +0 -1
- package/dist/BTCVaultRegistry.abi-DBW0dyqH.js.map +0 -1
- package/dist/BTCVaultRegistryKeyEpochs.abi-BWoxwPQv.js +0 -266
- package/dist/BTCVaultRegistryKeyEpochs.abi-BWoxwPQv.js.map +0 -1
- package/dist/BTCVaultRegistryKeyEpochs.abi-DCJnzKer.cjs +0 -2
- package/dist/BTCVaultRegistryKeyEpochs.abi-DCJnzKer.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../../../../src/tbv/integrations/aave/constants.ts","../../../../src/tbv/integrations/aave/clients/query.ts","../../../../src/tbv/integrations/aave/clients/spoke.ts","../../../../src/tbv/integrations/aave/clients/positionProxy.ts","../../../../src/tbv/integrations/aave/clients/oracle.ts","../../../../src/tbv/integrations/aave/clients/hub.ts","../../../../src/tbv/integrations/aave/clients/transaction.ts","../../../../src/tbv/integrations/aave/utils/aaveConversions.ts","../../../../src/tbv/integrations/aave/utils/debtUtils.ts","../../../../src/tbv/integrations/aave/utils/healthFactor.ts","../../../../src/tbv/integrations/aave/utils/cascadeSimulation.ts","../../../../src/tbv/integrations/aave/utils/optimalOrder.ts","../../../../src/tbv/integrations/aave/utils/vaultSplit.ts"],"sourcesContent":["/**\n * Aave Protocol Constants\n *\n * Constants for interacting with Aave v4 protocol.\n * Reference: https://github.com/aave/aave-v4 ISpoke.sol\n */\n\n/**\n * Aave contract function names\n * Centralized constants for contract interactions\n */\nexport const AAVE_FUNCTION_NAMES = {\n /** Withdraw selected vaults from position (partial withdrawal) */\n WITHDRAW_COLLATERALS: \"withdrawCollaterals\",\n /** Borrow from Core Spoke position */\n BORROW: \"borrowFromCorePosition\",\n /** Repay debt to Core Spoke position */\n REPAY: \"repayToCorePosition\",\n /** Reorder vault prefix ordering for liquidation priority */\n REORDER_VAULTS: \"reorderVaults\",\n} as const;\n\n/**\n * Full basis points scale (10000 BPS = 100%)\n *\n * Use this when converting BPS directly to decimal:\n * Example: 8000 BPS / 10000 = 0.80\n */\nexport const BPS_SCALE = 10000;\n\n/**\n * Aave base currency decimals\n * Account data values (collateral, debt) use 1e26 = $1 USD\n *\n * Reference: ISpoke.sol UserAccountData\n */\nexport const AAVE_BASE_CURRENCY_DECIMALS = 26;\n\n/**\n * Aave RAY-scaled base currency decimals\n * Debt values (totalDebtValueRay) use 1e53 = $1 USD\n * (base currency 1e26 scaled by RAY 1e27).\n *\n * Reference: IAaveSpoke.sol UserAccountData.totalDebtValueRay\n */\nexport const AAVE_BASE_CURRENCY_RAY_DECIMALS = 53;\n\n/**\n * WAD decimals (1e18 = 1.0)\n * Used for health factor and collateral factor values\n *\n * Reference: ISpoke.sol - \"healthFactor expressed in WAD. 1e18 represents a health factor of 1.00\"\n */\nexport const WAD_DECIMALS = 18;\n\n/**\n * Dimensionless health factor at or above which Aave cannot liquidate a position.\n *\n * ISpoke.sol defines this boundary as 1.0 (1e18 WAD), not a stored parameter.\n * Unlike display thresholds, it is not tunable. The per-asset\n * liquidationThresholdBps is fetched from chain and uses basis points.\n */\nexport const HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1.0;\n\n/**\n * Display threshold for positions close to liquidation, independent of the borrow floor.\n * A position from HEALTH_FACTOR_LIQUIDATION_THRESHOLD up to and including this\n * value is \"risky\".\n */\nexport const HEALTH_FACTOR_RISKY_THRESHOLD = 1.1;\n\n/**\n * Health factor warning threshold.\n * A position above HEALTH_FACTOR_RISKY_THRESHOLD and up to and including this\n * value is \"warning\". A position above this value is \"safe\".\n */\nexport const HEALTH_FACTOR_WARNING_THRESHOLD = 2.0;\n\n/**\n * Minimum health factor allowed for borrowing. Collateral factor doubles as the\n * liquidation threshold here, so this floor is the only borrow→liquidation cushion.\n */\nexport const MIN_HEALTH_FACTOR_FOR_BORROW = 1.05;\n\n/**\n * Approval headroom for repay-all, sized against interest accrual between\n * quoting the debt and transaction execution.\n *\n * 0.5% buffer (50 basis points). Sized to absorb hours of execution delay\n * (e.g. Safe-multisig quorum collection). The repay itself sends the\n * repay-all sentinel and the adapter pulls only what's actually owed; the\n * buffer only pads the approval cap, and the cap is additionally bounded by\n * the user's balance, so a larger buffer never blocks a legitimate repay.\n */\nexport const FULL_REPAY_BUFFER_DIVISOR = 200n; // 1/200 = 0.5% buffer\n\n/**\n * Sentinel the Spoke stores in `MAX_USER_RESERVES_LIMIT` to mean \"no cap\"\n * (`type(uint16).max`). The contract skips the reserve-count check on this\n * value, so callers must treat it as unlimited and never show it to a user.\n *\n * Reference: Spoke.sol MAX_ALLOWED_USER_RESERVES_LIMIT\n */\nexport const MAX_ALLOWED_USER_RESERVES_LIMIT = 65535;\n","/**\n * Aave Integration Adapter - Read operations (queries)\n *\n * Only includes functions that provide data NOT available from the indexer.\n * Most position/vault data should be fetched from the GraphQL indexer instead.\n */\n\nimport { type Address, type Hex, type PublicClient, zeroAddress } from \"viem\";\n\nimport type { AaveMarketPosition, PositionSizeParams } from \"../types.js\";\nimport AaveIntegrationAdapterABI from \"./abis/AaveIntegrationAdapter.abi.json\";\n\n/**\n * The adapter's custom error for \"this account has no position proxy\".\n *\n * `AaveAdapter._getBorrowerProxy` reverts with it when `userToProxy[user]` is\n * the zero address, which is every account that has never opened a position —\n * so on a freshly deployed adapter it is the normal answer for a new user, not\n * a failure. Matched by name (the ABI carries the error entry, so viem decodes\n * it) rather than by raw selector.\n */\nconst NO_POSITION_ERROR_NAME = \"InvalidProxyContract\";\n\n/** Bound on the `cause` walk, so a self-referencing chain cannot spin. */\nconst MAX_ERROR_CAUSE_DEPTH = 10;\n\n/**\n * True when `err` is the adapter reverting because the account has no proxy.\n *\n * Matches structurally on viem's decoded `data.errorName` rather than with\n * `instanceof BaseError` / `err.walk(...)`. The SDK lists `viem` as external,\n * so a consumer can — and in this monorepo does — resolve a physically\n * different copy of the same viem version than the one bundled here (pnpm\n * keys the store path on peer versions). Errors thrown by the caller's client\n * are instances of the caller's classes, so `instanceof` silently returns\n * false across that boundary and the revert would escape as an opaque throw.\n *\n * Narrow on purpose: any other revert, and every transport/RPC failure, must\n * keep propagating. Reporting those as \"no position\" would turn an infra\n * outage into a silent, wrong answer for callers that gate signing on it.\n */\nfunction isNoPositionRevert(err: unknown): boolean {\n let current: unknown = err;\n for (let depth = 0; depth < MAX_ERROR_CAUSE_DEPTH; depth++) {\n if (typeof current !== \"object\" || current === null) return false;\n const { data, cause } = current as {\n data?: { errorName?: unknown };\n cause?: unknown;\n };\n if (data?.errorName === NO_POSITION_ERROR_NAME) return true;\n if (cause === undefined || cause === current) return false;\n current = cause;\n }\n return false;\n}\n\n/**\n * Get a position by user address.\n *\n * The adapter resolves the user's proxy contract and collateralized vault IDs.\n *\n * NOTE: Prefer using the indexer (fetchAavePositionWithCollaterals) for position data.\n * This function is only needed when you need data not available in the indexer,\n * or when you need to verify on-chain state.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param contractAddress - AaveIntegrationAdapter contract address\n * @param user - User's Ethereum address\n * @returns Market position data or null if position doesn't exist\n */\nexport async function getPosition(\n publicClient: PublicClient,\n contractAddress: Address,\n user: Address,\n): Promise<AaveMarketPosition | null> {\n type PositionResult = {\n proxyContract: Address;\n vaultIds: Hex[];\n totalCollateralBTC: bigint;\n };\n\n let result: unknown;\n try {\n result = await publicClient.readContract({\n address: contractAddress,\n abi: AaveIntegrationAdapterABI,\n functionName: \"getPosition\",\n args: [user],\n });\n } catch (err) {\n // \"No proxy yet\" is a revert, not a zero-address return value, so it has\n // to be caught here for this function to honour its documented contract.\n if (isNoPositionRevert(err)) return null;\n throw err;\n }\n\n const position = result as PositionResult;\n\n // Defence in depth: the adapter reverts rather than returning a zero proxy,\n // so this should be unreachable. Kept because the return value is external\n // input — a zero proxy must never reach a caller as a real position.\n if (position.proxyContract === zeroAddress) {\n return null;\n }\n\n return {\n proxyContract: position.proxyContract,\n vaultIds: position.vaultIds,\n totalCollateralBTC: position.totalCollateralBTC,\n };\n}\n\n/**\n * Get position size parameters from the adapter contract.\n *\n * Returns the maximum BTC position size and maximum vaults per position\n * as configured on-chain.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param contractAddress - AaveIntegrationAdapter contract address\n * @returns Position size parameters (maxPositionBTC, maxVaultsPerPosition)\n */\nexport async function getPositionSizeParams(\n publicClient: PublicClient,\n contractAddress: Address,\n): Promise<PositionSizeParams> {\n const result = await publicClient.readContract({\n address: contractAddress,\n abi: AaveIntegrationAdapterABI,\n functionName: \"getPositionSizeParams\",\n });\n\n const [maxPositionBTC, maxVaultsPerPosition] = result as [bigint, bigint];\n\n return {\n maxPositionBTC,\n maxVaultsPerPosition,\n };\n}\n","/**\n * Aave Spoke Client - Read operations\n *\n * Provides read operations for interacting with Aave v4 Spoke contracts.\n * Used to fetch live user position data (debt, collateral) from the Core Spoke.\n *\n * Note: Reserve data should be fetched from the indexer via fetchReserves.ts\n * since it doesn't need to be live and benefits from caching.\n */\n\nimport type { Abi, Address, PublicClient } from \"viem\";\n\nimport type {\n AaveSpokeUserAccountData,\n AaveSpokeUserPosition,\n} from \"../types.js\";\nimport AaveSpokeABI from \"./abis/AaveSpoke.abi.json\";\n\n/** Account data result type from contract */\ntype AccountDataResult = {\n riskPremium: bigint;\n avgCollateralFactor: bigint;\n healthFactor: bigint;\n totalCollateralValue: bigint;\n totalDebtValueRay: bigint;\n activeCollateralCount: bigint;\n borrowCount: bigint;\n};\n\n/** Position result type from contract */\ntype PositionResult = {\n drawnShares: bigint;\n premiumShares: bigint;\n premiumOffsetRay: bigint;\n suppliedShares: bigint;\n dynamicConfigKey: number;\n};\n\n/**\n * Maps contract result to AaveSpokeUserPosition\n */\nfunction mapPositionResult(result: PositionResult): AaveSpokeUserPosition {\n return {\n drawnShares: result.drawnShares,\n premiumShares: result.premiumShares,\n premiumOffsetRay: result.premiumOffsetRay,\n suppliedShares: result.suppliedShares,\n dynamicConfigKey: result.dynamicConfigKey,\n };\n}\n\n/** Maps contract result to AaveSpokeUserAccountData */\nfunction mapAccountDataResult(\n data: AccountDataResult,\n): AaveSpokeUserAccountData {\n return {\n riskPremium: data.riskPremium,\n avgCollateralFactor: data.avgCollateralFactor,\n healthFactor: data.healthFactor,\n totalCollateralValue: data.totalCollateralValue,\n totalDebtValueRay: data.totalDebtValueRay,\n activeCollateralCount: data.activeCollateralCount,\n borrowCount: data.borrowCount,\n };\n}\n\n/**\n * Get aggregated user account health data from AAVE spoke.\n *\n * **Live data** - Fetches real-time account health including health factor, total collateral,\n * and total debt across all reserves. Values are calculated on-chain using AAVE oracles\n * and are the authoritative source for liquidation decisions.\n *\n * @param publicClient - Viem public client for reading contracts (from `createPublicClient()`)\n * @param spokeAddress - AAVE Spoke contract address (BTC Vault Core Spoke for vBTC collateral)\n * @param userAddress - User's proxy contract address (NOT user's wallet address)\n * @returns User account data with health metrics, collateral, and debt values\n *\n * @example\n * ```typescript\n * import { getUserAccountData } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n * import { createPublicClient, http } from \"viem\";\n * import { sepolia } from \"viem/chains\";\n *\n * const publicClient = createPublicClient({\n * chain: sepolia,\n * transport: http()\n * });\n *\n * const accountData = await getUserAccountData(\n * publicClient,\n * \"0x123...\", // AAVE Spoke address\n * \"0x456...\" // User's AAVE proxy address (from getPosition)\n * );\n *\n * console.log(\"Health Factor:\", accountData.healthFactor);\n * console.log(\"Collateral (USD):\", accountData.totalCollateralValue);\n * console.log(\"Debt (USD):\", accountData.totalDebtValueRay);\n * ```\n *\n * @remarks\n * **Return values:**\n * - `healthFactor` - WAD format (1e18 = 1.0). Below 1.0 = liquidatable\n * - `totalCollateralValue` - USD value in base currency (1e26 = $1)\n * - `totalDebtValueRay` - USD value in RAY-scaled base currency (1e53 = $1)\n * - `avgCollateralFactor` - Weighted average collateral factor in WAD (1e18 = 100%)\n * - `riskPremium` - Additional risk premium\n *\n * **Use cases:**\n * - Check liquidation risk before borrowing\n * - Calculate safe borrow amount\n * - Monitor position health\n * - Display UI health indicators\n */\nexport async function getUserAccountData(\n publicClient: PublicClient,\n spokeAddress: Address,\n userAddress: Address,\n): Promise<AaveSpokeUserAccountData> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"getUserAccountData\",\n args: [userAddress],\n });\n\n return mapAccountDataResult(result as AccountDataResult);\n}\n\n/**\n * Read a user's position for one reserve and their aggregate account data in a\n * single hard-fail multicall. Both reads are required for the live position\n * view, so a revert on either rejects the whole call (matching the prior\n * `Promise.all`); the gain is one round-trip instead of two `eth_call`s.\n */\nexport async function getUserPositionAndAccountData(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveId: bigint,\n userAddress: Address,\n): Promise<{\n position: AaveSpokeUserPosition;\n accountData: AaveSpokeUserAccountData;\n}> {\n const [positionResult, accountDataResult] = await publicClient.multicall({\n contracts: [\n {\n address: spokeAddress,\n abi: AaveSpokeABI as Abi,\n functionName: \"getUserPosition\" as const,\n args: [reserveId, userAddress] as const,\n },\n {\n address: spokeAddress,\n abi: AaveSpokeABI as Abi,\n functionName: \"getUserAccountData\" as const,\n args: [userAddress] as const,\n },\n ],\n allowFailure: false,\n });\n\n return {\n position: mapPositionResult(positionResult as unknown as PositionResult),\n accountData: mapAccountDataResult(\n accountDataResult as unknown as AccountDataResult,\n ),\n };\n}\n\n/**\n * Get user position from the Spoke\n *\n * This fetches live data from the contract because debt accrues interest\n * and needs to be current for accurate health factor calculations.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - Aave Spoke contract address\n * @param reserveId - Reserve ID\n * @param userAddress - User's proxy contract address\n * @returns User position data\n */\nexport async function getUserPosition(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveId: bigint,\n userAddress: Address,\n): Promise<AaveSpokeUserPosition> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"getUserPosition\",\n args: [reserveId, userAddress],\n });\n\n return mapPositionResult(result as PositionResult);\n}\n\n/**\n * Get user's exact total debt in a reserve (token units, not shares).\n *\n * Returns the Spoke-side amount owed including accrued interest — but NOT the\n * adapter's uncollected interest fee. Display/routing only; for full repayment\n * see the remarks below. Debt accrues interest every block, so fetch it live.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - AAVE Spoke contract address\n * @param reserveId - Reserve ID for the debt asset (e.g., `2n` for USDC)\n * @param userAddress - User's proxy contract address\n * @returns Total debt amount in token units (e.g., for USDC: `100000000n` = 100 USDC)\n *\n * @example\n * ```typescript\n * import { getUserTotalDebt } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n * import { formatUnits } from \"viem\";\n *\n * const totalDebt = await getUserTotalDebt(\n * publicClient,\n * AAVE_SPOKE_ADDRESS,\n * 2n, // USDC reserve\n * proxyAddress\n * );\n *\n * console.log(\"Debt:\", formatUnits(totalDebt, 6), \"USDC\");\n * ```\n *\n * @remarks\n * **Important for full repayment:** do NOT repay a plain amount derived from\n * this quote — it excludes the adapter's interest fee, and rounding can leave\n * residual debt shares (dust). Send the repay-all sentinel\n * (`type(uint256).max`) with an approval sized from the position proxy's\n * fee-inclusive `getPositionReserveTotalDebt` plus\n * `FULL_REPAY_BUFFER_DIVISOR` headroom; the adapter pulls only what's owed.\n * For partial repayment, use any amount less than total debt.\n */\nexport async function getUserTotalDebt(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveId: bigint,\n userAddress: Address,\n): Promise<bigint> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"getUserTotalDebt\",\n args: [reserveId, userAddress],\n });\n\n return result as bigint;\n}\n\n/**\n * Probe `getUserPosition` for many reserves in a single multicall.\n *\n * Returns one entry per `reserveId` in input order. Per-reserve reverts are\n * isolated (`allowFailure: true`): that entry is `null` while the rest of the\n * batch still resolves. Use for debt-reserve discovery, where a failed read\n * means \"treat as no debt\", not a fatal error.\n */\nexport async function getUserPositions(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveIds: bigint[],\n userAddress: Address,\n): Promise<(AaveSpokeUserPosition | null)[]> {\n if (reserveIds.length === 0) return [];\n const results = await publicClient.multicall({\n contracts: reserveIds.map((reserveId) => ({\n address: spokeAddress,\n abi: AaveSpokeABI as Abi,\n functionName: \"getUserPosition\" as const,\n args: [reserveId, userAddress] as const,\n })),\n allowFailure: true,\n });\n return results.map((r) =>\n r.status === \"success\"\n ? mapPositionResult(r.result as PositionResult)\n : null,\n );\n}\n\n/**\n * Read `getUserTotalDebt` for many reserves in a single multicall.\n *\n * Hard-fails (`allowFailure: false`): any reserve's revert rejects the whole\n * call. Use only for reserves already known to carry debt — there a failed\n * read is a genuine error, not a \"no debt\" signal.\n */\nexport async function getUserTotalDebts(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveIds: bigint[],\n userAddress: Address,\n): Promise<bigint[]> {\n if (reserveIds.length === 0) return [];\n const results = await publicClient.multicall({\n contracts: reserveIds.map((reserveId) => ({\n address: spokeAddress,\n abi: AaveSpokeABI as Abi,\n functionName: \"getUserTotalDebt\" as const,\n args: [reserveId, userAddress] as const,\n })),\n allowFailure: false,\n });\n return results as unknown as bigint[];\n}\n\n/** Result type from the `getReserve` contract call.\n *\n * Matches the on-chain `Reserve` struct defined in `ITBVAaveSpoke.sol`:\n * struct Reserve {\n * address underlying;\n * address hub;\n * uint16 assetId;\n * uint8 decimals;\n * uint24 collateralRisk;\n * ReserveFlags flags; // uint8 bitmap\n * uint32 dynamicConfigKey;\n * }\n *\n * Note: this is the `Reserve` struct, NOT `ReserveConfig` — the contract\n * exposes both as separate functions and they return different shapes.\n */\ntype ReserveResult = {\n underlying: Address;\n hub: Address;\n assetId: number;\n decimals: number;\n collateralRisk: number;\n flags: number;\n dynamicConfigKey: number;\n};\n\n/**\n * Get reserve data from the Core Spoke contract via the `getReserve` selector.\n *\n * Returns static reserve properties including the `dynamicConfigKey` needed\n * for `getDynamicReserveConfig` calls. Use this as a fallback when reserve\n * data is not available from the GraphQL indexer.\n *\n * Do NOT confuse with the contract's separate `getReserveConfig` function,\n * which returns `{collateralRisk, paused, frozen, borrowable, receiveSharesEnabled}`.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - Core Spoke contract address\n * @param reserveId - Reserve ID\n * @returns Reserve data including `dynamicConfigKey`\n */\nexport async function getReserve(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveId: bigint,\n): Promise<ReserveResult> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"getReserve\",\n args: [reserveId],\n });\n return result as ReserveResult;\n}\n\n/**\n * Read `getReserve` for many reserves in a single multicall.\n *\n * Returns one entry per `reserveId` in input order. Hard-fails\n * (`allowFailure: false`): any revert, including `ReserveNotListed` for an id\n * the spoke never listed, rejects the whole call.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - Core Spoke contract address\n * @param reserveIds - Reserve IDs to read\n * @returns Reserve data for each ID, in input order\n */\nexport async function getReserves(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveIds: bigint[],\n): Promise<ReserveResult[]> {\n if (reserveIds.length === 0) return [];\n const results = await publicClient.multicall({\n contracts: reserveIds.map((reserveId) => ({\n address: spokeAddress,\n abi: AaveSpokeABI as Abi,\n functionName: \"getReserve\" as const,\n args: [reserveId] as const,\n })),\n allowFailure: false,\n });\n return results as unknown as ReserveResult[];\n}\n\n/**\n * The fields of the getLiquidationConfig result that this client reads.\n * `targetHealthFactor` is left out on purpose: the Babylon Spoke never uses\n * it, and the split target health factor is `SPLIT_TARGET_HEALTH_FACTOR`.\n * viem decodes the `uint16` factor as a number and the `uint64` as a bigint.\n */\ntype LiquidationConfigResult = {\n healthFactorForMaxBonus: bigint;\n liquidationBonusFactor: number | bigint;\n};\n\n/** Liquidation-bonus curve parameters from the Spoke's liquidation config. */\nexport interface LiquidationBonusConfig {\n /** Health factor at or below which the maximum bonus applies, WAD */\n healthFactorForMaxBonus: bigint;\n /** Share of the maximum bonus that still applies at HF 1.0, BPS */\n liquidationBonusFactor: bigint;\n}\n\n/**\n * Result type from getDynamicReserveConfig contract call. The fields are\n * uint16/uint32 on-chain, which viem decodes as numbers, not bigints.\n */\ntype DynamicReserveConfigResult = {\n collateralFactor: number;\n maxLiquidationBonus: number;\n liquidationFee: number;\n};\n\n/**\n * Get the liquidation-bonus curve parameters from the Core Spoke contract.\n *\n * Per-spoke governance parameters. Combined with a reserve's\n * `maxLiquidationBonus` they give the bonus at any health factor; see\n * `computeLiquidationBonusBps`.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - Core Spoke contract address\n * @returns healthFactorForMaxBonus (WAD) and liquidationBonusFactor (BPS)\n */\nexport async function getLiquidationBonusConfig(\n publicClient: PublicClient,\n spokeAddress: Address,\n): Promise<LiquidationBonusConfig> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"getLiquidationConfig\",\n });\n const config = result as LiquidationConfigResult;\n return {\n healthFactorForMaxBonus: config.healthFactorForMaxBonus,\n liquidationBonusFactor: BigInt(config.liquidationBonusFactor),\n };\n}\n\n/**\n * Get the dynamic reserve config from the Core Spoke contract.\n *\n * Returns collateral factor, max liquidation bonus, and liquidation fee\n * for a specific reserve and dynamic config key.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - Core Spoke contract address\n * @param reserveId - Reserve ID (e.g., vBTC reserve ID from indexer config)\n * @param dynamicConfigKey - Dynamic config key (from reserve data)\n * @returns Dynamic reserve config with collateralFactor (BPS), maxLiquidationBonus (BPS), liquidationFee (BPS)\n */\nexport async function getDynamicReserveConfig(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveId: bigint,\n dynamicConfigKey: number,\n): Promise<DynamicReserveConfigResult> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"getDynamicReserveConfig\",\n args: [reserveId, dynamicConfigKey],\n });\n return result as DynamicReserveConfigResult;\n}\n\n/**\n * Get the maximum number of reserves one user may hold on this Spoke.\n *\n * `uint16` immutable, set in the Spoke constructor. Collateral reserves and\n * borrow reserves are counted separately against the same number: a borrow\n * into a new reserve reverts with `MaximumUserReservesExceeded` once the\n * user's borrow count has reached it. Borrowing more of a reserve the user\n * already borrows is always allowed.\n *\n * `MAX_ALLOWED_USER_RESERVES_LIMIT` (65535) is the contract's \"no cap\"\n * sentinel — the check is skipped on that value.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - Aave Spoke contract address\n * @returns The limit as stored on the Spoke\n */\nexport async function getMaxUserReservesLimit(\n publicClient: PublicClient,\n spokeAddress: Address,\n): Promise<number> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"MAX_USER_RESERVES_LIMIT\",\n });\n\n return result as number;\n}\n","/**\n * Aave Adapter Position Proxy Client - Read operations\n *\n * Reads against a depositor's own AaveAdapterPositionProxy instance.\n *\n * NOT the Spoke's same-signature `getReserveTotalDebt` (reserve-wide debt of\n * ALL users): the proxy's returns THIS position's total debt including the\n * adapter's uncollected interest fee — the value the adapter resolves the\n * repay-all sentinel to, and therefore the right quote for sizing a repay-all\n * approval.\n */\n\nimport type { Address, PublicClient } from \"viem\";\n\nimport AaveAdapterPositionProxyABI from \"./abis/AaveAdapterPositionProxy.abi.json\";\n\n/**\n * Fee-inclusive total debt of the position held by `proxyContract` for\n * `reserveId`: Spoke debt plus the adapter's uncollected interest fee\n * (rounded up), computed lazily at the current block.\n */\nexport async function getPositionReserveTotalDebt(\n publicClient: PublicClient,\n proxyContract: Address,\n reserveId: bigint,\n): Promise<bigint> {\n const result = await publicClient.readContract({\n address: proxyContract,\n abi: AaveAdapterPositionProxyABI,\n functionName: \"getReserveTotalDebt\",\n args: [reserveId],\n });\n\n // This quote sizes a real approval — assert the shape instead of casting.\n if (typeof result !== \"bigint\") {\n throw new Error(\n `getReserveTotalDebt returned a non-bigint result for reserve ${reserveId}`,\n );\n }\n return result;\n}\n","/**\n * Read-only access to `IAaveOracle`. Prices are 8-decimal base units\n * ($80,000 = 8_000_000_000_000n).\n */\n\nimport type { Abi, Address, PublicClient } from \"viem\";\n\nimport AaveOracleABI from \"./abis/AaveOracle.abi.json\";\nimport AaveSpokeABI from \"./abis/AaveSpoke.abi.json\";\n\n/** `Spoke.ORACLE` is `immutable`; the result is safe to cache forever. */\nexport async function getOracleAddress(\n publicClient: PublicClient,\n spokeAddress: Address,\n): Promise<Address> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"ORACLE\",\n });\n return result as Address;\n}\n\n/** Batch read; reverts the WHOLE batch on the first bad reserve. */\nexport async function getReservesPrices(\n publicClient: PublicClient,\n oracleAddress: Address,\n reserveIds: bigint[],\n): Promise<bigint[]> {\n const result = await publicClient.readContract({\n address: oracleAddress,\n abi: AaveOracleABI,\n functionName: \"getReservesPrices\",\n args: [reserveIds],\n });\n return result as bigint[];\n}\n\nexport interface ReservePriceResult {\n reserveId: bigint;\n /** Raw 1e8 base units, or null on revert. */\n priceRaw: bigint | null;\n error: Error | null;\n}\n\n/**\n * Per-reserve isolated read for display lists (one bad source ≠ whole list\n * blank). One multicall round-trip instead of one `eth_call` per reserve:\n * each entry is `getReservesPrices([reserveId])` with `allowFailure: true`, so\n * a single reverting reserve isolates to its own error entry. A network-level\n * multicall failure marks every reserve failed rather than throwing — callers\n * (display hooks) rely on always getting a per-reserve result array.\n */\nexport async function getReservesPricesSafe(\n publicClient: PublicClient,\n oracleAddress: Address,\n reserveIds: bigint[],\n): Promise<ReservePriceResult[]> {\n if (reserveIds.length === 0) return [];\n\n let results;\n try {\n results = await publicClient.multicall({\n contracts: reserveIds.map((reserveId) => ({\n address: oracleAddress,\n abi: AaveOracleABI as Abi,\n functionName: \"getReservesPrices\" as const,\n args: [[reserveId]] as const,\n })),\n allowFailure: true,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return reserveIds.map((reserveId) => ({\n reserveId,\n priceRaw: null,\n error,\n }));\n }\n\n return results.map((result, i): ReservePriceResult => {\n const reserveId = reserveIds[i];\n if (result.status !== \"success\") {\n const error =\n result.error instanceof Error\n ? result.error\n : new Error(String(result.error ?? \"getReservesPrices reverted\"));\n return { reserveId, priceRaw: null, error };\n }\n const [priceRaw] = result.result as bigint[];\n return { reserveId, priceRaw, error: null };\n });\n}\n","/**\n * Read-only access to the Aave v4 Hub (`IHub`). Each spoke reserve points at\n * a Hub asset (`reserve.hub` + `reserve.assetId`); the Hub is where interest\n * accrues, so live borrow rates are read here rather than from the Spoke.\n */\n\nimport type { Abi, Address, PublicClient } from \"viem\";\n\nimport AaveHubABI from \"./abis/AaveHub.abi.json\";\n\n/** Identifies one Hub asset to read the drawn rate for. */\nexport interface AssetDrawnRateRequest {\n /** Hub contract address (from the reserve's `hub` field). */\n hub: Address;\n /** Asset identifier on that Hub (from the reserve's `assetId` field). */\n assetId: number;\n}\n\nexport interface AssetDrawnRateResult {\n hub: Address;\n assetId: number;\n /** Annual borrow (drawn) rate in RAY (1e27 = 100%), or null on revert. */\n rateRay: bigint | null;\n error: Error | null;\n}\n\n/**\n * Per-asset isolated read of `getAssetDrawnRate` for display lists (one bad\n * asset ≠ whole list blank). One multicall round-trip instead of one\n * `eth_call` per asset, with `allowFailure: true` so a single reverting asset\n * isolates to its own error entry. A network-level multicall failure marks\n * every asset failed rather than throwing — callers (display hooks) rely on\n * always getting a per-asset result array.\n *\n * The returned rate is the linear annual rate in RAY (the Hub accrues\n * interest as `rate * dt / SECONDS_PER_YEAR`), i.e. an APR, not an APY.\n */\nexport async function getAssetDrawnRatesSafe(\n publicClient: PublicClient,\n requests: AssetDrawnRateRequest[],\n): Promise<AssetDrawnRateResult[]> {\n if (requests.length === 0) return [];\n\n let results;\n try {\n results = await publicClient.multicall({\n contracts: requests.map(({ hub, assetId }) => ({\n address: hub,\n abi: AaveHubABI as Abi,\n functionName: \"getAssetDrawnRate\" as const,\n args: [BigInt(assetId)] as const,\n })),\n allowFailure: true,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return requests.map(({ hub, assetId }) => ({\n hub,\n assetId,\n rateRay: null,\n error,\n }));\n }\n\n return results.map((result, i): AssetDrawnRateResult => {\n const { hub, assetId } = requests[i];\n if (result.status !== \"success\") {\n const error =\n result.error instanceof Error\n ? result.error\n : new Error(String(result.error ?? \"getAssetDrawnRate reverted\"));\n return { hub, assetId, rateRay: null, error };\n }\n return { hub, assetId, rateRay: result.result as bigint, error: null };\n });\n}\n","/**\n * Aave Integration Adapter - Transaction builders\n *\n * Provides transaction builders for the AaveIntegrationAdapter contract.\n * Only includes Core Spoke operations for regular users (no Arbitrageur operations).\n *\n * These functions return unsigned transaction parameters that can be executed\n * by the vault service using its wallet client and transaction factory.\n */\n\nimport { type Address, type Hex, encodeFunctionData } from \"viem\";\n\nimport { AAVE_FUNCTION_NAMES } from \"../config.js\";\nimport type { TransactionParams } from \"../types.js\";\nimport AaveIntegrationAdapterABI from \"./abis/AaveIntegrationAdapter.abi.json\";\n\n/**\n * Build transaction to reorder vaults for liquidation priority.\n *\n * The permuted array must contain exactly the same vault IDs as the\n * current position, in the desired new order. Vaults are seized in\n * prefix order (index 0 first) during liquidation.\n *\n * @param contractAddress - AaveIntegrationAdapter contract address\n * @param permutedVaultIds - Vault IDs in desired new order (must be a permutation of current vaults)\n * @returns Unsigned transaction parameters\n */\nexport function buildReorderVaultsTx(\n contractAddress: Address,\n permutedVaultIds: Hex[],\n): TransactionParams {\n const data = encodeFunctionData({\n abi: AaveIntegrationAdapterABI,\n functionName: AAVE_FUNCTION_NAMES.REORDER_VAULTS,\n args: [permutedVaultIds],\n });\n\n return {\n to: contractAddress,\n data,\n };\n}\n\n/**\n * Build transaction to withdraw selected vaults from AAVE position.\n *\n * Withdraws specific vaults (partial withdrawal) and redeems them back to the depositor.\n * **Requires zero debt** - position must have no outstanding borrows.\n *\n * @param contractAddress - AaveIntegrationAdapter contract address\n * @param vaultIds - Array of vault IDs (bytes32) to withdraw\n * @returns Unsigned transaction parameters for execution with viem wallet\n */\nexport function buildWithdrawCollateralsTx(\n contractAddress: Address,\n vaultIds: Hex[],\n): TransactionParams {\n const data = encodeFunctionData({\n abi: AaveIntegrationAdapterABI,\n functionName: AAVE_FUNCTION_NAMES.WITHDRAW_COLLATERALS,\n args: [vaultIds],\n });\n\n return {\n to: contractAddress,\n data,\n };\n}\n\n/**\n * Build transaction to borrow assets against vBTC collateral.\n *\n * Borrows stablecoins (e.g., USDC) against your BTC collateral position.\n * Health factor must remain above 1.0 after borrowing, otherwise transaction will revert.\n *\n * @param contractAddress - AaveIntegrationAdapter contract address\n * @param debtReserveId - AAVE reserve ID for the debt asset (e.g., `2n` for USDC reserve)\n * @param amount - Amount to borrow in token units with decimals (e.g., for USDC with 6 decimals: `100000000n` = 100 USDC). Use `parseUnits()` from viem.\n * @param receiver - Address to receive borrowed tokens (usually user's address)\n * @returns Unsigned transaction parameters for execution with viem wallet\n *\n * @example\n * ```typescript\n * import { buildBorrowTx } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n * import { parseUnits } from \"viem\";\n *\n * // Borrow 100 USDC (6 decimals)\n * const borrowAmount = parseUnits(\"100\", 6);\n *\n * const txParams = buildBorrowTx(\n * \"0x123...\", // Adapter address\n * 2n, // USDC reserve ID\n * borrowAmount,\n * \"0x456...\" // Receiver address\n * );\n *\n * const hash = await walletClient.sendTransaction({\n * to: txParams.to,\n * data: txParams.data,\n * chain: sepolia,\n * });\n * ```\n *\n * @remarks\n * **What happens on-chain:**\n * 1. Checks health factor won't drop below liquidation threshold (1.0)\n * 2. Mints debt tokens to user's proxy contract\n * 3. Transfers borrowed asset to receiver address\n * 4. Updates position debt\n * 5. Emits `Borrowed` event\n *\n * **Possible errors:**\n * - Borrow would make health factor < 1.0\n * - Insufficient collateral\n * - Reserve doesn't exist\n * - Position doesn't exist\n *\n * **Important:** Calculate safe borrow amount using `calculateHealthFactor()` to avoid liquidation.\n */\nexport function buildBorrowTx(\n contractAddress: Address,\n debtReserveId: bigint,\n amount: bigint,\n receiver: Address,\n): TransactionParams {\n const data = encodeFunctionData({\n abi: AaveIntegrationAdapterABI,\n functionName: AAVE_FUNCTION_NAMES.BORROW,\n args: [debtReserveId, amount, receiver],\n });\n\n return {\n to: contractAddress,\n data,\n };\n}\n\n/**\n * Build transaction to repay debt on AAVE position.\n *\n * **Requires token approval** - user must approve adapter to spend debt token first.\n * Repays borrowed assets (partial or full repayment supported).\n *\n * @param contractAddress - AaveIntegrationAdapter contract address\n * @param borrower - Borrower's address (for self-repay, use connected wallet address)\n * @param debtReserveId - AAVE reserve ID for the debt asset\n * @param amount - Amount to repay in token units for a partial repay. For a\n * FULL repay, pass `type(uint256).max` (the repay-all sentinel): the adapter\n * resolves it to the position's fee-inclusive debt in the same transaction\n * and pulls exactly that. Size the prior approval from\n * `getPositionReserveTotalDebt()` plus ceiling-divided\n * `FULL_REPAY_BUFFER_DIVISOR` headroom.\n * @returns Unsigned transaction parameters for execution with viem wallet\n *\n * @example\n * ```typescript\n * import { buildRepayTx } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n *\n * // Build repay transaction (self-repay)\n * const txParams = buildRepayTx(\n * AAVE_ADAPTER,\n * borrowerAddress, // Connected wallet address for self-repay\n * USDC_RESERVE_ID,\n * repayAmount\n * );\n *\n * const hash = await walletClient.sendTransaction({\n * to: txParams.to,\n * data: txParams.data,\n * chain: sepolia,\n * });\n * ```\n *\n * @remarks\n * **What happens on-chain:**\n * 1. Transfers tokens from user to adapter (requires approval)\n * 2. Burns debt tokens from user's proxy\n * 3. Updates position debt\n * 4. Emits `Repaid` event\n *\n * **Possible errors:**\n * - Insufficient token approval\n * - User doesn't have enough tokens\n * - Repay amount exceeds debt\n * - Position doesn't exist\n */\nexport function buildRepayTx(\n contractAddress: Address,\n borrower: Address,\n debtReserveId: bigint,\n amount: bigint,\n): TransactionParams {\n const data = encodeFunctionData({\n abi: AaveIntegrationAdapterABI,\n functionName: AAVE_FUNCTION_NAMES.REPAY,\n args: [borrower, debtReserveId, amount],\n });\n\n return {\n to: contractAddress,\n data,\n };\n}\n\n","/**\n * Aave Value Conversion Utilities\n *\n * Converts Aave on-chain values to human-readable numbers.\n */\n\nimport {\n AAVE_BASE_CURRENCY_DECIMALS,\n AAVE_BASE_CURRENCY_RAY_DECIMALS,\n WAD_DECIMALS,\n} from \"../constants.js\";\n\n/**\n * Convert Aave base currency value to USD\n *\n * Aave uses 1e26 = $1 USD for collateral values.\n *\n * @param value - Value in Aave base currency (1e26 = $1)\n * @returns Value in USD\n */\nexport function aaveValueToUsd(value: bigint): number {\n return Number(value) / 10 ** AAVE_BASE_CURRENCY_DECIMALS;\n}\n\n/**\n * Convert Aave RAY-scaled base currency value to USD\n *\n * Debt values use higher precision: 1e53 = $1 USD.\n *\n * @param value - Value in RAY-scaled base currency (1e53 = $1)\n * @returns Value in USD\n */\nexport function aaveRayValueToUsd(value: bigint): number {\n return Number(value) / 10 ** AAVE_BASE_CURRENCY_RAY_DECIMALS;\n}\n\n/**\n * Convert Aave WAD value to number\n *\n * WAD is used for health factor and collateral factor (1e18 = 1.0).\n *\n * @param value - Value in WAD (1e18 = 1.0)\n * @returns Decimal number\n */\nexport function wadToNumber(value: bigint): number {\n return Number(value) / 10 ** WAD_DECIMALS;\n}\n","/**\n * Aave Debt Utilities\n *\n * Shared utility functions for debt calculations.\n */\n\nimport type { AaveSpokeUserPosition } from \"../types.js\";\n\n/**\n * Check if a position has any debt based on Spoke position data.\n *\n * A position is considered to have debt if any of:\n * - drawnShares > 0 (borrowed principal)\n * - premiumShares > 0 (accrued interest shares)\n *\n * @param position - User position data from Spoke\n * @returns true if the position has any debt\n */\nexport function hasDebtFromPosition(position: AaveSpokeUserPosition): boolean {\n return position.drawnShares > 0n || position.premiumShares > 0n;\n}\n","/**\n * Health Factor Utilities for Aave\n *\n * Health factor is calculated by Aave on-chain using oracle prices.\n * A position below HEALTH_FACTOR_LIQUIDATION_THRESHOLD can be liquidated.\n *\n * Status thresholds:\n * - no_debt: No active debt (null health factor)\n * - danger: below HEALTH_FACTOR_LIQUIDATION_THRESHOLD (can be liquidated)\n * - risky: HEALTH_FACTOR_LIQUIDATION_THRESHOLD to HEALTH_FACTOR_RISKY_THRESHOLD (inclusive)\n * - warning: above HEALTH_FACTOR_RISKY_THRESHOLD, up to and including\n * HEALTH_FACTOR_WARNING_THRESHOLD\n * - safe: > HEALTH_FACTOR_WARNING_THRESHOLD (healthy)\n */\n\nimport {\n BPS_SCALE,\n HEALTH_FACTOR_LIQUIDATION_THRESHOLD,\n HEALTH_FACTOR_RISKY_THRESHOLD,\n HEALTH_FACTOR_WARNING_THRESHOLD,\n} from \"../constants.js\";\n\nexport type HealthFactorStatus =\n | \"safe\"\n | \"warning\"\n | \"risky\"\n | \"danger\"\n | \"no_debt\";\n\n/**\n * Determine health factor status for UI display\n *\n * @param healthFactor - The health factor as a number (null if no debt)\n * @param hasDebt - Whether the position has active debt\n * @returns The status classification\n */\nexport function getHealthFactorStatus(\n healthFactor: number | null,\n hasDebt: boolean,\n): HealthFactorStatus {\n if (!hasDebt) return \"no_debt\";\n if (healthFactor === null) {\n throw new Error(\"A health factor is required for a position with debt.\");\n }\n if (healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD) return \"danger\";\n if (healthFactor <= HEALTH_FACTOR_RISKY_THRESHOLD) return \"risky\";\n if (healthFactor <= HEALTH_FACTOR_WARNING_THRESHOLD) return \"warning\";\n return \"safe\";\n}\n\n/**\n * Get health factor status from a numeric value.\n * Used for UI components that work with Infinity for no-debt scenarios.\n *\n * @param value - Health factor value (Infinity when no debt)\n * @returns The status classification\n */\nexport function getHealthFactorStatusFromValue(\n value: number,\n): HealthFactorStatus {\n const hasDebt = isFinite(value);\n const healthFactor = isFinite(value) ? value : null;\n return getHealthFactorStatus(healthFactor, hasDebt);\n}\n\n/**\n * Calculate health factor for an AAVE position.\n *\n * **Formula:** `HF = (Collateral × Liquidation Threshold) / Total Debt`\n *\n * Health factor determines liquidation risk:\n * - `> 2.0` - Safe (green)\n * - `> 1.1 and <= 2.0` - Warning (amber)\n * - `>= 1.0 and <= 1.1` - Risky (red)\n * - `< 1.0` - Danger, position can be liquidated (red)\n *\n * @param collateralValueUsd - Total collateral value in USD (as number, not bigint)\n * @param totalDebtUsd - Total debt value in USD (as number, not bigint)\n * @param liquidationThresholdBps - Liquidation threshold in basis points (e.g., `8000` = 80%)\n * @returns Health factor value (e.g., `1.5`), or `Infinity` if no debt\n *\n * @example\n * ```typescript\n * import { calculateHealthFactor, HEALTH_FACTOR_WARNING_THRESHOLD } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n *\n * // User has $10,000 BTC collateral, $5,000 debt, 80% LT\n * const hf = calculateHealthFactor(10000, 5000, 8000);\n * // Result: 1.6 (warning: at or below the warning threshold)\n *\n * if (hf < 1.0) {\n * console.error(\"Position can be liquidated!\");\n * } else if (hf <= HEALTH_FACTOR_WARNING_THRESHOLD) {\n * console.warn(\"Position at risk, consider repaying\");\n * } else {\n * console.log(\"Position is safe\");\n * }\n * ```\n *\n * @remarks\n * **Before borrowing:**\n * Use this to calculate resulting health factor and ensure it stays above safe threshold.\n *\n * **Unit conversions:**\n * - Convert AAVE base currency (1e26) to USD by dividing by 1e26\n * - Use `aaveValueToUsd()` helper for automatic conversion\n */\nexport function calculateHealthFactor(\n collateralValueUsd: number,\n totalDebtUsd: number,\n liquidationThresholdBps: number,\n): number {\n if (totalDebtUsd <= 0) return Infinity;\n return (\n (collateralValueUsd * (liquidationThresholdBps / BPS_SCALE)) / totalDebtUsd\n );\n}\n","/**\n * Cascade Liquidation Simulation\n *\n * Simulates multi-group liquidation cascades for Aave positions backed by\n * indivisible BTC vaults (UTXOs). Each liquidation event seizes a prefix of\n * vaults until the target seizure is covered, then debt is reduced and the\n * cascade continues with remaining vaults.\n *\n * Used by the optimizer to score different vault orderings by how much\n * collateral survives the cascade.\n */\n\n/**\n * Minimal vault shape for cascade simulation.\n * UI layers extend this with display fields (e.g. `name`).\n */\nexport interface CascadeVault {\n id: string;\n btc: number;\n}\n\n/** 1% tolerance for prefix walk coverage — avoids cliff flip at boundary */\nexport const SEIZURE_TOL = 0.01;\n\n/** Circuit breaker for group cascade loop */\nexport const MAX_GROUPS = 20;\n\n/** Minimum debt threshold to continue cascade (avoids infinite loop on dust) */\nexport const MIN_DEBT_THRESHOLD = 0.01;\n\n/**\n * Prefix walk: consume vaults front-to-back until target seizure is covered.\n * Returns the vaults in the first liquidation group.\n */\nexport function getGroup1FromOrder<T extends CascadeVault>(\n order: T[],\n seizedFraction: number,\n seizureTol: number,\n): T[] {\n if (order.length === 0) return [];\n const totalBtc = order.reduce((s, v) => s + v.btc, 0);\n const coverThreshold = totalBtc * seizedFraction * (1 - seizureTol);\n let prefixSum = 0;\n let i = 0;\n while (i < order.length && prefixSum < coverThreshold) {\n prefixSum += order[i].btc;\n i++;\n }\n return order.slice(0, i);\n}\n\n/**\n * Simulate one liquidation group, returning updated debt and remaining BTC.\n * Handles both safe (non-last) and full (last) liquidation paths.\n */\nfunction simulateOneGroup(\n seizedBtc: number,\n totalBtc: number,\n debt: number,\n isLastGroup: boolean,\n seizedFraction: number,\n CF: number,\n THF: number,\n LB: number,\n expectedHF: number,\n): { debtAfter: number; btcAfter: number } {\n const liqPenalty = LB * CF;\n const pLiq = debt / (totalBtc * CF);\n const targetSeizure = totalBtc * seizedFraction;\n const overSeizureBtc = Math.max(0, seizedBtc - targetSeizure);\n const denominator = THF - liqPenalty;\n const debtToRepay =\n denominator === 0 ? debt : debt * ((THF - expectedHF) / denominator);\n\n let debtAfter: number;\n if (isLastGroup) {\n debtAfter = 0;\n } else {\n const overSeizureVal = (overSeizureBtc * pLiq) / LB;\n const fairnessDebtRepay = Math.min(overSeizureVal, debt - debtToRepay);\n debtAfter = Math.max(0, debt - debtToRepay - fairnessDebtRepay);\n }\n return { debtAfter, btcAfter: Math.max(0, totalBtc - seizedBtc) };\n}\n\n/**\n * Simulate full liquidation cascade with debt model.\n *\n * PRIMARY score: sumBtcAfterEvents — sum of BTC remaining after every event.\n * Captures how much collateral survives at each stage.\n * TIEBREAKER: btcAfterG1 — BTC remaining after the first (most likely) event.\n */\nexport function simulateCascade<T extends CascadeVault>(\n order: T[],\n totalDebt: number,\n seizedFraction: number,\n seizureTol: number,\n CF: number,\n THF: number,\n LB: number,\n expectedHF: number,\n): { sumBtcAfterEvents: number; btcAfterG1: number } {\n let remaining = [...order];\n let debt = totalDebt;\n const initialTotalBtc = order.reduce((s, v) => s + v.btc, 0);\n let btcAfterG1 = -1;\n let sumBtcAfterEvents = 0;\n let groupCount = 0;\n\n while (\n remaining.length > 0 &&\n debt > MIN_DEBT_THRESHOLD &&\n groupCount < MAX_GROUPS\n ) {\n const totalBtc = remaining.reduce((s, v) => s + v.btc, 0);\n const coverThreshold = totalBtc * seizedFraction * (1 - seizureTol);\n let prefixSum = 0;\n let i = 0;\n while (i < remaining.length && prefixSum < coverThreshold) {\n prefixSum += remaining[i].btc;\n i++;\n }\n const isLastGroup = i >= remaining.length;\n const { debtAfter } = simulateOneGroup(\n prefixSum,\n totalBtc,\n debt,\n isLastGroup,\n seizedFraction,\n CF,\n THF,\n LB,\n expectedHF,\n );\n remaining = remaining.slice(i);\n debt = debtAfter;\n const btcNow = totalBtc - prefixSum;\n sumBtcAfterEvents += btcNow;\n if (btcAfterG1 < 0) btcAfterG1 = btcNow;\n groupCount++;\n }\n\n return {\n sumBtcAfterEvents,\n btcAfterG1:\n btcAfterG1 < 0 ? initialTotalBtc : btcAfterG1,\n };\n}\n","/**\n * Optimal Vault Ordering for Liquidation Protection\n *\n * Finds the vault ordering that maximizes collateral surviving a multi-group\n * liquidation cascade.\n *\n * The optimizer is a bitmask DP over seized subsets. 2^n memory and 3^n work\n * blow up past `MAX_DP_N` vaults, so for larger sets the optimizer falls back to\n * a largest-first heuristic — the suggested order is then no longer guaranteed\n * optimal, and `calculate()` surfaces a `too-many-vaults` warning to say so.\n */\n\nimport {\n simulateCascade,\n type CascadeVault,\n} from \"./cascadeSimulation.js\";\n\n/**\n * Hard cap on vault count for the bitmask DP optimizer. 2^n memory + 3^n work\n * blow up past this. For n > MAX_DP_N the optimizer falls back to a\n * largest-first heuristic. Benchmark: n=18 ≈ 720ms, n=20 ≈ 5.8s — anything past\n * n=17 is too slow for interactive UI, so we cap here.\n */\nexport const MAX_DP_N = 17;\n\n/**\n * Main optimizer: bitmask DP over seized subsets.\n *\n * State: T = bitmask of vaults that have already been seized.\n * Transition: for each valid \"last group\" G ⊆ T, dp[T] = dp[T\\G] + btcAfter\n * where btcAfter = totalBtc − btcOf(T) (BTC remaining after T is seized).\n * Validation: btcOf(G) must cover target seizure at the moment G fires, i.e.\n * btcOf(G) ≥ (totalBtc − btcOf(T\\G)) × seizedFraction × (1 − seizureTol).\n *\n * Complexity: O(3^n) — the subset-of-subset enumeration visits exactly\n * Σ C(n,k) × 2^k = 3^n state-transition pairs. Single pass, no refinement loop.\n *\n * Objective: maximize sumBtcAfterEvents assuming all events fire. Debt is not\n * part of the DP state — it is used only when computing final metrics via\n * simulateCascade() on the reconstructed order.\n */\nexport function computeOptimalOrder<T extends CascadeVault>(\n vaults: T[],\n totalDebt: number,\n seizedFraction: number,\n seizureTol: number,\n CF: number,\n THF: number,\n LB: number,\n expectedHF: number,\n): { order: T[]; sumBtcAfterEvents: number; btcAfterG1: number } {\n const n = vaults.length;\n if (n === 0) return { order: [], sumBtcAfterEvents: 0, btcAfterG1: 0 };\n if (n === 1) {\n const sim = simulateCascade(\n vaults,\n totalDebt,\n seizedFraction,\n seizureTol,\n CF,\n THF,\n LB,\n expectedHF,\n );\n return { order: [...vaults], ...sim };\n }\n\n // Safety cap: 2^n memory and 3^n work blow up past MAX_DP_N vaults. Real\n // users have far fewer — each vault is a separate peg-in with a fixed fee.\n // For the unlikely n > MAX_DP_N, fall back to a largest-first heuristic;\n // calculate() surfaces a 'too-many-vaults' warning to flag the loss of the\n // optimality guarantee.\n if (n > MAX_DP_N) {\n const order = [...vaults].sort((a, b) => b.btc - a.btc);\n const sim = simulateCascade(\n order,\n totalDebt,\n seizedFraction,\n seizureTol,\n CF,\n THF,\n LB,\n expectedHF,\n );\n return { order, ...sim };\n }\n\n const N = 1 << n;\n const totalBtc = vaults.reduce((s, v) => s + v.btc, 0);\n const coverFraction = seizedFraction * (1 - seizureTol);\n\n // Precompute btcOf[T] — standard bitmask sum trick in O(2^n).\n const btcOf = new Float64Array(N);\n for (let T = 1; T < N; T++) {\n const lsb = T & -T;\n const bit = 31 - Math.clz32(lsb);\n btcOf[T] = btcOf[T ^ lsb] + vaults[bit].btc;\n }\n\n // dpSum[T] = best sumBtcAfterEvents to reach state T (T = seized subset).\n // dpG1[T] = among the paths achieving dpSum[T], the largest BTC remaining\n // after the FIRST event — the tiebreaker, so a tie on total\n // cascade survival prefers the order that survives the first\n // (most likely) liquidation event best. Matches what calculate()\n // uses to decide whether a reorder is worth suggesting.\n // prev[T] = the \"last group\" G chosen when reaching T (for reconstruction).\n const dpSum = new Float64Array(N);\n const dpG1 = new Float64Array(N);\n const prev = new Int32Array(N);\n dpSum.fill(-Infinity);\n prev.fill(-1);\n dpSum[0] = 0;\n // dpG1[0] is never read: when Tprev === 0 the first event is scored directly.\n\n const EPS = 1e-9;\n\n for (let T = 1; T < N; T++) {\n const btcAfter = totalBtc - btcOf[T]; // BTC remaining after the last group brings us to state T\n let bestSum = -Infinity;\n let bestG1 = -Infinity;\n let bestG = -1;\n\n // Enumerate non-empty subsets G ⊆ T. Across all T this visits exactly 3^n pairs.\n for (let G = T; G > 0; G = (G - 1) & T) {\n const Tprev = T ^ G;\n const prevSum = dpSum[Tprev];\n if (prevSum === -Infinity) continue;\n\n // Validate: G covers target seizure when fired from state Tprev.\n const remainingBeforeG = totalBtc - btcOf[Tprev];\n if (btcOf[G] < remainingBeforeG * coverFraction) continue;\n\n const candSum = prevSum + btcAfter;\n // First-event remainder along this path: if Tprev is empty, G itself is\n // the first event; otherwise it was fixed earlier in the subpath.\n const candG1 = Tprev === 0 ? totalBtc - btcOf[G] : dpG1[Tprev];\n\n if (\n candSum > bestSum + EPS ||\n (Math.abs(candSum - bestSum) <= EPS && candG1 > bestG1 + EPS)\n ) {\n bestSum = candSum;\n bestG1 = candG1;\n bestG = G;\n }\n }\n\n dpSum[T] = bestSum;\n dpG1[T] = bestG1;\n prev[T] = bestG;\n }\n\n // Reconstruct firing order by walking prev[] from fullMask back to 0.\n const fullMask = N - 1;\n const groupsReversed: T[][] = [];\n for (let T = fullMask; T > 0; ) {\n const G = prev[T];\n if (G === -1) break; // unreachable chain — fall back below\n const gVaults: T[] = [];\n for (let bit = 0; bit < n; bit++) {\n if (G & (1 << bit)) gVaults.push(vaults[bit]);\n }\n gVaults.sort((a, b) => b.btc - a.btc); // canonical within-group order\n groupsReversed.push(gVaults);\n T ^= G;\n }\n groupsReversed.reverse();\n const order = groupsReversed.flat();\n\n // Reconstruction safety: if the chain is incomplete for any reason, fall back\n // to largest-first rather than throwing (calculate() runs in the notification\n // render and must not crash). This path is a DP-invariant violation that\n // should never occur for valid input — log it so a regression is observable\n // even though the fallback keeps the UI alive and the result well-shaped.\n if (order.length !== n) {\n console.error(\n `computeOptimalOrder: DP reconstruction produced ${order.length}/${n} vaults; ` +\n `falling back to largest-first. This indicates a DP-invariant regression.`,\n );\n const fallback = [...vaults].sort((a, b) => b.btc - a.btc);\n const sim = simulateCascade(\n fallback,\n totalDebt,\n seizedFraction,\n seizureTol,\n CF,\n THF,\n LB,\n expectedHF,\n );\n return { order: fallback, ...sim };\n }\n\n // Compute real metrics via cascade simulation (debt-aware, matches what\n // calculate() produces for the group breakdown shown in the UI).\n const sim = simulateCascade(\n order,\n totalDebt,\n seizedFraction,\n seizureTol,\n CF,\n THF,\n LB,\n expectedHF,\n );\n return {\n order,\n sumBtcAfterEvents: sim.sumBtcAfterEvents,\n btcAfterG1: sim.btcAfterG1,\n };\n}\n","/**\n * Vault Split Utilities for Aave Liquidation Protection\n *\n * BTC vaults are indivisible UTXOs. The Babylon Spoke liquidates one whole\n * vault per call, taking the head of the borrower's ordered vault list.\n * Splitting a deposit into 2 vaults (sacrificial + protected) lets a\n * liquidation take the small head vault and leave the rest of the position.\n *\n * The sacrificial vault (index 0) is sized so that seizing it at the expected\n * liquidation health factor lifts the position back to the split target\n * health factor. The protected vault (index 1) holds the remainder.\n *\n * Seizure formula:\n * ```\n * liq_penalty = LB × CF\n * debt_to_repay = total_debt × (THF - current_HF) / (THF - liq_penalty)\n * target_seizure = debt_to_repay × LB\n * ```\n */\n\nconst MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);\n\n/** 1.0 in WAD fixed point, the unit of on-chain health factors. */\nconst WAD = 10n ** 18n;\n\n/** 100% in basis points, the unit of on-chain liquidation bonuses. */\nconst PERCENTAGE_FACTOR_BPS = 10_000n;\n\n/**\n * Split target health factor: the health factor a two-vault position lands\n * on after its sacrificial vault is seized. The sacrificial vault is sized so\n * that seizing it lifts the position back to this value.\n *\n * This is a Babylon sizing parameter, not a contract parameter. The Babylon\n * Spoke sizes each liquidation by the head vault's BTC and never reads the\n * Aave `LiquidationConfig.targetHealthFactor`, so no contract can supply it.\n * At 1.08 the sacrificial vault stays smaller than the protected vault for\n * every collateral factor up to 86%, and after the first liquidation BTC can\n * fall about 7.4% more before the protected vault is at risk. The risk model\n * and the decision are recorded in babylon-toolkit#2577.\n *\n * Changing this value resizes every new split. A change needs sign-off from\n * the risk model owner and two code-owner approvals: this file is the\n * \"Multi-vault split transactions\" critical path in CLAUDE.md and has its own\n * entry in .github/CODEOWNERS.\n */\nexport const SPLIT_TARGET_HEALTH_FACTOR = 1.08;\n\n/**\n * Health factor at which a liquidation is assumed to land, in WAD.\n *\n * Aave liquidations settle close to 1.0 (median about 0.997 on Aave V3 over\n * a year), so the split is sized for 0.99. The liquidation bonus used for\n * sizing is read from the Spoke's bonus curve at this health factor. Same\n * ownership and review rules as {@link SPLIT_TARGET_HEALTH_FACTOR}.\n */\nexport const EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION_WAD =\n 990_000_000_000_000_000n;\n\n/**\n * {@link EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION_WAD} as a plain number (0.99),\n * for the floating-point split formulas.\n */\nexport const EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION =\n Number(EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION_WAD) / Number(WAD);\n\n/**\n * The sacrificial vault must be strictly smaller than the protected vault.\n * Otherwise, after the first liquidation, restoring partial liquidation would\n * need a new vault larger than everything left in the position.\n */\nconst MAX_SACRIFICIAL_SHARE = 0.5;\n\n/**\n * Why the split parameters alone refuse a two-vault split. These are the\n * verdicts {@link findSplitSizingViolation} can return; it sees only the\n * parameters, so each one holds at any deposit amount. One of them,\n * `sacrificial-not-smaller`, is also what {@link computeOptimalSplit} reports\n * when rounding the seizure up leaves the sacrificial vault not strictly\n * smaller, so seeing it in an {@link OptimalSplitResult} does not by itself\n * mean the parameters are bad:\n * - `target-not-above-expected-hf`: THF ≤ expected HF, so the seizure formula\n * has no valid target.\n * - `target-not-above-liquidation-penalty`: THF ≤ LB × CF, so one liquidation\n * takes the whole position.\n * - `sacrificial-not-smaller`: the sacrificial vault would not be strictly\n * smaller than the protected vault.\n * - `no-seizure-expected`: the seized fraction is zero (for example CF 0), so\n * a sacrificial vault would be empty and the split would protect nothing.\n */\nexport type SplitParamsViolation =\n | \"target-not-above-expected-hf\"\n | \"target-not-above-liquidation-penalty\"\n | \"sacrificial-not-smaller\"\n | \"no-seizure-expected\";\n\n/**\n * Why {@link computeOptimalSplit} refuses a split: the parameter verdicts\n * above, plus one only a deposit amount can reach.\n * - `below-dust`: the vaults would be below the HTLC dust threshold. A\n * non-positive deposit reports this too, but only when the parameters\n * themselves allow a split — otherwise the parameter verdict wins.\n */\nexport type SplitSizingViolation = SplitParamsViolation | \"below-dust\";\n\n/**\n * Inputs of the Aave v4 liquidation bonus curve, in their on-chain units.\n */\nexport interface LiquidationBonusCurve {\n /** `LiquidationConfig.healthFactorForMaxBonus`, WAD. Below 1e18. */\n healthFactorForMaxBonus: bigint;\n /** `LiquidationConfig.liquidationBonusFactor`, BPS. At most 10_000. */\n liquidationBonusFactor: bigint;\n /** `DynamicReserveConfig.maxLiquidationBonus`, BPS. At least 10_000. */\n maxLiquidationBonus: bigint;\n}\n\n/**\n * Effective dust threshold for HTLC outputs in satoshis.\n *\n * Standard P2TR dust is 330 sats, but HTLC scripts are larger than a standard\n * P2TR key-path spend. A conservative estimate for an HTLC script-path output\n * is ~2000 sats. This is a defense-in-depth check — in practice, minDeposit\n * from the on-chain contract is orders of magnitude larger.\n */\nconst HTLC_EFFECTIVE_DUST_THRESHOLD = 2000n;\n\nfunction assertSafePrecision(value: bigint, name: string): void {\n if (value > MAX_SAFE_BIGINT) {\n throw new RangeError(\n `${name} (${value}) exceeds Number.MAX_SAFE_INTEGER; precision would be lost`,\n );\n }\n}\n\n/**\n * Parameters for computing the optimal vault split.\n */\nexport interface OptimalSplitParams {\n /** Total deposit amount in satoshis */\n totalBtc: bigint;\n /** Collateral factor (e.g. 0.78 for 78%) */\n CF: number;\n /** Liquidation bonus at the expected health factor (e.g. 1.0504) */\n LB: number;\n /** Split target health factor (e.g. {@link SPLIT_TARGET_HEALTH_FACTOR}) */\n THF: number;\n /** Expected health factor at liquidation (e.g. {@link EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION}) */\n expectedHF: number;\n}\n\n/**\n * Result of the optimal vault split computation.\n */\nexport interface OptimalSplitResult {\n /** Sacrificial vault amount in satoshis (index 0, seized first) */\n sacrificialVault: bigint;\n /** Protected vault amount in satoshis (index 1, survives liquidation) */\n protectedVault: bigint;\n /** Fraction of collateral that would be seized (0–1) */\n seizedFraction: number;\n /** Target seizure amount in satoshis */\n targetSeizureBtc: bigint;\n /**\n * Why the split is refused, or null when it is valid. Non-null exactly when\n * the split is not usable: both vault amounts are then 0n, and null always\n * means two positive amounts that sum to `totalBtc`.\n */\n sizingViolation: SplitSizingViolation | null;\n}\n\n/**\n * Parameters for computing the minimum deposit required for a split.\n */\nexport interface MinDepositForSplitParams {\n /** Minimum peg-in amount in satoshis */\n minPegin: bigint;\n /** Seized fraction (0–1), from computeOptimalSplit or computeSeizedFraction */\n seizedFraction: number;\n}\n\n/**\n * Compute the Aave v4 liquidation bonus at a health factor.\n *\n * Integer-exact port of `LiquidationLogic.calculateLiquidationBonus`\n * (aave-v4 `src/spoke/libraries/LiquidationLogic.sol`), which the Babylon\n * liquidation path calls with the same inputs: at or below\n * `healthFactorForMaxBonus` the bonus is the maximum; above it the bonus falls\n * linearly towards a minimum of\n * `(maxLiquidationBonus − 100%) × liquidationBonusFactor + 100%` at HF 1.0.\n * Every division rounds down, as on-chain.\n *\n * @param curve - Bonus curve inputs in on-chain units\n * @param healthFactorWad - Health factor in WAD, at most 1e18\n * @returns Liquidation bonus in BPS (e.g. 10504n for 105.04%)\n * @throws {RangeError} when an input is outside the range the contract\n * accepts (the contract would revert or was configured out of range)\n */\nexport function computeLiquidationBonusBps(\n curve: LiquidationBonusCurve,\n healthFactorWad: bigint,\n): bigint {\n const {\n healthFactorForMaxBonus,\n liquidationBonusFactor,\n maxLiquidationBonus,\n } = curve;\n\n if (healthFactorForMaxBonus < 0n || healthFactorForMaxBonus >= WAD) {\n throw new RangeError(\n `healthFactorForMaxBonus (${healthFactorForMaxBonus}) must be in [0, 1e18)`,\n );\n }\n if (\n liquidationBonusFactor < 0n ||\n liquidationBonusFactor > PERCENTAGE_FACTOR_BPS\n ) {\n throw new RangeError(\n `liquidationBonusFactor (${liquidationBonusFactor}) must be in [0, 10000] BPS`,\n );\n }\n if (maxLiquidationBonus < PERCENTAGE_FACTOR_BPS) {\n throw new RangeError(\n `maxLiquidationBonus (${maxLiquidationBonus}) must be at least 10000 BPS`,\n );\n }\n if (healthFactorWad < 0n || healthFactorWad > WAD) {\n throw new RangeError(\n `healthFactor (${healthFactorWad}) must be in [0, 1e18]; the contract reverts above 1e18`,\n );\n }\n\n if (healthFactorWad <= healthFactorForMaxBonus) {\n return maxLiquidationBonus;\n }\n\n const minLiquidationBonus =\n ((maxLiquidationBonus - PERCENTAGE_FACTOR_BPS) * liquidationBonusFactor) /\n PERCENTAGE_FACTOR_BPS +\n PERCENTAGE_FACTOR_BPS;\n\n return (\n minLiquidationBonus +\n ((maxLiquidationBonus - minLiquidationBonus) * (WAD - healthFactorWad)) /\n (WAD - healthFactorForMaxBonus)\n );\n}\n\n/**\n * The liquidation bonus a split is sized against: the Spoke's bonus curve at\n * {@link EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION_WAD}, as a multiplier.\n *\n * Every consumer of the split inputs (the deposit split, the liquidation\n * warnings, the reorder guard, the E2E CLI) must use this one value, or a\n * fresh split falls short of the seizure the warnings expect.\n *\n * @param bonusConfig - `healthFactorForMaxBonus` (WAD) and\n * `liquidationBonusFactor` (BPS) from the Spoke's liquidation config\n * @param maxLiquidationBonusBps - `maxLiquidationBonus` (BPS) of the dynamic\n * config the position's liquidation uses\n * @returns Liquidation bonus multiplier (e.g. 1.0504)\n * @throws {RangeError} when an input is outside the range the contract accepts\n */\nexport function computeSplitLiquidationBonus(\n bonusConfig: Omit<LiquidationBonusCurve, \"maxLiquidationBonus\">,\n maxLiquidationBonusBps: number,\n): number {\n const bonusBps = computeLiquidationBonusBps(\n { ...bonusConfig, maxLiquidationBonus: BigInt(maxLiquidationBonusBps) },\n EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION_WAD,\n );\n return Number(bonusBps) / Number(PERCENTAGE_FACTOR_BPS);\n}\n\n/**\n * Check the split sizing parameters before a two-vault split is offered.\n *\n * @param params - Split target health factor, expected health factor at\n * liquidation, collateral factor and liquidation bonus\n * @returns The first violated rule, or null when a split may be offered\n */\nexport function findSplitSizingViolation(params: {\n CF: number;\n LB: number;\n THF: number;\n expectedHF: number;\n}): SplitParamsViolation | null {\n const { CF, LB, THF, expectedHF } = params;\n\n // Negated comparisons so a NaN input is refused rather than let through.\n if (!(THF > expectedHF)) {\n return \"target-not-above-expected-hf\";\n }\n if (!(THF > LB * CF)) {\n return \"target-not-above-liquidation-penalty\";\n }\n const seizedFraction = computeSeizedFraction(CF, LB, THF, expectedHF);\n if (!(seizedFraction < MAX_SACRIFICIAL_SHARE)) {\n return \"sacrificial-not-smaller\";\n }\n // Nothing would be seized, so a sacrificial vault would be empty and the\n // split would protect nothing.\n if (!(seizedFraction > 0)) {\n return \"no-seizure-expected\";\n }\n return null;\n}\n\n/**\n * Compute the fraction of collateral that would be seized during liquidation,\n * returning both the raw (unclamped) and clamped values.\n *\n * The raw value is useful for detecting unusual protocol parameter combinations\n * (values outside [0, 1] indicate something unexpected).\n *\n * Formula:\n * ```\n * liq_penalty = LB × CF\n * seized_fraction = CF × (THF - expectedHF) / (THF - liq_penalty) × LB / expectedHF\n * ```\n *\n * @param CF - Collateral factor (e.g. 0.78)\n * @param LB - Liquidation bonus at the expected health factor (e.g. 1.0504, see {@link computeSplitLiquidationBonus})\n * @param THF - Split target health factor (e.g. {@link SPLIT_TARGET_HEALTH_FACTOR})\n * @param expectedHF - Expected health factor at liquidation (e.g. {@link EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION})\n * @returns Both the raw seized fraction and the clamped [0, 1] value\n */\nexport function computeSeizedFractionDetailed(\n CF: number,\n LB: number,\n THF: number,\n expectedHF: number,\n): { seizedFraction: number; seizedFractionRaw: number } {\n // HF ≤ 0 means position is fully underwater — full seizure\n if (expectedHF <= 0) {\n return { seizedFraction: 1, seizedFractionRaw: Infinity };\n }\n\n const liqPenalty = LB * CF;\n\n // If THF <= liq_penalty, full liquidation is inevitable\n if (THF <= liqPenalty) {\n return { seizedFraction: 1, seizedFractionRaw: Infinity };\n }\n\n // Floating-point errors here are ~1e-15, below one satoshi for any BTC\n // amount, and computeOptimalSplit rounds the sacrificial vault up.\n const seizedFractionRaw =\n ((CF * (THF - expectedHF)) / (THF - liqPenalty)) * (LB / expectedHF);\n\n return {\n seizedFraction: Math.max(0, Math.min(1, seizedFractionRaw)),\n seizedFractionRaw,\n };\n}\n\n/**\n * Compute the fraction of collateral that would be seized during liquidation.\n *\n * @param CF - Collateral factor (e.g. 0.78)\n * @param LB - Liquidation bonus at the expected health factor (e.g. 1.0504, see {@link computeSplitLiquidationBonus})\n * @param THF - Split target health factor (e.g. {@link SPLIT_TARGET_HEALTH_FACTOR})\n * @param expectedHF - Expected health factor at liquidation (e.g. {@link EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION})\n * @returns Seized fraction clamped to [0, 1]\n */\nexport function computeSeizedFraction(\n CF: number,\n LB: number,\n THF: number,\n expectedHF: number,\n): number {\n return computeSeizedFractionDetailed(CF, LB, THF, expectedHF).seizedFraction;\n}\n\n/**\n * Compute the optimal split between a sacrificial vault and a protected vault.\n *\n * The sacrificial vault (index 0) is the target seizure rounded up to a whole\n * satoshi. The protected vault (index 1) holds the remainder. The split is\n * refused when the parameters fail {@link findSplitSizingViolation}, when\n * rounding the seizure up leaves the sacrificial vault not strictly smaller,\n * or when the vaults would be dust — a non-positive deposit reaches the last\n * of these whenever the parameters themselves allow a split. Both amounts are\n * then 0n and `sizingViolation` says why; a null violation means two positive\n * amounts that sum to `totalBtc`.\n *\n * @param params - Total BTC and split sizing parameters\n * @returns Split result with vault sizes, seized fraction, target seizure and\n * any sizing violation\n *\n * @example\n * ```typescript\n * import {\n * computeOptimalSplit,\n * EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION,\n * SPLIT_TARGET_HEALTH_FACTOR,\n * } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n *\n * const result = computeOptimalSplit({\n * totalBtc: 1_000_000_000n, // 10 BTC in sats\n * CF: 0.78,\n * LB: 1.0504,\n * THF: SPLIT_TARGET_HEALTH_FACTOR,\n * expectedHF: EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION,\n * });\n * // result.sacrificialVault === 285_716_677n (2.86 BTC)\n * // result.protectedVault === 714_283_323n (7.14 BTC)\n * ```\n */\nexport function computeOptimalSplit(\n params: OptimalSplitParams,\n): OptimalSplitResult {\n const { totalBtc, CF, LB, THF, expectedHF } = params;\n\n const sizingViolation = findSplitSizingViolation({ CF, LB, THF, expectedHF });\n const seizedFraction = computeSeizedFraction(CF, LB, THF, expectedHF);\n\n // Nothing to split: report the parameters' own verdict when they already\n // refuse one, and otherwise the amount reason, so a null violation always\n // means two usable vaults.\n if (totalBtc <= 0n) {\n return {\n sacrificialVault: 0n,\n protectedVault: 0n,\n seizedFraction,\n targetSeizureBtc: 0n,\n sizingViolation: sizingViolation ?? \"below-dust\",\n };\n }\n\n assertSafePrecision(totalBtc, \"totalBtc\");\n\n if (sizingViolation !== null) {\n return {\n sacrificialVault: 0n,\n protectedVault: 0n,\n seizedFraction,\n targetSeizureBtc: 0n,\n sizingViolation,\n };\n }\n\n const totalBtcNum = Number(totalBtc);\n const targetSeizureBtc = BigInt(Math.ceil(totalBtcNum * seizedFraction));\n const sacrificialVault = targetSeizureBtc;\n const protectedVault = totalBtc - sacrificialVault;\n\n // The share check above passed, but rounding the seizure up can still leave\n // the sacrificial vault equal to or larger than the protected one.\n if (sacrificialVault >= protectedVault) {\n return {\n sacrificialVault: 0n,\n protectedVault: 0n,\n seizedFraction,\n targetSeizureBtc: 0n,\n sizingViolation: \"sacrificial-not-smaller\",\n };\n }\n\n // The sacrificial vault is the smaller of the two by the check above, so\n // testing it covers both. Below the HTLC dust threshold the split is not\n // viable — return zeroed vaults so the caller treats this as\n // non-splittable. We avoid throwing because this function is called during\n // render (useMemo) and throwing would crash the component instead of\n // showing validation feedback.\n if (sacrificialVault < HTLC_EFFECTIVE_DUST_THRESHOLD) {\n return {\n sacrificialVault: 0n,\n protectedVault: 0n,\n seizedFraction,\n targetSeizureBtc: 0n,\n sizingViolation: \"below-dust\",\n };\n }\n\n return {\n sacrificialVault,\n protectedVault,\n seizedFraction,\n targetSeizureBtc,\n sizingViolation: null,\n };\n}\n\n/**\n * Compute the minimum total deposit required for a 2-vault split.\n *\n * Both vaults must be at least `minPegin` satoshis. This function returns\n * the minimum total deposit where both the sacrificial and protected vaults\n * would meet the minimum peg-in requirement.\n *\n * @param params - Minimum peg-in and seized fraction\n * @returns Minimum total deposit in satoshis. Returns 0n in two cases:\n * - `seizedFraction >= 0.5`: split refused (the sacrificial vault would not\n * be smaller than the protected vault)\n * - `seizedFraction <= 0`: split not useful (no seizure expected at this health factor)\n *\n * @example\n * ```typescript\n * import { computeMinDepositForSplit } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n *\n * const minDeposit = computeMinDepositForSplit({\n * minPegin: 50_000n, // 0.0005 BTC\n * seizedFraction: 0.2857,\n * });\n * // minDeposit === 175_009n: 50_000 / 0.2857, rounded up\n * ```\n */\nexport function computeMinDepositForSplit(\n params: MinDepositForSplitParams,\n): bigint {\n const { minPegin, seizedFraction } = params;\n\n assertSafePrecision(minPegin, \"minPegin\");\n\n // The sacrificial vault must stay smaller than the protected vault\n if (!(seizedFraction < MAX_SACRIFICIAL_SHARE)) {\n return 0n;\n }\n\n // If seized fraction is effectively zero, split is not useful\n if (seizedFraction <= 0) {\n return 0n;\n }\n\n // Minimum total so the sacrificial vault >= minPegin. The guard above keeps\n // the seized fraction under half, so the sacrificial vault is always the\n // smaller of the two and sizing it is what binds; the protected vault clears\n // minPegin at any total that satisfies this one.\n return BigInt(Math.ceil(Number(minPegin) / seizedFraction));\n}\n"],"names":["AAVE_FUNCTION_NAMES","BPS_SCALE","AAVE_BASE_CURRENCY_DECIMALS","AAVE_BASE_CURRENCY_RAY_DECIMALS","WAD_DECIMALS","HEALTH_FACTOR_LIQUIDATION_THRESHOLD","HEALTH_FACTOR_RISKY_THRESHOLD","HEALTH_FACTOR_WARNING_THRESHOLD","MIN_HEALTH_FACTOR_FOR_BORROW","FULL_REPAY_BUFFER_DIVISOR","MAX_ALLOWED_USER_RESERVES_LIMIT","NO_POSITION_ERROR_NAME","MAX_ERROR_CAUSE_DEPTH","isNoPositionRevert","err","current","depth","data","cause","getPosition","publicClient","contractAddress","user","result","AaveIntegrationAdapterABI","position","zeroAddress","getPositionSizeParams","maxPositionBTC","maxVaultsPerPosition","mapPositionResult","mapAccountDataResult","getUserAccountData","spokeAddress","userAddress","AaveSpokeABI","getUserPositionAndAccountData","reserveId","positionResult","accountDataResult","getUserPosition","getUserTotalDebt","getUserPositions","reserveIds","getUserTotalDebts","getReserve","getReserves","getLiquidationBonusConfig","config","getDynamicReserveConfig","dynamicConfigKey","getMaxUserReservesLimit","getPositionReserveTotalDebt","proxyContract","AaveAdapterPositionProxyABI","getOracleAddress","getReservesPrices","oracleAddress","AaveOracleABI","getReservesPricesSafe","results","error","i","priceRaw","getAssetDrawnRatesSafe","requests","hub","assetId","AaveHubABI","buildReorderVaultsTx","permutedVaultIds","encodeFunctionData","buildWithdrawCollateralsTx","vaultIds","buildBorrowTx","debtReserveId","amount","receiver","buildRepayTx","borrower","aaveValueToUsd","value","aaveRayValueToUsd","wadToNumber","hasDebtFromPosition","getHealthFactorStatus","healthFactor","hasDebt","getHealthFactorStatusFromValue","calculateHealthFactor","collateralValueUsd","totalDebtUsd","liquidationThresholdBps","SEIZURE_TOL","MAX_GROUPS","MIN_DEBT_THRESHOLD","getGroup1FromOrder","order","seizedFraction","seizureTol","coverThreshold","s","v","prefixSum","simulateOneGroup","seizedBtc","totalBtc","debt","isLastGroup","CF","THF","LB","expectedHF","liqPenalty","pLiq","targetSeizure","overSeizureBtc","denominator","debtToRepay","debtAfter","overSeizureVal","fairnessDebtRepay","simulateCascade","totalDebt","remaining","initialTotalBtc","btcAfterG1","sumBtcAfterEvents","groupCount","btcNow","MAX_DP_N","computeOptimalOrder","vaults","n","sim","a","b","N","coverFraction","btcOf","T","lsb","bit","dpSum","dpG1","prev","EPS","btcAfter","bestSum","bestG1","bestG","G","Tprev","prevSum","remainingBeforeG","candSum","candG1","fullMask","groupsReversed","gVaults","fallback","MAX_SAFE_BIGINT","WAD","PERCENTAGE_FACTOR_BPS","SPLIT_TARGET_HEALTH_FACTOR","EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION_WAD","EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION","MAX_SACRIFICIAL_SHARE","HTLC_EFFECTIVE_DUST_THRESHOLD","assertSafePrecision","name","computeLiquidationBonusBps","curve","healthFactorWad","healthFactorForMaxBonus","liquidationBonusFactor","maxLiquidationBonus","minLiquidationBonus","computeSplitLiquidationBonus","bonusConfig","maxLiquidationBonusBps","bonusBps","findSplitSizingViolation","params","computeSeizedFraction","computeSeizedFractionDetailed","seizedFractionRaw","computeOptimalSplit","sizingViolation","totalBtcNum","targetSeizureBtc","sacrificialVault","protectedVault","computeMinDepositForSplit","minPegin"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../../../src/tbv/integrations/aave/constants.ts","../../../../src/tbv/integrations/aave/clients/query.ts","../../../../src/tbv/integrations/aave/clients/spoke.ts","../../../../src/tbv/integrations/aave/clients/positionProxy.ts","../../../../src/tbv/integrations/aave/clients/oracle.ts","../../../../src/tbv/integrations/aave/clients/hub.ts","../../../../src/tbv/integrations/aave/clients/transaction.ts","../../../../src/tbv/integrations/aave/utils/aaveConversions.ts","../../../../src/tbv/integrations/aave/utils/debtUtils.ts","../../../../src/tbv/integrations/aave/utils/healthFactor.ts","../../../../src/tbv/integrations/aave/utils/cascadeSimulation.ts","../../../../src/tbv/integrations/aave/utils/optimalOrder.ts","../../../../src/tbv/integrations/aave/utils/vaultSplit.ts"],"sourcesContent":["/**\n * Aave Protocol Constants\n *\n * Constants for interacting with Aave v4 protocol.\n * Reference: https://github.com/aave/aave-v4 ISpoke.sol\n */\n\n/**\n * Aave contract function names\n * Centralized constants for contract interactions\n */\nexport const AAVE_FUNCTION_NAMES = {\n /** Withdraw selected vaults from position (partial withdrawal) */\n WITHDRAW_COLLATERALS: \"withdrawCollaterals\",\n /** Borrow from Core Spoke position */\n BORROW: \"borrowFromCorePosition\",\n /** Repay debt to Core Spoke position */\n REPAY: \"repayToCorePosition\",\n /** Reorder vault prefix ordering for liquidation priority */\n REORDER_VAULTS: \"reorderVaults\",\n} as const;\n\n/**\n * Full basis points scale (10000 BPS = 100%)\n *\n * Use this when converting BPS directly to decimal:\n * Example: 8000 BPS / 10000 = 0.80\n */\nexport const BPS_SCALE = 10000;\n\n/**\n * Aave base currency decimals\n * Account data values (collateral, debt) use 1e26 = $1 USD\n *\n * Reference: ISpoke.sol UserAccountData\n */\nexport const AAVE_BASE_CURRENCY_DECIMALS = 26;\n\n/**\n * Aave RAY-scaled base currency decimals\n * Debt values (totalDebtValueRay) use 1e53 = $1 USD\n * (base currency 1e26 scaled by RAY 1e27).\n *\n * Reference: IAaveSpoke.sol UserAccountData.totalDebtValueRay\n */\nexport const AAVE_BASE_CURRENCY_RAY_DECIMALS = 53;\n\n/**\n * WAD decimals (1e18 = 1.0)\n * Used for health factor and collateral factor values\n *\n * Reference: ISpoke.sol - \"healthFactor expressed in WAD. 1e18 represents a health factor of 1.00\"\n */\nexport const WAD_DECIMALS = 18;\n\n/**\n * Dimensionless health factor at or above which Aave cannot liquidate a position.\n *\n * ISpoke.sol defines this boundary as 1.0 (1e18 WAD), not a stored parameter.\n * Unlike display thresholds, it is not tunable. The per-asset\n * liquidationThresholdBps is fetched from chain and uses basis points.\n */\nexport const HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1.0;\n\n/**\n * Display threshold for positions close to liquidation, independent of the borrow floor.\n * A position from HEALTH_FACTOR_LIQUIDATION_THRESHOLD up to and including this\n * value is \"risky\".\n */\nexport const HEALTH_FACTOR_RISKY_THRESHOLD = 1.1;\n\n/**\n * Health factor warning threshold.\n * A position above HEALTH_FACTOR_RISKY_THRESHOLD and up to and including this\n * value is \"warning\". A position above this value is \"safe\".\n */\nexport const HEALTH_FACTOR_WARNING_THRESHOLD = 2.0;\n\n/**\n * Minimum health factor allowed for borrowing. Collateral factor doubles as the\n * liquidation threshold here, so this floor is the only borrow→liquidation cushion.\n */\nexport const MIN_HEALTH_FACTOR_FOR_BORROW = 1.05;\n\n/**\n * Approval headroom for repay-all, sized against interest accrual between\n * quoting the debt and transaction execution.\n *\n * 0.5% buffer (50 basis points). Sized to absorb hours of execution delay\n * (e.g. Safe-multisig quorum collection). The repay itself sends the\n * repay-all sentinel and the adapter pulls only what's actually owed; the\n * buffer only pads the approval cap, and the cap is additionally bounded by\n * the user's balance, so a larger buffer never blocks a legitimate repay.\n */\nexport const FULL_REPAY_BUFFER_DIVISOR = 200n; // 1/200 = 0.5% buffer\n\n/**\n * Sentinel the Spoke stores in `MAX_USER_RESERVES_LIMIT` to mean \"no cap\"\n * (`type(uint16).max`). The contract skips the reserve-count check on this\n * value, so callers must treat it as unlimited and never show it to a user.\n *\n * Reference: Spoke.sol MAX_ALLOWED_USER_RESERVES_LIMIT\n */\nexport const MAX_ALLOWED_USER_RESERVES_LIMIT = 65535;\n","/**\n * Aave Integration Adapter - Read operations (queries)\n *\n * Only includes functions that provide data NOT available from the indexer.\n * Most position/vault data should be fetched from the GraphQL indexer instead.\n */\n\nimport { type Address, type Hex, type PublicClient, zeroAddress } from \"viem\";\n\nimport type { AaveMarketPosition, PositionSizeParams } from \"../types.js\";\nimport AaveIntegrationAdapterABI from \"./abis/AaveIntegrationAdapter.abi.json\";\n\n/**\n * The adapter's custom error for \"this account has no position proxy\".\n *\n * `AaveAdapter._getBorrowerProxy` reverts with it when `userToProxy[user]` is\n * the zero address, which is every account that has never opened a position —\n * so on a freshly deployed adapter it is the normal answer for a new user, not\n * a failure. Matched by name (the ABI carries the error entry, so viem decodes\n * it) rather than by raw selector.\n */\nconst NO_POSITION_ERROR_NAME = \"InvalidProxyContract\";\n\n/** Bound on the `cause` walk, so a self-referencing chain cannot spin. */\nconst MAX_ERROR_CAUSE_DEPTH = 10;\n\n/**\n * True when `err` is the adapter reverting because the account has no proxy.\n *\n * Matches structurally on viem's decoded `data.errorName` rather than with\n * `instanceof BaseError` / `err.walk(...)`. The SDK lists `viem` as external,\n * so a consumer can — and in this monorepo does — resolve a physically\n * different copy of the same viem version than the one bundled here (pnpm\n * keys the store path on peer versions). Errors thrown by the caller's client\n * are instances of the caller's classes, so `instanceof` silently returns\n * false across that boundary and the revert would escape as an opaque throw.\n *\n * Narrow on purpose: any other revert, and every transport/RPC failure, must\n * keep propagating. Reporting those as \"no position\" would turn an infra\n * outage into a silent, wrong answer for callers that gate signing on it.\n */\nfunction isNoPositionRevert(err: unknown): boolean {\n let current: unknown = err;\n for (let depth = 0; depth < MAX_ERROR_CAUSE_DEPTH; depth++) {\n if (typeof current !== \"object\" || current === null) return false;\n const { data, cause } = current as {\n data?: { errorName?: unknown };\n cause?: unknown;\n };\n if (data?.errorName === NO_POSITION_ERROR_NAME) return true;\n if (cause === undefined || cause === current) return false;\n current = cause;\n }\n return false;\n}\n\n/**\n * Get a position by user address.\n *\n * The adapter resolves the user's proxy contract and collateralized vault IDs.\n *\n * NOTE: Prefer using the indexer (fetchAavePositionWithCollaterals) for position data.\n * This function is only needed when you need data not available in the indexer,\n * or when you need to verify on-chain state.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param contractAddress - AaveIntegrationAdapter contract address\n * @param user - User's Ethereum address\n * @returns Market position data or null if position doesn't exist\n */\nexport async function getPosition(\n publicClient: PublicClient,\n contractAddress: Address,\n user: Address,\n): Promise<AaveMarketPosition | null> {\n type PositionResult = {\n proxyContract: Address;\n vaultIds: Hex[];\n totalCollateralBTC: bigint;\n };\n\n let result: unknown;\n try {\n result = await publicClient.readContract({\n address: contractAddress,\n abi: AaveIntegrationAdapterABI,\n functionName: \"getPosition\",\n args: [user],\n });\n } catch (err) {\n // \"No proxy yet\" is a revert, not a zero-address return value, so it has\n // to be caught here for this function to honour its documented contract.\n if (isNoPositionRevert(err)) return null;\n throw err;\n }\n\n const position = result as PositionResult;\n\n // Defence in depth: the adapter reverts rather than returning a zero proxy,\n // so this should be unreachable. Kept because the return value is external\n // input — a zero proxy must never reach a caller as a real position.\n if (position.proxyContract === zeroAddress) {\n return null;\n }\n\n return {\n proxyContract: position.proxyContract,\n vaultIds: position.vaultIds,\n totalCollateralBTC: position.totalCollateralBTC,\n };\n}\n\n/**\n * Get position size parameters from the adapter config contract.\n *\n * Returns the maximum BTC position size and maximum vaults per position\n * as configured on-chain.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param contractAddress - AaveAdapterConfig contract address\n * @returns Position size parameters (maxPositionBTC, maxVaultsPerPosition)\n */\nexport async function getPositionSizeParams(\n publicClient: PublicClient,\n contractAddress: Address,\n): Promise<PositionSizeParams> {\n const result = await publicClient.readContract({\n address: contractAddress,\n abi: AaveIntegrationAdapterABI,\n functionName: \"getPositionSizeParams\",\n });\n\n const [maxPositionBTC, maxVaultsPerPosition] = result as [bigint, bigint];\n\n return {\n maxPositionBTC,\n maxVaultsPerPosition,\n };\n}\n","/**\n * Aave Spoke Client - Read operations\n *\n * Provides read operations for interacting with Aave v4 Spoke contracts.\n * Used to fetch live user position data (debt, collateral) from the Core Spoke.\n *\n * Note: Reserve data should be fetched from the indexer via fetchReserves.ts\n * since it doesn't need to be live and benefits from caching.\n */\n\nimport type { Abi, Address, PublicClient } from \"viem\";\n\nimport type {\n AaveSpokeUserAccountData,\n AaveSpokeUserPosition,\n} from \"../types.js\";\nimport AaveSpokeABI from \"./abis/AaveSpoke.abi.json\";\n\n/** Account data result type from contract */\ntype AccountDataResult = {\n riskPremium: bigint;\n avgCollateralFactor: bigint;\n healthFactor: bigint;\n totalCollateralValue: bigint;\n totalDebtValueRay: bigint;\n activeCollateralCount: bigint;\n borrowCount: bigint;\n};\n\n/** Position result type from contract */\ntype PositionResult = {\n drawnShares: bigint;\n premiumShares: bigint;\n premiumOffsetRay: bigint;\n suppliedShares: bigint;\n dynamicConfigKey: number;\n};\n\n/**\n * Maps contract result to AaveSpokeUserPosition\n */\nfunction mapPositionResult(result: PositionResult): AaveSpokeUserPosition {\n return {\n drawnShares: result.drawnShares,\n premiumShares: result.premiumShares,\n premiumOffsetRay: result.premiumOffsetRay,\n suppliedShares: result.suppliedShares,\n dynamicConfigKey: result.dynamicConfigKey,\n };\n}\n\n/** Maps contract result to AaveSpokeUserAccountData */\nfunction mapAccountDataResult(\n data: AccountDataResult,\n): AaveSpokeUserAccountData {\n return {\n riskPremium: data.riskPremium,\n avgCollateralFactor: data.avgCollateralFactor,\n healthFactor: data.healthFactor,\n totalCollateralValue: data.totalCollateralValue,\n totalDebtValueRay: data.totalDebtValueRay,\n activeCollateralCount: data.activeCollateralCount,\n borrowCount: data.borrowCount,\n };\n}\n\n/**\n * Get aggregated user account health data from AAVE spoke.\n *\n * **Live data** - Fetches real-time account health including health factor, total collateral,\n * and total debt across all reserves. Values are calculated on-chain using AAVE oracles\n * and are the authoritative source for liquidation decisions.\n *\n * @param publicClient - Viem public client for reading contracts (from `createPublicClient()`)\n * @param spokeAddress - AAVE Spoke contract address (BTC Vault Core Spoke for vBTC collateral)\n * @param userAddress - User's proxy contract address (NOT user's wallet address)\n * @returns User account data with health metrics, collateral, and debt values\n *\n * @example\n * ```typescript\n * import { getUserAccountData } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n * import { createPublicClient, http } from \"viem\";\n * import { sepolia } from \"viem/chains\";\n *\n * const publicClient = createPublicClient({\n * chain: sepolia,\n * transport: http()\n * });\n *\n * const accountData = await getUserAccountData(\n * publicClient,\n * \"0x123...\", // AAVE Spoke address\n * \"0x456...\" // User's AAVE proxy address (from getPosition)\n * );\n *\n * console.log(\"Health Factor:\", accountData.healthFactor);\n * console.log(\"Collateral (USD):\", accountData.totalCollateralValue);\n * console.log(\"Debt (USD):\", accountData.totalDebtValueRay);\n * ```\n *\n * @remarks\n * **Return values:**\n * - `healthFactor` - WAD format (1e18 = 1.0). Below 1.0 = liquidatable\n * - `totalCollateralValue` - USD value in base currency (1e26 = $1)\n * - `totalDebtValueRay` - USD value in RAY-scaled base currency (1e53 = $1)\n * - `avgCollateralFactor` - Weighted average collateral factor in WAD (1e18 = 100%)\n * - `riskPremium` - Additional risk premium\n *\n * **Use cases:**\n * - Check liquidation risk before borrowing\n * - Calculate safe borrow amount\n * - Monitor position health\n * - Display UI health indicators\n */\nexport async function getUserAccountData(\n publicClient: PublicClient,\n spokeAddress: Address,\n userAddress: Address,\n): Promise<AaveSpokeUserAccountData> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"getUserAccountData\",\n args: [userAddress],\n });\n\n return mapAccountDataResult(result as AccountDataResult);\n}\n\n/**\n * Read a user's position for one reserve and their aggregate account data in a\n * single hard-fail multicall. Both reads are required for the live position\n * view, so a revert on either rejects the whole call (matching the prior\n * `Promise.all`); the gain is one round-trip instead of two `eth_call`s.\n */\nexport async function getUserPositionAndAccountData(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveId: bigint,\n userAddress: Address,\n): Promise<{\n position: AaveSpokeUserPosition;\n accountData: AaveSpokeUserAccountData;\n}> {\n const [positionResult, accountDataResult] = await publicClient.multicall({\n contracts: [\n {\n address: spokeAddress,\n abi: AaveSpokeABI as Abi,\n functionName: \"getUserPosition\" as const,\n args: [reserveId, userAddress] as const,\n },\n {\n address: spokeAddress,\n abi: AaveSpokeABI as Abi,\n functionName: \"getUserAccountData\" as const,\n args: [userAddress] as const,\n },\n ],\n allowFailure: false,\n });\n\n return {\n position: mapPositionResult(positionResult as unknown as PositionResult),\n accountData: mapAccountDataResult(\n accountDataResult as unknown as AccountDataResult,\n ),\n };\n}\n\n/**\n * Get user position from the Spoke\n *\n * This fetches live data from the contract because debt accrues interest\n * and needs to be current for accurate health factor calculations.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - Aave Spoke contract address\n * @param reserveId - Reserve ID\n * @param userAddress - User's proxy contract address\n * @returns User position data\n */\nexport async function getUserPosition(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveId: bigint,\n userAddress: Address,\n): Promise<AaveSpokeUserPosition> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"getUserPosition\",\n args: [reserveId, userAddress],\n });\n\n return mapPositionResult(result as PositionResult);\n}\n\n/**\n * Get user's exact total debt in a reserve (token units, not shares).\n *\n * Returns the Spoke-side amount owed including accrued interest — but NOT the\n * adapter's uncollected interest fee. Display/routing only; for full repayment\n * see the remarks below. Debt accrues interest every block, so fetch it live.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - AAVE Spoke contract address\n * @param reserveId - Reserve ID for the debt asset (e.g., `2n` for USDC)\n * @param userAddress - User's proxy contract address\n * @returns Total debt amount in token units (e.g., for USDC: `100000000n` = 100 USDC)\n *\n * @example\n * ```typescript\n * import { getUserTotalDebt } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n * import { formatUnits } from \"viem\";\n *\n * const totalDebt = await getUserTotalDebt(\n * publicClient,\n * AAVE_SPOKE_ADDRESS,\n * 2n, // USDC reserve\n * proxyAddress\n * );\n *\n * console.log(\"Debt:\", formatUnits(totalDebt, 6), \"USDC\");\n * ```\n *\n * @remarks\n * **Important for full repayment:** do NOT repay a plain amount derived from\n * this quote — it excludes the adapter's interest fee, and rounding can leave\n * residual debt shares (dust). Send the repay-all sentinel\n * (`type(uint256).max`) with an approval sized from the position proxy's\n * fee-inclusive `getPositionReserveTotalDebt` plus\n * `FULL_REPAY_BUFFER_DIVISOR` headroom; the adapter pulls only what's owed.\n * For partial repayment, use any amount less than total debt.\n */\nexport async function getUserTotalDebt(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveId: bigint,\n userAddress: Address,\n): Promise<bigint> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"getUserTotalDebt\",\n args: [reserveId, userAddress],\n });\n\n return result as bigint;\n}\n\n/**\n * Probe `getUserPosition` for many reserves in a single multicall.\n *\n * Returns one entry per `reserveId` in input order. Per-reserve reverts are\n * isolated (`allowFailure: true`): that entry is `null` while the rest of the\n * batch still resolves. Use for debt-reserve discovery, where a failed read\n * means \"treat as no debt\", not a fatal error.\n */\nexport async function getUserPositions(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveIds: bigint[],\n userAddress: Address,\n): Promise<(AaveSpokeUserPosition | null)[]> {\n if (reserveIds.length === 0) return [];\n const results = await publicClient.multicall({\n contracts: reserveIds.map((reserveId) => ({\n address: spokeAddress,\n abi: AaveSpokeABI as Abi,\n functionName: \"getUserPosition\" as const,\n args: [reserveId, userAddress] as const,\n })),\n allowFailure: true,\n });\n return results.map((r) =>\n r.status === \"success\"\n ? mapPositionResult(r.result as PositionResult)\n : null,\n );\n}\n\n/**\n * Read `getUserTotalDebt` for many reserves in a single multicall.\n *\n * Hard-fails (`allowFailure: false`): any reserve's revert rejects the whole\n * call. Use only for reserves already known to carry debt — there a failed\n * read is a genuine error, not a \"no debt\" signal.\n */\nexport async function getUserTotalDebts(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveIds: bigint[],\n userAddress: Address,\n): Promise<bigint[]> {\n if (reserveIds.length === 0) return [];\n const results = await publicClient.multicall({\n contracts: reserveIds.map((reserveId) => ({\n address: spokeAddress,\n abi: AaveSpokeABI as Abi,\n functionName: \"getUserTotalDebt\" as const,\n args: [reserveId, userAddress] as const,\n })),\n allowFailure: false,\n });\n return results as unknown as bigint[];\n}\n\n/** Result type from the `getReserve` contract call.\n *\n * Matches the on-chain `Reserve` struct defined in `ITBVAaveSpoke.sol`:\n * struct Reserve {\n * address underlying;\n * address hub;\n * uint16 assetId;\n * uint8 decimals;\n * uint24 collateralRisk;\n * ReserveFlags flags; // uint8 bitmap\n * uint32 dynamicConfigKey;\n * }\n *\n * Note: this is the `Reserve` struct, NOT `ReserveConfig` — the contract\n * exposes both as separate functions and they return different shapes.\n */\ntype ReserveResult = {\n underlying: Address;\n hub: Address;\n assetId: number;\n decimals: number;\n collateralRisk: number;\n flags: number;\n dynamicConfigKey: number;\n};\n\n/**\n * Get reserve data from the Core Spoke contract via the `getReserve` selector.\n *\n * Returns static reserve properties including the `dynamicConfigKey` needed\n * for `getDynamicReserveConfig` calls. Use this as a fallback when reserve\n * data is not available from the GraphQL indexer.\n *\n * Do NOT confuse with the contract's separate `getReserveConfig` function,\n * which returns `{collateralRisk, paused, frozen, borrowable, receiveSharesEnabled}`.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - Core Spoke contract address\n * @param reserveId - Reserve ID\n * @returns Reserve data including `dynamicConfigKey`\n */\nexport async function getReserve(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveId: bigint,\n): Promise<ReserveResult> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"getReserve\",\n args: [reserveId],\n });\n return result as ReserveResult;\n}\n\n/**\n * Read `getReserve` for many reserves in a single multicall.\n *\n * Returns one entry per `reserveId` in input order. Hard-fails\n * (`allowFailure: false`): any revert, including `ReserveNotListed` for an id\n * the spoke never listed, rejects the whole call.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - Core Spoke contract address\n * @param reserveIds - Reserve IDs to read\n * @returns Reserve data for each ID, in input order\n */\nexport async function getReserves(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveIds: bigint[],\n): Promise<ReserveResult[]> {\n if (reserveIds.length === 0) return [];\n const results = await publicClient.multicall({\n contracts: reserveIds.map((reserveId) => ({\n address: spokeAddress,\n abi: AaveSpokeABI as Abi,\n functionName: \"getReserve\" as const,\n args: [reserveId] as const,\n })),\n allowFailure: false,\n });\n return results as unknown as ReserveResult[];\n}\n\n/**\n * The fields of the getLiquidationConfig result that this client reads.\n * `targetHealthFactor` is left out on purpose: the Babylon Spoke never uses\n * it, and the split target health factor is `SPLIT_TARGET_HEALTH_FACTOR`.\n * viem decodes the `uint16` factor as a number and the `uint64` as a bigint.\n */\ntype LiquidationConfigResult = {\n healthFactorForMaxBonus: bigint;\n liquidationBonusFactor: number | bigint;\n};\n\n/** Liquidation-bonus curve parameters from the Spoke's liquidation config. */\nexport interface LiquidationBonusConfig {\n /** Health factor at or below which the maximum bonus applies, WAD */\n healthFactorForMaxBonus: bigint;\n /** Share of the maximum bonus that still applies at HF 1.0, BPS */\n liquidationBonusFactor: bigint;\n}\n\n/**\n * Result type from getDynamicReserveConfig contract call. The fields are\n * uint16/uint32 on-chain, which viem decodes as numbers, not bigints.\n */\ntype DynamicReserveConfigResult = {\n collateralFactor: number;\n maxLiquidationBonus: number;\n liquidationFee: number;\n};\n\n/**\n * Get the liquidation-bonus curve parameters from the Core Spoke contract.\n *\n * Per-spoke governance parameters. Combined with a reserve's\n * `maxLiquidationBonus` they give the bonus at any health factor; see\n * `computeLiquidationBonusBps`.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - Core Spoke contract address\n * @returns healthFactorForMaxBonus (WAD) and liquidationBonusFactor (BPS)\n */\nexport async function getLiquidationBonusConfig(\n publicClient: PublicClient,\n spokeAddress: Address,\n): Promise<LiquidationBonusConfig> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"getLiquidationConfig\",\n });\n const config = result as LiquidationConfigResult;\n return {\n healthFactorForMaxBonus: config.healthFactorForMaxBonus,\n liquidationBonusFactor: BigInt(config.liquidationBonusFactor),\n };\n}\n\n/**\n * Get the dynamic reserve config from the Core Spoke contract.\n *\n * Returns collateral factor, max liquidation bonus, and liquidation fee\n * for a specific reserve and dynamic config key.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - Core Spoke contract address\n * @param reserveId - Reserve ID (e.g., vBTC reserve ID from indexer config)\n * @param dynamicConfigKey - Dynamic config key (from reserve data)\n * @returns Dynamic reserve config with collateralFactor (BPS), maxLiquidationBonus (BPS), liquidationFee (BPS)\n */\nexport async function getDynamicReserveConfig(\n publicClient: PublicClient,\n spokeAddress: Address,\n reserveId: bigint,\n dynamicConfigKey: number,\n): Promise<DynamicReserveConfigResult> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"getDynamicReserveConfig\",\n args: [reserveId, dynamicConfigKey],\n });\n return result as DynamicReserveConfigResult;\n}\n\n/**\n * Get the maximum number of reserves one user may hold on this Spoke.\n *\n * `uint16` immutable, set in the Spoke constructor. Collateral reserves and\n * borrow reserves are counted separately against the same number: a borrow\n * into a new reserve reverts with `MaximumUserReservesExceeded` once the\n * user's borrow count has reached it. Borrowing more of a reserve the user\n * already borrows is always allowed.\n *\n * `MAX_ALLOWED_USER_RESERVES_LIMIT` (65535) is the contract's \"no cap\"\n * sentinel — the check is skipped on that value.\n *\n * @param publicClient - Viem public client for reading contracts\n * @param spokeAddress - Aave Spoke contract address\n * @returns The limit as stored on the Spoke\n */\nexport async function getMaxUserReservesLimit(\n publicClient: PublicClient,\n spokeAddress: Address,\n): Promise<number> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"MAX_USER_RESERVES_LIMIT\",\n });\n\n return result as number;\n}\n","/**\n * Aave Adapter Position Proxy Client - Read operations\n *\n * Reads against a depositor's own AaveAdapterPositionProxy instance.\n *\n * NOT the Spoke's same-signature `getReserveTotalDebt` (reserve-wide debt of\n * ALL users): the proxy's returns THIS position's total debt including the\n * adapter's uncollected interest fee — the value the adapter resolves the\n * repay-all sentinel to, and therefore the right quote for sizing a repay-all\n * approval.\n */\n\nimport type { Address, PublicClient } from \"viem\";\n\nimport AaveAdapterPositionProxyABI from \"./abis/AaveAdapterPositionProxy.abi.json\";\n\n/**\n * Fee-inclusive total debt of the position held by `proxyContract` for\n * `reserveId`: Spoke debt plus the adapter's uncollected interest fee\n * (rounded up), computed lazily at the current block.\n */\nexport async function getPositionReserveTotalDebt(\n publicClient: PublicClient,\n proxyContract: Address,\n reserveId: bigint,\n): Promise<bigint> {\n const result = await publicClient.readContract({\n address: proxyContract,\n abi: AaveAdapterPositionProxyABI,\n functionName: \"getReserveTotalDebt\",\n args: [reserveId],\n });\n\n // This quote sizes a real approval — assert the shape instead of casting.\n if (typeof result !== \"bigint\") {\n throw new Error(\n `getReserveTotalDebt returned a non-bigint result for reserve ${reserveId}`,\n );\n }\n return result;\n}\n","/**\n * Read-only access to `IAaveOracle`. Prices are 8-decimal base units\n * ($80,000 = 8_000_000_000_000n).\n */\n\nimport type { Abi, Address, PublicClient } from \"viem\";\n\nimport AaveOracleABI from \"./abis/AaveOracle.abi.json\";\nimport AaveSpokeABI from \"./abis/AaveSpoke.abi.json\";\n\n/** `Spoke.ORACLE` is `immutable`; the result is safe to cache forever. */\nexport async function getOracleAddress(\n publicClient: PublicClient,\n spokeAddress: Address,\n): Promise<Address> {\n const result = await publicClient.readContract({\n address: spokeAddress,\n abi: AaveSpokeABI,\n functionName: \"ORACLE\",\n });\n return result as Address;\n}\n\n/** Batch read; reverts the WHOLE batch on the first bad reserve. */\nexport async function getReservesPrices(\n publicClient: PublicClient,\n oracleAddress: Address,\n reserveIds: bigint[],\n): Promise<bigint[]> {\n const result = await publicClient.readContract({\n address: oracleAddress,\n abi: AaveOracleABI,\n functionName: \"getReservesPrices\",\n args: [reserveIds],\n });\n return result as bigint[];\n}\n\nexport interface ReservePriceResult {\n reserveId: bigint;\n /** Raw 1e8 base units, or null on revert. */\n priceRaw: bigint | null;\n error: Error | null;\n}\n\n/**\n * Per-reserve isolated read for display lists (one bad source ≠ whole list\n * blank). One multicall round-trip instead of one `eth_call` per reserve:\n * each entry is `getReservesPrices([reserveId])` with `allowFailure: true`, so\n * a single reverting reserve isolates to its own error entry. A network-level\n * multicall failure marks every reserve failed rather than throwing — callers\n * (display hooks) rely on always getting a per-reserve result array.\n */\nexport async function getReservesPricesSafe(\n publicClient: PublicClient,\n oracleAddress: Address,\n reserveIds: bigint[],\n): Promise<ReservePriceResult[]> {\n if (reserveIds.length === 0) return [];\n\n let results;\n try {\n results = await publicClient.multicall({\n contracts: reserveIds.map((reserveId) => ({\n address: oracleAddress,\n abi: AaveOracleABI as Abi,\n functionName: \"getReservesPrices\" as const,\n args: [[reserveId]] as const,\n })),\n allowFailure: true,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return reserveIds.map((reserveId) => ({\n reserveId,\n priceRaw: null,\n error,\n }));\n }\n\n return results.map((result, i): ReservePriceResult => {\n const reserveId = reserveIds[i];\n if (result.status !== \"success\") {\n const error =\n result.error instanceof Error\n ? result.error\n : new Error(String(result.error ?? \"getReservesPrices reverted\"));\n return { reserveId, priceRaw: null, error };\n }\n const [priceRaw] = result.result as bigint[];\n return { reserveId, priceRaw, error: null };\n });\n}\n","/**\n * Read-only access to the Aave v4 Hub (`IHub`). Each spoke reserve points at\n * a Hub asset (`reserve.hub` + `reserve.assetId`); the Hub is where interest\n * accrues, so live borrow rates are read here rather than from the Spoke.\n */\n\nimport type { Abi, Address, PublicClient } from \"viem\";\n\nimport AaveHubABI from \"./abis/AaveHub.abi.json\";\n\n/** Identifies one Hub asset to read the drawn rate for. */\nexport interface AssetDrawnRateRequest {\n /** Hub contract address (from the reserve's `hub` field). */\n hub: Address;\n /** Asset identifier on that Hub (from the reserve's `assetId` field). */\n assetId: number;\n}\n\nexport interface AssetDrawnRateResult {\n hub: Address;\n assetId: number;\n /** Annual borrow (drawn) rate in RAY (1e27 = 100%), or null on revert. */\n rateRay: bigint | null;\n error: Error | null;\n}\n\n/**\n * Per-asset isolated read of `getAssetDrawnRate` for display lists (one bad\n * asset ≠ whole list blank). One multicall round-trip instead of one\n * `eth_call` per asset, with `allowFailure: true` so a single reverting asset\n * isolates to its own error entry. A network-level multicall failure marks\n * every asset failed rather than throwing — callers (display hooks) rely on\n * always getting a per-asset result array.\n *\n * The returned rate is the linear annual rate in RAY (the Hub accrues\n * interest as `rate * dt / SECONDS_PER_YEAR`), i.e. an APR, not an APY.\n */\nexport async function getAssetDrawnRatesSafe(\n publicClient: PublicClient,\n requests: AssetDrawnRateRequest[],\n): Promise<AssetDrawnRateResult[]> {\n if (requests.length === 0) return [];\n\n let results;\n try {\n results = await publicClient.multicall({\n contracts: requests.map(({ hub, assetId }) => ({\n address: hub,\n abi: AaveHubABI as Abi,\n functionName: \"getAssetDrawnRate\" as const,\n args: [BigInt(assetId)] as const,\n })),\n allowFailure: true,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n return requests.map(({ hub, assetId }) => ({\n hub,\n assetId,\n rateRay: null,\n error,\n }));\n }\n\n return results.map((result, i): AssetDrawnRateResult => {\n const { hub, assetId } = requests[i];\n if (result.status !== \"success\") {\n const error =\n result.error instanceof Error\n ? result.error\n : new Error(String(result.error ?? \"getAssetDrawnRate reverted\"));\n return { hub, assetId, rateRay: null, error };\n }\n return { hub, assetId, rateRay: result.result as bigint, error: null };\n });\n}\n","/**\n * Aave Integration Adapter - Transaction builders\n *\n * Provides transaction builders for the AaveIntegrationAdapter contract.\n * Only includes Core Spoke operations for regular users (no Arbitrageur operations).\n *\n * These functions return unsigned transaction parameters that can be executed\n * by the vault service using its wallet client and transaction factory.\n */\n\nimport { type Address, type Hex, encodeFunctionData } from \"viem\";\n\nimport { AAVE_FUNCTION_NAMES } from \"../config.js\";\nimport type { TransactionParams } from \"../types.js\";\nimport AaveIntegrationAdapterABI from \"./abis/AaveIntegrationAdapter.abi.json\";\n\n/**\n * Build transaction to reorder vaults for liquidation priority.\n *\n * The permuted array must contain exactly the same vault IDs as the\n * current position, in the desired new order. Vaults are seized in\n * prefix order (index 0 first) during liquidation.\n *\n * @param contractAddress - AaveIntegrationAdapter contract address\n * @param permutedVaultIds - Vault IDs in desired new order (must be a permutation of current vaults)\n * @returns Unsigned transaction parameters\n */\nexport function buildReorderVaultsTx(\n contractAddress: Address,\n permutedVaultIds: Hex[],\n): TransactionParams {\n const data = encodeFunctionData({\n abi: AaveIntegrationAdapterABI,\n functionName: AAVE_FUNCTION_NAMES.REORDER_VAULTS,\n args: [permutedVaultIds],\n });\n\n return {\n to: contractAddress,\n data,\n };\n}\n\n/**\n * Build transaction to withdraw selected vaults from AAVE position.\n *\n * Withdraws specific vaults (partial withdrawal) and redeems them back to the depositor.\n * **Requires zero debt** - position must have no outstanding borrows.\n *\n * @param contractAddress - AaveIntegrationAdapter contract address\n * @param vaultIds - Array of vault IDs (bytes32) to withdraw\n * @returns Unsigned transaction parameters for execution with viem wallet\n */\nexport function buildWithdrawCollateralsTx(\n contractAddress: Address,\n vaultIds: Hex[],\n): TransactionParams {\n const data = encodeFunctionData({\n abi: AaveIntegrationAdapterABI,\n functionName: AAVE_FUNCTION_NAMES.WITHDRAW_COLLATERALS,\n args: [vaultIds],\n });\n\n return {\n to: contractAddress,\n data,\n };\n}\n\n/**\n * Build transaction to borrow assets against vBTC collateral.\n *\n * Borrows stablecoins (e.g., USDC) against your BTC collateral position.\n * Health factor must remain above 1.0 after borrowing, otherwise transaction will revert.\n *\n * @param contractAddress - AaveIntegrationAdapter contract address\n * @param debtReserveId - AAVE reserve ID for the debt asset (e.g., `2n` for USDC reserve)\n * @param amount - Amount to borrow in token units with decimals (e.g., for USDC with 6 decimals: `100000000n` = 100 USDC). Use `parseUnits()` from viem.\n * @param receiver - Address to receive borrowed tokens (usually user's address)\n * @returns Unsigned transaction parameters for execution with viem wallet\n *\n * @example\n * ```typescript\n * import { buildBorrowTx } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n * import { parseUnits } from \"viem\";\n *\n * // Borrow 100 USDC (6 decimals)\n * const borrowAmount = parseUnits(\"100\", 6);\n *\n * const txParams = buildBorrowTx(\n * \"0x123...\", // Adapter address\n * 2n, // USDC reserve ID\n * borrowAmount,\n * \"0x456...\" // Receiver address\n * );\n *\n * const hash = await walletClient.sendTransaction({\n * to: txParams.to,\n * data: txParams.data,\n * chain: sepolia,\n * });\n * ```\n *\n * @remarks\n * **What happens on-chain:**\n * 1. Checks health factor won't drop below liquidation threshold (1.0)\n * 2. Mints debt tokens to user's proxy contract\n * 3. Transfers borrowed asset to receiver address\n * 4. Updates position debt\n * 5. Emits `Borrowed` event\n *\n * **Possible errors:**\n * - Borrow would make health factor < 1.0\n * - Insufficient collateral\n * - Reserve doesn't exist\n * - Position doesn't exist\n *\n * **Important:** Calculate safe borrow amount using `calculateHealthFactor()` to avoid liquidation.\n */\nexport function buildBorrowTx(\n contractAddress: Address,\n debtReserveId: bigint,\n amount: bigint,\n receiver: Address,\n): TransactionParams {\n const data = encodeFunctionData({\n abi: AaveIntegrationAdapterABI,\n functionName: AAVE_FUNCTION_NAMES.BORROW,\n args: [debtReserveId, amount, receiver],\n });\n\n return {\n to: contractAddress,\n data,\n };\n}\n\n/**\n * Build transaction to repay debt on AAVE position.\n *\n * **Requires token approval** - user must approve adapter to spend debt token first.\n * Repays borrowed assets (partial or full repayment supported).\n *\n * @param contractAddress - AaveIntegrationAdapter contract address\n * @param borrower - Borrower's address (for self-repay, use connected wallet address)\n * @param debtReserveId - AAVE reserve ID for the debt asset\n * @param amount - Amount to repay in token units for a partial repay. For a\n * FULL repay, pass `type(uint256).max` (the repay-all sentinel): the adapter\n * resolves it to the position's fee-inclusive debt in the same transaction\n * and pulls exactly that. Size the prior approval from\n * `getPositionReserveTotalDebt()` plus ceiling-divided\n * `FULL_REPAY_BUFFER_DIVISOR` headroom.\n * @returns Unsigned transaction parameters for execution with viem wallet\n *\n * @example\n * ```typescript\n * import { buildRepayTx } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n *\n * // Build repay transaction (self-repay)\n * const txParams = buildRepayTx(\n * AAVE_ADAPTER,\n * borrowerAddress, // Connected wallet address for self-repay\n * USDC_RESERVE_ID,\n * repayAmount\n * );\n *\n * const hash = await walletClient.sendTransaction({\n * to: txParams.to,\n * data: txParams.data,\n * chain: sepolia,\n * });\n * ```\n *\n * @remarks\n * **What happens on-chain:**\n * 1. Transfers tokens from user to adapter (requires approval)\n * 2. Burns debt tokens from user's proxy\n * 3. Updates position debt\n * 4. Emits `Repaid` event\n *\n * **Possible errors:**\n * - Insufficient token approval\n * - User doesn't have enough tokens\n * - Repay amount exceeds debt\n * - Position doesn't exist\n */\nexport function buildRepayTx(\n contractAddress: Address,\n borrower: Address,\n debtReserveId: bigint,\n amount: bigint,\n): TransactionParams {\n const data = encodeFunctionData({\n abi: AaveIntegrationAdapterABI,\n functionName: AAVE_FUNCTION_NAMES.REPAY,\n args: [borrower, debtReserveId, amount],\n });\n\n return {\n to: contractAddress,\n data,\n };\n}\n\n","/**\n * Aave Value Conversion Utilities\n *\n * Converts Aave on-chain values to human-readable numbers.\n */\n\nimport {\n AAVE_BASE_CURRENCY_DECIMALS,\n AAVE_BASE_CURRENCY_RAY_DECIMALS,\n WAD_DECIMALS,\n} from \"../constants.js\";\n\n/**\n * Convert Aave base currency value to USD\n *\n * Aave uses 1e26 = $1 USD for collateral values.\n *\n * @param value - Value in Aave base currency (1e26 = $1)\n * @returns Value in USD\n */\nexport function aaveValueToUsd(value: bigint): number {\n return Number(value) / 10 ** AAVE_BASE_CURRENCY_DECIMALS;\n}\n\n/**\n * Convert Aave RAY-scaled base currency value to USD\n *\n * Debt values use higher precision: 1e53 = $1 USD.\n *\n * @param value - Value in RAY-scaled base currency (1e53 = $1)\n * @returns Value in USD\n */\nexport function aaveRayValueToUsd(value: bigint): number {\n return Number(value) / 10 ** AAVE_BASE_CURRENCY_RAY_DECIMALS;\n}\n\n/**\n * Convert Aave WAD value to number\n *\n * WAD is used for health factor and collateral factor (1e18 = 1.0).\n *\n * @param value - Value in WAD (1e18 = 1.0)\n * @returns Decimal number\n */\nexport function wadToNumber(value: bigint): number {\n return Number(value) / 10 ** WAD_DECIMALS;\n}\n","/**\n * Aave Debt Utilities\n *\n * Shared utility functions for debt calculations.\n */\n\nimport type { AaveSpokeUserPosition } from \"../types.js\";\n\n/**\n * Check if a position has any debt based on Spoke position data.\n *\n * A position is considered to have debt if any of:\n * - drawnShares > 0 (borrowed principal)\n * - premiumShares > 0 (accrued interest shares)\n *\n * @param position - User position data from Spoke\n * @returns true if the position has any debt\n */\nexport function hasDebtFromPosition(position: AaveSpokeUserPosition): boolean {\n return position.drawnShares > 0n || position.premiumShares > 0n;\n}\n","/**\n * Health Factor Utilities for Aave\n *\n * Health factor is calculated by Aave on-chain using oracle prices.\n * A position below HEALTH_FACTOR_LIQUIDATION_THRESHOLD can be liquidated.\n *\n * Status thresholds:\n * - no_debt: No active debt (null health factor)\n * - danger: below HEALTH_FACTOR_LIQUIDATION_THRESHOLD (can be liquidated)\n * - risky: HEALTH_FACTOR_LIQUIDATION_THRESHOLD to HEALTH_FACTOR_RISKY_THRESHOLD (inclusive)\n * - warning: above HEALTH_FACTOR_RISKY_THRESHOLD, up to and including\n * HEALTH_FACTOR_WARNING_THRESHOLD\n * - safe: > HEALTH_FACTOR_WARNING_THRESHOLD (healthy)\n */\n\nimport {\n BPS_SCALE,\n HEALTH_FACTOR_LIQUIDATION_THRESHOLD,\n HEALTH_FACTOR_RISKY_THRESHOLD,\n HEALTH_FACTOR_WARNING_THRESHOLD,\n} from \"../constants.js\";\n\nexport type HealthFactorStatus =\n | \"safe\"\n | \"warning\"\n | \"risky\"\n | \"danger\"\n | \"no_debt\";\n\n/**\n * Determine health factor status for UI display\n *\n * @param healthFactor - The health factor as a number (null if no debt)\n * @param hasDebt - Whether the position has active debt\n * @returns The status classification\n */\nexport function getHealthFactorStatus(\n healthFactor: number | null,\n hasDebt: boolean,\n): HealthFactorStatus {\n if (!hasDebt) return \"no_debt\";\n if (healthFactor === null) {\n throw new Error(\"A health factor is required for a position with debt.\");\n }\n if (healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD) return \"danger\";\n if (healthFactor <= HEALTH_FACTOR_RISKY_THRESHOLD) return \"risky\";\n if (healthFactor <= HEALTH_FACTOR_WARNING_THRESHOLD) return \"warning\";\n return \"safe\";\n}\n\n/**\n * Get health factor status from a numeric value.\n * Used for UI components that work with Infinity for no-debt scenarios.\n *\n * @param value - Health factor value (Infinity when no debt)\n * @returns The status classification\n */\nexport function getHealthFactorStatusFromValue(\n value: number,\n): HealthFactorStatus {\n const hasDebt = isFinite(value);\n const healthFactor = isFinite(value) ? value : null;\n return getHealthFactorStatus(healthFactor, hasDebt);\n}\n\n/**\n * Calculate health factor for an AAVE position.\n *\n * **Formula:** `HF = (Collateral × Liquidation Threshold) / Total Debt`\n *\n * Health factor determines liquidation risk:\n * - `> 2.0` - Safe (green)\n * - `> 1.1 and <= 2.0` - Warning (amber)\n * - `>= 1.0 and <= 1.1` - Risky (red)\n * - `< 1.0` - Danger, position can be liquidated (red)\n *\n * @param collateralValueUsd - Total collateral value in USD (as number, not bigint)\n * @param totalDebtUsd - Total debt value in USD (as number, not bigint)\n * @param liquidationThresholdBps - Liquidation threshold in basis points (e.g., `8000` = 80%)\n * @returns Health factor value (e.g., `1.5`), or `Infinity` if no debt\n *\n * @example\n * ```typescript\n * import { calculateHealthFactor, HEALTH_FACTOR_WARNING_THRESHOLD } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n *\n * // User has $10,000 BTC collateral, $5,000 debt, 80% LT\n * const hf = calculateHealthFactor(10000, 5000, 8000);\n * // Result: 1.6 (warning: at or below the warning threshold)\n *\n * if (hf < 1.0) {\n * console.error(\"Position can be liquidated!\");\n * } else if (hf <= HEALTH_FACTOR_WARNING_THRESHOLD) {\n * console.warn(\"Position at risk, consider repaying\");\n * } else {\n * console.log(\"Position is safe\");\n * }\n * ```\n *\n * @remarks\n * **Before borrowing:**\n * Use this to calculate resulting health factor and ensure it stays above safe threshold.\n *\n * **Unit conversions:**\n * - Convert AAVE base currency (1e26) to USD by dividing by 1e26\n * - Use `aaveValueToUsd()` helper for automatic conversion\n */\nexport function calculateHealthFactor(\n collateralValueUsd: number,\n totalDebtUsd: number,\n liquidationThresholdBps: number,\n): number {\n if (totalDebtUsd <= 0) return Infinity;\n return (\n (collateralValueUsd * (liquidationThresholdBps / BPS_SCALE)) / totalDebtUsd\n );\n}\n","/**\n * Cascade Liquidation Simulation\n *\n * Simulates multi-group liquidation cascades for Aave positions backed by\n * indivisible BTC vaults (UTXOs). Each liquidation event seizes a prefix of\n * vaults until the target seizure is covered, then debt is reduced and the\n * cascade continues with remaining vaults.\n *\n * Used by the optimizer to score different vault orderings by how much\n * collateral survives the cascade.\n */\n\n/**\n * Minimal vault shape for cascade simulation.\n * UI layers extend this with display fields (e.g. `name`).\n */\nexport interface CascadeVault {\n id: string;\n btc: number;\n}\n\n/** 1% tolerance for prefix walk coverage — avoids cliff flip at boundary */\nexport const SEIZURE_TOL = 0.01;\n\n/** Circuit breaker for group cascade loop */\nexport const MAX_GROUPS = 20;\n\n/** Minimum debt threshold to continue cascade (avoids infinite loop on dust) */\nexport const MIN_DEBT_THRESHOLD = 0.01;\n\n/**\n * Prefix walk: consume vaults front-to-back until target seizure is covered.\n * Returns the vaults in the first liquidation group.\n */\nexport function getGroup1FromOrder<T extends CascadeVault>(\n order: T[],\n seizedFraction: number,\n seizureTol: number,\n): T[] {\n if (order.length === 0) return [];\n const totalBtc = order.reduce((s, v) => s + v.btc, 0);\n const coverThreshold = totalBtc * seizedFraction * (1 - seizureTol);\n let prefixSum = 0;\n let i = 0;\n while (i < order.length && prefixSum < coverThreshold) {\n prefixSum += order[i].btc;\n i++;\n }\n return order.slice(0, i);\n}\n\n/**\n * Simulate one liquidation group, returning updated debt and remaining BTC.\n * Handles both safe (non-last) and full (last) liquidation paths.\n */\nfunction simulateOneGroup(\n seizedBtc: number,\n totalBtc: number,\n debt: number,\n isLastGroup: boolean,\n seizedFraction: number,\n CF: number,\n THF: number,\n LB: number,\n expectedHF: number,\n): { debtAfter: number; btcAfter: number } {\n const liqPenalty = LB * CF;\n const pLiq = debt / (totalBtc * CF);\n const targetSeizure = totalBtc * seizedFraction;\n const overSeizureBtc = Math.max(0, seizedBtc - targetSeizure);\n const denominator = THF - liqPenalty;\n const debtToRepay =\n denominator === 0 ? debt : debt * ((THF - expectedHF) / denominator);\n\n let debtAfter: number;\n if (isLastGroup) {\n debtAfter = 0;\n } else {\n const overSeizureVal = (overSeizureBtc * pLiq) / LB;\n const fairnessDebtRepay = Math.min(overSeizureVal, debt - debtToRepay);\n debtAfter = Math.max(0, debt - debtToRepay - fairnessDebtRepay);\n }\n return { debtAfter, btcAfter: Math.max(0, totalBtc - seizedBtc) };\n}\n\n/**\n * Simulate full liquidation cascade with debt model.\n *\n * PRIMARY score: sumBtcAfterEvents — sum of BTC remaining after every event.\n * Captures how much collateral survives at each stage.\n * TIEBREAKER: btcAfterG1 — BTC remaining after the first (most likely) event.\n */\nexport function simulateCascade<T extends CascadeVault>(\n order: T[],\n totalDebt: number,\n seizedFraction: number,\n seizureTol: number,\n CF: number,\n THF: number,\n LB: number,\n expectedHF: number,\n): { sumBtcAfterEvents: number; btcAfterG1: number } {\n let remaining = [...order];\n let debt = totalDebt;\n const initialTotalBtc = order.reduce((s, v) => s + v.btc, 0);\n let btcAfterG1 = -1;\n let sumBtcAfterEvents = 0;\n let groupCount = 0;\n\n while (\n remaining.length > 0 &&\n debt > MIN_DEBT_THRESHOLD &&\n groupCount < MAX_GROUPS\n ) {\n const totalBtc = remaining.reduce((s, v) => s + v.btc, 0);\n const coverThreshold = totalBtc * seizedFraction * (1 - seizureTol);\n let prefixSum = 0;\n let i = 0;\n while (i < remaining.length && prefixSum < coverThreshold) {\n prefixSum += remaining[i].btc;\n i++;\n }\n const isLastGroup = i >= remaining.length;\n const { debtAfter } = simulateOneGroup(\n prefixSum,\n totalBtc,\n debt,\n isLastGroup,\n seizedFraction,\n CF,\n THF,\n LB,\n expectedHF,\n );\n remaining = remaining.slice(i);\n debt = debtAfter;\n const btcNow = totalBtc - prefixSum;\n sumBtcAfterEvents += btcNow;\n if (btcAfterG1 < 0) btcAfterG1 = btcNow;\n groupCount++;\n }\n\n return {\n sumBtcAfterEvents,\n btcAfterG1:\n btcAfterG1 < 0 ? initialTotalBtc : btcAfterG1,\n };\n}\n","/**\n * Optimal Vault Ordering for Liquidation Protection\n *\n * Finds the vault ordering that maximizes collateral surviving a multi-group\n * liquidation cascade.\n *\n * The optimizer is a bitmask DP over seized subsets. 2^n memory and 3^n work\n * blow up past `MAX_DP_N` vaults, so for larger sets the optimizer falls back to\n * a largest-first heuristic — the suggested order is then no longer guaranteed\n * optimal, and `calculate()` surfaces a `too-many-vaults` warning to say so.\n */\n\nimport {\n simulateCascade,\n type CascadeVault,\n} from \"./cascadeSimulation.js\";\n\n/**\n * Hard cap on vault count for the bitmask DP optimizer. 2^n memory + 3^n work\n * blow up past this. For n > MAX_DP_N the optimizer falls back to a\n * largest-first heuristic. Benchmark: n=18 ≈ 720ms, n=20 ≈ 5.8s — anything past\n * n=17 is too slow for interactive UI, so we cap here.\n */\nexport const MAX_DP_N = 17;\n\n/**\n * Main optimizer: bitmask DP over seized subsets.\n *\n * State: T = bitmask of vaults that have already been seized.\n * Transition: for each valid \"last group\" G ⊆ T, dp[T] = dp[T\\G] + btcAfter\n * where btcAfter = totalBtc − btcOf(T) (BTC remaining after T is seized).\n * Validation: btcOf(G) must cover target seizure at the moment G fires, i.e.\n * btcOf(G) ≥ (totalBtc − btcOf(T\\G)) × seizedFraction × (1 − seizureTol).\n *\n * Complexity: O(3^n) — the subset-of-subset enumeration visits exactly\n * Σ C(n,k) × 2^k = 3^n state-transition pairs. Single pass, no refinement loop.\n *\n * Objective: maximize sumBtcAfterEvents assuming all events fire. Debt is not\n * part of the DP state — it is used only when computing final metrics via\n * simulateCascade() on the reconstructed order.\n */\nexport function computeOptimalOrder<T extends CascadeVault>(\n vaults: T[],\n totalDebt: number,\n seizedFraction: number,\n seizureTol: number,\n CF: number,\n THF: number,\n LB: number,\n expectedHF: number,\n): { order: T[]; sumBtcAfterEvents: number; btcAfterG1: number } {\n const n = vaults.length;\n if (n === 0) return { order: [], sumBtcAfterEvents: 0, btcAfterG1: 0 };\n if (n === 1) {\n const sim = simulateCascade(\n vaults,\n totalDebt,\n seizedFraction,\n seizureTol,\n CF,\n THF,\n LB,\n expectedHF,\n );\n return { order: [...vaults], ...sim };\n }\n\n // Safety cap: 2^n memory and 3^n work blow up past MAX_DP_N vaults. Real\n // users have far fewer — each vault is a separate peg-in with a fixed fee.\n // For the unlikely n > MAX_DP_N, fall back to a largest-first heuristic;\n // calculate() surfaces a 'too-many-vaults' warning to flag the loss of the\n // optimality guarantee.\n if (n > MAX_DP_N) {\n const order = [...vaults].sort((a, b) => b.btc - a.btc);\n const sim = simulateCascade(\n order,\n totalDebt,\n seizedFraction,\n seizureTol,\n CF,\n THF,\n LB,\n expectedHF,\n );\n return { order, ...sim };\n }\n\n const N = 1 << n;\n const totalBtc = vaults.reduce((s, v) => s + v.btc, 0);\n const coverFraction = seizedFraction * (1 - seizureTol);\n\n // Precompute btcOf[T] — standard bitmask sum trick in O(2^n).\n const btcOf = new Float64Array(N);\n for (let T = 1; T < N; T++) {\n const lsb = T & -T;\n const bit = 31 - Math.clz32(lsb);\n btcOf[T] = btcOf[T ^ lsb] + vaults[bit].btc;\n }\n\n // dpSum[T] = best sumBtcAfterEvents to reach state T (T = seized subset).\n // dpG1[T] = among the paths achieving dpSum[T], the largest BTC remaining\n // after the FIRST event — the tiebreaker, so a tie on total\n // cascade survival prefers the order that survives the first\n // (most likely) liquidation event best. Matches what calculate()\n // uses to decide whether a reorder is worth suggesting.\n // prev[T] = the \"last group\" G chosen when reaching T (for reconstruction).\n const dpSum = new Float64Array(N);\n const dpG1 = new Float64Array(N);\n const prev = new Int32Array(N);\n dpSum.fill(-Infinity);\n prev.fill(-1);\n dpSum[0] = 0;\n // dpG1[0] is never read: when Tprev === 0 the first event is scored directly.\n\n const EPS = 1e-9;\n\n for (let T = 1; T < N; T++) {\n const btcAfter = totalBtc - btcOf[T]; // BTC remaining after the last group brings us to state T\n let bestSum = -Infinity;\n let bestG1 = -Infinity;\n let bestG = -1;\n\n // Enumerate non-empty subsets G ⊆ T. Across all T this visits exactly 3^n pairs.\n for (let G = T; G > 0; G = (G - 1) & T) {\n const Tprev = T ^ G;\n const prevSum = dpSum[Tprev];\n if (prevSum === -Infinity) continue;\n\n // Validate: G covers target seizure when fired from state Tprev.\n const remainingBeforeG = totalBtc - btcOf[Tprev];\n if (btcOf[G] < remainingBeforeG * coverFraction) continue;\n\n const candSum = prevSum + btcAfter;\n // First-event remainder along this path: if Tprev is empty, G itself is\n // the first event; otherwise it was fixed earlier in the subpath.\n const candG1 = Tprev === 0 ? totalBtc - btcOf[G] : dpG1[Tprev];\n\n if (\n candSum > bestSum + EPS ||\n (Math.abs(candSum - bestSum) <= EPS && candG1 > bestG1 + EPS)\n ) {\n bestSum = candSum;\n bestG1 = candG1;\n bestG = G;\n }\n }\n\n dpSum[T] = bestSum;\n dpG1[T] = bestG1;\n prev[T] = bestG;\n }\n\n // Reconstruct firing order by walking prev[] from fullMask back to 0.\n const fullMask = N - 1;\n const groupsReversed: T[][] = [];\n for (let T = fullMask; T > 0; ) {\n const G = prev[T];\n if (G === -1) break; // unreachable chain — fall back below\n const gVaults: T[] = [];\n for (let bit = 0; bit < n; bit++) {\n if (G & (1 << bit)) gVaults.push(vaults[bit]);\n }\n gVaults.sort((a, b) => b.btc - a.btc); // canonical within-group order\n groupsReversed.push(gVaults);\n T ^= G;\n }\n groupsReversed.reverse();\n const order = groupsReversed.flat();\n\n // Reconstruction safety: if the chain is incomplete for any reason, fall back\n // to largest-first rather than throwing (calculate() runs in the notification\n // render and must not crash). This path is a DP-invariant violation that\n // should never occur for valid input — log it so a regression is observable\n // even though the fallback keeps the UI alive and the result well-shaped.\n if (order.length !== n) {\n console.error(\n `computeOptimalOrder: DP reconstruction produced ${order.length}/${n} vaults; ` +\n `falling back to largest-first. This indicates a DP-invariant regression.`,\n );\n const fallback = [...vaults].sort((a, b) => b.btc - a.btc);\n const sim = simulateCascade(\n fallback,\n totalDebt,\n seizedFraction,\n seizureTol,\n CF,\n THF,\n LB,\n expectedHF,\n );\n return { order: fallback, ...sim };\n }\n\n // Compute real metrics via cascade simulation (debt-aware, matches what\n // calculate() produces for the group breakdown shown in the UI).\n const sim = simulateCascade(\n order,\n totalDebt,\n seizedFraction,\n seizureTol,\n CF,\n THF,\n LB,\n expectedHF,\n );\n return {\n order,\n sumBtcAfterEvents: sim.sumBtcAfterEvents,\n btcAfterG1: sim.btcAfterG1,\n };\n}\n","/**\n * Vault Split Utilities for Aave Liquidation Protection\n *\n * BTC vaults are indivisible UTXOs. The Babylon Spoke liquidates one whole\n * vault per call, taking the head of the borrower's ordered vault list.\n * Splitting a deposit into 2 vaults (sacrificial + protected) lets a\n * liquidation take the small head vault and leave the rest of the position.\n *\n * The sacrificial vault (index 0) is sized so that seizing it at the expected\n * liquidation health factor lifts the position back to the split target\n * health factor. The protected vault (index 1) holds the remainder.\n *\n * Seizure formula:\n * ```\n * liq_penalty = LB × CF\n * debt_to_repay = total_debt × (THF - current_HF) / (THF - liq_penalty)\n * target_seizure = debt_to_repay × LB\n * ```\n */\n\nconst MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);\n\n/** 1.0 in WAD fixed point, the unit of on-chain health factors. */\nconst WAD = 10n ** 18n;\n\n/** 100% in basis points, the unit of on-chain liquidation bonuses. */\nconst PERCENTAGE_FACTOR_BPS = 10_000n;\n\n/**\n * Split target health factor: the health factor a two-vault position lands\n * on after its sacrificial vault is seized. The sacrificial vault is sized so\n * that seizing it lifts the position back to this value.\n *\n * This is a Babylon sizing parameter, not a contract parameter. The Babylon\n * Spoke sizes each liquidation by the head vault's BTC and never reads the\n * Aave `LiquidationConfig.targetHealthFactor`, so no contract can supply it.\n * At 1.08 the sacrificial vault stays smaller than the protected vault for\n * every collateral factor up to 86%, and after the first liquidation BTC can\n * fall about 7.4% more before the protected vault is at risk. The risk model\n * and the decision are recorded in babylon-toolkit#2577.\n *\n * Changing this value resizes every new split. A change needs sign-off from\n * the risk model owner and two code-owner approvals: this file is the\n * \"Multi-vault split transactions\" critical path in CLAUDE.md and has its own\n * entry in .github/CODEOWNERS.\n */\nexport const SPLIT_TARGET_HEALTH_FACTOR = 1.08;\n\n/**\n * Health factor at which a liquidation is assumed to land, in WAD.\n *\n * Aave liquidations settle close to 1.0 (median about 0.997 on Aave V3 over\n * a year), so the split is sized for 0.99. The liquidation bonus used for\n * sizing is read from the Spoke's bonus curve at this health factor. Same\n * ownership and review rules as {@link SPLIT_TARGET_HEALTH_FACTOR}.\n */\nexport const EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION_WAD =\n 990_000_000_000_000_000n;\n\n/**\n * {@link EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION_WAD} as a plain number (0.99),\n * for the floating-point split formulas.\n */\nexport const EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION =\n Number(EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION_WAD) / Number(WAD);\n\n/**\n * The sacrificial vault must be strictly smaller than the protected vault.\n * Otherwise, after the first liquidation, restoring partial liquidation would\n * need a new vault larger than everything left in the position.\n */\nconst MAX_SACRIFICIAL_SHARE = 0.5;\n\n/**\n * Why the split parameters alone refuse a two-vault split. These are the\n * verdicts {@link findSplitSizingViolation} can return; it sees only the\n * parameters, so each one holds at any deposit amount. One of them,\n * `sacrificial-not-smaller`, is also what {@link computeOptimalSplit} reports\n * when rounding the seizure up leaves the sacrificial vault not strictly\n * smaller, so seeing it in an {@link OptimalSplitResult} does not by itself\n * mean the parameters are bad:\n * - `target-not-above-expected-hf`: THF ≤ expected HF, so the seizure formula\n * has no valid target.\n * - `target-not-above-liquidation-penalty`: THF ≤ LB × CF, so one liquidation\n * takes the whole position.\n * - `sacrificial-not-smaller`: the sacrificial vault would not be strictly\n * smaller than the protected vault.\n * - `no-seizure-expected`: the seized fraction is zero (for example CF 0), so\n * a sacrificial vault would be empty and the split would protect nothing.\n */\nexport type SplitParamsViolation =\n | \"target-not-above-expected-hf\"\n | \"target-not-above-liquidation-penalty\"\n | \"sacrificial-not-smaller\"\n | \"no-seizure-expected\";\n\n/**\n * Why {@link computeOptimalSplit} refuses a split: the parameter verdicts\n * above, plus one only a deposit amount can reach.\n * - `below-dust`: the vaults would be below the HTLC dust threshold. A\n * non-positive deposit reports this too, but only when the parameters\n * themselves allow a split — otherwise the parameter verdict wins.\n */\nexport type SplitSizingViolation = SplitParamsViolation | \"below-dust\";\n\n/**\n * Inputs of the Aave v4 liquidation bonus curve, in their on-chain units.\n */\nexport interface LiquidationBonusCurve {\n /** `LiquidationConfig.healthFactorForMaxBonus`, WAD. Below 1e18. */\n healthFactorForMaxBonus: bigint;\n /** `LiquidationConfig.liquidationBonusFactor`, BPS. At most 10_000. */\n liquidationBonusFactor: bigint;\n /** `DynamicReserveConfig.maxLiquidationBonus`, BPS. At least 10_000. */\n maxLiquidationBonus: bigint;\n}\n\n/**\n * Effective dust threshold for HTLC outputs in satoshis.\n *\n * Standard P2TR dust is 330 sats, but HTLC scripts are larger than a standard\n * P2TR key-path spend. A conservative estimate for an HTLC script-path output\n * is ~2000 sats. This is a defense-in-depth check — in practice, minDeposit\n * from the on-chain contract is orders of magnitude larger.\n */\nconst HTLC_EFFECTIVE_DUST_THRESHOLD = 2000n;\n\nfunction assertSafePrecision(value: bigint, name: string): void {\n if (value > MAX_SAFE_BIGINT) {\n throw new RangeError(\n `${name} (${value}) exceeds Number.MAX_SAFE_INTEGER; precision would be lost`,\n );\n }\n}\n\n/**\n * Parameters for computing the optimal vault split.\n */\nexport interface OptimalSplitParams {\n /** Total deposit amount in satoshis */\n totalBtc: bigint;\n /** Collateral factor (e.g. 0.78 for 78%) */\n CF: number;\n /** Liquidation bonus at the expected health factor (e.g. 1.0504) */\n LB: number;\n /** Split target health factor (e.g. {@link SPLIT_TARGET_HEALTH_FACTOR}) */\n THF: number;\n /** Expected health factor at liquidation (e.g. {@link EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION}) */\n expectedHF: number;\n}\n\n/**\n * Result of the optimal vault split computation.\n */\nexport interface OptimalSplitResult {\n /** Sacrificial vault amount in satoshis (index 0, seized first) */\n sacrificialVault: bigint;\n /** Protected vault amount in satoshis (index 1, survives liquidation) */\n protectedVault: bigint;\n /** Fraction of collateral that would be seized (0–1) */\n seizedFraction: number;\n /** Target seizure amount in satoshis */\n targetSeizureBtc: bigint;\n /**\n * Why the split is refused, or null when it is valid. Non-null exactly when\n * the split is not usable: both vault amounts are then 0n, and null always\n * means two positive amounts that sum to `totalBtc`.\n */\n sizingViolation: SplitSizingViolation | null;\n}\n\n/**\n * Parameters for computing the minimum deposit required for a split.\n */\nexport interface MinDepositForSplitParams {\n /** Minimum peg-in amount in satoshis */\n minPegin: bigint;\n /** Seized fraction (0–1), from computeOptimalSplit or computeSeizedFraction */\n seizedFraction: number;\n}\n\n/**\n * Compute the Aave v4 liquidation bonus at a health factor.\n *\n * Integer-exact port of `LiquidationLogic.calculateLiquidationBonus`\n * (aave-v4 `src/spoke/libraries/LiquidationLogic.sol`), which the Babylon\n * liquidation path calls with the same inputs: at or below\n * `healthFactorForMaxBonus` the bonus is the maximum; above it the bonus falls\n * linearly towards a minimum of\n * `(maxLiquidationBonus − 100%) × liquidationBonusFactor + 100%` at HF 1.0.\n * Every division rounds down, as on-chain.\n *\n * @param curve - Bonus curve inputs in on-chain units\n * @param healthFactorWad - Health factor in WAD, at most 1e18\n * @returns Liquidation bonus in BPS (e.g. 10504n for 105.04%)\n * @throws {RangeError} when an input is outside the range the contract\n * accepts (the contract would revert or was configured out of range)\n */\nexport function computeLiquidationBonusBps(\n curve: LiquidationBonusCurve,\n healthFactorWad: bigint,\n): bigint {\n const {\n healthFactorForMaxBonus,\n liquidationBonusFactor,\n maxLiquidationBonus,\n } = curve;\n\n if (healthFactorForMaxBonus < 0n || healthFactorForMaxBonus >= WAD) {\n throw new RangeError(\n `healthFactorForMaxBonus (${healthFactorForMaxBonus}) must be in [0, 1e18)`,\n );\n }\n if (\n liquidationBonusFactor < 0n ||\n liquidationBonusFactor > PERCENTAGE_FACTOR_BPS\n ) {\n throw new RangeError(\n `liquidationBonusFactor (${liquidationBonusFactor}) must be in [0, 10000] BPS`,\n );\n }\n if (maxLiquidationBonus < PERCENTAGE_FACTOR_BPS) {\n throw new RangeError(\n `maxLiquidationBonus (${maxLiquidationBonus}) must be at least 10000 BPS`,\n );\n }\n if (healthFactorWad < 0n || healthFactorWad > WAD) {\n throw new RangeError(\n `healthFactor (${healthFactorWad}) must be in [0, 1e18]; the contract reverts above 1e18`,\n );\n }\n\n if (healthFactorWad <= healthFactorForMaxBonus) {\n return maxLiquidationBonus;\n }\n\n const minLiquidationBonus =\n ((maxLiquidationBonus - PERCENTAGE_FACTOR_BPS) * liquidationBonusFactor) /\n PERCENTAGE_FACTOR_BPS +\n PERCENTAGE_FACTOR_BPS;\n\n return (\n minLiquidationBonus +\n ((maxLiquidationBonus - minLiquidationBonus) * (WAD - healthFactorWad)) /\n (WAD - healthFactorForMaxBonus)\n );\n}\n\n/**\n * The liquidation bonus a split is sized against: the Spoke's bonus curve at\n * {@link EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION_WAD}, as a multiplier.\n *\n * Every consumer of the split inputs (the deposit split, the liquidation\n * warnings, the reorder guard, the E2E CLI) must use this one value, or a\n * fresh split falls short of the seizure the warnings expect.\n *\n * @param bonusConfig - `healthFactorForMaxBonus` (WAD) and\n * `liquidationBonusFactor` (BPS) from the Spoke's liquidation config\n * @param maxLiquidationBonusBps - `maxLiquidationBonus` (BPS) of the dynamic\n * config the position's liquidation uses\n * @returns Liquidation bonus multiplier (e.g. 1.0504)\n * @throws {RangeError} when an input is outside the range the contract accepts\n */\nexport function computeSplitLiquidationBonus(\n bonusConfig: Omit<LiquidationBonusCurve, \"maxLiquidationBonus\">,\n maxLiquidationBonusBps: number,\n): number {\n const bonusBps = computeLiquidationBonusBps(\n { ...bonusConfig, maxLiquidationBonus: BigInt(maxLiquidationBonusBps) },\n EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION_WAD,\n );\n return Number(bonusBps) / Number(PERCENTAGE_FACTOR_BPS);\n}\n\n/**\n * Check the split sizing parameters before a two-vault split is offered.\n *\n * @param params - Split target health factor, expected health factor at\n * liquidation, collateral factor and liquidation bonus\n * @returns The first violated rule, or null when a split may be offered\n */\nexport function findSplitSizingViolation(params: {\n CF: number;\n LB: number;\n THF: number;\n expectedHF: number;\n}): SplitParamsViolation | null {\n const { CF, LB, THF, expectedHF } = params;\n\n // Negated comparisons so a NaN input is refused rather than let through.\n if (!(THF > expectedHF)) {\n return \"target-not-above-expected-hf\";\n }\n if (!(THF > LB * CF)) {\n return \"target-not-above-liquidation-penalty\";\n }\n const seizedFraction = computeSeizedFraction(CF, LB, THF, expectedHF);\n if (!(seizedFraction < MAX_SACRIFICIAL_SHARE)) {\n return \"sacrificial-not-smaller\";\n }\n // Nothing would be seized, so a sacrificial vault would be empty and the\n // split would protect nothing.\n if (!(seizedFraction > 0)) {\n return \"no-seizure-expected\";\n }\n return null;\n}\n\n/**\n * Compute the fraction of collateral that would be seized during liquidation,\n * returning both the raw (unclamped) and clamped values.\n *\n * The raw value is useful for detecting unusual protocol parameter combinations\n * (values outside [0, 1] indicate something unexpected).\n *\n * Formula:\n * ```\n * liq_penalty = LB × CF\n * seized_fraction = CF × (THF - expectedHF) / (THF - liq_penalty) × LB / expectedHF\n * ```\n *\n * @param CF - Collateral factor (e.g. 0.78)\n * @param LB - Liquidation bonus at the expected health factor (e.g. 1.0504, see {@link computeSplitLiquidationBonus})\n * @param THF - Split target health factor (e.g. {@link SPLIT_TARGET_HEALTH_FACTOR})\n * @param expectedHF - Expected health factor at liquidation (e.g. {@link EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION})\n * @returns Both the raw seized fraction and the clamped [0, 1] value\n */\nexport function computeSeizedFractionDetailed(\n CF: number,\n LB: number,\n THF: number,\n expectedHF: number,\n): { seizedFraction: number; seizedFractionRaw: number } {\n // HF ≤ 0 means position is fully underwater — full seizure\n if (expectedHF <= 0) {\n return { seizedFraction: 1, seizedFractionRaw: Infinity };\n }\n\n const liqPenalty = LB * CF;\n\n // If THF <= liq_penalty, full liquidation is inevitable\n if (THF <= liqPenalty) {\n return { seizedFraction: 1, seizedFractionRaw: Infinity };\n }\n\n // Floating-point errors here are ~1e-15, below one satoshi for any BTC\n // amount, and computeOptimalSplit rounds the sacrificial vault up.\n const seizedFractionRaw =\n ((CF * (THF - expectedHF)) / (THF - liqPenalty)) * (LB / expectedHF);\n\n return {\n seizedFraction: Math.max(0, Math.min(1, seizedFractionRaw)),\n seizedFractionRaw,\n };\n}\n\n/**\n * Compute the fraction of collateral that would be seized during liquidation.\n *\n * @param CF - Collateral factor (e.g. 0.78)\n * @param LB - Liquidation bonus at the expected health factor (e.g. 1.0504, see {@link computeSplitLiquidationBonus})\n * @param THF - Split target health factor (e.g. {@link SPLIT_TARGET_HEALTH_FACTOR})\n * @param expectedHF - Expected health factor at liquidation (e.g. {@link EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION})\n * @returns Seized fraction clamped to [0, 1]\n */\nexport function computeSeizedFraction(\n CF: number,\n LB: number,\n THF: number,\n expectedHF: number,\n): number {\n return computeSeizedFractionDetailed(CF, LB, THF, expectedHF).seizedFraction;\n}\n\n/**\n * Compute the optimal split between a sacrificial vault and a protected vault.\n *\n * The sacrificial vault (index 0) is the target seizure rounded up to a whole\n * satoshi. The protected vault (index 1) holds the remainder. The split is\n * refused when the parameters fail {@link findSplitSizingViolation}, when\n * rounding the seizure up leaves the sacrificial vault not strictly smaller,\n * or when the vaults would be dust — a non-positive deposit reaches the last\n * of these whenever the parameters themselves allow a split. Both amounts are\n * then 0n and `sizingViolation` says why; a null violation means two positive\n * amounts that sum to `totalBtc`.\n *\n * @param params - Total BTC and split sizing parameters\n * @returns Split result with vault sizes, seized fraction, target seizure and\n * any sizing violation\n *\n * @example\n * ```typescript\n * import {\n * computeOptimalSplit,\n * EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION,\n * SPLIT_TARGET_HEALTH_FACTOR,\n * } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n *\n * const result = computeOptimalSplit({\n * totalBtc: 1_000_000_000n, // 10 BTC in sats\n * CF: 0.78,\n * LB: 1.0504,\n * THF: SPLIT_TARGET_HEALTH_FACTOR,\n * expectedHF: EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION,\n * });\n * // result.sacrificialVault === 285_716_677n (2.86 BTC)\n * // result.protectedVault === 714_283_323n (7.14 BTC)\n * ```\n */\nexport function computeOptimalSplit(\n params: OptimalSplitParams,\n): OptimalSplitResult {\n const { totalBtc, CF, LB, THF, expectedHF } = params;\n\n const sizingViolation = findSplitSizingViolation({ CF, LB, THF, expectedHF });\n const seizedFraction = computeSeizedFraction(CF, LB, THF, expectedHF);\n\n // Nothing to split: report the parameters' own verdict when they already\n // refuse one, and otherwise the amount reason, so a null violation always\n // means two usable vaults.\n if (totalBtc <= 0n) {\n return {\n sacrificialVault: 0n,\n protectedVault: 0n,\n seizedFraction,\n targetSeizureBtc: 0n,\n sizingViolation: sizingViolation ?? \"below-dust\",\n };\n }\n\n assertSafePrecision(totalBtc, \"totalBtc\");\n\n if (sizingViolation !== null) {\n return {\n sacrificialVault: 0n,\n protectedVault: 0n,\n seizedFraction,\n targetSeizureBtc: 0n,\n sizingViolation,\n };\n }\n\n const totalBtcNum = Number(totalBtc);\n const targetSeizureBtc = BigInt(Math.ceil(totalBtcNum * seizedFraction));\n const sacrificialVault = targetSeizureBtc;\n const protectedVault = totalBtc - sacrificialVault;\n\n // The share check above passed, but rounding the seizure up can still leave\n // the sacrificial vault equal to or larger than the protected one.\n if (sacrificialVault >= protectedVault) {\n return {\n sacrificialVault: 0n,\n protectedVault: 0n,\n seizedFraction,\n targetSeizureBtc: 0n,\n sizingViolation: \"sacrificial-not-smaller\",\n };\n }\n\n // The sacrificial vault is the smaller of the two by the check above, so\n // testing it covers both. Below the HTLC dust threshold the split is not\n // viable — return zeroed vaults so the caller treats this as\n // non-splittable. We avoid throwing because this function is called during\n // render (useMemo) and throwing would crash the component instead of\n // showing validation feedback.\n if (sacrificialVault < HTLC_EFFECTIVE_DUST_THRESHOLD) {\n return {\n sacrificialVault: 0n,\n protectedVault: 0n,\n seizedFraction,\n targetSeizureBtc: 0n,\n sizingViolation: \"below-dust\",\n };\n }\n\n return {\n sacrificialVault,\n protectedVault,\n seizedFraction,\n targetSeizureBtc,\n sizingViolation: null,\n };\n}\n\n/**\n * Compute the minimum total deposit required for a 2-vault split.\n *\n * Both vaults must be at least `minPegin` satoshis. This function returns\n * the minimum total deposit where both the sacrificial and protected vaults\n * would meet the minimum peg-in requirement.\n *\n * @param params - Minimum peg-in and seized fraction\n * @returns Minimum total deposit in satoshis. Returns 0n in two cases:\n * - `seizedFraction >= 0.5`: split refused (the sacrificial vault would not\n * be smaller than the protected vault)\n * - `seizedFraction <= 0`: split not useful (no seizure expected at this health factor)\n *\n * @example\n * ```typescript\n * import { computeMinDepositForSplit } from \"@babylonlabs-io/ts-sdk/tbv/integrations/aave\";\n *\n * const minDeposit = computeMinDepositForSplit({\n * minPegin: 50_000n, // 0.0005 BTC\n * seizedFraction: 0.2857,\n * });\n * // minDeposit === 175_009n: 50_000 / 0.2857, rounded up\n * ```\n */\nexport function computeMinDepositForSplit(\n params: MinDepositForSplitParams,\n): bigint {\n const { minPegin, seizedFraction } = params;\n\n assertSafePrecision(minPegin, \"minPegin\");\n\n // The sacrificial vault must stay smaller than the protected vault\n if (!(seizedFraction < MAX_SACRIFICIAL_SHARE)) {\n return 0n;\n }\n\n // If seized fraction is effectively zero, split is not useful\n if (seizedFraction <= 0) {\n return 0n;\n }\n\n // Minimum total so the sacrificial vault >= minPegin. The guard above keeps\n // the seized fraction under half, so the sacrificial vault is always the\n // smaller of the two and sizing it is what binds; the protected vault clears\n // minPegin at any total that satisfies this one.\n return BigInt(Math.ceil(Number(minPegin) / seizedFraction));\n}\n"],"names":["AAVE_FUNCTION_NAMES","BPS_SCALE","AAVE_BASE_CURRENCY_DECIMALS","AAVE_BASE_CURRENCY_RAY_DECIMALS","WAD_DECIMALS","HEALTH_FACTOR_LIQUIDATION_THRESHOLD","HEALTH_FACTOR_RISKY_THRESHOLD","HEALTH_FACTOR_WARNING_THRESHOLD","MIN_HEALTH_FACTOR_FOR_BORROW","FULL_REPAY_BUFFER_DIVISOR","MAX_ALLOWED_USER_RESERVES_LIMIT","NO_POSITION_ERROR_NAME","MAX_ERROR_CAUSE_DEPTH","isNoPositionRevert","err","current","depth","data","cause","getPosition","publicClient","contractAddress","user","result","AaveIntegrationAdapterABI","position","zeroAddress","getPositionSizeParams","maxPositionBTC","maxVaultsPerPosition","mapPositionResult","mapAccountDataResult","getUserAccountData","spokeAddress","userAddress","AaveSpokeABI","getUserPositionAndAccountData","reserveId","positionResult","accountDataResult","getUserPosition","getUserTotalDebt","getUserPositions","reserveIds","getUserTotalDebts","getReserve","getReserves","getLiquidationBonusConfig","config","getDynamicReserveConfig","dynamicConfigKey","getMaxUserReservesLimit","getPositionReserveTotalDebt","proxyContract","AaveAdapterPositionProxyABI","getOracleAddress","getReservesPrices","oracleAddress","AaveOracleABI","getReservesPricesSafe","results","error","i","priceRaw","getAssetDrawnRatesSafe","requests","hub","assetId","AaveHubABI","buildReorderVaultsTx","permutedVaultIds","encodeFunctionData","buildWithdrawCollateralsTx","vaultIds","buildBorrowTx","debtReserveId","amount","receiver","buildRepayTx","borrower","aaveValueToUsd","value","aaveRayValueToUsd","wadToNumber","hasDebtFromPosition","getHealthFactorStatus","healthFactor","hasDebt","getHealthFactorStatusFromValue","calculateHealthFactor","collateralValueUsd","totalDebtUsd","liquidationThresholdBps","SEIZURE_TOL","MAX_GROUPS","MIN_DEBT_THRESHOLD","getGroup1FromOrder","order","seizedFraction","seizureTol","coverThreshold","s","v","prefixSum","simulateOneGroup","seizedBtc","totalBtc","debt","isLastGroup","CF","THF","LB","expectedHF","liqPenalty","pLiq","targetSeizure","overSeizureBtc","denominator","debtToRepay","debtAfter","overSeizureVal","fairnessDebtRepay","simulateCascade","totalDebt","remaining","initialTotalBtc","btcAfterG1","sumBtcAfterEvents","groupCount","btcNow","MAX_DP_N","computeOptimalOrder","vaults","n","sim","a","b","N","coverFraction","btcOf","T","lsb","bit","dpSum","dpG1","prev","EPS","btcAfter","bestSum","bestG1","bestG","G","Tprev","prevSum","remainingBeforeG","candSum","candG1","fullMask","groupsReversed","gVaults","fallback","MAX_SAFE_BIGINT","WAD","PERCENTAGE_FACTOR_BPS","SPLIT_TARGET_HEALTH_FACTOR","EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION_WAD","EXPECTED_HEALTH_FACTOR_AT_LIQUIDATION","MAX_SACRIFICIAL_SHARE","HTLC_EFFECTIVE_DUST_THRESHOLD","assertSafePrecision","name","computeLiquidationBonusBps","curve","healthFactorWad","healthFactorForMaxBonus","liquidationBonusFactor","maxLiquidationBonus","minLiquidationBonus","computeSplitLiquidationBonus","bonusConfig","maxLiquidationBonusBps","bonusBps","findSplitSizingViolation","params","computeSeizedFraction","computeSeizedFractionDetailed","seizedFractionRaw","computeOptimalSplit","sizingViolation","totalBtcNum","targetSeizureBtc","sacrificialVault","protectedVault","computeMinDepositForSplit","minPegin"],"mappings":"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"}
|