@epicentral/sos-sdk 0.11.2-beta → 0.12.0-beta

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
@@ -287,13 +287,24 @@ Use `calculateRequiredCollateral` to estimate collateral needs before minting:
287
287
  ```ts
288
288
  import { calculateRequiredCollateral } from "@epicentral/sos-sdk";
289
289
 
290
- const required = calculateRequiredCollateral(
291
- 1_000_000n, // 1 contract in base units
292
- 150.0, // $150 strike price
293
- 145.23, // Current spot price (USD)
294
- 6 // USDC decimals
290
+ // Cash-settled SOL call backed by USDC (~$1 collateral price)
291
+ const requiredUsdc = calculateRequiredCollateral(
292
+ 1_000_000n,
293
+ 150.0,
294
+ 1.0,
295
+ 6,
296
+ "cash"
297
+ );
298
+ // ~15_000_000_000 micro-USDC for 1 contract at $150 strike
299
+
300
+ // Physical SOL collateral (underlying spot)
301
+ const requiredSol = calculateRequiredCollateral(
302
+ 1_000_000n,
303
+ 150.0,
304
+ 145.23,
305
+ 9,
306
+ "physical"
295
307
  );
296
- // Returns: USDC base units needed
297
308
  ```
298
309
 
299
310
  ## Unwind with Loan Repayment (theta-hedge model)
@@ -567,19 +578,22 @@ The SDK exports `calculateRequiredCollateral` for pre-flight collateral estimati
567
578
  import { calculateRequiredCollateral } from "@epicentral/sos-sdk";
568
579
 
569
580
  const required = calculateRequiredCollateral(
570
- 1_000_000n, // 1 contract in base units
571
- 150.0, // $150 strike price
572
- 145.23, // Current spot price (USD)
573
- 9 // Token decimals (9 for SOL)
581
+ 1_000_000n,
582
+ 150.0,
583
+ 145.23,
584
+ 9,
585
+ "physical"
574
586
  );
575
- // Returns: token base units needed (e.g., 103_280_000_000 lamports for ~103.28 SOL)
576
587
  ```
577
588
 
578
589
  **Formula:**
579
590
  ```
580
591
  contracts = quantity / 1_000_000
581
592
  usd_value = contracts * 100 * strike_price
