@epicentral/sos-sdk 0.4.0-alpha.2 → 0.4.0-alpha.3

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/README.md CHANGED
@@ -90,11 +90,23 @@ Borrow/repay for writers: use `buildOptionMintTransactionWithDerivation` (with v
90
90
 
91
91
  ## Unwind with Loan Repayment
92
92
 
93
- When a writer unwinds an unsold short that had borrowed from the OMLP pool, the program now repays in this order inside `unwind_writer_unsold`:
93
+ When a writer unwinds an unsold short that had borrowed from the OMLP pool, the program repays proportionally to the unwind ratio inside `unwind_writer_unsold`:
94
94
 
95
+ **Proportional Repayment (partial unwinds):**
96
+ - Unwind ratio = `unwind_qty / written_qty`
97
+ - Principal repaid = `total_loan_principal * unwind_ratio`
98
+ - Interest repaid = `total_accrued_interest * unwind_ratio`
99
+ - Protocol fees repaid = `total_accrued_fees * unwind_ratio`
100
+
101
+ **Repayment order:**
95
102
  1. Collateral vault funds first.
96
103
  2. Writer fallback wallet source (`writerRepaymentAccount`) for any shortfall.
97
- 3. If combined funds cannot cover principal + interest + protocol fees, unwind fails with a protocol custom error (not a generic SPL `0x1`).
104
+ 3. If combined funds cannot cover proportional principal + interest + protocol fees, unwind fails with a protocol custom error (not a generic SPL `0x1`).
105
+
106
+ **Collateral Return:**
107
+ - Proportional collateral share = `(collateral_deposited * unwind_qty) / written_qty`
108
+ - Returnable collateral = `proportional_share - amount_already_repaid_from_vault`
109
+ - If vault lacks sufficient post-repayment balance, fails with `InsufficientCollateralVault` (6090)
98
110
 
99
111
  Use **`buildUnwindWriterUnsoldWithLoanRepayment`** so that:
100
112
 
@@ -106,7 +118,10 @@ Use **`buildUnwindWriterUnsoldWithLoanRepayment`** so that:
106
118
  Use **`preflightUnwindWriterUnsold`** before building the transaction to get:
107
119
 
108
120
  - Per-loan principal/interest/protocol-fee breakdown.
109
- - Aggregate owed, collateral-vault available, wallet fallback required, and shortfall.
121
+ - **Proportional obligations** for partial unwinds (principal, interest, fees, total owed).
122
+ - **Collateral return calculation** (proportional share, returnable amount).
123
+ - Collateral-vault available, wallet fallback required, and shortfall.
124
+ - **Top-up UX fields:** `collateralVaultShortfall`, `needsWalletTopUp`.
110
125
  - `canRepayFully` so UI can block early with actionable messaging.
111
126
 
112
127
  If there are no active pool loans for that vault, the API still works and passes empty `remaining_accounts`.
@@ -198,14 +213,18 @@ const tx = await buildBuyFromPoolMarketOrderTransactionWithDerivation({
198
213
  - Convenience for SOL/WSOL: `slippageBufferLamports`
199
214
  - Default buffer: `500_000` base units (0.0005 SOL lamports)
200
215
 
201
- ### Buy liquidity errors (6041)
216
+ ### Buy liquidity errors (6041 split into 6042/6043)
217
+
218
+ The program uses distinct error codes for liquidity failures:
219
+
220
+ - `InsufficientPoolAggregateLiquidity` (6042) – `option_pool.total_available < quantity`
221
+ - `InsufficientWriterPositionLiquidity` (6043) – remaining writer-position accounts cannot cover full quantity in the smallest-first fill loop
222
+
223
+ **Ghost Liquidity:** When `total_available` appears sufficient but active writer positions cannot cover the request. This happens when positions are settled/liquidated but still counted in the aggregate. The SDK now filters inactive positions, and the program skips them in the fill loop.
202
224
 
203
- - `InsufficientPoolLiquidity` can happen when:
204
- - `option_pool.total_available < quantity`, or
205
- - remaining writer-position accounts cannot cover full quantity in the smallest-first fill loop.
206
- - Recommended client flow:
207
- 1. Run `preflightBuyFromPoolMarketOrder` for UX gating.
208
- 2. Build via `buildBuyFromPoolMarketOrderTransactionWithDerivation` so pool + remaining accounts are refetched immediately before build.
225
+ **Recommended client flow:**
226
+ 1. Run `preflightBuyFromPoolMarketOrder` for UX gating (checks both pool and active writer liquidity).
227
+ 2. Build via `buildBuyFromPoolMarketOrderTransactionWithDerivation` – it refetches pool + remaining accounts and asserts active writer liquidity >= requested quantity before building.
209
228
 
210
229
  ### Unwind with loan repayment
211
230
 
@@ -98,98 +98,104 @@ export const OPTION_PROGRAM_ERROR__PRICE_IMPACT_TOO_HIGH = 0x1797; // 6039
98
98
  export const OPTION_PROGRAM_ERROR__SLIPPAGE_TOLERANCE_EXCEEDED = 0x1798; // 6040
99
99
  /** InsufficientPoolLiquidity: Insufficient pool liquidity available */
100
100
  export const OPTION_PROGRAM_ERROR__INSUFFICIENT_POOL_LIQUIDITY = 0x1799; // 6041
101
+ /** InsufficientPoolAggregateLiquidity: Insufficient aggregate pool liquidity for requested quantity */
102
+ export const OPTION_PROGRAM_ERROR__INSUFFICIENT_POOL_AGGREGATE_LIQUIDITY = 0x179a; // 6042
103
+ /** InsufficientWriterPositionLiquidity: Insufficient active writer liquidity to fill requested quantity */
104
+ export const OPTION_PROGRAM_ERROR__INSUFFICIENT_WRITER_POSITION_LIQUIDITY = 0x179b; // 6043
101
105
  /** InsufficientUserBalance: Insufficient user balance for withdrawal */
102
- export const OPTION_PROGRAM_ERROR__INSUFFICIENT_USER_BALANCE = 0x179a; // 6042
106
+ export const OPTION_PROGRAM_ERROR__INSUFFICIENT_USER_BALANCE = 0x179c; // 6044
103
107
  /** UnhealthyPosition: Health ratio below liquidation threshold */
104
- export const OPTION_PROGRAM_ERROR__UNHEALTHY_POSITION = 0x179b; // 6043
108
+ export const OPTION_PROGRAM_ERROR__UNHEALTHY_POSITION = 0x179d; // 6045
105
109
  /** UnauthorizedOmlp: Unauthorized to perform this OMLP action */
106
- export const OPTION_PROGRAM_ERROR__UNAUTHORIZED_OMLP = 0x179c; // 6044
110
+ export const OPTION_PROGRAM_ERROR__UNAUTHORIZED_OMLP = 0x179e; // 6046
107
111
  /** InvalidTenor: Invalid loan tenor */
108
- export const OPTION_PROGRAM_ERROR__INVALID_TENOR = 0x179d; // 6045
112
+ export const OPTION_PROGRAM_ERROR__INVALID_TENOR = 0x179f; // 6047
109
113
  /** InvalidRate: Invalid interest rate */
110
- export const OPTION_PROGRAM_ERROR__INVALID_RATE = 0x179e; // 6046
114
+ export const OPTION_PROGRAM_ERROR__INVALID_RATE = 0x17a0; // 6048
111
115
  /** NotForeclosable: Loan is not eligible for foreclosure */
112
- export const OPTION_PROGRAM_ERROR__NOT_FORECLOSABLE = 0x179f; // 6047
116
+ export const OPTION_PROGRAM_ERROR__NOT_FORECLOSABLE = 0x17a1; // 6049
113
117
  /** InsufficientVaultLiquidity: Vault has insufficient liquidity */
114
- export const OPTION_PROGRAM_ERROR__INSUFFICIENT_VAULT_LIQUIDITY = 0x17a0; // 6048
118
+ export const OPTION_PROGRAM_ERROR__INSUFFICIENT_VAULT_LIQUIDITY = 0x17a2; // 6050
115
119
  /** InsufficientLoanCollateral: Collateral insufficient for loan */
116
- export const OPTION_PROGRAM_ERROR__INSUFFICIENT_LOAN_COLLATERAL = 0x17a1; // 6049
120
+ export const OPTION_PROGRAM_ERROR__INSUFFICIENT_LOAN_COLLATERAL = 0x17a3; // 6051
117
121
  /** OracleTooStale: Oracle price is stale - cannot validate */
118
- export const OPTION_PROGRAM_ERROR__ORACLE_TOO_STALE = 0x17a2; // 6050
122
+ export const OPTION_PROGRAM_ERROR__ORACLE_TOO_STALE = 0x17a4; // 6052
119
123
  /** ValidationRequired: Must call option_validate before borrow/settlement */
120
- export const OPTION_PROGRAM_ERROR__VALIDATION_REQUIRED = 0x17a3; // 6051
124
+ export const OPTION_PROGRAM_ERROR__VALIDATION_REQUIRED = 0x17a5; // 6053
121
125
  /** HealthCalculationFailed: Health ratio calculation failed */
122
- export const OPTION_PROGRAM_ERROR__HEALTH_CALCULATION_FAILED = 0x17a4; // 6052
126
+ export const OPTION_PROGRAM_ERROR__HEALTH_CALCULATION_FAILED = 0x17a6; // 6054
123
127
  /** ContractAlreadySettled: Contract already settled */
124
- export const OPTION_PROGRAM_ERROR__CONTRACT_ALREADY_SETTLED = 0x17a5; // 6053
128
+ export const OPTION_PROGRAM_ERROR__CONTRACT_ALREADY_SETTLED = 0x17a7; // 6055
125
129
  /** NoYieldAvailable: No yield available to claim */
126
- export const OPTION_PROGRAM_ERROR__NO_YIELD_AVAILABLE = 0x17a6; // 6054
130
+ export const OPTION_PROGRAM_ERROR__NO_YIELD_AVAILABLE = 0x17a8; // 6056
127
131
  /** UnauthorizedAccess: Unauthorized access - you don't own this resource */
128
- export const OPTION_PROGRAM_ERROR__UNAUTHORIZED_ACCESS = 0x17a7; // 6055
132
+ export const OPTION_PROGRAM_ERROR__UNAUTHORIZED_ACCESS = 0x17a9; // 6057
129
133
  /** InsufficientQuantity: Insufficient quantity available in ask position */
130
- export const OPTION_PROGRAM_ERROR__INSUFFICIENT_QUANTITY = 0x17a8; // 6056
134
+ export const OPTION_PROGRAM_ERROR__INSUFFICIENT_QUANTITY = 0x17aa; // 6058
131
135
  /** NothingToClaim: Nothing to claim - no unclaimed premium */
132
- export const OPTION_PROGRAM_ERROR__NOTHING_TO_CLAIM = 0x17a9; // 6057
136
+ export const OPTION_PROGRAM_ERROR__NOTHING_TO_CLAIM = 0x17ab; // 6059
133
137
  /** PoolNotActive: Pool is not active */
134
- export const OPTION_PROGRAM_ERROR__POOL_NOT_ACTIVE = 0x17aa; // 6058
138
+ export const OPTION_PROGRAM_ERROR__POOL_NOT_ACTIVE = 0x17ac; // 6060
135
139
  /** PoolAlreadyExists: Pool already exists for this option */
136
- export const OPTION_PROGRAM_ERROR__POOL_ALREADY_EXISTS = 0x17ab; // 6059
140
+ export const OPTION_PROGRAM_ERROR__POOL_ALREADY_EXISTS = 0x17ad; // 6061
137
141
  /** PoolNotExercised: Pool has not been exercised yet */
138
- export const OPTION_PROGRAM_ERROR__POOL_NOT_EXERCISED = 0x17ac; // 6060
142
+ export const OPTION_PROGRAM_ERROR__POOL_NOT_EXERCISED = 0x17ae; // 6062
139
143
  /** AlreadySettled: Maker's collateral share has already been settled */
140
- export const OPTION_PROGRAM_ERROR__ALREADY_SETTLED = 0x17ad; // 6061
144
+ export const OPTION_PROGRAM_ERROR__ALREADY_SETTLED = 0x17af; // 6063
141
145
  /** InsufficientPoolCollateral: Insufficient collateral in pool for exercise */
142
- export const OPTION_PROGRAM_ERROR__INSUFFICIENT_POOL_COLLATERAL = 0x17ae; // 6062
146
+ export const OPTION_PROGRAM_ERROR__INSUFFICIENT_POOL_COLLATERAL = 0x17b0; // 6064
143
147
  /** CollateralPoolNotFound: Collateral pool does not exist */
144
- export const OPTION_PROGRAM_ERROR__COLLATERAL_POOL_NOT_FOUND = 0x17af; // 6063
148
+ export const OPTION_PROGRAM_ERROR__COLLATERAL_POOL_NOT_FOUND = 0x17b1; // 6065
145
149
  /** NoCollateralToWithdraw: No collateral to withdraw */
146
- export const OPTION_PROGRAM_ERROR__NO_COLLATERAL_TO_WITHDRAW = 0x17b0; // 6064
150
+ export const OPTION_PROGRAM_ERROR__NO_COLLATERAL_TO_WITHDRAW = 0x17b2; // 6066
147
151
  /** OptionNotExpired: Option has not expired yet - cannot settle */
148
- export const OPTION_PROGRAM_ERROR__OPTION_NOT_EXPIRED = 0x17b1; // 6065
152
+ export const OPTION_PROGRAM_ERROR__OPTION_NOT_EXPIRED = 0x17b3; // 6067
149
153
  /** SupplyLimitExceeded: Deposit would exceed vault supply limit */
150
- export const OPTION_PROGRAM_ERROR__SUPPLY_LIMIT_EXCEEDED = 0x17b2; // 6066
154
+ export const OPTION_PROGRAM_ERROR__SUPPLY_LIMIT_EXCEEDED = 0x17b4; // 6068
151
155
  /** InvalidFeeWallet: Invalid fee wallet - must match protocol constant */
152
- export const OPTION_PROGRAM_ERROR__INVALID_FEE_WALLET = 0x17b3; // 6067
156
+ export const OPTION_PROGRAM_ERROR__INVALID_FEE_WALLET = 0x17b5; // 6069
153
157
  /** InvalidProtocolFee: Invalid protocol fee rate */
154
- export const OPTION_PROGRAM_ERROR__INVALID_PROTOCOL_FEE = 0x17b4; // 6068
158
+ export const OPTION_PROGRAM_ERROR__INVALID_PROTOCOL_FEE = 0x17b6; // 6070
155
159
  /** UnderlyingAssetMismatch: Underlying asset mismatch - market data or mint does not match option */
156
- export const OPTION_PROGRAM_ERROR__UNDERLYING_ASSET_MISMATCH = 0x17b5; // 6069
160
+ export const OPTION_PROGRAM_ERROR__UNDERLYING_ASSET_MISMATCH = 0x17b7; // 6071
157
161
  /** InvalidMint: Invalid token mint - does not match expected underlying asset */
158
- export const OPTION_PROGRAM_ERROR__INVALID_MINT = 0x17b6; // 6070
162
+ export const OPTION_PROGRAM_ERROR__INVALID_MINT = 0x17b8; // 6072
159
163
  /** BatchSizeExceeded: Batch size exceeds maximum allowed (10 positions) */
160
- export const OPTION_PROGRAM_ERROR__BATCH_SIZE_EXCEEDED = 0x17b7; // 6071
164
+ export const OPTION_PROGRAM_ERROR__BATCH_SIZE_EXCEEDED = 0x17b9; // 6073
161
165
  /** NoPositionsProvided: No positions provided in batch */
162
- export const OPTION_PROGRAM_ERROR__NO_POSITIONS_PROVIDED = 0x17b8; // 6072
166
+ export const OPTION_PROGRAM_ERROR__NO_POSITIONS_PROVIDED = 0x17ba; // 6074
163
167
  /** PositionOptionMismatch: Position account does not belong to this option */
164
- export const OPTION_PROGRAM_ERROR__POSITION_OPTION_MISMATCH = 0x17b9; // 6073
168
+ export const OPTION_PROGRAM_ERROR__POSITION_OPTION_MISMATCH = 0x17bb; // 6075
165
169
  /** OptionPoolMismatch: Option account does not match the option pool's option account */
166
- export const OPTION_PROGRAM_ERROR__OPTION_POOL_MISMATCH = 0x17ba; // 6074
170
+ export const OPTION_PROGRAM_ERROR__OPTION_POOL_MISMATCH = 0x17bc; // 6076
167
171
  /** InvalidSeed: Invalid seed - must be exactly 32 bytes */
168
- export const OPTION_PROGRAM_ERROR__INVALID_SEED = 0x17bb; // 6075
172
+ export const OPTION_PROGRAM_ERROR__INVALID_SEED = 0x17bd; // 6077
169
173
  /** InvalidAuthority: Invalid authority - does not match escrow authority */
170
- export const OPTION_PROGRAM_ERROR__INVALID_AUTHORITY = 0x17bc; // 6076
174
+ export const OPTION_PROGRAM_ERROR__INVALID_AUTHORITY = 0x17be; // 6078
171
175
  /** EscrowAccountRequired: Escrow accounts required when borrowed_amount > 0 */
172
- export const OPTION_PROGRAM_ERROR__ESCROW_ACCOUNT_REQUIRED = 0x17bd; // 6077
176
+ export const OPTION_PROGRAM_ERROR__ESCROW_ACCOUNT_REQUIRED = 0x17bf; // 6079
173
177
  /** InvalidEscrowMaker: Escrow state maker does not match instruction maker */
174
- export const OPTION_PROGRAM_ERROR__INVALID_ESCROW_MAKER = 0x17be; // 6078
178
+ export const OPTION_PROGRAM_ERROR__INVALID_ESCROW_MAKER = 0x17c0; // 6080
175
179
  /** EscrowMintMismatch: Escrow collateral mint does not match collateral pool mint */
176
- export const OPTION_PROGRAM_ERROR__ESCROW_MINT_MISMATCH = 0x17bf; // 6079
180
+ export const OPTION_PROGRAM_ERROR__ESCROW_MINT_MISMATCH = 0x17c1; // 6081
177
181
  /** InsufficientEscrowBalance: Insufficient balance in escrow token account */
178
- export const OPTION_PROGRAM_ERROR__INSUFFICIENT_ESCROW_BALANCE = 0x17c0; // 6080
182
+ export const OPTION_PROGRAM_ERROR__INSUFFICIENT_ESCROW_BALANCE = 0x17c2; // 6082
179
183
  /** InvalidEscrowOwner: Escrow token account owner mismatch */
180
- export const OPTION_PROGRAM_ERROR__INVALID_ESCROW_OWNER = 0x17c1; // 6081
184
+ export const OPTION_PROGRAM_ERROR__INVALID_ESCROW_OWNER = 0x17c3; // 6083
181
185
  /** InvalidEscrowMint: Escrow token account mint mismatch */
182
- export const OPTION_PROGRAM_ERROR__INVALID_ESCROW_MINT = 0x17c2; // 6082
186
+ export const OPTION_PROGRAM_ERROR__INVALID_ESCROW_MINT = 0x17c4; // 6084
183
187
  /** AccountFrozen: Token account is frozen and cannot be burned */
184
- export const OPTION_PROGRAM_ERROR__ACCOUNT_FROZEN = 0x17c3; // 6083
188
+ export const OPTION_PROGRAM_ERROR__ACCOUNT_FROZEN = 0x17c5; // 6085
185
189
  /** InvalidAccount: Invalid account - does not match expected account */
186
- export const OPTION_PROGRAM_ERROR__INVALID_ACCOUNT = 0x17c4; // 6084
190
+ export const OPTION_PROGRAM_ERROR__INVALID_ACCOUNT = 0x17c6; // 6086
187
191
  /** UnwindRepayAccountsMissing: Unwind repayment accounts are required when active pool loans exist */
188
- export const OPTION_PROGRAM_ERROR__UNWIND_REPAY_ACCOUNTS_MISSING = 0x17c5; // 6085
192
+ export const OPTION_PROGRAM_ERROR__UNWIND_REPAY_ACCOUNTS_MISSING = 0x17c7; // 6087
189
193
  /** UnwindRepayWalletSourceMissing: Writer repayment account is required for unwind shortfall fallback */
190
- export const OPTION_PROGRAM_ERROR__UNWIND_REPAY_WALLET_SOURCE_MISSING = 0x17c6; // 6086
194
+ export const OPTION_PROGRAM_ERROR__UNWIND_REPAY_WALLET_SOURCE_MISSING = 0x17c8; // 6088
191
195
  /** UnwindRepayInsufficientTotalFunds: Insufficient total funds to fully repay unwind loans (principal + interest + protocol fees) */
192
- export const OPTION_PROGRAM_ERROR__UNWIND_REPAY_INSUFFICIENT_TOTAL_FUNDS = 0x17c7; // 6087
196
+ export const OPTION_PROGRAM_ERROR__UNWIND_REPAY_INSUFFICIENT_TOTAL_FUNDS = 0x17c9; // 6089
197
+ /** InsufficientCollateralVault: Collateral vault has insufficient funds for unwind collateral return */
198
+ export const OPTION_PROGRAM_ERROR__INSUFFICIENT_COLLATERAL_VAULT = 0x17ca; // 6090
193
199
 
194
200
  export type OptionProgramError =
195
201
  | typeof OPTION_PROGRAM_ERROR__ACCOUNT_FROZEN
@@ -208,13 +214,16 @@ export type OptionProgramError =
208
214
  | typeof OPTION_PROGRAM_ERROR__HEALTH_CALCULATION_FAILED
209
215
  | typeof OPTION_PROGRAM_ERROR__INSUFFICIENT_BALANCE
210
216
  | typeof OPTION_PROGRAM_ERROR__INSUFFICIENT_COLLATERAL
217
+ | typeof OPTION_PROGRAM_ERROR__INSUFFICIENT_COLLATERAL_VAULT
211
218
  | typeof OPTION_PROGRAM_ERROR__INSUFFICIENT_ESCROW_BALANCE
212
219
  | typeof OPTION_PROGRAM_ERROR__INSUFFICIENT_LOAN_COLLATERAL
220
+ | typeof OPTION_PROGRAM_ERROR__INSUFFICIENT_POOL_AGGREGATE_LIQUIDITY
213
221
  | typeof OPTION_PROGRAM_ERROR__INSUFFICIENT_POOL_COLLATERAL
214
222
  | typeof OPTION_PROGRAM_ERROR__INSUFFICIENT_POOL_LIQUIDITY
215
223
  | typeof OPTION_PROGRAM_ERROR__INSUFFICIENT_QUANTITY
216
224
  | typeof OPTION_PROGRAM_ERROR__INSUFFICIENT_USER_BALANCE
217
225
  | typeof OPTION_PROGRAM_ERROR__INSUFFICIENT_VAULT_LIQUIDITY
226
+ | typeof OPTION_PROGRAM_ERROR__INSUFFICIENT_WRITER_POSITION_LIQUIDITY
218
227
  | typeof OPTION_PROGRAM_ERROR__INVALID_ACCOUNT
219
228
  | typeof OPTION_PROGRAM_ERROR__INVALID_AUTHORITY
220
229
  | typeof OPTION_PROGRAM_ERROR__INVALID_BUYER_AUTHORITY
@@ -300,13 +309,16 @@ if (process.env.NODE_ENV !== "production") {
300
309
  [OPTION_PROGRAM_ERROR__HEALTH_CALCULATION_FAILED]: `Health ratio calculation failed`,
301
310
  [OPTION_PROGRAM_ERROR__INSUFFICIENT_BALANCE]: `Insufficient balance for premium payment`,
302
311
  [OPTION_PROGRAM_ERROR__INSUFFICIENT_COLLATERAL]: `Insufficient collateral provided`,
312
+ [OPTION_PROGRAM_ERROR__INSUFFICIENT_COLLATERAL_VAULT]: `Collateral vault has insufficient funds for unwind collateral return`,
303
313
  [OPTION_PROGRAM_ERROR__INSUFFICIENT_ESCROW_BALANCE]: `Insufficient balance in escrow token account`,
304
314
  [OPTION_PROGRAM_ERROR__INSUFFICIENT_LOAN_COLLATERAL]: `Collateral insufficient for loan`,
315
+ [OPTION_PROGRAM_ERROR__INSUFFICIENT_POOL_AGGREGATE_LIQUIDITY]: `Insufficient aggregate pool liquidity for requested quantity`,
305
316
  [OPTION_PROGRAM_ERROR__INSUFFICIENT_POOL_COLLATERAL]: `Insufficient collateral in pool for exercise`,
306
317
  [OPTION_PROGRAM_ERROR__INSUFFICIENT_POOL_LIQUIDITY]: `Insufficient pool liquidity available`,
307
318
  [OPTION_PROGRAM_ERROR__INSUFFICIENT_QUANTITY]: `Insufficient quantity available in ask position`,
308
319
  [OPTION_PROGRAM_ERROR__INSUFFICIENT_USER_BALANCE]: `Insufficient user balance for withdrawal`,
309
320
  [OPTION_PROGRAM_ERROR__INSUFFICIENT_VAULT_LIQUIDITY]: `Vault has insufficient liquidity`,
321
+ [OPTION_PROGRAM_ERROR__INSUFFICIENT_WRITER_POSITION_LIQUIDITY]: `Insufficient active writer liquidity to fill requested quantity`,
310
322
  [OPTION_PROGRAM_ERROR__INVALID_ACCOUNT]: `Invalid account - does not match expected account`,
311
323
  [OPTION_PROGRAM_ERROR__INVALID_AUTHORITY]: `Invalid authority - does not match escrow authority`,
312
324
  [OPTION_PROGRAM_ERROR__INVALID_BUYER_AUTHORITY]: `Invalid buyer authority`,
@@ -55,6 +55,8 @@ export type LiquidateWriterPositionInstruction<
55
55
  TAccountOptionAccount extends string | AccountMeta<string> = string,
56
56
  TAccountCollateralPool extends string | AccountMeta<string> = string,
57
57
  TAccountWriterPosition extends string | AccountMeta<string> = string,
58
+ TAccountLongMint extends string | AccountMeta<string> = string,
59
+ TAccountEscrowLongAccount extends string | AccountMeta<string> = string,
58
60
  TAccountOmlpVault extends string | AccountMeta<string> = string,
59
61
  TAccountUnderlyingMint extends string | AccountMeta<string> = string,
60
62
  TAccountMarketData extends string | AccountMeta<string> = string,
@@ -84,6 +86,12 @@ export type LiquidateWriterPositionInstruction<
84
86
  TAccountWriterPosition extends string
85
87
  ? WritableAccount<TAccountWriterPosition>
86
88
  : TAccountWriterPosition,
89
+ TAccountLongMint extends string
90
+ ? WritableAccount<TAccountLongMint>
91
+ : TAccountLongMint,
92
+ TAccountEscrowLongAccount extends string
93
+ ? WritableAccount<TAccountEscrowLongAccount>
94
+ : TAccountEscrowLongAccount,
87
95
  TAccountOmlpVault extends string
88
96
  ? WritableAccount<TAccountOmlpVault>
89
97
  : TAccountOmlpVault,
@@ -156,6 +164,8 @@ export type LiquidateWriterPositionAsyncInput<
156
164
  TAccountOptionAccount extends string = string,
157
165
  TAccountCollateralPool extends string = string,
158
166
  TAccountWriterPosition extends string = string,
167
+ TAccountLongMint extends string = string,
168
+ TAccountEscrowLongAccount extends string = string,
159
169
  TAccountOmlpVault extends string = string,
160
170
  TAccountUnderlyingMint extends string = string,
161
171
  TAccountMarketData extends string = string,
@@ -175,6 +185,10 @@ export type LiquidateWriterPositionAsyncInput<
175
185
  collateralPool?: Address<TAccountCollateralPool>;
176
186
  /** Writer's position to liquidate */
177
187
  writerPosition: Address<TAccountWriterPosition>;
188
+ /** LONG token mint (for burning unsold tokens) */
189
+ longMint: Address<TAccountLongMint>;
190
+ /** Pool's LONG escrow account (holds unsold LONG tokens to burn) */
191
+ escrowLongAccount: Address<TAccountEscrowLongAccount>;
178
192
  /** OMLP Vault (for repayment tracking) */
179
193
  omlpVault: Address<TAccountOmlpVault>;
180
194
  /** Underlying token mint (for decimal handling) */
@@ -200,6 +214,8 @@ export async function getLiquidateWriterPositionInstructionAsync<
200
214
  TAccountOptionAccount extends string,
201
215
  TAccountCollateralPool extends string,
202
216
  TAccountWriterPosition extends string,
217
+ TAccountLongMint extends string,
218
+ TAccountEscrowLongAccount extends string,
203
219
  TAccountOmlpVault extends string,
204
220
  TAccountUnderlyingMint extends string,
205
221
  TAccountMarketData extends string,
@@ -217,6 +233,8 @@ export async function getLiquidateWriterPositionInstructionAsync<
217
233
  TAccountOptionAccount,
218
234
  TAccountCollateralPool,
219
235
  TAccountWriterPosition,
236
+ TAccountLongMint,
237
+ TAccountEscrowLongAccount,
220
238
  TAccountOmlpVault,
221
239
  TAccountUnderlyingMint,
222
240
  TAccountMarketData,
@@ -236,6 +254,8 @@ export async function getLiquidateWriterPositionInstructionAsync<
236
254
  TAccountOptionAccount,
237
255
  TAccountCollateralPool,
238
256
  TAccountWriterPosition,
257
+ TAccountLongMint,
258
+ TAccountEscrowLongAccount,
239
259
  TAccountOmlpVault,
240
260
  TAccountUnderlyingMint,
241
261
  TAccountMarketData,
@@ -258,6 +278,11 @@ export async function getLiquidateWriterPositionInstructionAsync<
258
278
  optionAccount: { value: input.optionAccount ?? null, isWritable: true },
259
279
  collateralPool: { value: input.collateralPool ?? null, isWritable: true },
260
280
  writerPosition: { value: input.writerPosition ?? null, isWritable: true },
281
+ longMint: { value: input.longMint ?? null, isWritable: true },
282
+ escrowLongAccount: {
283
+ value: input.escrowLongAccount ?? null,
284
+ isWritable: true,
285
+ },
261
286
  omlpVault: { value: input.omlpVault ?? null, isWritable: true },
262
287
  underlyingMint: { value: input.underlyingMint ?? null, isWritable: false },
263
288
  marketData: { value: input.marketData ?? null, isWritable: false },
@@ -308,6 +333,8 @@ export async function getLiquidateWriterPositionInstructionAsync<
308
333
  getAccountMeta(accounts.optionAccount),
309
334
  getAccountMeta(accounts.collateralPool),
310
335
  getAccountMeta(accounts.writerPosition),
336
+ getAccountMeta(accounts.longMint),
337
+ getAccountMeta(accounts.escrowLongAccount),
311
338
  getAccountMeta(accounts.omlpVault),
312
339
  getAccountMeta(accounts.underlyingMint),
313
340
  getAccountMeta(accounts.marketData),
@@ -327,6 +354,8 @@ export async function getLiquidateWriterPositionInstructionAsync<
327
354
  TAccountOptionAccount,
328
355
  TAccountCollateralPool,
329
356
  TAccountWriterPosition,
357
+ TAccountLongMint,
358
+ TAccountEscrowLongAccount,
330
359
  TAccountOmlpVault,
331
360
  TAccountUnderlyingMint,
332
361
  TAccountMarketData,
@@ -345,6 +374,8 @@ export type LiquidateWriterPositionInput<
345
374
  TAccountOptionAccount extends string = string,
346
375
  TAccountCollateralPool extends string = string,
347
376
  TAccountWriterPosition extends string = string,
377
+ TAccountLongMint extends string = string,
378
+ TAccountEscrowLongAccount extends string = string,
348
379
  TAccountOmlpVault extends string = string,
349
380
  TAccountUnderlyingMint extends string = string,
350
381
  TAccountMarketData extends string = string,
@@ -364,6 +395,10 @@ export type LiquidateWriterPositionInput<
364
395
  collateralPool: Address<TAccountCollateralPool>;
365
396
  /** Writer's position to liquidate */
366
397
  writerPosition: Address<TAccountWriterPosition>;
398
+ /** LONG token mint (for burning unsold tokens) */
399
+ longMint: Address<TAccountLongMint>;
400
+ /** Pool's LONG escrow account (holds unsold LONG tokens to burn) */
401
+ escrowLongAccount: Address<TAccountEscrowLongAccount>;
367
402
  /** OMLP Vault (for repayment tracking) */
368
403
  omlpVault: Address<TAccountOmlpVault>;
369
404
  /** Underlying token mint (for decimal handling) */
@@ -389,6 +424,8 @@ export function getLiquidateWriterPositionInstruction<
389
424
  TAccountOptionAccount extends string,
390
425
  TAccountCollateralPool extends string,
391
426
  TAccountWriterPosition extends string,
427
+ TAccountLongMint extends string,
428
+ TAccountEscrowLongAccount extends string,
392
429
  TAccountOmlpVault extends string,
393
430
  TAccountUnderlyingMint extends string,
394
431
  TAccountMarketData extends string,
@@ -406,6 +443,8 @@ export function getLiquidateWriterPositionInstruction<
406
443
  TAccountOptionAccount,
407
444
  TAccountCollateralPool,
408
445
  TAccountWriterPosition,
446
+ TAccountLongMint,
447
+ TAccountEscrowLongAccount,
409
448
  TAccountOmlpVault,
410
449
  TAccountUnderlyingMint,
411
450
  TAccountMarketData,
@@ -424,6 +463,8 @@ export function getLiquidateWriterPositionInstruction<
424
463
  TAccountOptionAccount,
425
464
  TAccountCollateralPool,
426
465
  TAccountWriterPosition,
466
+ TAccountLongMint,
467
+ TAccountEscrowLongAccount,
427
468
  TAccountOmlpVault,
428
469
  TAccountUnderlyingMint,
429
470
  TAccountMarketData,
@@ -445,6 +486,11 @@ export function getLiquidateWriterPositionInstruction<
445
486
  optionAccount: { value: input.optionAccount ?? null, isWritable: true },
446
487
  collateralPool: { value: input.collateralPool ?? null, isWritable: true },
447
488
  writerPosition: { value: input.writerPosition ?? null, isWritable: true },
489
+ longMint: { value: input.longMint ?? null, isWritable: true },
490
+ escrowLongAccount: {
491
+ value: input.escrowLongAccount ?? null,
492
+ isWritable: true,
493
+ },
448
494
  omlpVault: { value: input.omlpVault ?? null, isWritable: true },
449
495
  underlyingMint: { value: input.underlyingMint ?? null, isWritable: false },
450
496
  marketData: { value: input.marketData ?? null, isWritable: false },
@@ -481,6 +527,8 @@ export function getLiquidateWriterPositionInstruction<
481
527
  getAccountMeta(accounts.optionAccount),
482
528
  getAccountMeta(accounts.collateralPool),
483
529
  getAccountMeta(accounts.writerPosition),
530
+ getAccountMeta(accounts.longMint),
531
+ getAccountMeta(accounts.escrowLongAccount),
484
532
  getAccountMeta(accounts.omlpVault),
485
533
  getAccountMeta(accounts.underlyingMint),
486
534
  getAccountMeta(accounts.marketData),
@@ -500,6 +548,8 @@ export function getLiquidateWriterPositionInstruction<
500
548
  TAccountOptionAccount,
501
549
  TAccountCollateralPool,
502
550
  TAccountWriterPosition,
551
+ TAccountLongMint,
552
+ TAccountEscrowLongAccount,
503
553
  TAccountOmlpVault,
504
554
  TAccountUnderlyingMint,
505
555
  TAccountMarketData,
@@ -527,24 +577,28 @@ export type ParsedLiquidateWriterPositionInstruction<
527
577
  collateralPool: TAccountMetas[2];
528
578
  /** Writer's position to liquidate */
529
579
  writerPosition: TAccountMetas[3];
580
+ /** LONG token mint (for burning unsold tokens) */
581
+ longMint: TAccountMetas[4];
582
+ /** Pool's LONG escrow account (holds unsold LONG tokens to burn) */
583
+ escrowLongAccount: TAccountMetas[5];
530
584
  /** OMLP Vault (for repayment tracking) */
531
- omlpVault: TAccountMetas[4];
585
+ omlpVault: TAccountMetas[6];
532
586
  /** Underlying token mint (for decimal handling) */
533
- underlyingMint: TAccountMetas[5];
587
+ underlyingMint: TAccountMetas[7];
534
588
  /** Market data account (provides pyth_feed_id for price check) */
535
- marketData: TAccountMetas[6];
589
+ marketData: TAccountMetas[8];
536
590
  /** Pyth price update account for current underlying price */
537
- priceUpdate: TAccountMetas[7];
591
+ priceUpdate: TAccountMetas[9];
538
592
  /** Collateral vault (source of repayments) */
539
- collateralVault: TAccountMetas[8];
593
+ collateralVault: TAccountMetas[10];
540
594
  /** OMLP vault token account (receives loan repayments) */
541
- omlpVaultTokenAccount: TAccountMetas[9];
595
+ omlpVaultTokenAccount: TAccountMetas[11];
542
596
  /** Protocol fee wallet (receives liquidation fees) */
543
- feeWallet: TAccountMetas[10];
597
+ feeWallet: TAccountMetas[12];
544
598
  /** Liquidator/keeper (permissionless - anyone can call) */
545
- liquidator: TAccountMetas[11];
546
- tokenProgram: TAccountMetas[12];
547
- systemProgram: TAccountMetas[13];
599
+ liquidator: TAccountMetas[13];
600
+ tokenProgram: TAccountMetas[14];
601
+ systemProgram: TAccountMetas[15];
548
602
  };
549
603
  data: LiquidateWriterPositionInstructionData;
550
604
  };
@@ -557,7 +611,7 @@ export function parseLiquidateWriterPositionInstruction<
557
611
  InstructionWithAccounts<TAccountMetas> &
558
612
  InstructionWithData<ReadonlyUint8Array>,
559
613
  ): ParsedLiquidateWriterPositionInstruction<TProgram, TAccountMetas> {
560
- if (instruction.accounts.length < 14) {
614
+ if (instruction.accounts.length < 16) {
561
615
  // TODO: Coded error.
562
616
  throw new Error("Not enough accounts");
563
617
  }
@@ -574,6 +628,8 @@ export function parseLiquidateWriterPositionInstruction<
574
628
  optionAccount: getNextAccount(),
575
629
  collateralPool: getNextAccount(),
576
630
  writerPosition: getNextAccount(),
631
+ longMint: getNextAccount(),
632
+ escrowLongAccount: getNextAccount(),
577
633
  omlpVault: getNextAccount(),
578
634
  underlyingMint: getNextAccount(),
579
635
  marketData: getNextAccount(),
@@ -88,7 +88,7 @@ import {
88
88
  } from "../instructions";
89
89
 
90
90
  export const OPTION_PROGRAM_PROGRAM_ADDRESS =
91
- "818mtjAGCGMfFAmnuQummzyFweVw1odD7gcAT32iTjDz" as Address<"818mtjAGCGMfFAmnuQummzyFweVw1odD7gcAT32iTjDz">;
91
+ "BUszj34jkTxGik2AuVC4oQ3oKNSbkUaZsyNT3DSV8Qgm" as Address<"BUszj34jkTxGik2AuVC4oQ3oKNSbkUaZsyNT3DSV8Qgm">;
92
92
 
93
93
  export enum OptionProgramAccount {
94
94
  CollateralPool,
@@ -679,7 +679,7 @@ export function identifyOptionProgramInstruction(
679
679
  }
680
680
 
681
681
  export type ParsedOptionProgramInstruction<
682
- TProgram extends string = "818mtjAGCGMfFAmnuQummzyFweVw1odD7gcAT32iTjDz",
682
+ TProgram extends string = "BUszj34jkTxGik2AuVC4oQ3oKNSbkUaZsyNT3DSV8Qgm",
683
683
  > =
684
684
  | ({
685
685
  instructionType: OptionProgramInstruction.AcceptAdmin;
package/long/builders.ts CHANGED
@@ -20,6 +20,7 @@ import type { OptionType } from "../generated/types";
20
20
  import { getCreateAssociatedTokenIdempotentInstructionWithAddress, NATIVE_MINT } from "../wsol/instructions";
21
21
  import { fetchOptionPool } from "../accounts/fetchers";
22
22
  import { getBuyFromPoolRemainingAccounts } from "./remaining-accounts";
23
+ import { fetchWriterPositionsForPool } from "../accounts/list";
23
24
 
24
25
  export interface BuildBuyFromPoolParams {
25
26
  optionPool: AddressLike;
@@ -266,6 +267,24 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
266
267
  "Option pool must exist; ensure rpc is provided and pool is initialized."
267
268
  );
268
269
 
270
+ // Build-time coverage assertion: verify active writer liquidity >= requested quantity
271
+ // This catches data staleness between preflight and build
272
+ const quantity = BigInt(params.quantity);
273
+ const writerPositions = await fetchWriterPositionsForPool(
274
+ params.rpc,
275
+ resolved.optionPool,
276
+ params.programId
277
+ );
278
+ const activeUnsoldTotal = writerPositions
279
+ .filter((p) => !p.data.isSettled && !p.data.isLiquidated && p.data.unsoldQty > 0n)
280
+ .reduce((sum, p) => sum + p.data.unsoldQty, 0n);
281
+
282
+ invariant(
283
+ activeUnsoldTotal >= quantity,
284
+ `Insufficient active writer liquidity: available=${activeUnsoldTotal}, requested=${quantity}. ` +
285
+ `This may indicate data staleness - please refresh and retry.`
286
+ );
287
+
269
288
  const slippageBuffer = normalizeMarketOrderSlippageBuffer(
270
289
  params,
271
290
  refetchedPool.underlyingMint
package/long/preflight.ts CHANGED
@@ -63,9 +63,13 @@ export async function preflightBuyFromPoolMarketOrder(
63
63
  "Option pool must exist; ensure rpc is provided and pool is initialized."
64
64
  );
65
65
 
66
- const availableWriterPositions = writerPositions.filter(
67
- ({ data }) => toBigInt(data.unsoldQty) > 0n
66
+ // Filter out inactive positions (settled, liquidated, or zero unsold)
67
+ const activeWriterPositions = writerPositions.filter(
68
+ ({ data }) => !data.isSettled && !data.isLiquidated && toBigInt(data.unsoldQty) > 0n
68
69
  );
70
+
71
+ // Use active positions for coverage calculation
72
+ const availableWriterPositions = activeWriterPositions;
69
73
  const remainingUnsoldAggregate = availableWriterPositions.reduce(
70
74
  (acc, { data }) => acc + toBigInt(data.unsoldQty),
71
75
  0n
@@ -19,7 +19,20 @@ export async function getBuyFromPoolRemainingAccounts(
19
19
  optionPool,
20
20
  programId ?? PROGRAM_ID
21
21
  );
22
- const sorted = [...positions].sort((a, b) => {
22
+
23
+ // Filter out inactive positions (settled, liquidated, or zero unsold)
24
+ const activePositions = positions.filter((p) => {
25
+ const isActive = !p.data.isSettled && !p.data.isLiquidated && p.data.unsoldQty > 0;
26
+ if (!isActive) {
27
+ console.log(
28
+ `Filtering out inactive writer position: ${p.address}, ` +
29
+ `isSettled=${p.data.isSettled}, isLiquidated=${p.data.isLiquidated}, unsoldQty=${p.data.unsoldQty}`
30
+ );
31
+ }
32
+ return isActive;
33
+ });
34
+
35
+ const sorted = [...activePositions].sort((a, b) => {
23
36
  const aQty = a.data.unsoldQty;
24
37
  const bQty = b.data.unsoldQty;
25
38
  return aQty < bQty ? -1 : aQty > bQty ? 1 : 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicentral/sos-sdk",
3
- "version": "0.4.0-alpha.2",
3
+ "version": "0.4.0-alpha.3",
4
4
  "private": false,
5
5
  "description": "Solana Option Standard SDK. The frontend-first SDK for Native Options Trading on Solana. Created by Epicentral Labs.",
6
6
  "type": "module",
@@ -63,10 +63,21 @@ export interface UnwindPreflightSummary {
63
63
  totalInterest: bigint;
64
64
  totalProtocolFees: bigint;
65
65
  totalOwed: bigint;
66
+ /** Proportional obligations for partial unwind (based on unwind ratio) */
67
+ proportionalPrincipal: bigint;
68
+ proportionalInterest: bigint;
69
+ proportionalProtocolFees: bigint;
70
+ proportionalTotalOwed: bigint;
71
+ /** Collateral return calculation */
72
+ proportionalCollateralShare: bigint;
73
+ returnableCollateral: bigint;
66
74
  collateralVaultAvailable: bigint;
67
75
  walletFallbackAvailable: bigint;
68
76
  walletFallbackRequired: bigint;
69
77
  shortfall: bigint;
78
+ /** For top-up UX: explicit shortfall fields */
79
+ collateralVaultShortfall: bigint;
80
+ needsWalletTopUp: boolean;
70
81
  }
71
82
 
72
83
  export interface UnwindPreflightResult {
@@ -139,10 +150,18 @@ export async function preflightUnwindWriterUnsold(
139
150
  totalInterest: 0n,
140
151
  totalProtocolFees: 0n,
141
152
  totalOwed: 0n,
153
+ proportionalPrincipal: 0n,
154
+ proportionalInterest: 0n,
155
+ proportionalProtocolFees: 0n,
156
+ proportionalTotalOwed: 0n,
157
+ proportionalCollateralShare: 0n,
158
+ returnableCollateral: 0n,
142
159
  collateralVaultAvailable: 0n,
143
160
  walletFallbackAvailable: 0n,
144
161
  walletFallbackRequired: 0n,
145
162
  shortfall: 0n,
163
+ collateralVaultShortfall: 0n,
164
+ needsWalletTopUp: false,
146
165
  },
147
166
  };
148
167
  }
@@ -161,10 +180,18 @@ export async function preflightUnwindWriterUnsold(
161
180
  totalInterest: 0n,
162
181
  totalProtocolFees: 0n,
163
182
  totalOwed: 0n,
183
+ proportionalPrincipal: 0n,
184
+ proportionalInterest: 0n,
185
+ proportionalProtocolFees: 0n,
186
+ proportionalTotalOwed: 0n,
187
+ proportionalCollateralShare: 0n,
188
+ returnableCollateral: 0n,
164
189
  collateralVaultAvailable: 0n,
165
190
  walletFallbackAvailable: 0n,
166
191
  walletFallbackRequired: 0n,
167
192
  shortfall: 0n,
193
+ collateralVaultShortfall: 0n,
194
+ needsWalletTopUp: false,
168
195
  },
169
196
  };
170
197
  }
@@ -220,10 +247,35 @@ export async function preflightUnwindWriterUnsold(
220
247
  fetchTokenAmount(params.rpc, writerRepaymentAddress),
221
248
  ]);
222
249
 
250
+ // Calculate proportional obligations for partial unwinds
251
+ const writtenQty = toBigInt(writerPosition.writtenQty);
252
+ const unwindRatio = writtenQty > 0n ? (unwindQty * 1_000_000n) / writtenQty : 0n; // Basis points precision
253
+ const unwindRatioDecimal = Number(unwindRatio) / 1_000_000; // Convert to decimal
254
+
255
+ // Proportional obligations (for partial unwind logic)
256
+ const proportionalPrincipal = writtenQty > 0n ? (totals.principal * unwindQty) / writtenQty : 0n;
257
+ const proportionalInterest = writtenQty > 0n ? (totals.interest * unwindQty) / writtenQty : 0n;
258
+ const proportionalProtocolFees = writtenQty > 0n ? (totals.fees * unwindQty) / writtenQty : 0n;
259
+ const proportionalTotalOwed = proportionalPrincipal + proportionalInterest + proportionalProtocolFees;
260
+
261
+ // Collateral return calculation (proportional share minus proportional obligations)
262
+ const collateralDeposited = toBigInt(writerPosition.collateralDeposited);
263
+ const proportionalCollateralShare = writtenQty > 0n ? (collateralDeposited * unwindQty) / writtenQty : 0n;
264
+ const returnableCollateral = proportionalCollateralShare > proportionalTotalOwed
265
+ ? proportionalCollateralShare - proportionalTotalOwed
266
+ : 0n;
267
+
268
+ // Calculate shortfall against proportional obligations
223
269
  const walletFallbackRequired =
224
- totals.owed > collateralVaultAvailable ? totals.owed - collateralVaultAvailable : 0n;
270
+ proportionalTotalOwed > collateralVaultAvailable ? proportionalTotalOwed - collateralVaultAvailable : 0n;
225
271
  const totalAvailable = collateralVaultAvailable + walletFallbackAvailable;
226
- const shortfall = totals.owed > totalAvailable ? totals.owed - totalAvailable : 0n;
272
+ const shortfall = proportionalTotalOwed > totalAvailable ? proportionalTotalOwed - totalAvailable : 0n;
273
+
274
+ // For top-up UX: explicit collateral vault shortfall
275
+ const collateralVaultShortfall = returnableCollateral > collateralVaultAvailable
276
+ ? returnableCollateral - collateralVaultAvailable
277
+ : 0n;
278
+ const needsWalletTopUp = collateralVaultShortfall > 0n && walletFallbackAvailable < collateralVaultShortfall;
227
279
 
228
280
  return {
229
281
  canUnwind: true,
@@ -239,10 +291,18 @@ export async function preflightUnwindWriterUnsold(
239
291
  totalInterest: totals.interest,
240
292
  totalProtocolFees: totals.fees,
241
293
  totalOwed: totals.owed,
294
+ proportionalPrincipal,
295
+ proportionalInterest,
296
+ proportionalProtocolFees,
297
+ proportionalTotalOwed,
298
+ proportionalCollateralShare,
299
+ returnableCollateral,
242
300
  collateralVaultAvailable,
243
301
  walletFallbackAvailable,
244
302
  walletFallbackRequired,
245
303
  shortfall,
304
+ collateralVaultShortfall,
305
+ needsWalletTopUp,
246
306
  },
247
307
  };
248
308
  }