@epicentral/sos-sdk 0.9.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.
Files changed (113) hide show
  1. package/.env.example +1 -0
  2. package/AGENTS.md +7 -0
  3. package/LICENSE +21 -0
  4. package/README.md +568 -0
  5. package/accounts/fetchers.ts +196 -0
  6. package/accounts/list.ts +184 -0
  7. package/accounts/pdas.ts +325 -0
  8. package/accounts/resolve-option.ts +104 -0
  9. package/client/lookup-table.ts +114 -0
  10. package/client/program.ts +13 -0
  11. package/client/types.ts +9 -0
  12. package/generated/accounts/collateralPool.ts +217 -0
  13. package/generated/accounts/config.ts +156 -0
  14. package/generated/accounts/escrowState.ts +183 -0
  15. package/generated/accounts/index.ts +20 -0
  16. package/generated/accounts/lenderPosition.ts +211 -0
  17. package/generated/accounts/makerCollateralShare.ts +229 -0
  18. package/generated/accounts/marketDataAccount.ts +176 -0
  19. package/generated/accounts/optionAccount.ts +247 -0
  20. package/generated/accounts/optionPool.ts +285 -0
  21. package/generated/accounts/poolLoan.ts +232 -0
  22. package/generated/accounts/positionAccount.ts +201 -0
  23. package/generated/accounts/vault.ts +366 -0
  24. package/generated/accounts/writerPosition.ts +327 -0
  25. package/generated/errors/index.ts +9 -0
  26. package/generated/errors/optionProgram.ts +476 -0
  27. package/generated/index.ts +13 -0
  28. package/generated/instructions/acceptAdmin.ts +230 -0
  29. package/generated/instructions/autoExerciseAllExpired.ts +685 -0
  30. package/generated/instructions/autoExerciseExpired.ts +754 -0
  31. package/generated/instructions/borrowFromPool.ts +619 -0
  32. package/generated/instructions/buyFromPool.ts +761 -0
  33. package/generated/instructions/closeLongToPool.ts +762 -0
  34. package/generated/instructions/closeOption.ts +235 -0
  35. package/generated/instructions/createEscrowV2.ts +518 -0
  36. package/generated/instructions/depositCollateral.ts +624 -0
  37. package/generated/instructions/depositToPosition.ts +429 -0
  38. package/generated/instructions/index.ts +47 -0
  39. package/generated/instructions/initCollateralPool.ts +513 -0
  40. package/generated/instructions/initConfig.ts +279 -0
  41. package/generated/instructions/initOptionPool.ts +587 -0
  42. package/generated/instructions/initializeMarketData.ts +359 -0
  43. package/generated/instructions/liquidateWriterPosition.ts +750 -0
  44. package/generated/instructions/liquidateWriterPositionRescue.ts +623 -0
  45. package/generated/instructions/omlpCreateVault.ts +553 -0
  46. package/generated/instructions/omlpUpdateFeeWallet.ts +473 -0
  47. package/generated/instructions/omlpUpdateInterestModel.ts +322 -0
  48. package/generated/instructions/omlpUpdateLiquidationThreshold.ts +304 -0
  49. package/generated/instructions/omlpUpdateMaintenanceBuffer.ts +304 -0
  50. package/generated/instructions/omlpUpdateMaxBorrowCap.ts +304 -0
  51. package/generated/instructions/omlpUpdateMaxLeverage.ts +304 -0
  52. package/generated/instructions/omlpUpdateProtocolFee.ts +304 -0
  53. package/generated/instructions/omlpUpdateSupplyLimit.ts +304 -0
  54. package/generated/instructions/optionExercise.ts +617 -0
  55. package/generated/instructions/optionMint.ts +1373 -0
  56. package/generated/instructions/optionValidate.ts +302 -0
  57. package/generated/instructions/repayPoolLoan.ts +558 -0
  58. package/generated/instructions/repayPoolLoanFromCollateral.ts +514 -0
  59. package/generated/instructions/repayPoolLoanFromWallet.ts +542 -0
  60. package/generated/instructions/settleMakerCollateral.ts +509 -0
  61. package/generated/instructions/syncWriterPosition.ts +206 -0
  62. package/generated/instructions/transferAdmin.ts +245 -0
  63. package/generated/instructions/unwindWriterUnsold.ts +764 -0
  64. package/generated/instructions/updateImpliedVolatility.ts +226 -0
  65. package/generated/instructions/updateMarketData.ts +315 -0
  66. package/generated/instructions/withdrawFromPosition.ts +405 -0
  67. package/generated/instructions/writeToPool.ts +619 -0
  68. package/generated/programs/index.ts +9 -0
  69. package/generated/programs/optionProgram.ts +1144 -0
  70. package/generated/shared/index.ts +164 -0
  71. package/generated/types/impliedVolatilityUpdated.ts +73 -0
  72. package/generated/types/index.ts +28 -0
  73. package/generated/types/liquidationExecuted.ts +73 -0
  74. package/generated/types/liquidationRescueEvent.ts +82 -0
  75. package/generated/types/marketDataInitialized.ts +61 -0
  76. package/generated/types/marketDataUpdated.ts +69 -0
  77. package/generated/types/optionClosed.ts +56 -0
  78. package/generated/types/optionExercised.ts +62 -0
  79. package/generated/types/optionExpired.ts +49 -0
  80. package/generated/types/optionMinted.ts +78 -0
  81. package/generated/types/optionType.ts +38 -0
  82. package/generated/types/optionValidated.ts +82 -0
  83. package/generated/types/poolLoanCreated.ts +74 -0
  84. package/generated/types/poolLoanRepaid.ts +74 -0
  85. package/generated/types/positionDeposited.ts +73 -0
  86. package/generated/types/positionWithdrawn.ts +81 -0
  87. package/generated/types/protocolFeeUpdated.ts +69 -0
  88. package/generated/types/vaultCreated.ts +60 -0
  89. package/generated/types/vaultFeeWalletUpdated.ts +67 -0
  90. package/generated/types/vaultInterestModelUpdated.ts +77 -0
  91. package/generated/types/vaultLiquidationThresholdUpdated.ts +69 -0
  92. package/index.ts +68 -0
  93. package/long/builders.ts +690 -0
  94. package/long/exercise.ts +123 -0
  95. package/long/preflight.ts +214 -0
  96. package/long/quotes.ts +48 -0
  97. package/long/remaining-accounts.ts +111 -0
  98. package/omlp/builders.ts +94 -0
  99. package/omlp/service.ts +136 -0
  100. package/oracle/switchboard.ts +315 -0
  101. package/package.json +34 -0
  102. package/shared/amounts.ts +53 -0
  103. package/shared/balances.ts +57 -0
  104. package/shared/errors.ts +12 -0
  105. package/shared/remaining-accounts.ts +41 -0
  106. package/shared/trade-config.ts +27 -0
  107. package/shared/transactions.ts +121 -0
  108. package/short/builders.ts +874 -0
  109. package/short/close-option.ts +34 -0
  110. package/short/pool.ts +189 -0
  111. package/short/preflight.ts +619 -0
  112. package/tsconfig.json +13 -0
  113. package/wsol/instructions.ts +247 -0
