@epicentral/sos-sdk 0.3.0-alpha.1 → 0.3.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,6 +64,7 @@ Additional modules:
64
64
  | `buildClaimThetaTransaction` | Claims theta (time-decay share) for writer. |
65
65
  | `buildRepayPoolLoanFromCollateralInstruction` | Repays pool loan from collateral (short/pool). |
66
66
  | `buildRepayPoolLoanInstruction` | Repays pool loan with external funds (short/pool). |
67
+ | `buildRepayPoolLoanFromWalletInstruction` | Repays pool loan from maker's wallet (stuck loan recovery). |
67
68
 
68
69
  ### OMLP (Lending)
69
70
 
@@ -87,18 +88,20 @@ Borrow/repay for writers: use `buildOptionMintTransactionWithDerivation` (with v
87
88
 
88
89
  ## Unwind with Loan Repayment
89
90
 
90
- When a writer unwinds an unsold short that had borrowed from the OMLP pool, the borrowed amount must be repaid to the pool, not sent to the writer.
91
+ When a writer unwinds an unsold short that had borrowed from the OMLP pool, the program repays lenders from the collateral vault inside `unwind_writer_unsold` (burn LONG+SHORT, repay loans, then return collateral to writer) in one instruction.
91
92
 
92
93
  Use **`buildUnwindWriterUnsoldWithLoanRepayment`** so that:
93
94
 
94
95
  1. Active pool loans for the option’s underlying vault are fetched.
95
- 2. `remaining_accounts` are built in the correct order: **[Vault PDA, PoolLoan₁, PoolLoan₂, ...]** (all writable).
96
- 3. OMLP vault token account and fee wallet are resolved.
97
- 4. One transaction both unwinds and repays the pool loan(s).
96
+ 2. `omlpVaultState` (Vault PDA), `omlpVault`, and `feeWallet` are passed as named accounts.
97
+ 3. `remaining_accounts` = **[PoolLoan₁, PoolLoan₂, ...]** only (capped at 20 loans per tx).
98
+ 4. One transaction burns, repays lenders from collateral vault, and returns collateral to the writer.
98
99
 
99
100
  If there are no active pool loans for that vault, the API still works and passes empty `remaining_accounts`.
100
101
 
101
- **Alternative (repay then unwind):** For flexibility, you can (1) build `repay_pool_loan_from_collateral` instructions via `buildRepayPoolLoanFromCollateralInstruction`, then (2) build `unwind_writer_unsold` without remaining_accounts.
102
+ **Alternative (repay then unwind):** For writers with more than ~20 active loans, (1) build `repay_pool_loan_from_collateral` instructions first to reduce loans, then (2) unwind with the remaining loans.
103
+
104
+ **Stuck loan (InsufficientEscrowBalance):** When standard repay fails with `InsufficientEscrowBalance` (escrow underfunded or drained), use `buildRepayPoolLoanFromWalletInstruction` or `buildRepayPoolLoanFromWalletTransaction`. Same accounts as `buildRepayPoolLoanInstruction`; maker pays full principal + interest + fees from their wallet.
102
105
 
103
106
  ## Usage Examples
104
107
 
@@ -31,6 +31,7 @@ export * from "./optionMint";
31
31
  export * from "./optionValidate";
32
32
  export * from "./repayPoolLoan";
33
33
  export * from "./repayPoolLoanFromCollateral";
34
+ export * from "./repayPoolLoanFromWallet";
34
35
  export * from "./settleMakerCollateral";
35
36
  export * from "./syncWriterPosition";
36
37
  export * from "./transferAdmin";
@@ -0,0 +1,483 @@
1
+ /**
2
+ * This code was AUTOGENERATED using the Codama library.
3
+ * Please DO NOT EDIT THIS FILE, instead use visitors
4
+ * to add features, then rerun Codama to update it.
5
+ *
6
+ * @see https://github.com/codama-idl/codama
7
+ */
8
+
9
+ import {
10
+ combineCodec,
11
+ fixDecoderSize,
12
+ fixEncoderSize,
13
+ getAddressEncoder,
14
+ getBytesDecoder,
15
+ getBytesEncoder,
16
+ getProgramDerivedAddress,
17
+ getStructDecoder,
18
+ getStructEncoder,
19
+ transformEncoder,
20
+ type AccountMeta,
21
+ type AccountSignerMeta,
22
+ type Address,
23
+ type FixedSizeCodec,
24
+ type FixedSizeDecoder,
25
+ type FixedSizeEncoder,
26
+ type Instruction,
27
+ type InstructionWithAccounts,
28
+ type InstructionWithData,
29
+ type ReadonlyAccount,
30
+ type ReadonlySignerAccount,
31
+ type ReadonlyUint8Array,
32
+ type TransactionSigner,
33
+ type WritableAccount,
34
+ } from "@solana/kit";
35
+ import { OPTION_PROGRAM_PROGRAM_ADDRESS } from "../programs";
36
+ import {
37
+ expectAddress,
38
+ getAccountMetaFactory,
39
+ type ResolvedAccount,
40
+ } from "../shared";
41
+
42
+ export const REPAY_POOL_LOAN_FROM_WALLET_DISCRIMINATOR = new Uint8Array([
43
+ 78, 130, 135, 90, 211, 21, 247, 247,
44
+ ]);
45
+
46
+ export function getRepayPoolLoanFromWalletDiscriminatorBytes() {
47
+ return fixEncoderSize(getBytesEncoder(), 8).encode(
48
+ REPAY_POOL_LOAN_FROM_WALLET_DISCRIMINATOR,
49
+ );
50
+ }
51
+
52
+ export type RepayPoolLoanFromWalletInstruction<
53
+ TProgram extends string = typeof OPTION_PROGRAM_PROGRAM_ADDRESS,
54
+ TAccountPoolLoan extends string | AccountMeta<string> = string,
55
+ TAccountVault extends string | AccountMeta<string> = string,
56
+ TAccountVaultTokenAccount extends string | AccountMeta<string> = string,
57
+ TAccountEscrowState extends string | AccountMeta<string> = string,
58
+ TAccountEscrowAuthority extends string | AccountMeta<string> = string,
59
+ TAccountEscrowTokenAccount extends string | AccountMeta<string> = string,
60
+ TAccountMakerTokenAccount extends string | AccountMeta<string> = string,
61
+ TAccountFeeWalletTokenAccount extends string | AccountMeta<string> = string,
62
+ TAccountMaker extends string | AccountMeta<string> = string,
63
+ TAccountTokenProgram extends string | AccountMeta<string> =
64
+ "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
65
+ TRemainingAccounts extends readonly AccountMeta<string>[] = [],
66
+ > = Instruction<TProgram> &
67
+ InstructionWithData<ReadonlyUint8Array> &
68
+ InstructionWithAccounts<
69
+ [
70
+ TAccountPoolLoan extends string
71
+ ? WritableAccount<TAccountPoolLoan>
72
+ : TAccountPoolLoan,
73
+ TAccountVault extends string
74
+ ? WritableAccount<TAccountVault>
75
+ : TAccountVault,
76
+ TAccountVaultTokenAccount extends string
77
+ ? WritableAccount<TAccountVaultTokenAccount>
78
+ : TAccountVaultTokenAccount,
79
+ TAccountEscrowState extends string
80
+ ? ReadonlyAccount<TAccountEscrowState>
81
+ : TAccountEscrowState,
82
+ TAccountEscrowAuthority extends string
83
+ ? ReadonlyAccount<TAccountEscrowAuthority>
84
+ : TAccountEscrowAuthority,
85
+ TAccountEscrowTokenAccount extends string
86
+ ? WritableAccount<TAccountEscrowTokenAccount>
87
+ : TAccountEscrowTokenAccount,
88
+ TAccountMakerTokenAccount extends string
89
+ ? WritableAccount<TAccountMakerTokenAccount>
90
+ : TAccountMakerTokenAccount,
91
+ TAccountFeeWalletTokenAccount extends string
92
+ ? WritableAccount<TAccountFeeWalletTokenAccount>
93
+ : TAccountFeeWalletTokenAccount,
94
+ TAccountMaker extends string
95
+ ? ReadonlySignerAccount<TAccountMaker> &
96
+ AccountSignerMeta<TAccountMaker>
97
+ : TAccountMaker,
98
+ TAccountTokenProgram extends string
99
+ ? ReadonlyAccount<TAccountTokenProgram>
100
+ : TAccountTokenProgram,
101
+ ...TRemainingAccounts,
102
+ ]
103
+ >;
104
+
105
+ export type RepayPoolLoanFromWalletInstructionData = {
106
+ discriminator: ReadonlyUint8Array;
107
+ };
108
+
109
+ export type RepayPoolLoanFromWalletInstructionDataArgs = {};
110
+
111
+ export function getRepayPoolLoanFromWalletInstructionDataEncoder(): FixedSizeEncoder<RepayPoolLoanFromWalletInstructionDataArgs> {
112
+ return transformEncoder(
113
+ getStructEncoder([["discriminator", fixEncoderSize(getBytesEncoder(), 8)]]),
114
+ (value) => ({
115
+ ...value,
116
+ discriminator: REPAY_POOL_LOAN_FROM_WALLET_DISCRIMINATOR,
117
+ }),
118
+ );
119
+ }
120
+
121
+ export function getRepayPoolLoanFromWalletInstructionDataDecoder(): FixedSizeDecoder<RepayPoolLoanFromWalletInstructionData> {
122
+ return getStructDecoder([
123
+ ["discriminator", fixDecoderSize(getBytesDecoder(), 8)],
124
+ ]);
125
+ }
126
+
127
+ export function getRepayPoolLoanFromWalletInstructionDataCodec(): FixedSizeCodec<
128
+ RepayPoolLoanFromWalletInstructionDataArgs,
129
+ RepayPoolLoanFromWalletInstructionData
130
+ > {
131
+ return combineCodec(
132
+ getRepayPoolLoanFromWalletInstructionDataEncoder(),
133
+ getRepayPoolLoanFromWalletInstructionDataDecoder(),
134
+ );
135
+ }
136
+
137
+ export type RepayPoolLoanFromWalletAsyncInput<
138
+ TAccountPoolLoan extends string = string,
139
+ TAccountVault extends string = string,
140
+ TAccountVaultTokenAccount extends string = string,
141
+ TAccountEscrowState extends string = string,
142
+ TAccountEscrowAuthority extends string = string,
143
+ TAccountEscrowTokenAccount extends string = string,
144
+ TAccountMakerTokenAccount extends string = string,
145
+ TAccountFeeWalletTokenAccount extends string = string,
146
+ TAccountMaker extends string = string,
147
+ TAccountTokenProgram extends string = string,
148
+ > = {
149
+ poolLoan: Address<TAccountPoolLoan>;
150
+ vault: Address<TAccountVault>;
151
+ vaultTokenAccount: Address<TAccountVaultTokenAccount>;
152
+ escrowState: Address<TAccountEscrowState>;
153
+ escrowAuthority?: Address<TAccountEscrowAuthority>;
154
+ escrowTokenAccount: Address<TAccountEscrowTokenAccount>;
155
+ makerTokenAccount: Address<TAccountMakerTokenAccount>;
156
+ feeWalletTokenAccount: Address<TAccountFeeWalletTokenAccount>;
157
+ maker: TransactionSigner<TAccountMaker>;
158
+ tokenProgram?: Address<TAccountTokenProgram>;
159
+ };
160
+
161
+ export async function getRepayPoolLoanFromWalletInstructionAsync<
162
+ TAccountPoolLoan extends string,
163
+ TAccountVault extends string,
164
+ TAccountVaultTokenAccount extends string,
165
+ TAccountEscrowState extends string,
166
+ TAccountEscrowAuthority extends string,
167
+ TAccountEscrowTokenAccount extends string,
168
+ TAccountMakerTokenAccount extends string,
169
+ TAccountFeeWalletTokenAccount extends string,
170
+ TAccountMaker extends string,
171
+ TAccountTokenProgram extends string,
172
+ TProgramAddress extends Address = typeof OPTION_PROGRAM_PROGRAM_ADDRESS,
173
+ >(
174
+ input: RepayPoolLoanFromWalletAsyncInput<
175
+ TAccountPoolLoan,
176
+ TAccountVault,
177
+ TAccountVaultTokenAccount,
178
+ TAccountEscrowState,
179
+ TAccountEscrowAuthority,
180
+ TAccountEscrowTokenAccount,
181
+ TAccountMakerTokenAccount,
182
+ TAccountFeeWalletTokenAccount,
183
+ TAccountMaker,
184
+ TAccountTokenProgram
185
+ >,
186
+ config?: { programAddress?: TProgramAddress },
187
+ ): Promise<
188
+ RepayPoolLoanFromWalletInstruction<
189
+ TProgramAddress,
190
+ TAccountPoolLoan,
191
+ TAccountVault,
192
+ TAccountVaultTokenAccount,
193
+ TAccountEscrowState,
194
+ TAccountEscrowAuthority,
195
+ TAccountEscrowTokenAccount,
196
+ TAccountMakerTokenAccount,
197
+ TAccountFeeWalletTokenAccount,
198
+ TAccountMaker,
199
+ TAccountTokenProgram
200
+ >
201
+ > {
202
+ // Program address.
203
+ const programAddress =
204
+ config?.programAddress ?? OPTION_PROGRAM_PROGRAM_ADDRESS;
205
+
206
+ // Original accounts.
207
+ const originalAccounts = {
208
+ poolLoan: { value: input.poolLoan ?? null, isWritable: true },
209
+ vault: { value: input.vault ?? null, isWritable: true },
210
+ vaultTokenAccount: {
211
+ value: input.vaultTokenAccount ?? null,
212
+ isWritable: true,
213
+ },
214
+ escrowState: { value: input.escrowState ?? null, isWritable: false },
215
+ escrowAuthority: {
216
+ value: input.escrowAuthority ?? null,
217
+ isWritable: false,
218
+ },
219
+ escrowTokenAccount: {
220
+ value: input.escrowTokenAccount ?? null,
221
+ isWritable: true,
222
+ },
223
+ makerTokenAccount: {
224
+ value: input.makerTokenAccount ?? null,
225
+ isWritable: true,
226
+ },
227
+ feeWalletTokenAccount: {
228
+ value: input.feeWalletTokenAccount ?? null,
229
+ isWritable: true,
230
+ },
231
+ maker: { value: input.maker ?? null, isWritable: false },
232
+ tokenProgram: { value: input.tokenProgram ?? null, isWritable: false },
233
+ };
234
+ const accounts = originalAccounts as Record<
235
+ keyof typeof originalAccounts,
236
+ ResolvedAccount
237
+ >;
238
+
239
+ // Resolve default values.
240
+ if (!accounts.escrowAuthority.value) {
241
+ accounts.escrowAuthority.value = await getProgramDerivedAddress({
242
+ programAddress,
243
+ seeds: [
244
+ getBytesEncoder().encode(
245
+ new Uint8Array([
246
+ 101, 115, 99, 114, 111, 119, 95, 97, 117, 116, 104, 111, 114, 105,
247
+ 116, 121, 95, 118, 50,
248
+ ]),
249
+ ),
250
+ getAddressEncoder().encode(expectAddress(accounts.escrowState.value)),
251
+ ],
252
+ });
253
+ }
254
+ if (!accounts.tokenProgram.value) {
255
+ accounts.tokenProgram.value =
256
+ "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" as Address<"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA">;
257
+ }
258
+
259
+ const getAccountMeta = getAccountMetaFactory(programAddress, "programId");
260
+ return Object.freeze({
261
+ accounts: [
262
+ getAccountMeta(accounts.poolLoan),
263
+ getAccountMeta(accounts.vault),
264
+ getAccountMeta(accounts.vaultTokenAccount),
265
+ getAccountMeta(accounts.escrowState),
266
+ getAccountMeta(accounts.escrowAuthority),
267
+ getAccountMeta(accounts.escrowTokenAccount),
268
+ getAccountMeta(accounts.makerTokenAccount),
269
+ getAccountMeta(accounts.feeWalletTokenAccount),
270
+ getAccountMeta(accounts.maker),
271
+ getAccountMeta(accounts.tokenProgram),
272
+ ],
273
+ data: getRepayPoolLoanFromWalletInstructionDataEncoder().encode({}),
274
+ programAddress,
275
+ } as RepayPoolLoanFromWalletInstruction<
276
+ TProgramAddress,
277
+ TAccountPoolLoan,
278
+ TAccountVault,
279
+ TAccountVaultTokenAccount,
280
+ TAccountEscrowState,
281
+ TAccountEscrowAuthority,
282
+ TAccountEscrowTokenAccount,
283
+ TAccountMakerTokenAccount,
284
+ TAccountFeeWalletTokenAccount,
285
+ TAccountMaker,
286
+ TAccountTokenProgram
287
+ >);
288
+ }
289
+
290
+ export type RepayPoolLoanFromWalletInput<
291
+ TAccountPoolLoan extends string = string,
292
+ TAccountVault extends string = string,
293
+ TAccountVaultTokenAccount extends string = string,
294
+ TAccountEscrowState extends string = string,
295
+ TAccountEscrowAuthority extends string = string,
296
+ TAccountEscrowTokenAccount extends string = string,
297
+ TAccountMakerTokenAccount extends string = string,
298
+ TAccountFeeWalletTokenAccount extends string = string,
299
+ TAccountMaker extends string = string,
300
+ TAccountTokenProgram extends string = string,
301
+ > = {
302
+ poolLoan: Address<TAccountPoolLoan>;
303
+ vault: Address<TAccountVault>;
304
+ vaultTokenAccount: Address<TAccountVaultTokenAccount>;
305
+ escrowState: Address<TAccountEscrowState>;
306
+ escrowAuthority: Address<TAccountEscrowAuthority>;
307
+ escrowTokenAccount: Address<TAccountEscrowTokenAccount>;
308
+ makerTokenAccount: Address<TAccountMakerTokenAccount>;
309
+ feeWalletTokenAccount: Address<TAccountFeeWalletTokenAccount>;
310
+ maker: TransactionSigner<TAccountMaker>;
311
+ tokenProgram?: Address<TAccountTokenProgram>;
312
+ };
313
+
314
+ export function getRepayPoolLoanFromWalletInstruction<
315
+ TAccountPoolLoan extends string,
316
+ TAccountVault extends string,
317
+ TAccountVaultTokenAccount extends string,
318
+ TAccountEscrowState extends string,
319
+ TAccountEscrowAuthority extends string,
320
+ TAccountEscrowTokenAccount extends string,
321
+ TAccountMakerTokenAccount extends string,
322
+ TAccountFeeWalletTokenAccount extends string,
323
+ TAccountMaker extends string,
324
+ TAccountTokenProgram extends string,
325
+ TProgramAddress extends Address = typeof OPTION_PROGRAM_PROGRAM_ADDRESS,
326
+ >(
327
+ input: RepayPoolLoanFromWalletInput<
328
+ TAccountPoolLoan,
329
+ TAccountVault,
330
+ TAccountVaultTokenAccount,
331
+ TAccountEscrowState,
332
+ TAccountEscrowAuthority,
333
+ TAccountEscrowTokenAccount,
334
+ TAccountMakerTokenAccount,
335
+ TAccountFeeWalletTokenAccount,
336
+ TAccountMaker,
337
+ TAccountTokenProgram
338
+ >,
339
+ config?: { programAddress?: TProgramAddress },
340
+ ): RepayPoolLoanFromWalletInstruction<
341
+ TProgramAddress,
342
+ TAccountPoolLoan,
343
+ TAccountVault,
344
+ TAccountVaultTokenAccount,
345
+ TAccountEscrowState,
346
+ TAccountEscrowAuthority,
347
+ TAccountEscrowTokenAccount,
348
+ TAccountMakerTokenAccount,
349
+ TAccountFeeWalletTokenAccount,
350
+ TAccountMaker,
351
+ TAccountTokenProgram
352
+ > {
353
+ // Program address.
354
+ const programAddress =
355
+ config?.programAddress ?? OPTION_PROGRAM_PROGRAM_ADDRESS;
356
+
357
+ // Original accounts.
358
+ const originalAccounts = {
359
+ poolLoan: { value: input.poolLoan ?? null, isWritable: true },
360
+ vault: { value: input.vault ?? null, isWritable: true },
361
+ vaultTokenAccount: {
362
+ value: input.vaultTokenAccount ?? null,
363
+ isWritable: true,
364
+ },
365
+ escrowState: { value: input.escrowState ?? null, isWritable: false },
366
+ escrowAuthority: {
367
+ value: input.escrowAuthority ?? null,
368
+ isWritable: false,
369
+ },
370
+ escrowTokenAccount: {
371
+ value: input.escrowTokenAccount ?? null,
372
+ isWritable: true,
373
+ },
374
+ makerTokenAccount: {
375
+ value: input.makerTokenAccount ?? null,
376
+ isWritable: true,
377
+ },
378
+ feeWalletTokenAccount: {
379
+ value: input.feeWalletTokenAccount ?? null,
380
+ isWritable: true,
381
+ },
382
+ maker: { value: input.maker ?? null, isWritable: false },
383
+ tokenProgram: { value: input.tokenProgram ?? null, isWritable: false },
384
+ };
385
+ const accounts = originalAccounts as Record<
386
+ keyof typeof originalAccounts,
387
+ ResolvedAccount
388
+ >;
389
+
390
+ // Resolve default values.
391
+ if (!accounts.tokenProgram.value) {
392
+ accounts.tokenProgram.value =
393
+ "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" as Address<"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA">;
394
+ }
395
+
396
+ const getAccountMeta = getAccountMetaFactory(programAddress, "programId");
397
+ return Object.freeze({
398
+ accounts: [
399
+ getAccountMeta(accounts.poolLoan),
400
+ getAccountMeta(accounts.vault),
401
+ getAccountMeta(accounts.vaultTokenAccount),
402
+ getAccountMeta(accounts.escrowState),
403
+ getAccountMeta(accounts.escrowAuthority),
404
+ getAccountMeta(accounts.escrowTokenAccount),
405
+ getAccountMeta(accounts.makerTokenAccount),
406
+ getAccountMeta(accounts.feeWalletTokenAccount),
407
+ getAccountMeta(accounts.maker),
408
+ getAccountMeta(accounts.tokenProgram),
409
+ ],
410
+ data: getRepayPoolLoanFromWalletInstructionDataEncoder().encode({}),
411
+ programAddress,
412
+ } as RepayPoolLoanFromWalletInstruction<
413
+ TProgramAddress,
414
+ TAccountPoolLoan,
415
+ TAccountVault,
416
+ TAccountVaultTokenAccount,
417
+ TAccountEscrowState,
418
+ TAccountEscrowAuthority,
419
+ TAccountEscrowTokenAccount,
420
+ TAccountMakerTokenAccount,
421
+ TAccountFeeWalletTokenAccount,
422
+ TAccountMaker,
423
+ TAccountTokenProgram
424
+ >);
425
+ }
426
+
427
+ export type ParsedRepayPoolLoanFromWalletInstruction<
428
+ TProgram extends string = typeof OPTION_PROGRAM_PROGRAM_ADDRESS,
429
+ TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[],
430
+ > = {
431
+ programAddress: Address<TProgram>;
432
+ accounts: {
433
+ poolLoan: TAccountMetas[0];
434
+ vault: TAccountMetas[1];
435
+ vaultTokenAccount: TAccountMetas[2];
436
+ escrowState: TAccountMetas[3];
437
+ escrowAuthority: TAccountMetas[4];
438
+ escrowTokenAccount: TAccountMetas[5];
439
+ makerTokenAccount: TAccountMetas[6];
440
+ feeWalletTokenAccount: TAccountMetas[7];
441
+ maker: TAccountMetas[8];
442
+ tokenProgram: TAccountMetas[9];
443
+ };
444
+ data: RepayPoolLoanFromWalletInstructionData;
445
+ };
446
+
447
+ export function parseRepayPoolLoanFromWalletInstruction<
448
+ TProgram extends string,
449
+ TAccountMetas extends readonly AccountMeta[],
450
+ >(
451
+ instruction: Instruction<TProgram> &
452
+ InstructionWithAccounts<TAccountMetas> &
453
+ InstructionWithData<ReadonlyUint8Array>,
454
+ ): ParsedRepayPoolLoanFromWalletInstruction<TProgram, TAccountMetas> {
455
+ if (instruction.accounts.length < 10) {
456
+ // TODO: Coded error.
457
+ throw new Error("Not enough accounts");
458
+ }
459
+ let accountIndex = 0;
460
+ const getNextAccount = () => {
461
+ const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!;
462
+ accountIndex += 1;
463
+ return accountMeta;
464
+ };
465
+ return {
466
+ programAddress: instruction.programAddress,
467
+ accounts: {
468
+ poolLoan: getNextAccount(),
469
+ vault: getNextAccount(),
470
+ vaultTokenAccount: getNextAccount(),
471
+ escrowState: getNextAccount(),
472
+ escrowAuthority: getNextAccount(),
473
+ escrowTokenAccount: getNextAccount(),
474
+ makerTokenAccount: getNextAccount(),
475
+ feeWalletTokenAccount: getNextAccount(),
476
+ maker: getNextAccount(),
477
+ tokenProgram: getNextAccount(),
478
+ },
479
+ data: getRepayPoolLoanFromWalletInstructionDataDecoder().decode(
480
+ instruction.data,
481
+ ),
482
+ };
483
+ }
@@ -63,6 +63,7 @@ export type UnwindWriterUnsoldInstruction<
63
63
  TAccountWriterShortAccount extends string | AccountMeta<string> = string,
64
64
  TAccountCollateralVault extends string | AccountMeta<string> = string,
65
65
  TAccountWriterCollateralAccount extends string | AccountMeta<string> = string,
66
+ TAccountOmlpVaultState extends string | AccountMeta<string> = string,
66
67
  TAccountOmlpVault extends string | AccountMeta<string> = string,
67
68
  TAccountFeeWallet extends string | AccountMeta<string> = string,
68
69
  TAccountWriter extends string | AccountMeta<string> = string,
@@ -105,6 +106,9 @@ export type UnwindWriterUnsoldInstruction<
105
106
  TAccountWriterCollateralAccount extends string
106
107
  ? WritableAccount<TAccountWriterCollateralAccount>
107
108
  : TAccountWriterCollateralAccount,
109
+ TAccountOmlpVaultState extends string
110
+ ? WritableAccount<TAccountOmlpVaultState>
111
+ : TAccountOmlpVaultState,
108
112
  TAccountOmlpVault extends string
109
113
  ? WritableAccount<TAccountOmlpVault>
110
114
  : TAccountOmlpVault,
@@ -175,6 +179,7 @@ export type UnwindWriterUnsoldAsyncInput<
175
179
  TAccountWriterShortAccount extends string = string,
176
180
  TAccountCollateralVault extends string = string,
177
181
  TAccountWriterCollateralAccount extends string = string,
182
+ TAccountOmlpVaultState extends string = string,
178
183
  TAccountOmlpVault extends string = string,
179
184
  TAccountFeeWallet extends string = string,
180
185
  TAccountWriter extends string = string,
@@ -201,6 +206,8 @@ export type UnwindWriterUnsoldAsyncInput<
201
206
  collateralVault: Address<TAccountCollateralVault>;
202
207
  /** Writer's underlying token account (receives returned collateral) */
203
208
  writerCollateralAccount: Address<TAccountWriterCollateralAccount>;
209
+ /** OMLP Vault state PDA (for total_loans, add_interest_to_index) - optional, required when repaying loans */
210
+ omlpVaultState?: Address<TAccountOmlpVaultState>;
204
211
  /** OMLP vault token account (receives loan repayments) - optional */
205
212
  omlpVault?: Address<TAccountOmlpVault>;
206
213
  /** Protocol fee wallet (receives protocol fees) - optional */
@@ -222,6 +229,7 @@ export async function getUnwindWriterUnsoldInstructionAsync<
222
229
  TAccountWriterShortAccount extends string,
223
230
  TAccountCollateralVault extends string,
224
231
  TAccountWriterCollateralAccount extends string,
232
+ TAccountOmlpVaultState extends string,
225
233
  TAccountOmlpVault extends string,
226
234
  TAccountFeeWallet extends string,
227
235
  TAccountWriter extends string,
@@ -240,6 +248,7 @@ export async function getUnwindWriterUnsoldInstructionAsync<
240
248
  TAccountWriterShortAccount,
241
249
  TAccountCollateralVault,
242
250
  TAccountWriterCollateralAccount,
251
+ TAccountOmlpVaultState,
243
252
  TAccountOmlpVault,
244
253
  TAccountFeeWallet,
245
254
  TAccountWriter,
@@ -260,6 +269,7 @@ export async function getUnwindWriterUnsoldInstructionAsync<
260
269
  TAccountWriterShortAccount,
261
270
  TAccountCollateralVault,
262
271
  TAccountWriterCollateralAccount,
272
+ TAccountOmlpVaultState,
263
273
  TAccountOmlpVault,
264
274
  TAccountFeeWallet,
265
275
  TAccountWriter,
@@ -292,6 +302,7 @@ export async function getUnwindWriterUnsoldInstructionAsync<
292
302
  value: input.writerCollateralAccount ?? null,
293
303
  isWritable: true,
294
304
  },