582
- collateral = (usd_value / spot_price) * 10^token_decimals
593
+ physical = (usd_value / underlying_spot) * 10^decimals
594
+ cash = usd_value * 10^collateral_decimals // conversionPrice ≈ $1 for USDC
583
595
  ```
584
596
 
597
+ `buildCloseLongToPoolTransactionWithDerivation` auto-derives `buyerPayoutAccount` (collateral mint) and `buyerUnderlyingPayoutAccount` (premium leg) when omitted.
598
+
585
599
  This matches the on-chain formula in `Vault::calculate_required_collateral` and can be used to display required collateral to users before submitting an `option_mint` transaction.
@@ -68,6 +68,7 @@ export type BuyFromPoolInstruction<
68
68
  TAccountBuyerPaymentAccount extends string | AccountMeta<string> = string,
69
69
  TAccountEscrowLongAccount extends string | AccountMeta<string> = string,
70
70
  TAccountPremiumVault extends string | AccountMeta<string> = string,
71
+ TAccountCollateralPool extends string | AccountMeta<string> = string,
71
72
  TAccountBuyer extends string | AccountMeta<string> = string,
72
73
  TAccountTokenProgram extends string | AccountMeta<string> =
73
74
  "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
@@ -119,6 +120,9 @@ export type BuyFromPoolInstruction<
119
120
  TAccountPremiumVault extends string
120
121
  ? WritableAccount<TAccountPremiumVault>
121
122
  : TAccountPremiumVault,
123
+ TAccountCollateralPool extends string
124
+ ? ReadonlyAccount<TAccountCollateralPool>
125
+ : TAccountCollateralPool,
122
126
  TAccountBuyer extends string
123
127
  ? WritableSignerAccount<TAccountBuyer> &
124
128
  AccountSignerMeta<TAccountBuyer>
@@ -190,6 +194,7 @@ export type BuyFromPoolAsyncInput<
190
194
  TAccountBuyerPaymentAccount extends string = string,
191
195
  TAccountEscrowLongAccount extends string = string,
192
196
  TAccountPremiumVault extends string = string,
197
+ TAccountCollateralPool extends string = string,
193
198
  TAccountBuyer extends string = string,
194
199
  TAccountTokenProgram extends string = string,
195
200
  TAccountAssociatedTokenProgram extends string = string,
@@ -221,6 +226,8 @@ export type BuyFromPoolAsyncInput<
221
226
  escrowLongAccount: Address<TAccountEscrowLongAccount>;
222
227
  /** Pool's premium vault (receives premium) */
223
228
  premiumVault: Address<TAccountPremiumVault>;
229
+ /** Collateral pool (settlement mint for buyer position tracking) */
230
+ collateralPool?: Address<TAccountCollateralPool>;
224
231
  buyer: TransactionSigner<TAccountBuyer>;
225
232
  tokenProgram?: Address<TAccountTokenProgram>;
226
233
  associatedTokenProgram?: Address<TAccountAssociatedTokenProgram>;
@@ -243,6 +250,7 @@ export async function getBuyFromPoolInstructionAsync<
243
250
  TAccountBuyerPaymentAccount extends string,
244
251
  TAccountEscrowLongAccount extends string,
245
252
  TAccountPremiumVault extends string,
253
+ TAccountCollateralPool extends string,
246
254
  TAccountBuyer extends string,
247
255
  TAccountTokenProgram extends string,
248
256
  TAccountAssociatedTokenProgram extends string,
@@ -263,6 +271,7 @@ export async function getBuyFromPoolInstructionAsync<
263
271
  TAccountBuyerPaymentAccount,
264
272
  TAccountEscrowLongAccount,
265
273
  TAccountPremiumVault,
274
+ TAccountCollateralPool,
266
275
  TAccountBuyer,
267
276
  TAccountTokenProgram,
268
277
  TAccountAssociatedTokenProgram,
@@ -285,6 +294,7 @@ export async function getBuyFromPoolInstructionAsync<
285
294
  TAccountBuyerPaymentAccount,
286
295
  TAccountEscrowLongAccount,
287
296
  TAccountPremiumVault,
297
+ TAccountCollateralPool,
288
298
  TAccountBuyer,
289
299
  TAccountTokenProgram,
290
300
  TAccountAssociatedTokenProgram,
@@ -328,6 +338,7 @@ export async function getBuyFromPoolInstructionAsync<
328
338
  isWritable: true,
329
339
  },
330
340
  premiumVault: { value: input.premiumVault ?? null, isWritable: true },
341
+ collateralPool: { value: input.collateralPool ?? null, isWritable: false },
331
342
  buyer: { value: input.buyer ?? null, isWritable: true },
332
343
  tokenProgram: { value: input.tokenProgram ?? null, isWritable: false },
333
344
  associatedTokenProgram: {
@@ -382,6 +393,20 @@ export async function getBuyFromPoolInstructionAsync<
382
393
  ],
383
394
  });
384
395
  }
396
+ if (!accounts.collateralPool.value) {
397
+ accounts.collateralPool.value = await getProgramDerivedAddress({
398
+ programAddress,
399
+ seeds: [
400
+ getBytesEncoder().encode(
401
+ new Uint8Array([
402
+ 99, 111, 108, 108, 97, 116, 101, 114, 97, 108, 95, 112, 111, 111,
403
+ 108,
404
+ ]),
405
+ ),
406
+ getAddressEncoder().encode(expectAddress(accounts.optionAccount.value)),
407
+ ],
408
+ });
409
+ }
385
410
  if (!accounts.tokenProgram.value) {
386
411
  accounts.tokenProgram.value =
387
412
  "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" as Address<"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA">;
@@ -411,6 +436,7 @@ export async function getBuyFromPoolInstructionAsync<
411
436
  getAccountMeta(accounts.buyerPaymentAccount),
412
437
  getAccountMeta(accounts.escrowLongAccount),
413
438
  getAccountMeta(accounts.premiumVault),
439
+ getAccountMeta(accounts.collateralPool),
414
440
  getAccountMeta(accounts.buyer),
415
441
  getAccountMeta(accounts.tokenProgram),
416
442
  getAccountMeta(accounts.associatedTokenProgram),
@@ -435,6 +461,7 @@ export async function getBuyFromPoolInstructionAsync<
435
461
  TAccountBuyerPaymentAccount,
436
462
  TAccountEscrowLongAccount,
437
463
  TAccountPremiumVault,
464
+ TAccountCollateralPool,
438
465
  TAccountBuyer,
439
466
  TAccountTokenProgram,
440
467
  TAccountAssociatedTokenProgram,
@@ -456,6 +483,7 @@ export type BuyFromPoolInput<
456
483
  TAccountBuyerPaymentAccount extends string = string,
457
484
  TAccountEscrowLongAccount extends string = string,
458
485
  TAccountPremiumVault extends string = string,
486
+ TAccountCollateralPool extends string = string,
459
487
  TAccountBuyer extends string = string,
460
488
  TAccountTokenProgram extends string = string,
461
489
  TAccountAssociatedTokenProgram extends string = string,
@@ -487,6 +515,8 @@ export type BuyFromPoolInput<
487
515
  escrowLongAccount: Address<TAccountEscrowLongAccount>;
488
516
  /** Pool's premium vault (receives premium) */
489
517
  premiumVault: Address<TAccountPremiumVault>;
518
+ /** Collateral pool (settlement mint for buyer position tracking) */
519
+ collateralPool: Address<TAccountCollateralPool>;
490
520
  buyer: TransactionSigner<TAccountBuyer>;
491
521
  tokenProgram?: Address<TAccountTokenProgram>;
492
522
  associatedTokenProgram?: Address<TAccountAssociatedTokenProgram>;
@@ -509,6 +539,7 @@ export function getBuyFromPoolInstruction<
509
539
  TAccountBuyerPaymentAccount extends string,
510
540
  TAccountEscrowLongAccount extends string,
511
541
  TAccountPremiumVault extends string,
542
+ TAccountCollateralPool extends string,
512
543
  TAccountBuyer extends string,
513
544
  TAccountTokenProgram extends string,
514
545
  TAccountAssociatedTokenProgram extends string,
@@ -529,6 +560,7 @@ export function getBuyFromPoolInstruction<
529
560
  TAccountBuyerPaymentAccount,
530
561
  TAccountEscrowLongAccount,
531
562
  TAccountPremiumVault,
563
+ TAccountCollateralPool,
532
564
  TAccountBuyer,
533
565
  TAccountTokenProgram,
534
566
  TAccountAssociatedTokenProgram,
@@ -550,6 +582,7 @@ export function getBuyFromPoolInstruction<
550
582
  TAccountBuyerPaymentAccount,
551
583
  TAccountEscrowLongAccount,
552
584
  TAccountPremiumVault,
585
+ TAccountCollateralPool,
553
586
  TAccountBuyer,
554
587
  TAccountTokenProgram,
555
588
  TAccountAssociatedTokenProgram,
@@ -592,6 +625,7 @@ export function getBuyFromPoolInstruction<
592
625
  isWritable: true,
593
626
  },
594
627
  premiumVault: { value: input.premiumVault ?? null, isWritable: true },
628
+ collateralPool: { value: input.collateralPool ?? null, isWritable: false },
595
629
  buyer: { value: input.buyer ?? null, isWritable: true },
596
630
  tokenProgram: { value: input.tokenProgram ?? null, isWritable: false },
597
631
  associatedTokenProgram: {
@@ -646,6 +680,7 @@ export function getBuyFromPoolInstruction<
646
680
  getAccountMeta(accounts.buyerPaymentAccount),
647
681
  getAccountMeta(accounts.escrowLongAccount),
648
682
  getAccountMeta(accounts.premiumVault),
683
+ getAccountMeta(accounts.collateralPool),
649
684
  getAccountMeta(accounts.buyer),
650
685
  getAccountMeta(accounts.tokenProgram),
651
686
  getAccountMeta(accounts.associatedTokenProgram),
@@ -670,6 +705,7 @@ export function getBuyFromPoolInstruction<
670
705
  TAccountBuyerPaymentAccount,
671
706
  TAccountEscrowLongAccount,
672
707
  TAccountPremiumVault,
708
+ TAccountCollateralPool,
673
709
  TAccountBuyer,
674
710
  TAccountTokenProgram,
675
711
  TAccountAssociatedTokenProgram,
@@ -709,10 +745,12 @@ export type ParsedBuyFromPoolInstruction<
709
745
  escrowLongAccount: TAccountMetas[11];
710
746
  /** Pool's premium vault (receives premium) */
711
747
  premiumVault: TAccountMetas[12];
712
- buyer: TAccountMetas[13];
713
- tokenProgram: TAccountMetas[14];
714
- associatedTokenProgram: TAccountMetas[15];
715
- systemProgram: TAccountMetas[16];
748
+ /** Collateral pool (settlement mint for buyer position tracking) */
749
+ collateralPool: TAccountMetas[13];
750
+ buyer: TAccountMetas[14];
751
+ tokenProgram: TAccountMetas[15];
752
+ associatedTokenProgram: TAccountMetas[16];
753
+ systemProgram: TAccountMetas[17];
716
754
  };
717
755
  data: BuyFromPoolInstructionData;
718
756
  };
@@ -725,7 +763,7 @@ export function parseBuyFromPoolInstruction<
725
763
  InstructionWithAccounts<TAccountMetas> &
726
764
  InstructionWithData<ReadonlyUint8Array>,
727
765
  ): ParsedBuyFromPoolInstruction<TProgram, TAccountMetas> {
728
- if (instruction.accounts.length < 17) {
766
+ if (instruction.accounts.length < 18) {
729
767
  // TODO: Coded error.
730
768
  throw new Error("Not enough accounts");
731
769
  }
@@ -751,6 +789,7 @@ export function parseBuyFromPoolInstruction<
751
789
  buyerPaymentAccount: getNextAccount(),
752
790
  escrowLongAccount: getNextAccount(),
753
791
  premiumVault: getNextAccount(),
792
+ collateralPool: getNextAccount(),
754
793
  buyer: getNextAccount(),
755
794
  tokenProgram: getNextAccount(),
756
795
  associatedTokenProgram: getNextAccount(),
@@ -57,6 +57,7 @@ export type CloseLongToPoolInstruction<
57
57
  TAccountOptionAccount extends string | AccountMeta<string> = string,
58
58
  TAccountCollateralPool extends string | AccountMeta<string> = string,
59
59
  TAccountUnderlyingMint extends string | AccountMeta<string> = string,
60
+ TAccountCollateralMint extends string | AccountMeta<string> = string,
60
61
  TAccountLongMint extends string | AccountMeta<string> = string,
61
62
  TAccountEscrowLongAccount extends string | AccountMeta<string> = string,
62
63
  TAccountPremiumVault extends string | AccountMeta<string> = string,
@@ -69,6 +70,8 @@ export type CloseLongToPoolInstruction<
69
70
  TAccountBuyerPosition extends string | AccountMeta<string> = string,
70
71
  TAccountBuyerLongAccount extends string | AccountMeta<string> = string,
71
72
  TAccountBuyerPayoutAccount extends string | AccountMeta<string> = string,
73
+ TAccountBuyerUnderlyingPayoutAccount extends string | AccountMeta<string> =
74
+ string,
72
75
  TAccountCollateralVault extends string | AccountMeta<string> = string,
73
76
  TAccountBuyer extends string | AccountMeta<string> = string,
74
77
  TAccountTokenProgram extends string | AccountMeta<string> =
@@ -92,6 +95,9 @@ export type CloseLongToPoolInstruction<
92
95
  TAccountUnderlyingMint extends string
93
96
  ? ReadonlyAccount<TAccountUnderlyingMint>
94
97
  : TAccountUnderlyingMint,
98
+ TAccountCollateralMint extends string
99
+ ? ReadonlyAccount<TAccountCollateralMint>
100
+ : TAccountCollateralMint,
95
101
  TAccountLongMint extends string
96
102
  ? WritableAccount<TAccountLongMint>
97
103
  : TAccountLongMint,
@@ -122,6 +128,9 @@ export type CloseLongToPoolInstruction<
122
128
  TAccountBuyerPayoutAccount extends string
123
129
  ? WritableAccount<TAccountBuyerPayoutAccount>
124
130
  : TAccountBuyerPayoutAccount,
131
+ TAccountBuyerUnderlyingPayoutAccount extends string
132
+ ? WritableAccount<TAccountBuyerUnderlyingPayoutAccount>
133
+ : TAccountBuyerUnderlyingPayoutAccount,
125
134
  TAccountCollateralVault extends string
126
135
  ? WritableAccount<TAccountCollateralVault>
127
136
  : TAccountCollateralVault,
@@ -184,6 +193,7 @@ export type CloseLongToPoolAsyncInput<
184
193
  TAccountOptionAccount extends string = string,
185
194
  TAccountCollateralPool extends string = string,
186
195
  TAccountUnderlyingMint extends string = string,
196
+ TAccountCollateralMint extends string = string,
187
197
  TAccountLongMint extends string = string,
188
198
  TAccountEscrowLongAccount extends string = string,
189
199
  TAccountPremiumVault extends string = string,
@@ -194,6 +204,7 @@ export type CloseLongToPoolAsyncInput<
194
204
  TAccountBuyerPosition extends string = string,
195
205
  TAccountBuyerLongAccount extends string = string,
196
206
  TAccountBuyerPayoutAccount extends string = string,
207
+ TAccountBuyerUnderlyingPayoutAccount extends string = string,
197
208
  TAccountCollateralVault extends string = string,
198
209
  TAccountBuyer extends string = string,
199
210
  TAccountTokenProgram extends string = string,
@@ -207,6 +218,8 @@ export type CloseLongToPoolAsyncInput<
207
218
  collateralPool?: Address<TAccountCollateralPool>;
208
219
  /** Underlying token mint (for decimal handling) */
209
220
  underlyingMint: Address<TAccountUnderlyingMint>;
221
+ /** Collateral mint (settlement currency for collateral-vault payout leg) */
222
+ collateralMint: Address<TAccountCollateralMint>;
210
223
  /** LONG token mint (for transfer validation) */
211
224
  longMint: Address<TAccountLongMint>;
212
225
  /** Pool LONG escrow (destination when buyer closes back to pool) */
@@ -222,8 +235,10 @@ export type CloseLongToPoolAsyncInput<
222
235
  buyerPosition?: Address<TAccountBuyerPosition>;
223
236
  /** Buyer's LONG token account (source of tokens to return) */
224
237
  buyerLongAccount: Address<TAccountBuyerLongAccount>;
225
- /** Buyer's underlying token account (receives payout) */
238
+ /** Buyer's settlement token account (receives collateral-vault payout leg) */
226
239
  buyerPayoutAccount: Address<TAccountBuyerPayoutAccount>;
240
+ /** Buyer's underlying token account (receives premium-vault payout leg; same ATA when physical) */
241
+ buyerUnderlyingPayoutAccount: Address<TAccountBuyerUnderlyingPayoutAccount>;
227
242
  /** Collateral vault (source of payout) */
228
243
  collateralVault: Address<TAccountCollateralVault>;
229
244
  buyer: TransactionSigner<TAccountBuyer>;
@@ -238,6 +253,7 @@ export async function getCloseLongToPoolInstructionAsync<
238
253
  TAccountOptionAccount extends string,
239
254
  TAccountCollateralPool extends string,
240
255
  TAccountUnderlyingMint extends string,
256
+ TAccountCollateralMint extends string,
241
257
  TAccountLongMint extends string,
242
258
  TAccountEscrowLongAccount extends string,
243
259
  TAccountPremiumVault extends string,
@@ -248,6 +264,7 @@ export async function getCloseLongToPoolInstructionAsync<
248
264
  TAccountBuyerPosition extends string,
249
265
  TAccountBuyerLongAccount extends string,
250
266
  TAccountBuyerPayoutAccount extends string,
267
+ TAccountBuyerUnderlyingPayoutAccount extends string,
251
268
  TAccountCollateralVault extends string,
252
269
  TAccountBuyer extends string,
253
270
  TAccountTokenProgram extends string,
@@ -259,6 +276,7 @@ export async function getCloseLongToPoolInstructionAsync<
259
276
  TAccountOptionAccount,
260
277
  TAccountCollateralPool,
261
278
  TAccountUnderlyingMint,
279
+ TAccountCollateralMint,
262
280
  TAccountLongMint,
263
281
  TAccountEscrowLongAccount,
264
282
  TAccountPremiumVault,
@@ -269,6 +287,7 @@ export async function getCloseLongToPoolInstructionAsync<
269
287
  TAccountBuyerPosition,
270
288
  TAccountBuyerLongAccount,
271
289
  TAccountBuyerPayoutAccount,
290
+ TAccountBuyerUnderlyingPayoutAccount,
272
291
  TAccountCollateralVault,
273
292
  TAccountBuyer,
274
293
  TAccountTokenProgram,
@@ -282,6 +301,7 @@ export async function getCloseLongToPoolInstructionAsync<
282
301
  TAccountOptionAccount,
283
302
  TAccountCollateralPool,
284
303
  TAccountUnderlyingMint,
304
+ TAccountCollateralMint,
285
305
  TAccountLongMint,
286
306
  TAccountEscrowLongAccount,
287
307
  TAccountPremiumVault,
@@ -292,6 +312,7 @@ export async function getCloseLongToPoolInstructionAsync<
292
312
  TAccountBuyerPosition,
293
313
  TAccountBuyerLongAccount,
294
314
  TAccountBuyerPayoutAccount,
315
+ TAccountBuyerUnderlyingPayoutAccount,
295
316
  TAccountCollateralVault,
296
317
  TAccountBuyer,
297
318
  TAccountTokenProgram,
@@ -308,6 +329,7 @@ export async function getCloseLongToPoolInstructionAsync<
308
329
  optionAccount: { value: input.optionAccount ?? null, isWritable: true },
309
330
  collateralPool: { value: input.collateralPool ?? null, isWritable: true },
310
331
  underlyingMint: { value: input.underlyingMint ?? null, isWritable: false },
332
+ collateralMint: { value: input.collateralMint ?? null, isWritable: false },
311
333
  longMint: { value: input.longMint ?? null, isWritable: true },
312
334
  escrowLongAccount: {
313
335
  value: input.escrowLongAccount ?? null,
@@ -336,6 +358,10 @@ export async function getCloseLongToPoolInstructionAsync<
336
358
  value: input.buyerPayoutAccount ?? null,
337
359
  isWritable: true,
338
360
  },
361
+ buyerUnderlyingPayoutAccount: {
362
+ value: input.buyerUnderlyingPayoutAccount ?? null,
363
+ isWritable: true,
364
+ },
339
365
  collateralVault: { value: input.collateralVault ?? null, isWritable: true },
340
366
  buyer: { value: input.buyer ?? null, isWritable: true },
341
367
  tokenProgram: { value: input.tokenProgram ?? null, isWritable: false },
@@ -400,6 +426,7 @@ export async function getCloseLongToPoolInstructionAsync<
400
426
  getAccountMeta(accounts.optionAccount),
401
427
  getAccountMeta(accounts.collateralPool),
402
428
  getAccountMeta(accounts.underlyingMint),
429
+ getAccountMeta(accounts.collateralMint),
403
430
  getAccountMeta(accounts.longMint),
404
431
  getAccountMeta(accounts.escrowLongAccount),
405
432
  getAccountMeta(accounts.premiumVault),
@@ -410,6 +437,7 @@ export async function getCloseLongToPoolInstructionAsync<
410
437
  getAccountMeta(accounts.buyerPosition),
411
438
  getAccountMeta(accounts.buyerLongAccount),
412
439
  getAccountMeta(accounts.buyerPayoutAccount),
440
+ getAccountMeta(accounts.buyerUnderlyingPayoutAccount),
413
441
  getAccountMeta(accounts.collateralVault),
414
442
  getAccountMeta(accounts.buyer),
415
443
  getAccountMeta(accounts.tokenProgram),
@@ -425,6 +453,7 @@ export async function getCloseLongToPoolInstructionAsync<
425
453
  TAccountOptionAccount,
426
454
  TAccountCollateralPool,
427
455
  TAccountUnderlyingMint,
456
+ TAccountCollateralMint,
428
457
  TAccountLongMint,
429
458
  TAccountEscrowLongAccount,
430
459
  TAccountPremiumVault,
@@ -435,6 +464,7 @@ export async function getCloseLongToPoolInstructionAsync<
435
464
  TAccountBuyerPosition,
436
465
  TAccountBuyerLongAccount,
437
466
  TAccountBuyerPayoutAccount,
467
+ TAccountBuyerUnderlyingPayoutAccount,
438
468
  TAccountCollateralVault,
439
469
  TAccountBuyer,
440
470
  TAccountTokenProgram,
@@ -447,6 +477,7 @@ export type CloseLongToPoolInput<
447
477
  TAccountOptionAccount extends string = string,
448
478
  TAccountCollateralPool extends string = string,
449
479
  TAccountUnderlyingMint extends string = string,
480
+ TAccountCollateralMint extends string = string,
450
481
  TAccountLongMint extends string = string,
451
482
  TAccountEscrowLongAccount extends string = string,
452
483
  TAccountPremiumVault extends string = string,
@@ -457,6 +488,7 @@ export type CloseLongToPoolInput<
457
488
  TAccountBuyerPosition extends string = string,
458
489
  TAccountBuyerLongAccount extends string = string,
459
490
  TAccountBuyerPayoutAccount extends string = string,
491
+ TAccountBuyerUnderlyingPayoutAccount extends string = string,
460
492
  TAccountCollateralVault extends string = string,
461
493
  TAccountBuyer extends string = string,
462
494
  TAccountTokenProgram extends string = string,
@@ -470,6 +502,8 @@ export type CloseLongToPoolInput<
470
502
  collateralPool: Address<TAccountCollateralPool>;
471
503
  /** Underlying token mint (for decimal handling) */
472
504
  underlyingMint: Address<TAccountUnderlyingMint>;
505
+ /** Collateral mint (settlement currency for collateral-vault payout leg) */
506
+ collateralMint: Address<TAccountCollateralMint>;
473
507
  /** LONG token mint (for transfer validation) */
474
508
  longMint: Address<TAccountLongMint>;
475
509
  /** Pool LONG escrow (destination when buyer closes back to pool) */
@@ -485,8 +519,10 @@ export type CloseLongToPoolInput<
485
519
  buyerPosition: Address<TAccountBuyerPosition>;
486
520
  /** Buyer's LONG token account (source of tokens to return) */
487
521
  buyerLongAccount: Address<TAccountBuyerLongAccount>;
488
- /** Buyer's underlying token account (receives payout) */
522
+ /** Buyer's settlement token account (receives collateral-vault payout leg) */
489
523
  buyerPayoutAccount: Address<TAccountBuyerPayoutAccount>;
524
+ /** Buyer's underlying token account (receives premium-vault payout leg; same ATA when physical) */
525
+ buyerUnderlyingPayoutAccount: Address<TAccountBuyerUnderlyingPayoutAccount>;
490
526
  /** Collateral vault (source of payout) */
491
527
  collateralVault: Address<TAccountCollateralVault>;
492
528
  buyer: TransactionSigner<TAccountBuyer>;
@@ -501,6 +537,7 @@ export function getCloseLongToPoolInstruction<
501
537
  TAccountOptionAccount extends string,
502
538
  TAccountCollateralPool extends string,
503
539
  TAccountUnderlyingMint extends string,
540
+ TAccountCollateralMint extends string,
504
541
  TAccountLongMint extends string,
505
542
  TAccountEscrowLongAccount extends string,
506
543
  TAccountPremiumVault extends string,
@@ -511,6 +548,7 @@ export function getCloseLongToPoolInstruction<
511
548
  TAccountBuyerPosition extends string,
512
549
  TAccountBuyerLongAccount extends string,
513
550
  TAccountBuyerPayoutAccount extends string,
551
+ TAccountBuyerUnderlyingPayoutAccount extends string,
514
552
  TAccountCollateralVault extends string,
515
553
  TAccountBuyer extends string,
516
554
  TAccountTokenProgram extends string,
@@ -522,6 +560,7 @@ export function getCloseLongToPoolInstruction<
522
560
  TAccountOptionAccount,
523
561
  TAccountCollateralPool,
524
562
  TAccountUnderlyingMint,
563
+ TAccountCollateralMint,
525
564
  TAccountLongMint,
526
565
  TAccountEscrowLongAccount,
527
566
  TAccountPremiumVault,
@@ -532,6 +571,7 @@ export function getCloseLongToPoolInstruction<
532
571
  TAccountBuyerPosition,
533
572
  TAccountBuyerLongAccount,
534
573
  TAccountBuyerPayoutAccount,
574
+ TAccountBuyerUnderlyingPayoutAccount,
535
575
  TAccountCollateralVault,
536
576
  TAccountBuyer,
537
577
  TAccountTokenProgram,
@@ -544,6 +584,7 @@ export function getCloseLongToPoolInstruction<
544
584
  TAccountOptionAccount,
545
585
  TAccountCollateralPool,
546
586
  TAccountUnderlyingMint,
587
+ TAccountCollateralMint,
547
588
  TAccountLongMint,
548
589
  TAccountEscrowLongAccount,
549
590
  TAccountPremiumVault,
@@ -554,6 +595,7 @@ export function getCloseLongToPoolInstruction<
554
595
  TAccountBuyerPosition,
555
596
  TAccountBuyerLongAccount,
556
597
  TAccountBuyerPayoutAccount,
598
+ TAccountBuyerUnderlyingPayoutAccount,
557
599
  TAccountCollateralVault,
558
600
  TAccountBuyer,
559
601
  TAccountTokenProgram,
@@ -569,6 +611,7 @@ export function getCloseLongToPoolInstruction<
569
611
  optionAccount: { value: input.optionAccount ?? null, isWritable: true },
570
612
  collateralPool: { value: input.collateralPool ?? null, isWritable: true },
571
613
  underlyingMint: { value: input.underlyingMint ?? null, isWritable: false },
614
+ collateralMint: { value: input.collateralMint ?? null, isWritable: false },
572
615
  longMint: { value: input.longMint ?? null, isWritable: true },
573
616
  escrowLongAccount: {
574
617
  value: input.escrowLongAccount ?? null,
@@ -597,6 +640,10 @@ export function getCloseLongToPoolInstruction<
597
640
  value: input.buyerPayoutAccount ?? null,
598
641
  isWritable: true,
599
642
  },
643
+ buyerUnderlyingPayoutAccount: {
644
+ value: input.buyerUnderlyingPayoutAccount ?? null,
645
+ isWritable: true,
646
+ },
600
647
  collateralVault: { value: input.collateralVault ?? null, isWritable: true },
601
648
  buyer: { value: input.buyer ?? null, isWritable: true },
602
649
  tokenProgram: { value: input.tokenProgram ?? null, isWritable: false },
@@ -635,6 +682,7 @@ export function getCloseLongToPoolInstruction<
635
682
  getAccountMeta(accounts.optionAccount),
636
683
  getAccountMeta(accounts.collateralPool),
637
684
  getAccountMeta(accounts.underlyingMint),
685
+ getAccountMeta(accounts.collateralMint),
638
686
  getAccountMeta(accounts.longMint),
639
687
  getAccountMeta(accounts.escrowLongAccount),
640
688
  getAccountMeta(accounts.premiumVault),
@@ -645,6 +693,7 @@ export function getCloseLongToPoolInstruction<
645
693
  getAccountMeta(accounts.buyerPosition),
646
694
  getAccountMeta(accounts.buyerLongAccount),
647
695
  getAccountMeta(accounts.buyerPayoutAccount),
696
+ getAccountMeta(accounts.buyerUnderlyingPayoutAccount),
648
697
  getAccountMeta(accounts.collateralVault),
649
698
  getAccountMeta(accounts.buyer),
650
699
  getAccountMeta(accounts.tokenProgram),
@@ -660,6 +709,7 @@ export function getCloseLongToPoolInstruction<
660
709
  TAccountOptionAccount,
661
710
  TAccountCollateralPool,
662
711
  TAccountUnderlyingMint,
712
+ TAccountCollateralMint,
663
713
  TAccountLongMint,
664
714
  TAccountEscrowLongAccount,
665
715
  TAccountPremiumVault,
@@ -670,6 +720,7 @@ export function getCloseLongToPoolInstruction<
670
720
  TAccountBuyerPosition,
671
721
  TAccountBuyerLongAccount,
672
722
  TAccountBuyerPayoutAccount,
723
+ TAccountBuyerUnderlyingPayoutAccount,
673
724
  TAccountCollateralVault,
674
725
  TAccountBuyer,
675
726
  TAccountTokenProgram,
@@ -691,28 +742,32 @@ export type ParsedCloseLongToPoolInstruction<
691
742
  collateralPool: TAccountMetas[2];
692
743
  /** Underlying token mint (for decimal handling) */
693
744
  underlyingMint: TAccountMetas[3];
745
+ /** Collateral mint (settlement currency for collateral-vault payout leg) */
746
+ collateralMint: TAccountMetas[4];
694
747
  /** LONG token mint (for transfer validation) */
695
- longMint: TAccountMetas[4];
748
+ longMint: TAccountMetas[5];
696
749
  /** Pool LONG escrow (destination when buyer closes back to pool) */
697
- escrowLongAccount: TAccountMetas[5];
750
+ escrowLongAccount: TAccountMetas[6];
698
751
  /** Premium vault (first source of buyer payout) */
699
- premiumVault: TAccountMetas[6];
752
+ premiumVault: TAccountMetas[7];
700
753
  /** Market data account (provides risk-free rate and switchboard_feed_id) */
701
- marketData: TAccountMetas[7];
702
- switchboardQueue: TAccountMetas[8];
703
- slotHashesSysvar: TAccountMetas[9];
704
- instructionsSysvar: TAccountMetas[10];
754
+ marketData: TAccountMetas[8];
755
+ switchboardQueue: TAccountMetas[9];
756
+ slotHashesSysvar: TAccountMetas[10];
757
+ instructionsSysvar: TAccountMetas[11];
705
758
  /** Buyer's position account */
706
- buyerPosition: TAccountMetas[11];
759
+ buyerPosition: TAccountMetas[12];
707
760
  /** Buyer's LONG token account (source of tokens to return) */
708
- buyerLongAccount: TAccountMetas[12];
709
- /** Buyer's underlying token account (receives payout) */
710
- buyerPayoutAccount: TAccountMetas[13];
761
+ buyerLongAccount: TAccountMetas[13];
762
+ /** Buyer's settlement token account (receives collateral-vault payout leg) */
763
+ buyerPayoutAccount: TAccountMetas[14];
764
+ /** Buyer's underlying token account (receives premium-vault payout leg; same ATA when physical) */
765
+ buyerUnderlyingPayoutAccount: TAccountMetas[15];
711
766
  /** Collateral vault (source of payout) */
712
- collateralVault: TAccountMetas[14];
713
- buyer: TAccountMetas[15];
714
- tokenProgram: TAccountMetas[16];
715
- systemProgram: TAccountMetas[17];
767
+ collateralVault: TAccountMetas[16];
768
+ buyer: TAccountMetas[17];
769
+ tokenProgram: TAccountMetas[18];
770
+ systemProgram: TAccountMetas[19];
716
771
  };
717
772
  data: CloseLongToPoolInstructionData;
718
773
  };
@@ -725,7 +780,7 @@ export function parseCloseLongToPoolInstruction<
725
780
  InstructionWithAccounts<TAccountMetas> &
726
781
  InstructionWithData<ReadonlyUint8Array>,
727
782
  ): ParsedCloseLongToPoolInstruction<TProgram, TAccountMetas> {
728
- if (instruction.accounts.length < 18) {
783
+ if (instruction.accounts.length < 20) {
729
784
  // TODO: Coded error.
730
785
  throw new Error("Not enough accounts");
731
786
  }
@@ -742,6 +797,7 @@ export function parseCloseLongToPoolInstruction<
742
797
  optionAccount: getNextAccount(),
743
798
  collateralPool: getNextAccount(),
744
799
  underlyingMint: getNextAccount(),
800
+ collateralMint: getNextAccount(),
745
801
  longMint: getNextAccount(),
746
802
  escrowLongAccount: getNextAccount(),
747
803
  premiumVault: getNextAccount(),
@@ -752,6 +808,7 @@ export function parseCloseLongToPoolInstruction<
752
808
  buyerPosition: getNextAccount(),
753
809
  buyerLongAccount: getNextAccount(),
754
810
  buyerPayoutAccount: getNextAccount(),
811
+ buyerUnderlyingPayoutAccount: getNextAccount(),
755
812
  collateralVault: getNextAccount(),
756
813
  buyer: getNextAccount(),
757
814
  tokenProgram: getNextAccount(),
@@ -222,9 +222,9 @@ export type UnwindWriterUnsoldAsyncInput<
222
222
  */
223
223
  premiumVault: Address<TAccountPremiumVault>;
224
224
  /**
225
- * Writer's token account for returned collateral + any leftover theta.
226
- * Must be the underlying_mint so leftover theta (denominated in
227
- * premium_vault.mint == underlying_mint) can be deposited here.
225
+ * Writer's token account for returned collateral (collateral_pool mint).
226
+ * Leftover theta from premium_vault is only transferred here when
227
+ * premium_vault.mint matches this account (physical settlement).
228
228
  */
229
229
  writerCollateralAccount: Address<TAccountWriterCollateralAccount>;
230
230
  /** OMLP Vault state PDA (for total_loans, add_interest_to_index) - optional, required when repaying loans */
@@ -480,9 +480,9 @@ export type UnwindWriterUnsoldInput<
480
480
  */
481
481
  premiumVault: Address<TAccountPremiumVault>;
482
482
  /**
483
- * Writer's token account for returned collateral + any leftover theta.
484
- * Must be the underlying_mint so leftover theta (denominated in
485
- * premium_vault.mint == underlying_mint) can be deposited here.
483
+ * Writer's token account for returned collateral (collateral_pool mint).
484
+ * Leftover theta from premium_vault is only transferred here when
485
+ * premium_vault.mint matches this account (physical settlement).
486
486
  */
487
487
  writerCollateralAccount: Address<TAccountWriterCollateralAccount>;
488
488
  /** OMLP Vault state PDA (for total_loans, add_interest_to_index) - optional, required when repaying loans */
@@ -693,9 +693,9 @@ export type ParsedUnwindWriterUnsoldInstruction<
693
693
  */
694
694
  premiumVault: TAccountMetas[10];
695
695
  /**
696
- * Writer's token account for returned collateral + any leftover theta.
697
- * Must be the underlying_mint so leftover theta (denominated in
698
- * premium_vault.mint == underlying_mint) can be deposited here.
696
+ * Writer's token account for returned collateral (collateral_pool mint).
697
+ * Leftover theta from premium_vault is only transferred here when
698
+ * premium_vault.mint matches this account (physical settlement).
699
699
  */
700
700
  writerCollateralAccount: TAccountMetas[11];
701
701
  /** OMLP Vault state PDA (for total_loans, add_interest_to_index) - optional, required when repaying loans */
package/long/builders.ts CHANGED
@@ -69,7 +69,11 @@ export interface BuildCloseLongToPoolParams {
69
69
  switchboardQueue: AddressLike;
70
70
  buyer: AddressLike;
71
71
  buyerLongAccount: AddressLike;
72
+ /** Settlement-mint ATA (collateral pool mint). */
72
73
  buyerPayoutAccount: AddressLike;
74
+ /** Underlying-mint ATA for premium-vault leg (same as buyerPayout when physical). */
75
+ buyerUnderlyingPayoutAccount: AddressLike;
76
+ collateralMint?: AddressLike;
73
77
  collateralVault: AddressLike;
74
78
  quantity: bigint | number;
75
79
  minPayoutAmount: bigint | number;
@@ -477,11 +481,14 @@ export async function buildCloseLongToPoolInstruction(
477
481
  "minPayoutAmount must be greater than or equal to zero."
478
482
  );
479
483
 
484
+ const collateralMint = params.collateralMint ?? params.underlyingMint;
485
+
480
486
  const kitInstruction = await getCloseLongToPoolInstructionAsync({
481
487
  optionPool: toAddress(params.optionPool),
482
488
  optionAccount: toAddress(params.optionAccount),
483
489
  collateralPool: toAddress(params.collateralPool),
484
490
  underlyingMint: toAddress(params.underlyingMint),
491
+ collateralMint: toAddress(collateralMint),
485
492
  longMint: toAddress(params.longMint),
486
493
  escrowLongAccount: toAddress(params.escrowLongAccount),
487
494
  premiumVault: toAddress(params.premiumVault),
@@ -490,6 +497,7 @@ export async function buildCloseLongToPoolInstruction(
490
497
  buyer: toAddress(params.buyer) as any,
491
498
  buyerLongAccount: toAddress(params.buyerLongAccount),
492
499
  buyerPayoutAccount: toAddress(params.buyerPayoutAccount),
500
+ buyerUnderlyingPayoutAccount: toAddress(params.buyerUnderlyingPayoutAccount),
493
501
  collateralVault: toAddress(params.collateralVault),
494
502
  buyerPosition: params.buyerPosition ? toAddress(params.buyerPosition) : undefined,
495
503
  quantity: params.quantity,
@@ -521,7 +529,7 @@ export async function buildCloseLongToPoolTransaction(
521
529
  if (shouldUnwrapPayout) {
522
530
  instructions.push(
523
531
  getCloseAccountInstruction(
524
- params.buyerPayoutAccount,
532
+ params.buyerUnderlyingPayoutAccount,
525
533
  params.buyer,
526
534
  params.buyer
527
535
  )
@@ -538,7 +546,10 @@ export interface BuildCloseLongToPoolTransactionWithDerivationParams {
538
546
  expirationDate: bigint | number;
539
547
  buyer: AddressLike;
540
548
  buyerLongAccount: AddressLike;
541
- buyerPayoutAccount: AddressLike;
549
+ /** Optional — derived from collateral pool mint when omitted. */
550
+ buyerPayoutAccount?: AddressLike;
551
+ /** Optional — derived from underlying mint when omitted. */
552
+ buyerUnderlyingPayoutAccount?: AddressLike;
542
553
  switchboardQueue?: AddressLike;
543
554
  quantity: bigint | number;
544
555
  minPayoutAmount: bigint | number;
@@ -595,6 +606,17 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
595
606
  params.programId
596
607
  ))[0];
597
608
 
609
+ const collateralMint =
610
+ resolved.collateralPoolData?.collateralMint ?? resolved.underlyingMint!;
611
+ const [buyerPayoutAccount, buyerUnderlyingPayoutAccount] = await Promise.all([
612
+ params.buyerPayoutAccount
613
+ ? Promise.resolve(params.buyerPayoutAccount)
614
+ : deriveAssociatedTokenAddress(params.buyer, collateralMint),
615
+ params.buyerUnderlyingPayoutAccount
616
+ ? Promise.resolve(params.buyerUnderlyingPayoutAccount)
617
+ : deriveAssociatedTokenAddress(params.buyer, resolved.underlyingMint!),
618
+ ]);
619
+
598
620
  const isWsolUnderlying =
599
621
  toAddress(resolved.underlyingMint!) === toAddress(NATIVE_MINT);
600
622
  const closeLongTokenAccount =
@@ -632,6 +654,7 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
632
654
  optionAccount: resolved.optionAccount,
633
655
  collateralPool: resolved.collateralPool,
634
656
  underlyingMint: resolved.underlyingMint!,
657
+ collateralMint,
635
658
  longMint: resolved.longMint,
636
659
  escrowLongAccount: resolved.escrowLongAccount!,
637
660
  premiumVault: resolved.premiumVault!,
@@ -639,7 +662,8 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
639
662
  switchboardQueue,
640
663
  buyer: params.buyer,
641
664
  buyerLongAccount: params.buyerLongAccount,
642
- buyerPayoutAccount: params.buyerPayoutAccount,
665
+ buyerPayoutAccount,
666
+ buyerUnderlyingPayoutAccount,
643
667
  collateralVault: resolved.collateralVault!,
644
668
  quantity: params.quantity,
645
669
  minPayoutAmount: params.minPayoutAmount,
@@ -669,6 +693,7 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
669
693
  optionAccount: resolved.optionAccount,
670
694
  collateralPool: resolved.collateralPool,
671
695
  underlyingMint: resolved.underlyingMint!,
696
+ collateralMint,
672
697
  longMint: resolved.longMint,
673
698
  escrowLongAccount: resolved.escrowLongAccount!,
674
699
  premiumVault: resolved.premiumVault!,
@@ -676,7 +701,8 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
676
701
  switchboardQueue: getDefaultSwitchboardQueueAddress(network),
677
702
  buyer: params.buyer,
678
703
  buyerLongAccount: params.buyerLongAccount,
679
- buyerPayoutAccount: params.buyerPayoutAccount,
704
+ buyerPayoutAccount,
705
+ buyerUnderlyingPayoutAccount,
680
706
  collateralVault: resolved.collateralVault!,
681
707
  quantity: params.quantity,
682
708
  minPayoutAmount: params.minPayoutAmount,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicentral/sos-sdk",
3
- "version": "0.11.2-beta",
3
+ "version": "0.12.0-beta",
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",
package/shared/amounts.ts CHANGED
@@ -24,30 +24,51 @@ export function assertNonNegativeAmount(value: bigint | number, label: string):
24
24
  }
25
25
  }
26
26
 
27
+ export type SettlementMode = "physical" | "cash";
28
+
27
29
  /**
28
- * Calculate required collateral for option position in token base units
29
- * Matches on-chain formula: ((qty / 1_000_000) * 100 * strike) / spot * 10^decimals
30
- *
31
- * @param quantity - Option quantity in base units (1 contract = 1_000_000)
32
- * @param strikePrice - Strike price in USD
33
- * @param spotPrice - Current spot price of underlying in USD (from oracle)
34
- * @param tokenDecimals - Number of decimals for the underlying token (e.g., 9 for SOL)
35
- * @returns Required collateral in token base units
30
+ * Calculate required collateral in **collateral mint** base units.
31
+ * Matches on-chain `Vault::calculate_required_collateral`:
32
+ * `((qty / 1_000_000) * 100 * strike) / conversionPrice * 10^decimals`.
33
+ *
34
+ * - **Physical:** pass underlying oracle spot + underlying/collateral decimals.
35
+ * - **Cash** (e.g. USDC backing SOL): pass collateral USD price (~1 for stables) + collateral decimals.
36
36
  */
37
37
  export function calculateRequiredCollateral(
38
38
  quantity: bigint | number,
39
39
  strikePrice: number,
40
- spotPrice: number,
41
- tokenDecimals: number
40
+ conversionPrice: number,
41
+ tokenDecimals: number,
42
+ settlementMode: SettlementMode = "physical"
42
43
  ): number {
43
- // Convert base units to contract count
44
+ if (conversionPrice <= 0 || !Number.isFinite(conversionPrice)) {
45
+ return Number.POSITIVE_INFINITY;
46
+ }
47
+
44
48
  const contracts = Number(quantity) / 1_000_000;
45
- const contractSize = 100; // 1 contract = 100 units of underlying
46
-
47
- // USD value needed for collateral
49
+ const contractSize = 100;
48
50
  const usdRequired = contracts * contractSize * strikePrice;
49
-
50
- // Convert USD to token base units
51
51
  const baseUnits = 10 ** tokenDecimals;
52
- return (usdRequired / spotPrice) * baseUnits;
52
+
53
+ if (settlementMode === "cash") {
54
+ return usdRequired * baseUnits;
55
+ }
56
+
57
+ return (usdRequired / conversionPrice) * baseUnits;
58
+ }
59
+
60
+ /** @deprecated Use `calculateRequiredCollateral` with explicit settlement mode. */
61
+ export function calculateRequiredCollateralLegacy(
62
+ quantity: bigint | number,
63
+ strikePrice: number,
64
+ spotPrice: number,
65
+ tokenDecimals: number
66
+ ): number {
67
+ return calculateRequiredCollateral(
68
+ quantity,
69
+ strikePrice,
70
+ spotPrice,
71
+ tokenDecimals,
72
+ "physical"
73
+ );
53
74
  }
package/short/builders.ts CHANGED
@@ -666,7 +666,9 @@ export async function buildUnwindWriterUnsoldWithLoanRepayment(
666
666
  "underlyingMint is required; ensure rpc is provided and option pool is initialized, or pass underlyingMint."
667
667
  );
668
668
 
669
- const [vaultPda] = await deriveVaultPda(underlyingMint, params.programId);
669
+ const collateralMint =
670
+ resolved.collateralPoolData?.collateralMint ?? underlyingMint;
671
+ const [vaultPda] = await deriveVaultPda(collateralMint, params.programId);
670
672
  const vaultPdaStr = toAddress(vaultPda);
671
673
 
672
674
  const [writerPositionAddress] = await deriveWriterPositionPda(
@@ -733,9 +735,9 @@ export async function buildUnwindWriterUnsoldWithLoanRepayment(
733
735
  isWritable: true,
734
736
  }));
735
737
 
736
- const omlpVault = await deriveAssociatedTokenAddress(vaultPda, underlyingMint);
738
+ const omlpVault = await deriveAssociatedTokenAddress(vaultPda, collateralMint);
737
739
  const feeWallet = vault
738
- ? await deriveAssociatedTokenAddress(vault.feeWallet, underlyingMint)
740
+ ? await deriveAssociatedTokenAddress(vault.feeWallet, collateralMint)
739
741
  : undefined;
740
742
 
741
743
  // Theta-hedge model: if proportional debt cannot be covered by
@@ -795,10 +797,14 @@ export async function buildUnwindWriterUnsoldTransactionWithDerivation(
795
797
  "Option pool and collateral pool must exist; ensure rpc is provided and pools are initialized."
796
798
  );
797
799
 
800
+ const collateralMint =
801
+ resolved.collateralPoolData?.collateralMint ?? resolved.underlyingMint;
802
+ invariant(!!collateralMint, "collateral mint is required for unwind.");
803
+
798
804
  const [writerShortAccount, writerCollateralAccount, writerPosition] =
799
805
  await Promise.all([
800
806
  deriveAssociatedTokenAddress(params.writer, resolved.shortMint),
801
- deriveAssociatedTokenAddress(params.writer, resolved.underlyingMint),
807
+ deriveAssociatedTokenAddress(params.writer, collateralMint),
802
808
  deriveWriterPositionPda(resolved.optionPool, params.writer, params.programId),
803
809
  ]);
804
810