@@ -0,0 +1,874 @@
1
+ import {
2
+ getLiquidateWriterPositionRescueInstructionAsync,
3
+ getOptionMintInstructionAsync,
4
+ getSettleMakerCollateralInstructionAsync,
5
+ getSyncWriterPositionInstruction,
6
+ getUnwindWriterUnsoldInstructionAsync,
7
+ type OptionType,
8
+ } from "../generated";
9
+ import type { Instruction, TransactionSigner } from "@solana/kit";
10
+ import { toAddress } from "../client/program";
11
+ import type { AddressLike, BuiltTransaction, KitRpc } from "../client/types";
12
+ import { fetchMarketDataAccount, fetchVault, fetchWriterPosition } from "../accounts/fetchers";
13
+ import { fetchPoolLoansByMaker } from "../accounts/list";
14
+ import { resolveOptionAccounts } from "../accounts/resolve-option";
15
+ import {
16
+ deriveAssociatedTokenAddress,
17
+ deriveMakerCollateralSharePda,
18
+ deriveMetadataPda,
19
+ deriveVaultPda,
20
+ deriveWriterPositionPda,
21
+ } from "../accounts/pdas";
22
+ import { assertNonNegativeAmount, assertPositiveAmount } from "../shared/amounts";
23
+ import { invariant } from "../shared/errors";
24
+ import {
25
+ appendRemainingAccounts,
26
+ type RemainingAccountInput,
27
+ } from "../shared/remaining-accounts";
28
+ import {
29
+ getCloseAccountInstruction,
30
+ getCreateAssociatedTokenIdempotentInstructionWithAddress,
31
+ } from "../wsol/instructions";
32
+ import { preflightUnwindWriterUnsold, writerPositionHasOngoingPoolLoanDebt } from "./preflight";
33
+ import {
34
+ buildSwitchboardQuoteInstruction,
35
+ feedIdBytesToHex,
36
+ inferSwitchboardNetwork,
37
+ INSTRUCTIONS_SYSVAR_ADDRESS,
38
+ SLOT_HASHES_SYSVAR_ADDRESS,
39
+ SWITCHBOARD_DEFAULT_DEVNET_QUEUE,
40
+ } from "../oracle/switchboard";
41
+ import { applySlippageBps } from "../long/quotes";
42
+ import { getGlobalTradeConfig } from "../shared/trade-config";
43
+
44
+ export interface BuildOptionMintParams {
45
+ optionType: OptionType;
46
+ strikePrice: number;
47
+ expirationDate: bigint | number;
48
+ quantity: bigint | number;
49
+ underlyingAsset: AddressLike;
50
+ underlyingSymbol: string;
51
+ /**
52
+ * Collateral mint (e.g., USDC, BTC, SOL) - Writer's choice for backing the position.
53
+ * Can differ from underlying asset - enables multi-collateral settlement.
54
+ * OMLP vault routing is based on this mint. Defaults to underlyingMint if not provided.
55
+ */
56
+ collateralMint?: AddressLike;
57
+ makerCollateralAmount: bigint | number;
58
+ borrowedAmount: bigint | number;
59
+ maxRequiredCollateralAmount?: bigint | number;
60
+ maker: AddressLike;
61
+ makerCollateralAccount: AddressLike;
62
+ underlyingMint: AddressLike;
63
+ /** Queue account required by direct Switchboard quote verification. */
64
+ switchboardQueue?: AddressLike;
65
+ longMetadataAccount?: AddressLike;
66
+ shortMetadataAccount?: AddressLike;
67
+ optionAccount?: AddressLike;
68
+ longMint?: AddressLike;
69
+ shortMint?: AddressLike;
70
+ mintAuthority?: AddressLike;
71
+ makerLongAccount?: AddressLike;
72
+ makerShortAccount?: AddressLike;
73
+ marketData?: AddressLike;
74
+ optionPool?: AddressLike;
75
+ escrowLongAccount?: AddressLike;
76
+ premiumVault?: AddressLike;
77
+ collateralPool?: AddressLike;
78
+ collateralVault?: AddressLike;
79
+ writerPosition?: AddressLike;
80
+ vault?: AddressLike;
81
+ vaultTokenAccount?: AddressLike;
82
+ escrowState?: AddressLike;
83
+ escrowAuthority?: AddressLike;
84
+ escrowTokenAccount?: AddressLike;
85
+ poolLoan?: AddressLike;
86
+ /**
87
+ * When true (default), appends an SPL CloseAccount instruction after option_mint to close the
88
+ * maker's LONG token account (reclaim rent). The program transfers all LONG to escrow, so the
89
+ * maker's LONG ATA is left with zero balance and can be closed in the same transaction.
90
+ */
91
+ closeMakerLongAccount?: boolean;
92
+ remainingAccounts?: RemainingAccountInput[];
93
+ }
94
+
95
+ /** Max PoolLoans per unwind to stay under 64-account tx limit (~18 named + ~20 loans) */
96
+ const MAX_POOL_LOANS_PER_UNWIND = 20;
97
+
98
+ export interface BuildUnwindWriterUnsoldParams {
99
+ optionPool: AddressLike;
100
+ optionAccount: AddressLike;
101
+ /** Market data PDA (`["market_data", underlying_asset]`). */
102
+ marketData: AddressLike;
103
+ longMint: AddressLike;
104
+ shortMint: AddressLike;
105
+ escrowLongAccount: AddressLike;
106
+ writerShortAccount: AddressLike;
107
+ collateralVault: AddressLike;
108
+ /** Pool premium vault (source of theta-first debt repayment + leftover theta release). */
109
+ premiumVault: AddressLike;
110
+ writerCollateralAccount: AddressLike;
111
+ writer: AddressLike;
112
+ unwindQty: bigint | number;
113
+ collateralPool?: AddressLike;
114
+ writerPosition?: AddressLike;
115
+ omlpVaultState?: AddressLike;
116
+ omlpVault?: AddressLike;
117
+ feeWallet?: AddressLike;
118
+ remainingAccounts?: RemainingAccountInput[];
119
+ }
120
+
121
+ export interface BuildSyncWriterPositionParams {
122
+ optionPool: AddressLike;
123
+ optionAccount: AddressLike;
124
+ writerPosition: AddressLike;
125
+ }
126
+
127
+ export interface BuildSettleMakerCollateralParams {
128
+ optionAccount: AddressLike;
129
+ collateralVault: AddressLike;
130
+ makerCollateralAccount: AddressLike;
131
+ /** OMLP Vault token account (destination for principal+interest). */
132
+ omlpVault: AddressLike;
133
+ /** OMLP Vault state PDA (mut: updates `total_loans`, `total_liquidity`, `acc_interest_per_share_fp`). */
134
+ omlpVaultState: AddressLike;
135
+ poolLoan: AddressLike;
136
+ maker: AddressLike;
137
+ makerCollateralShare?: AddressLike;
138
+ collateralPool?: AddressLike;
139
+ }
140
+
141
+ export async function buildOptionMintInstruction(
142
+ params: BuildOptionMintParams
143
+ ): Promise<Instruction<string>> {
144
+ assertPositiveAmount(params.quantity, "quantity");
145
+ assertNonNegativeAmount(params.makerCollateralAmount, "makerCollateralAmount");
146
+ assertNonNegativeAmount(params.borrowedAmount, "borrowedAmount");
147
+ invariant(params.strikePrice > 0, "strikePrice must be greater than zero.");
148
+ invariant(params.underlyingSymbol.length > 0, "underlyingSymbol is required.");
149
+
150
+ const borrowedAmount = BigInt(params.borrowedAmount);
151
+ const globalTradeConfig = getGlobalTradeConfig();
152
+ const maxRequiredCollateralAmount =
153
+ params.maxRequiredCollateralAmount !== undefined
154
+ ? BigInt(params.maxRequiredCollateralAmount)
155
+ : globalTradeConfig.slippageBps !== undefined
156
+ ? applySlippageBps(
157
+ BigInt(params.makerCollateralAmount) + borrowedAmount,
158
+ globalTradeConfig.slippageBps
159
+ )
160
+ : BigInt(params.makerCollateralAmount) + borrowedAmount;
161
+ if (borrowedAmount > 0n) {
162
+ invariant(!!params.vault, "vault is required when borrowedAmount > 0");
163
+ invariant(
164
+ !!params.vaultTokenAccount,
165
+ "vaultTokenAccount is required when borrowedAmount > 0"
166
+ );
167
+ invariant(!!params.escrowState, "escrowState is required when borrowedAmount > 0");
168
+ invariant(
169
+ !!params.escrowAuthority,
170
+ "escrowAuthority is required when borrowedAmount > 0"
171
+ );
172
+ invariant(
173
+ !!params.escrowTokenAccount,
174
+ "escrowTokenAccount is required when borrowedAmount > 0"
175
+ );
176
+ invariant(!!params.poolLoan, "poolLoan is required when borrowedAmount > 0");
177
+ }
178
+
179
+ const [derivedLongMetadata, derivedShortMetadata] = await Promise.all([
180
+ params.longMint ? deriveMetadataPda(params.longMint) : Promise.resolve(undefined),
181
+ params.shortMint ? deriveMetadataPda(params.shortMint) : Promise.resolve(undefined),
182
+ ]);
183
+ const longMetadata = params.longMetadataAccount ?? derivedLongMetadata?.[0];
184
+ const shortMetadata = params.shortMetadataAccount ?? derivedShortMetadata?.[0];
185
+
186
+ invariant(
187
+ !!longMetadata && !!shortMetadata,
188
+ "longMetadataAccount and shortMetadataAccount are required (or provide longMint/shortMint to derive)."
189
+ );
190
+
191
+ const kitInstruction = await getOptionMintInstructionAsync({
192
+ optionAccount: params.optionAccount ? toAddress(params.optionAccount) : undefined,
193
+ longMint: params.longMint ? toAddress(params.longMint) : undefined,
194
+ shortMint: params.shortMint ? toAddress(params.shortMint) : undefined,
195
+ mintAuthority: params.mintAuthority ? toAddress(params.mintAuthority) : undefined,
196
+ makerLongAccount: params.makerLongAccount
197
+ ? toAddress(params.makerLongAccount)
198
+ : undefined,
199
+ makerShortAccount: params.makerShortAccount
200
+ ? toAddress(params.makerShortAccount)
201
+ : undefined,
202
+ longMetadataAccount: toAddress(longMetadata!),
203
+ shortMetadataAccount: toAddress(shortMetadata!),
204
+ marketData: params.marketData ? toAddress(params.marketData) : undefined,
205
+ underlyingMint: toAddress(params.underlyingMint),
206
+ collateralMint: toAddress(params.collateralMint ?? params.underlyingMint),
207
+ optionPool: params.optionPool ? toAddress(params.optionPool) : undefined,
208
+ escrowLongAccount: params.escrowLongAccount
209
+ ? toAddress(params.escrowLongAccount)
210
+ : undefined,
211
+ premiumVault: params.premiumVault ? toAddress(params.premiumVault) : undefined,
212
+ collateralPool: params.collateralPool ? toAddress(params.collateralPool) : undefined,
213
+ collateralVault: params.collateralVault ? toAddress(params.collateralVault) : undefined,
214
+ makerCollateralAccount: toAddress(params.makerCollateralAccount),
215
+ writerPosition: params.writerPosition ? toAddress(params.writerPosition) : undefined,
216
+ vault: params.vault ? toAddress(params.vault) : undefined,
217
+ vaultTokenAccount: params.vaultTokenAccount
218
+ ? toAddress(params.vaultTokenAccount)
219
+ : undefined,
220
+ escrowState: params.escrowState ? toAddress(params.escrowState) : undefined,
221
+ escrowAuthority: params.escrowAuthority ? toAddress(params.escrowAuthority) : undefined,
222
+ escrowTokenAccount: params.escrowTokenAccount
223
+ ? toAddress(params.escrowTokenAccount)
224
+ : undefined,
225
+ poolLoan: params.poolLoan ? toAddress(params.poolLoan) : undefined,
226
+ maker: toAddress(params.maker) as any,
227
+ optionType: params.optionType,
228
+ strikePrice: params.strikePrice,
229
+ expirationDate: params.expirationDate,
230
+ quantity: params.quantity,
231
+ underlyingAsset: toAddress(params.underlyingAsset),
232
+ underlyingSymbol: params.underlyingSymbol,
233
+ collateralMintArg: toAddress(params.collateralMint ?? params.underlyingMint),
234
+ makerCollateralAmount: params.makerCollateralAmount,
235
+ borrowedAmount: params.borrowedAmount,
236
+ maxRequiredCollateralAmount,
237
+ });
238
+
239
+ const quoteVerificationAccounts: RemainingAccountInput[] = [
240
+ {
241
+ address: params.switchboardQueue ?? SWITCHBOARD_DEFAULT_DEVNET_QUEUE,
242
+ isWritable: false,
243
+ },
244
+ {
245
+ address: SLOT_HASHES_SYSVAR_ADDRESS,
246
+ isWritable: false,
247
+ },
248
+ {
249
+ address: INSTRUCTIONS_SYSVAR_ADDRESS,
250
+ isWritable: false,
251
+ },
252
+ ];
253
+
254
+ return appendRemainingAccounts(kitInstruction, [
255
+ ...quoteVerificationAccounts,
256
+ ...(params.remainingAccounts ?? []),
257
+ ]);
258
+ }
259
+
260
+ export async function buildOptionMintTransaction(
261
+ params: BuildOptionMintParams
262
+ ): Promise<BuiltTransaction> {
263
+ const instruction = await buildOptionMintInstruction(params);
264
+ const instructions: Instruction<string>[] = [instruction];
265
+
266
+ const shouldCloseMakerLong =
267
+ params.closeMakerLongAccount !== false && params.makerLongAccount != null;
268
+ if (shouldCloseMakerLong) {
269
+ instructions.push(
270
+ getCloseAccountInstruction(
271
+ params.makerLongAccount!,
272
+ params.maker,
273
+ params.maker
274
+ )
275
+ );
276
+ }
277
+
278
+ return { instructions };
279
+ }
280
+
281
+ export interface BuildOptionMintTransactionWithDerivationParams {
282
+ underlyingAsset: AddressLike;
283
+ optionType: OptionType;
284
+ strikePrice: number;
285
+ expirationDate: bigint | number;
286
+ quantity: bigint | number;
287
+ underlyingMint: AddressLike;
288
+ underlyingSymbol: string;
289
+ /**
290
+ * Collateral mint (e.g., USDC, BTC, SOL) - Writer's choice for backing the position.
291
+ * Can differ from underlying asset - enables multi-collateral settlement.
292
+ * OMLP vault routing is based on this mint. Defaults to underlyingMint if not provided.
293
+ */
294
+ collateralMint?: AddressLike;
295
+ makerCollateralAmount: bigint | number;
296
+ borrowedAmount: bigint | number;
297
+ maxRequiredCollateralAmount?: bigint | number;
298
+ maker: AddressLike;
299
+ /**
300
+ * Optional. When omitted, the SDK derives the maker's collateral ATA for collateralMint
301
+ * (or underlyingMint if collateralMint is not provided).
302
+ */
303
+ makerCollateralAccount?: AddressLike;
304
+ rpc: KitRpc;
305
+ rpcEndpoint?: string;
306
+ programId?: AddressLike;
307
+ vault?: AddressLike;
308
+ vaultTokenAccount?: AddressLike;
309
+ escrowState?: AddressLike;
310
+ escrowAuthority?: AddressLike;
311
+ escrowTokenAccount?: AddressLike;
312
+ poolLoan?: AddressLike;
313
+ remainingAccounts?: RemainingAccountInput[];
314
+ disableSwitchboardCrank?: boolean;
315
+ switchboardCrossbarUrl?: string;
316
+ switchboardNumSignatures?: number;
317
+ /**
318
+ * 0-based index of this Switchboard quote ix inside the **final** transaction after the
319
+ * wallet prepends compute-budget instructions (see `Queue.fetchQuoteIx` `instructionIdx`).
320
+ * Kit `sendInstructions` prepends **only** `SetComputeUnitLimit` before these ixs → default **1**.
321
+ * Use **0** if nothing is prepended; **2** if both limit and priority fee compute-budget ixs run first.
322
+ */
323
+ switchboardQuoteInstructionIndex?: number;
324
+ /**
325
+ * When true, omits the collateral ATA create-idempotent instruction from the mint transaction.
326
+ * Send that ix in a prior transaction (e.g. with leverage setup) so quote + option_mint stays
327
+ * under Solana's 1232-byte versioned-transaction limit when OMLP accounts are present.
328
+ */
329
+ skipCreateCollateralAta?: boolean;
330
+ }
331
+
332
+ export async function buildOptionMintTransactionWithDerivation(
333
+ params: BuildOptionMintTransactionWithDerivationParams
334
+ ): Promise<BuiltTransaction> {
335
+ const borrowedAmount = BigInt(params.borrowedAmount);
336
+ if (borrowedAmount > 0n) {
337
+ invariant(!!params.vault, "vault is required when borrowedAmount > 0");
338
+ invariant(
339
+ !!params.vaultTokenAccount,
340
+ "vaultTokenAccount is required when borrowedAmount > 0"
341
+ );
342
+ invariant(!!params.escrowState, "escrowState is required when borrowedAmount > 0");
343
+ invariant(
344
+ !!params.escrowAuthority,
345
+ "escrowAuthority is required when borrowedAmount > 0"
346
+ );
347
+ invariant(
348
+ !!params.escrowTokenAccount,
349
+ "escrowTokenAccount is required when borrowedAmount > 0"
350
+ );
351
+ invariant(!!params.poolLoan, "poolLoan is required when borrowedAmount > 0");
352
+ }
353
+
354
+ const resolved = await resolveOptionAccounts({
355
+ underlyingAsset: params.underlyingAsset,
356
+ optionType: params.optionType,
357
+ strikePrice: params.strikePrice,
358
+ expirationDate: params.expirationDate,
359
+ programId: params.programId,
360
+ rpc: params.rpc,
361
+ });
362
+
363
+ const underlyingMint = resolved.underlyingMint ?? params.underlyingMint;
364
+ const collateralMint = params.collateralMint ?? underlyingMint;
365
+ const existingCollateralMint = resolved.collateralPoolData?.collateralMint;
366
+ if (
367
+ existingCollateralMint &&
368
+ toAddress(existingCollateralMint) !== toAddress(collateralMint)
369
+ ) {
370
+ invariant(
371
+ false,
372
+ `Collateral pool mint mismatch. Option market is already collateralized with ${toAddress(
373
+ existingCollateralMint
374
+ )}, but request used ${toAddress(collateralMint)}.`
375
+ );
376
+ }
377
+
378
+ const collateralVault =
379
+ resolved.collateralVault ??
380
+ (await deriveAssociatedTokenAddress(resolved.collateralPool, collateralMint, true));
381
+
382
+ const [makerLongAccount, makerShortAccount] = await Promise.all([
383
+ deriveAssociatedTokenAddress(params.maker, resolved.longMint),
384
+ deriveAssociatedTokenAddress(params.maker, resolved.shortMint),
385
+ ]);
386
+ const makerCollateralAccount = params.makerCollateralAccount
387
+ ? toAddress(params.makerCollateralAccount)
388
+ : await deriveAssociatedTokenAddress(params.maker, collateralMint);
389
+ const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
390
+ invariant(
391
+ !!marketDataAccount,
392
+ "Market data account not found for resolved option market."
393
+ );
394
+ const switchboardFeedId = feedIdBytesToHex(
395
+ Uint8Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
396
+ );
397
+
398
+ const [writerPositionPdaForIx] = await deriveWriterPositionPda(
399
+ resolved.optionPool,
400
+ params.maker,
401
+ params.programId
402
+ );
403
+
404
+ // option_mint + borrow: `sync_collateral_pool_debt` requires
405
+ // snapshot.active_loan_count == writer.active_loan_count
406
+ // after the writer block (+1 for this borrow). The named `pool_loan` is the new loan from
407
+ // TX A. gPA can return more PoolLoans than `writer.active_loan_count` (on-chain desync
408
+ // or stale actives). We pass exactly `activeLoanCount` pre-ix prior loans, excluding the
409
+ // new `pool_loan`, matching `writer_position` (and preferring same vault) with canonical
410
+ // `toAddress` equality. Prefer *higher* `nonce` first so we do not pick a stale low-nonce
411
+ // account that the sync path skips (discriminator / borrow_mut), which yields under-count
412
+ // and 6102.
413
+ let priorActivePoolLoans: RemainingAccountInput[] = [];
414
+ if (borrowedAmount > 0n) {
415
+ const wp = toAddress(writerPositionPdaForIx);
416
+ const newLoan = toAddress(params.poolLoan!);
417
+ const vaultPda = toAddress(params.vault!);
418
+ const [writerPos, activeLoans] = await Promise.all([
419
+ fetchWriterPosition(params.rpc, writerPositionPdaForIx),
420
+ fetchPoolLoansByMaker(params.rpc, params.maker),
421
+ ]);
422
+ const priorCount = writerPos?.activeLoanCount ?? 0;
423
+ const forWriter = (l: (typeof activeLoans)[0]) =>
424
+ toAddress(l.data.writerPosition) === wp && toAddress(l.address) !== newLoan;
425
+ const sortByNonceDesc = (
426
+ a: (typeof activeLoans)[0],
427
+ b: (typeof activeLoans)[0]
428
+ ) => {
429
+ if (a.data.nonce > b.data.nonce) return -1;
430
+ if (a.data.nonce < b.data.nonce) return 1;
431
+ return String(a.address).localeCompare(String(b.address));
432
+ };
433
+ const withVault = activeLoans
434
+ .filter(
435
+ (l) => forWriter(l) && toAddress(l.data.vault) === vaultPda
436
+ )
437
+ .sort(sortByNonceDesc);
438
+ const withoutVault = activeLoans.filter(forWriter).sort(sortByNonceDesc);
439
+ const candidates =
440
+ withVault.length >= priorCount ? withVault : withoutVault;
441
+ priorActivePoolLoans = candidates
442
+ .slice(0, priorCount)
443
+ .map((l) => ({ address: l.address, isWritable: true }));
444
+ }
445
+
446
+ const tx = await buildOptionMintTransaction({
447
+ ...params,
448
+ underlyingAsset: params.underlyingAsset,
449
+ underlyingMint,
450
+ collateralMint,
451
+ makerCollateralAccount,
452
+ optionAccount: resolved.optionAccount,
453
+ longMint: resolved.longMint,
454
+ shortMint: resolved.shortMint,
455
+ mintAuthority: resolved.mintAuthority,
456
+ makerLongAccount,
457
+ makerShortAccount,
458
+ marketData: resolved.marketData,
459
+ optionPool: resolved.optionPool,
460
+ escrowLongAccount: resolved.escrowLongAccount,
461
+ premiumVault: resolved.premiumVault,
462
+ collateralPool: resolved.collateralPool,
463
+ collateralVault,
464
+ writerPosition: writerPositionPdaForIx,
465
+ vault: params.vault,
466
+ vaultTokenAccount: params.vaultTokenAccount,
467
+ escrowState: params.escrowState,
468
+ escrowAuthority: params.escrowAuthority,
469
+ escrowTokenAccount: params.escrowTokenAccount,
470
+ poolLoan: params.poolLoan,
471
+ switchboardQueue: SWITCHBOARD_DEFAULT_DEVNET_QUEUE,
472
+ remainingAccounts: [...priorActivePoolLoans, ...(params.remainingAccounts ?? [])],
473
+ });
474
+
475
+ const createAtaIx =
476
+ params.skipCreateCollateralAta === true
477
+ ? null
478
+ : await getCreateAssociatedTokenIdempotentInstructionWithAddress(
479
+ params.maker,
480
+ params.maker,
481
+ collateralMint,
482
+ makerCollateralAccount
483
+ );
484
+
485
+ const actionTx = {
486
+ instructions:
487
+ createAtaIx !== null ? [createAtaIx, ...tx.instructions] : [...tx.instructions],
488
+ };
489
+
490
+ if (params.disableSwitchboardCrank) {
491
+ return actionTx;
492
+ }
493
+
494
+ invariant(
495
+ !!params.rpcEndpoint,
496
+ "rpcEndpoint is required to fetch Switchboard quote instructions."
497
+ );
498
+ const quoteNetwork = await inferSwitchboardNetwork(params.rpc);
499
+ const quote = await buildSwitchboardQuoteInstruction({
500
+ rpcEndpoint: params.rpcEndpoint,
501
+ feedIdHex: switchboardFeedId,
502
+ network: quoteNetwork,
503
+ crossbarUrl: params.switchboardCrossbarUrl,
504
+ numSignatures: params.switchboardNumSignatures,
505
+ instructionIdx: params.switchboardQuoteInstructionIndex ?? 1,
506
+ });
507
+
508
+ return {
509
+ instructions: [quote.instruction, ...actionTx.instructions],
510
+ };
511
+ }
512
+
513
+ export async function buildUnwindWriterUnsoldInstruction(
514
+ params: BuildUnwindWriterUnsoldParams
515
+ ): Promise<Instruction<string>> {
516
+ assertPositiveAmount(params.unwindQty, "unwindQty");
517
+
518
+ const kitInstruction = await getUnwindWriterUnsoldInstructionAsync(
519
+ {
520
+ optionPool: toAddress(params.optionPool),
521
+ optionAccount: toAddress(params.optionAccount),
522
+ marketData: toAddress(params.marketData),
523
+ collateralPool: params.collateralPool ? toAddress(params.collateralPool) : undefined,
524
+ writerPosition: params.writerPosition ? toAddress(params.writerPosition) : undefined,
525
+ longMint: toAddress(params.longMint),
526
+ shortMint: toAddress(params.shortMint),
527
+ escrowLongAccount: toAddress(params.escrowLongAccount),
528
+ writerShortAccount: toAddress(params.writerShortAccount),
529
+ collateralVault: toAddress(params.collateralVault),
530
+ premiumVault: toAddress(params.premiumVault),
531
+ writerCollateralAccount: toAddress(params.writerCollateralAccount),
532
+ omlpVaultState: params.omlpVaultState ? toAddress(params.omlpVaultState) : undefined,
533
+ omlpVault: params.omlpVault ? toAddress(params.omlpVault) : undefined,
534
+ feeWallet: params.feeWallet ? toAddress(params.feeWallet) : undefined,
535
+ writer: toAddress(params.writer) as any,
536
+ unwindQty: params.unwindQty,
537
+ } as any
538
+ );
539
+
540
+ return appendRemainingAccounts(kitInstruction, params.remainingAccounts);
541
+ }
542
+
543
+ export async function buildUnwindWriterUnsoldTransaction(
544
+ params: BuildUnwindWriterUnsoldParams
545
+ ): Promise<BuiltTransaction> {
546
+ const instruction = await buildUnwindWriterUnsoldInstruction(params);
547
+ return { instructions: [instruction] };
548
+ }
549
+
550
+ export interface BuildUnwindWriterUnsoldTransactionWithDerivationParams {
551
+ underlyingAsset: AddressLike;
552
+ optionType: OptionType;
553
+ strikePrice: number;
554
+ expirationDate: bigint | number;
555
+ writer: AddressLike;
556
+ unwindQty: bigint | number;
557
+ rpc: KitRpc;
558
+ programId?: AddressLike;
559
+ omlpVaultState?: AddressLike;
560
+ omlpVault?: AddressLike;
561
+ feeWallet?: AddressLike;
562
+ /**
563
+ * When repaying pool loans: [PoolLoan₁, PoolLoan₂, ...] (all writable).
564
+ * omlpVaultState, omlpVault, feeWallet must also be passed.
565
+ * Prefer {@link buildUnwindWriterUnsoldWithLoanRepayment} to build this automatically.
566
+ */
567
+ remainingAccounts?: RemainingAccountInput[];
568
+ }
569
+
570
+ export interface BuildUnwindWriterUnsoldWithLoanRepaymentParams {
571
+ underlyingAsset: AddressLike;
572
+ optionType: OptionType;
573
+ strikePrice: number;
574
+ expirationDate: bigint | number;
575
+ writer: AddressLike;
576
+ unwindQty: bigint | number;
577
+ rpc: KitRpc;
578
+ programId?: AddressLike;
579
+ /** Override when pool fetch is not used; otherwise resolved from option pool. */
580
+ underlyingMint?: AddressLike;
581
+ }
582
+
583
+ /**
584
+ * Builds an unwind_writer_unsold transaction that also repays any active pool loans
585
+ * for the option's underlying vault. When a writer unwinds an unsold short that had
586
+ * borrowed from OMLP, the program repays lenders from the collateral vault (burn,
587
+ * repay, then return collateral to writer) in one instruction.
588
+ *
589
+ * Passes omlpVaultState (Vault PDA), omlpVault, feeWallet as named accounts.
590
+ * remaining_accounts: [PoolLoan₁, PoolLoan₂, ...] only (capped at 20).
591
+ * If no active pool loans exist, still passes vault accounts so the API works for all unwinds.
592
+ */
593
+ export async function buildUnwindWriterUnsoldWithLoanRepayment(
594
+ params: BuildUnwindWriterUnsoldWithLoanRepaymentParams
595
+ ): Promise<BuiltTransaction> {
596
+ const resolved = await resolveOptionAccounts({
597
+ underlyingAsset: params.underlyingAsset,
598
+ optionType: params.optionType,
599
+ strikePrice: params.strikePrice,
600
+ expirationDate: params.expirationDate,
601
+ programId: params.programId,
602
+ rpc: params.rpc,
603
+ });
604
+
605
+ const underlyingMint = params.underlyingMint ?? resolved.underlyingMint;
606
+ invariant(
607
+ !!underlyingMint,
608
+ "underlyingMint is required; ensure rpc is provided and option pool is initialized, or pass underlyingMint."
609
+ );
610
+
611
+ const [vaultPda] = await deriveVaultPda(underlyingMint, params.programId);
612
+ const vaultPdaStr = toAddress(vaultPda);
613
+
614
+ const [writerPositionAddress] = await deriveWriterPositionPda(
615
+ resolved.optionPool,
616
+ params.writer,
617
+ params.programId
618
+ );
619
+
620
+ const [loans, vault, writerPosition] = await Promise.all([
621
+ fetchPoolLoansByMaker(params.rpc, params.writer),
622
+ fetchVault(params.rpc, vaultPda),
623
+ fetchWriterPosition(params.rpc, writerPositionAddress),
624
+ ]);
625
+
626
+ invariant(!!writerPosition, "Writer position not found for unwind.");
627
+
628
+ const wpAddr = toAddress(writerPositionAddress);
629
+ const vaultLoansAll = loans.filter(
630
+ (item) => toAddress(item.data.vault) === vaultPdaStr && Number(item.data.status) === 1
631
+ );
632
+ const matchingLoans = writerPositionHasOngoingPoolLoanDebt(writerPosition)
633
+ ? vaultLoansAll.filter((item) => toAddress(item.data.writerPosition) === wpAddr)
634
+ : [];
635
+
636
+ // Mirror preflight: pick exactly `activeLoanCount` loans (top-N by nonce DESC) and verify
637
+ // their principal sum matches `writer.borrowed_principal`. Orphan status==1 PoolLoans
638
+ // not tracked by the writer would otherwise inflate `snapshot.active_loan_count` and
639
+ // trip the on-chain MaintenanceLoansIncomplete invariant.
640
+ const sortByNonceDesc = (
641
+ a: (typeof matchingLoans)[number],
642
+ b: (typeof matchingLoans)[number]
643
+ ) => {
644
+ if (a.data.nonce > b.data.nonce) return -1;
645
+ if (a.data.nonce < b.data.nonce) return 1;
646
+ return String(a.address).localeCompare(String(b.address));
647
+ };
648
+ const vaultLoans = writerPositionHasOngoingPoolLoanDebt(writerPosition)
649
+ ? matchingLoans.slice().sort(sortByNonceDesc).slice(0, writerPosition.activeLoanCount)
650
+ : [];
651
+
652
+ if (writerPositionHasOngoingPoolLoanDebt(writerPosition)) {
653
+ invariant(
654
+ vaultLoans.length === writerPosition.activeLoanCount,
655
+ `Expected ${writerPosition.activeLoanCount} active pool loan(s) for this writer position; only found ${matchingLoans.length} matching. Refresh positions.`
656
+ );
657
+ const selectedPrincipalSum = vaultLoans.reduce(
658
+ (sum, item) => sum + BigInt(item.data.principal),
659
+ 0n
660
+ );
661
+ const writerBorrowedPrincipal = BigInt(writerPosition.borrowedPrincipal);
662
+ invariant(
663
+ selectedPrincipalSum === writerBorrowedPrincipal,
664
+ `Active pool loan principal mismatch: top-${writerPosition.activeLoanCount} loans sum to ${selectedPrincipalSum} but writer.borrowed_principal=${writerBorrowedPrincipal}. On-chain state is inconsistent (orphan PoolLoans). Use the keeper rescue path.`
665
+ );
666
+ }
667
+
668
+ invariant(
669
+ vaultLoans.length <= MAX_POOL_LOANS_PER_UNWIND,
670
+ `Too many active pool loans for unwind: ${vaultLoans.length}. Max supported in one unwind is ${MAX_POOL_LOANS_PER_UNWIND}.`
671
+ );
672
+
673
+ const remainingAccounts: RemainingAccountInput[] = vaultLoans.map((item) => ({
674
+ address: item.address,
675
+ isWritable: true,
676
+ }));
677
+
678
+ const omlpVault = await deriveAssociatedTokenAddress(vaultPda, underlyingMint);
679
+ const feeWallet = vault
680
+ ? await deriveAssociatedTokenAddress(vault.feeWallet, underlyingMint)
681
+ : undefined;
682
+
683
+ // Theta-hedge model: if proportional debt cannot be covered by
684
+ // `theta_available + proportional_collateral`, the on-chain path reverts
685
+ // with `LiquidationInsufficientCollateralForDebt`. Writer-wallet fallback
686
+ // has been removed; the keeper-gated rescue ix handles insolvent
687
+ // positions instead.
688
+ const preflight = await preflightUnwindWriterUnsold({
689
+ underlyingAsset: params.underlyingAsset,
690
+ optionType: params.optionType,
691
+ strikePrice: params.strikePrice,
692
+ expirationDate: params.expirationDate,
693
+ writer: params.writer,
694
+ unwindQty: params.unwindQty,
695
+ rpc: params.rpc,
696
+ programId: params.programId,
697
+ underlyingMint,
698
+ });
699
+ invariant(
700
+ preflight.canRepayRequestedSlice,
701
+ `Unwind cannot repay the requested slice from collateral vault + theta. required=${preflight.summary.proportionalTotalOwed} collateral_available=${preflight.summary.collateralVaultAvailable}. Use liquidate_writer_position_rescue if the position is insolvent.`
702
+ );
703
+
704
+ return await buildUnwindWriterUnsoldTransactionWithDerivation({
705
+ underlyingAsset: params.underlyingAsset,
706
+ optionType: params.optionType,
707
+ strikePrice: params.strikePrice,
708
+ expirationDate: params.expirationDate,
709
+ writer: params.writer,
710
+ unwindQty: preflight.effectiveUnwindQty,
711
+ rpc: params.rpc,
712
+ programId: params.programId,
713
+ omlpVaultState: vaultPda,
714
+ omlpVault,
715
+ feeWallet,
716
+ remainingAccounts,
717
+ });
718
+ }
719
+
720
+ export async function buildUnwindWriterUnsoldTransactionWithDerivation(
721
+ params: BuildUnwindWriterUnsoldTransactionWithDerivationParams
722
+ ): Promise<BuiltTransaction> {
723
+ const resolved = await resolveOptionAccounts({
724
+ underlyingAsset: params.underlyingAsset,
725
+ optionType: params.optionType,
726
+ strikePrice: params.strikePrice,
727
+ expirationDate: params.expirationDate,
728
+ programId: params.programId,
729
+ rpc: params.rpc,
730
+ });
731
+
732
+ invariant(
733
+ !!resolved.escrowLongAccount &&
734
+ !!resolved.premiumVault &&
735
+ !!resolved.collateralVault &&
736
+ !!resolved.underlyingMint,
737
+ "Option pool and collateral pool must exist; ensure rpc is provided and pools are initialized."
738
+ );
739
+
740
+ const [writerShortAccount, writerCollateralAccount, writerPosition] =
741
+ await Promise.all([
742
+ deriveAssociatedTokenAddress(params.writer, resolved.shortMint),
743
+ deriveAssociatedTokenAddress(params.writer, resolved.underlyingMint),
744
+ deriveWriterPositionPda(resolved.optionPool, params.writer, params.programId),
745
+ ]);
746
+
747
+ return buildUnwindWriterUnsoldTransaction({
748
+ optionPool: resolved.optionPool,
749
+ optionAccount: resolved.optionAccount,
750
+ marketData: resolved.marketData,
751
+ longMint: resolved.longMint,
752
+ shortMint: resolved.shortMint,
753
+ escrowLongAccount: resolved.escrowLongAccount!,
754
+ writerShortAccount,
755
+ collateralVault: resolved.collateralVault!,
756
+ premiumVault: resolved.premiumVault!,
757
+ writerCollateralAccount,
758
+ writer: params.writer,
759
+ unwindQty: params.unwindQty,
760
+ collateralPool: resolved.collateralPool,
761
+ writerPosition: writerPosition[0],
762
+ omlpVaultState: params.omlpVaultState,
763
+ omlpVault: params.omlpVault,
764
+ feeWallet: params.feeWallet,
765
+ remainingAccounts: params.remainingAccounts,
766
+ });
767
+ }
768
+
769
+ export function buildSyncWriterPositionInstruction(
770
+ params: BuildSyncWriterPositionParams
771
+ ): Instruction<string> {
772
+ const kitInstruction = getSyncWriterPositionInstruction({
773
+ optionPool: toAddress(params.optionPool),
774
+ optionAccount: toAddress(params.optionAccount),
775
+ writerPosition: toAddress(params.writerPosition),
776
+ });
777
+ return kitInstruction;
778
+ }
779
+
780
+ export function buildSyncWriterPositionTransaction(
781
+ params: BuildSyncWriterPositionParams
782
+ ): BuiltTransaction {
783
+ const instruction = buildSyncWriterPositionInstruction(params);
784
+ return { instructions: [instruction] };
785
+ }
786
+
787
+ export async function buildSettleMakerCollateralInstruction(
788
+ params: BuildSettleMakerCollateralParams
789
+ ): Promise<Instruction<string>> {
790
+ const makerCollateralShare =
791
+ params.makerCollateralShare ??
792
+ (params.collateralPool
793
+ ? (await deriveMakerCollateralSharePda(params.collateralPool, params.maker))[0]
794
+ : undefined);
795
+
796
+ invariant(
797
+ !!makerCollateralShare,
798
+ "makerCollateralShare is required (or provide collateralPool + maker to derive)."
799
+ );
800
+
801
+ const kitInstruction = await getSettleMakerCollateralInstructionAsync({
802
+ optionAccount: toAddress(params.optionAccount),
803
+ collateralPool: params.collateralPool ? toAddress(params.collateralPool) : undefined,
804
+ makerCollateralShare: toAddress(makerCollateralShare),
805
+ collateralVault: toAddress(params.collateralVault),
806
+ makerCollateralAccount: toAddress(params.makerCollateralAccount),
807
+ omlpVault: toAddress(params.omlpVault),
808
+ omlpVaultState: toAddress(params.omlpVaultState),
809
+ poolLoan: toAddress(params.poolLoan),
810
+ });
811
+ return kitInstruction;
812
+ }
813
+
814
+ export async function buildSettleMakerCollateralTransaction(
815
+ params: BuildSettleMakerCollateralParams
816
+ ): Promise<BuiltTransaction> {
817
+ const instruction = await buildSettleMakerCollateralInstruction(params);
818
+ return { instructions: [instruction] };
819
+ }
820
+
821
+ /**
822
+ * Parameters for the permissioned rescue liquidation path. Gated on-chain
823
+ * to `Vault::keeper`; the SDK does not re-verify keeper identity, but the
824
+ * transaction will revert with `RescueUnauthorized` if the signer's pubkey
825
+ * does not match the vault's keeper.
826
+ */
827
+ export interface BuildLiquidateWriterPositionRescueParams {
828
+ optionPool: AddressLike;
829
+ optionAccount: AddressLike;
830
+ /** Market data PDA (`["market_data", underlying_asset]`). */
831
+ marketData: AddressLike;
832
+ collateralPool: AddressLike;
833
+ writerPosition: AddressLike;
834
+ longMint: AddressLike;
835
+ escrowLongAccount: AddressLike;
836
+ /** OMLP Vault state PDA (mut: decrements total_loans, writes bad_debt_lamports). */
837
+ omlpVault: AddressLike;
838
+ collateralVault: AddressLike;
839
+ premiumVault: AddressLike;
840
+ omlpVaultTokenAccount: AddressLike;
841
+ feeWallet: AddressLike;
842
+ /** Vault keeper — must sign and match `Vault::keeper`. */
843
+ keeper: TransactionSigner<string>;
844
+ /** Every active PoolLoan for this writer/vault (SDK: loadWriterLoans). */
845
+ remainingAccounts?: RemainingAccountInput[];
846
+ }
847
+
848
+ export async function buildLiquidateWriterPositionRescueInstruction(
849
+ params: BuildLiquidateWriterPositionRescueParams
850
+ ): Promise<Instruction<string>> {
851
+ const kitInstruction = await getLiquidateWriterPositionRescueInstructionAsync({
852
+ optionPool: toAddress(params.optionPool),
853
+ optionAccount: toAddress(params.optionAccount),
854
+ marketData: toAddress(params.marketData),
855
+ collateralPool: toAddress(params.collateralPool),
856
+ writerPosition: toAddress(params.writerPosition),
857
+ longMint: toAddress(params.longMint),
858
+ escrowLongAccount: toAddress(params.escrowLongAccount),
859
+ omlpVault: toAddress(params.omlpVault),
860
+ collateralVault: toAddress(params.collateralVault),
861
+ premiumVault: toAddress(params.premiumVault),
862
+ omlpVaultTokenAccount: toAddress(params.omlpVaultTokenAccount),
863
+ feeWallet: toAddress(params.feeWallet),
864
+ keeper: params.keeper as any,
865
+ });
866
+ return appendRemainingAccounts(kitInstruction, params.remainingAccounts);
867
+ }
868
+
869
+ export async function buildLiquidateWriterPositionRescueTransaction(
870
+ params: BuildLiquidateWriterPositionRescueParams
871
+ ): Promise<BuiltTransaction> {
872
+ const instruction = await buildLiquidateWriterPositionRescueInstruction(params);
873
+ return { instructions: [instruction] };
874
+ }