305
+ omlpVaultState: { value: input.omlpVaultState ?? null, isWritable: true },
295
306
  omlpVault: { value: input.omlpVault ?? null, isWritable: true },
296
307
  feeWallet: { value: input.feeWallet ?? null, isWritable: true },
297
308
  writer: { value: input.writer ?? null, isWritable: true },
@@ -358,6 +369,7 @@ export async function getUnwindWriterUnsoldInstructionAsync<
358
369
  getAccountMeta(accounts.writerShortAccount),
359
370
  getAccountMeta(accounts.collateralVault),
360
371
  getAccountMeta(accounts.writerCollateralAccount),
372
+ getAccountMeta(accounts.omlpVaultState),
361
373
  getAccountMeta(accounts.omlpVault),
362
374
  getAccountMeta(accounts.feeWallet),
363
375
  getAccountMeta(accounts.writer),
@@ -380,6 +392,7 @@ export async function getUnwindWriterUnsoldInstructionAsync<
380
392
  TAccountWriterShortAccount,
381
393
  TAccountCollateralVault,
382
394
  TAccountWriterCollateralAccount,
395
+ TAccountOmlpVaultState,
383
396
  TAccountOmlpVault,
384
397
  TAccountFeeWallet,
385
398
  TAccountWriter,
@@ -399,6 +412,7 @@ export type UnwindWriterUnsoldInput<
399
412
  TAccountWriterShortAccount extends string = string,
400
413
  TAccountCollateralVault extends string = string,
401
414
  TAccountWriterCollateralAccount extends string = string,
415
+ TAccountOmlpVaultState extends string = string,
402
416
  TAccountOmlpVault extends string = string,
403
417
  TAccountFeeWallet extends string = string,
404
418
  TAccountWriter extends string = string,
@@ -425,6 +439,8 @@ export type UnwindWriterUnsoldInput<
425
439
  collateralVault: Address<TAccountCollateralVault>;
426
440
  /** Writer's underlying token account (receives returned collateral) */
427
441
  writerCollateralAccount: Address<TAccountWriterCollateralAccount>;
442
+ /** OMLP Vault state PDA (for total_loans, add_interest_to_index) - optional, required when repaying loans */
443
+ omlpVaultState?: Address<TAccountOmlpVaultState>;
428
444
  /** OMLP vault token account (receives loan repayments) - optional */
429
445
  omlpVault?: Address<TAccountOmlpVault>;
430
446
  /** Protocol fee wallet (receives protocol fees) - optional */
@@ -446,6 +462,7 @@ export function getUnwindWriterUnsoldInstruction<
446
462
  TAccountWriterShortAccount extends string,
447
463
  TAccountCollateralVault extends string,
448
464
  TAccountWriterCollateralAccount extends string,
465
+ TAccountOmlpVaultState extends string,
449
466
  TAccountOmlpVault extends string,
450
467
  TAccountFeeWallet extends string,
451
468
  TAccountWriter extends string,
@@ -464,6 +481,7 @@ export function getUnwindWriterUnsoldInstruction<
464
481
  TAccountWriterShortAccount,
465
482
  TAccountCollateralVault,
466
483
  TAccountWriterCollateralAccount,
484
+ TAccountOmlpVaultState,
467
485
  TAccountOmlpVault,
468
486
  TAccountFeeWallet,
469
487
  TAccountWriter,
@@ -483,6 +501,7 @@ export function getUnwindWriterUnsoldInstruction<
483
501
  TAccountWriterShortAccount,
484
502
  TAccountCollateralVault,
485
503
  TAccountWriterCollateralAccount,
504
+ TAccountOmlpVaultState,
486
505
  TAccountOmlpVault,
487
506
  TAccountFeeWallet,
488
507
  TAccountWriter,
@@ -514,6 +533,7 @@ export function getUnwindWriterUnsoldInstruction<
514
533
  value: input.writerCollateralAccount ?? null,
515
534
  isWritable: true,
516
535
  },
536
+ omlpVaultState: { value: input.omlpVaultState ?? null, isWritable: true },
517
537
  omlpVault: { value: input.omlpVault ?? null, isWritable: true },
518
538
  feeWallet: { value: input.feeWallet ?? null, isWritable: true },
519
539
  writer: { value: input.writer ?? null, isWritable: true },
@@ -551,6 +571,7 @@ export function getUnwindWriterUnsoldInstruction<
551
571
  getAccountMeta(accounts.writerShortAccount),
552
572
  getAccountMeta(accounts.collateralVault),
553
573
  getAccountMeta(accounts.writerCollateralAccount),
574
+ getAccountMeta(accounts.omlpVaultState),
554
575
  getAccountMeta(accounts.omlpVault),
555
576
  getAccountMeta(accounts.feeWallet),
556
577
  getAccountMeta(accounts.writer),
@@ -573,6 +594,7 @@ export function getUnwindWriterUnsoldInstruction<
573
594
  TAccountWriterShortAccount,
574
595
  TAccountCollateralVault,
575
596
  TAccountWriterCollateralAccount,
597
+ TAccountOmlpVaultState,
576
598
  TAccountOmlpVault,
577
599
  TAccountFeeWallet,
578
600
  TAccountWriter,
@@ -607,13 +629,15 @@ export type ParsedUnwindWriterUnsoldInstruction<
607
629
  collateralVault: TAccountMetas[8];
608
630
  /** Writer's underlying token account (receives returned collateral) */
609
631
  writerCollateralAccount: TAccountMetas[9];
632
+ /** OMLP Vault state PDA (for total_loans, add_interest_to_index) - optional, required when repaying loans */
633
+ omlpVaultState?: TAccountMetas[10] | undefined;
610
634
  /** OMLP vault token account (receives loan repayments) - optional */
611
- omlpVault?: TAccountMetas[10] | undefined;
635
+ omlpVault?: TAccountMetas[11] | undefined;
612
636
  /** Protocol fee wallet (receives protocol fees) - optional */
613
- feeWallet?: TAccountMetas[11] | undefined;
614
- writer: TAccountMetas[12];
615
- tokenProgram: TAccountMetas[13];
616
- systemProgram: TAccountMetas[14];
637
+ feeWallet?: TAccountMetas[12] | undefined;
638
+ writer: TAccountMetas[13];
639
+ tokenProgram: TAccountMetas[14];
640
+ systemProgram: TAccountMetas[15];
617
641
  };
618
642
  data: UnwindWriterUnsoldInstructionData;
619
643
  };
@@ -626,7 +650,7 @@ export function parseUnwindWriterUnsoldInstruction<
626
650
  InstructionWithAccounts<TAccountMetas> &
627
651
  InstructionWithData<ReadonlyUint8Array>,
628
652
  ): ParsedUnwindWriterUnsoldInstruction<TProgram, TAccountMetas> {
629
- if (instruction.accounts.length < 15) {
653
+ if (instruction.accounts.length < 16) {
630
654
  // TODO: Coded error.
631
655
  throw new Error("Not enough accounts");
632
656
  }
@@ -655,6 +679,7 @@ export function parseUnwindWriterUnsoldInstruction<
655
679
  writerShortAccount: getNextAccount(),
656
680
  collateralVault: getNextAccount(),
657
681
  writerCollateralAccount: getNextAccount(),
682
+ omlpVaultState: getNextOptionalAccount(),
658
683
  omlpVault: getNextOptionalAccount(),
659
684
  feeWallet: getNextOptionalAccount(),
660
685
  writer: getNextAccount(),
@@ -41,6 +41,7 @@ import {
41
41
  parseOptionMintInstruction,
42
42
  parseOptionValidateInstruction,
43
43
  parseRepayPoolLoanFromCollateralInstruction,
44
+ parseRepayPoolLoanFromWalletInstruction,
44
45
  parseRepayPoolLoanInstruction,
45
46
  parseSettleMakerCollateralInstruction,
46
47
  parseSyncWriterPositionInstruction,
@@ -74,6 +75,7 @@ import {
74
75
  type ParsedOptionMintInstruction,
75
76
  type ParsedOptionValidateInstruction,
76
77
  type ParsedRepayPoolLoanFromCollateralInstruction,
78
+ type ParsedRepayPoolLoanFromWalletInstruction,
77
79
  type ParsedRepayPoolLoanInstruction,
78
80
  type ParsedSettleMakerCollateralInstruction,
79
81
  type ParsedSyncWriterPositionInstruction,
@@ -282,6 +284,7 @@ export enum OptionProgramInstruction {
282
284
  OptionValidate,
283
285
  RepayPoolLoan,
284
286
  RepayPoolLoanFromCollateral,
287
+ RepayPoolLoanFromWallet,
285
288
  SettleMakerCollateral,
286
289
  SyncWriterPosition,
287
290
  TransferAdmin,
@@ -571,6 +574,17 @@ export function identifyOptionProgramInstruction(
571
574
  ) {
572
575
  return OptionProgramInstruction.RepayPoolLoanFromCollateral;
573
576
  }
577
+ if (
578
+ containsBytes(
579
+ data,
580
+ fixEncoderSize(getBytesEncoder(), 8).encode(
581
+ new Uint8Array([78, 130, 135, 90, 211, 21, 247, 247]),
582
+ ),
583
+ 0,
584
+ )
585
+ ) {
586
+ return OptionProgramInstruction.RepayPoolLoanFromWallet;
587
+ }
574
588
  if (
575
589
  containsBytes(
576
590
  data,
@@ -742,6 +756,9 @@ export type ParsedOptionProgramInstruction<
742
756
  | ({
743
757
  instructionType: OptionProgramInstruction.RepayPoolLoanFromCollateral;
744
758
  } & ParsedRepayPoolLoanFromCollateralInstruction<TProgram>)
759
+ | ({
760
+ instructionType: OptionProgramInstruction.RepayPoolLoanFromWallet;
761
+ } & ParsedRepayPoolLoanFromWalletInstruction<TProgram>)
745
762
  | ({
746
763
  instructionType: OptionProgramInstruction.SettleMakerCollateral;
747
764
  } & ParsedSettleMakerCollateralInstruction<TProgram>)
@@ -947,6 +964,13 @@ export function parseOptionProgramInstruction<TProgram extends string>(
947
964
  ...parseRepayPoolLoanFromCollateralInstruction(instruction),
948
965
  };
949
966
  }
967
+ case OptionProgramInstruction.RepayPoolLoanFromWallet: {
968
+ assertIsInstructionWithAccounts(instruction);
969
+ return {
970
+ instructionType: OptionProgramInstruction.RepayPoolLoanFromWallet,
971
+ ...parseRepayPoolLoanFromWalletInstruction(instruction),
972
+ };
973
+ }
950
974
  case OptionProgramInstruction.SettleMakerCollateral: {
951
975
  assertIsInstructionWithAccounts(instruction);
952
976
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicentral/sos-sdk",
3
- "version": "0.3.0-alpha.1",
3
+ "version": "0.3.0-alpha.2",
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/short/builders.ts CHANGED
@@ -62,6 +62,9 @@ export interface BuildOptionMintParams {
62
62
  remainingAccounts?: RemainingAccountInput[];
63
63
  }
64
64
 
65
+ /** Max PoolLoans per unwind to stay under 64-account tx limit (~18 named + ~20 loans) */
66
+ const MAX_POOL_LOANS_PER_UNWIND = 20;
67
+
65
68
  export interface BuildUnwindWriterUnsoldParams {
66
69
  optionPool: AddressLike;
67
70
  optionAccount: AddressLike;
@@ -75,6 +78,7 @@ export interface BuildUnwindWriterUnsoldParams {
75
78
  unwindQty: bigint | number;
76
79
  collateralPool?: AddressLike;
77
80
  writerPosition?: AddressLike;
81
+ omlpVaultState?: AddressLike;
78
82
  omlpVault?: AddressLike;
79
83
  feeWallet?: AddressLike;
80
84
  remainingAccounts?: RemainingAccountInput[];
@@ -311,6 +315,7 @@ export async function buildUnwindWriterUnsoldInstruction(
311
315
  writerShortAccount: toAddress(params.writerShortAccount),
312
316
  collateralVault: toAddress(params.collateralVault),
313
317
  writerCollateralAccount: toAddress(params.writerCollateralAccount),
318
+ omlpVaultState: params.omlpVaultState ? toAddress(params.omlpVaultState) : undefined,
314
319
  omlpVault: params.omlpVault ? toAddress(params.omlpVault) : undefined,
315
320
  feeWallet: params.feeWallet ? toAddress(params.feeWallet) : undefined,
316
321
  writer: toAddress(params.writer) as any,
@@ -336,10 +341,12 @@ export interface BuildUnwindWriterUnsoldTransactionWithDerivationParams {
336
341
  unwindQty: bigint | number;
337
342
  rpc: KitRpc;
338
343
  programId?: AddressLike;
344
+ omlpVaultState?: AddressLike;
339
345
  omlpVault?: AddressLike;
340
346
  feeWallet?: AddressLike;
341
347
  /**
342
- * When repaying pool loans: [Vault PDA, PoolLoan₁, PoolLoan₂, ...] (all writable).
348
+ * When repaying pool loans: [PoolLoan₁, PoolLoan₂, ...] (all writable).
349
+ * omlpVaultState, omlpVault, feeWallet must also be passed.
343
350
  * Prefer {@link buildUnwindWriterUnsoldWithLoanRepayment} to build this automatically.
344
351
  */
345
352
  remainingAccounts?: RemainingAccountInput[];
@@ -361,11 +368,12 @@ export interface BuildUnwindWriterUnsoldWithLoanRepaymentParams {
361
368
  /**
362
369
  * Builds an unwind_writer_unsold transaction that also repays any active pool loans
363
370
  * for the option's underlying vault. When a writer unwinds an unsold short that had
364
- * borrowed from OMLP, borrowed funds are repaid to the pool (not sent to the writer).
371
+ * borrowed from OMLP, the program repays lenders from the collateral vault (burn,
372
+ * repay, then return collateral to writer) in one instruction.
365
373
  *
366
- * remaining_accounts order: [Vault PDA, PoolLoan₁, PoolLoan₂, ...] (all writable).
367
- * If no active pool loans exist for this vault, still passes omlpVault and feeWallet
368
- * so the API can be used for all unwinds.
374
+ * Passes omlpVaultState (Vault PDA), omlpVault, feeWallet as named accounts.
375
+ * remaining_accounts: [PoolLoan₁, PoolLoan₂, ...] only (capped at 20).
376
+ * If no active pool loans exist, still passes vault accounts so the API works for all unwinds.
369
377
  */
370
378
  export async function buildUnwindWriterUnsoldWithLoanRepayment(
371
379
  params: BuildUnwindWriterUnsoldWithLoanRepaymentParams
@@ -393,14 +401,14 @@ export async function buildUnwindWriterUnsoldWithLoanRepayment(
393
401
  fetchVault(params.rpc, vaultPda),
394
402
  ]);
395
403
 
396
- const vaultLoans = loans.filter(
397
- (item) => toAddress(item.data.vault) === vaultPdaStr
398
- );
404
+ const vaultLoans = loans
405
+ .filter((item) => toAddress(item.data.vault) === vaultPdaStr)
406
+ .slice(0, MAX_POOL_LOANS_PER_UNWIND);
399
407
 
400
- const remainingAccounts: RemainingAccountInput[] = [
401
- { address: vaultPda, isWritable: true },
402
- ...vaultLoans.map((item) => ({ address: item.address, isWritable: true })),
403
- ];
408
+ const remainingAccounts: RemainingAccountInput[] = vaultLoans.map((item) => ({
409
+ address: item.address,
410
+ isWritable: true,
411
+ }));
404
412
 
405
413
  const omlpVault = await deriveAssociatedTokenAddress(vaultPda, underlyingMint);
406
414
  const feeWallet = vault
@@ -416,6 +424,7 @@ export async function buildUnwindWriterUnsoldWithLoanRepayment(
416
424
  unwindQty: params.unwindQty,
417
425
  rpc: params.rpc,
418
426
  programId: params.programId,
427
+ omlpVaultState: vaultPda,
419
428
  omlpVault,
420
429
  feeWallet,
421
430
  remainingAccounts,
@@ -459,6 +468,7 @@ export async function buildUnwindWriterUnsoldTransactionWithDerivation(
459
468
  unwindQty: params.unwindQty,
460
469
  collateralPool: resolved.collateralPool,
461
470
  writerPosition: writerPosition[0],
471
+ omlpVaultState: params.omlpVaultState,
462
472
  omlpVault: params.omlpVault,
463
473
  feeWallet: params.feeWallet,
464
474
  remainingAccounts: params.remainingAccounts,
package/short/pool.ts CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  getBorrowFromPoolInstructionAsync,
3
3
  getRepayPoolLoanInstructionAsync,
4
4
  getRepayPoolLoanFromCollateralInstructionAsync,
5
+ getRepayPoolLoanFromWalletInstructionAsync,
5
6
  } from "../generated/instructions";
6
7
  import type { Instruction } from "@solana/kit";
7
8
  import { toAddress } from "../client/program";
@@ -114,6 +115,39 @@ export async function buildRepayPoolLoanTransaction(
114
115
  return { instructions: [instruction] };
115
116
  }
116
117
 
118
+ /**
119
+ * Repay a pool loan entirely from maker's wallet (no escrow used).
120
+ * Use when standard repay fails with InsufficientEscrowBalance (stuck loan).
121
+ */
122
+ export async function buildRepayPoolLoanFromWalletInstruction(
123
+ params: BuildRepayPoolLoanParams
124
+ ): Promise<Instruction<string>> {
125
+ return getRepayPoolLoanFromWalletInstructionAsync({
126
+ poolLoan: toAddress(params.poolLoan),
127
+ vault: toAddress(params.vault),
128
+ vaultTokenAccount: toAddress(params.vaultTokenAccount),
129
+ escrowState: toAddress(params.escrowState),
130
+ escrowAuthority: params.escrowAuthority
131
+ ? toAddress(params.escrowAuthority)
132
+ : undefined,
133
+ escrowTokenAccount: toAddress(params.escrowTokenAccount),
134
+ makerTokenAccount: toAddress(params.makerTokenAccount),
135
+ feeWalletTokenAccount: toAddress(params.feeWalletTokenAccount),
136
+ maker: toAddress(params.maker) as any,
137
+ tokenProgram: params.tokenProgram ? toAddress(params.tokenProgram) : undefined,
138
+ });
139
+ }
140
+
141
+ /**
142
+ * Builds the repay-from-wallet transaction (stuck loan recovery).
143
+ */
144
+ export async function buildRepayPoolLoanFromWalletTransaction(
145
+ params: BuildRepayPoolLoanParams
146
+ ): Promise<BuiltTransaction> {
147
+ const instruction = await buildRepayPoolLoanFromWalletInstruction(params);
148
+ return { instructions: [instruction] };
149
+ }
150
+
117
151
  export async function buildRepayPoolLoanFromCollateralInstruction(
118
152
  params: BuildRepayPoolLoanFromCollateralParams
119
153
  ): Promise<Instruction<string>> {