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

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
@@ -1,177 +1,161 @@
1
1
  # @epicentral/sos-sdk
2
2
 
3
- Solana Option Standard SDK. A frontend-first SDK for native options trading on Solana, built by Epicentral Labs.
3
+ TypeScript/JavaScript SDK for the Solana Option Standard (SOS) program. The frontend-first SDK for native options trading on Solana. Built with `@solana/kit` and related Solana libraries.
4
4
 
5
- Uses `@solana/kit` types (`Address`, `Instruction`, `Rpc`) across the public API.
6
-
7
- ## Install
5
+ ## Installation
8
6
 
9
7
  ```bash
10
- pnpm add @epicentral/sos-sdk @solana/kit decimal.js
8
+ pnpm add @epicentral/sos-sdk
11
9
  ```
12
10
 
11
+ Peer dependencies: `@solana/kit` (and your RPC client). The SDK uses `KitRpc` for account resolution and fetches.
12
+
13
13
  ## Overview
14
14
 
15
- - **LONG** Buy from pool, close to pool, exercise options.
16
- - **SHORT** — Mint options, unwind, sync, settle, claim premium, close option.
17
- - **Pool** — Deposit, withdraw, borrow, repay liquidity.
18
- - **OMLP** — Lender deposit and withdraw.
19
- - **Accounts** — PDA derivation and account fetchers for options, pools, vaults.
15
+ The SDK supports two main flows:
20
16
 
21
- Each flow exposes `build*Instruction` for single instruction composition and `build*Transaction` for full-flow `Instruction[]` construction.
17
+ - **Long (buyer)** Buy options from the pool, close positions, exercise.
18
+ - **Short (writer)** – Mint options (write), unwind unsold, settle collateral, claim theta.
22
19
 
23
- ## Usage
20
+ Additional modules:
24
21
 
25
- ### Build + send (recommended)
22
+ - **OMLP** Option Maker Liquidity Pool. Lenders deposit; writers borrow to collateralize shorts.
23
+ - **WSOL** – Helpers for wrapping/unwrapping SOL and creating token accounts.
26
24
 
27
- ```ts
28
- import {
29
- buildBuyFromPoolTransaction,
30
- sendBuiltTransaction,
31
- } from "@epicentral/sos-sdk";
25
+ ## High-Level Functions and Instructions
32
26
 
33
- const built = await buildBuyFromPoolTransaction(params);
34
- const signature = await sendBuiltTransaction({
35
- rpc,
36
- rpcSubscriptions,
37
- feePayer: walletSigner,
38
- instructions: built.instructions,
39
- });
40
- ```
27
+ ### Accounts, PDAs, and Fetchers
41
28
 
42
- ### Build + send (manual)
29
+ | Function | Description |
30
+ |----------|-------------|
31
+ | `resolveOptionAccounts` | Resolves option pool, mints, vaults, and collateral accounts from option identity (underlying, type, strike, expiration). |
32
+ | `deriveVaultPda` | Derives OMLP vault PDA from mint. |
33
+ | `derivePoolLoanPdaFromVault` | Derives PoolLoan PDA from vault, maker, nonce (canonical; matches program). |
34
+ | `derivePoolLoanPda` | *(Deprecated)* Legacy derivation; use `derivePoolLoanPdaFromVault`. |
35
+ | `deriveWriterPositionPda` | Derives writer position PDA from option pool and writer. |
36
+ | `deriveAssociatedTokenAddress` | Derives ATA for owner + mint. |
37
+ | `fetchVault` | Fetches vault account by address. |
38
+ | `fetchPoolLoansByMaker` | Fetches active pool loans for a maker. |
39
+ | `fetchOptionPool` | Fetches option pool account. |
40
+ | `fetchWriterPositionsByWriter` | Fetches writer positions for a writer. |
41
+ | `fetchWriterPositionsForPool` | Fetches writer positions for an option pool. |
42
+ | `fetchPositionAccountsByBuyer` | Fetches buyer position accounts. |
43
+ | `fetchAllOptionPools` | Fetches all option pools. |
44
+ | `fetchAllVaults` | Fetches all vaults. |
43
45
 
44
- ```ts
45
- import {
46
- appendTransactionMessageInstructions,
47
- createTransactionMessage,
48
- pipe,
49
- sendAndConfirmTransactionFactory,
50
- setTransactionMessageFeePayerSigner,
51
- setTransactionMessageLifetimeUsingBlockhash,
52
- signTransactionMessageWithSigners,
53
- } from "@solana/kit";
54
- import { buildBuyFromPoolTransaction } from "@epicentral/sos-sdk";
55
-
56
- const built = await buildBuyFromPoolTransaction(params);
57
- const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
58
-
59
- const txMessage = pipe(
60
- createTransactionMessage({ version: 0 }),
61
- (tx) => setTransactionMessageFeePayerSigner(walletSigner, tx),
62
- (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
63
- (tx) => appendTransactionMessageInstructions(built.instructions, tx)
64
- );
65
-
66
- const signedTx = await signTransactionMessageWithSigners(txMessage);
67
- await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTx, {
68
- commitment: "confirmed",
69
- });
70
- ```
46
+ ### Long (Buyer) Flows
71
47
 
72
- ### LONG
48
+ | Function | Description |
49
+ |----------|-------------|
50
+ | `buildBuyFromPoolTransactionWithDerivation` | Builds buy-from-pool transaction; resolves accounts from option identity. |
51
+ | `buildCloseLongToPoolTransactionWithDerivation` | Builds close-long-to-pool transaction. |
52
+ | `getBuyFromPoolRemainingAccounts` | Builds remaining_accounts for buy (writer positions, etc.). |
73
53
 
74
- ```ts
75
- import {
76
- buildBuyFromPoolTransaction,
77
- buildCloseLongToPoolTransaction,
78
- buildOptionExerciseTransaction,
79
- } from "@epicentral/sos-sdk";
54
+ ### Short (Writer) Flows
80
55
 
81
- // Open LONG
82
- const openLong = await buildBuyFromPoolTransaction(openLongParams);
56
+ | Function | Description |
57
+ |----------|-------------|
58
+ | `buildOptionMintTransactionWithDerivation` | Builds option mint (write) transaction. |
59
+ | `buildUnwindWriterUnsoldTransactionWithDerivation` | Builds unwind unsold transaction. |
60
+ | `buildUnwindWriterUnsoldWithLoanRepayment` | **Unwind + repay pool loans in one tx.** Use when closing unsold shorts that borrowed from OMLP. |
61
+ | `buildSyncWriterPositionTransaction` | Syncs writer position with pool accumulators. |
62
+ | `buildSettleMakerCollateralTransaction` | Settles maker collateral after buyer closes. |
63
+ | `buildCloseOptionTransaction` | Closes option token account. |
64
+ | `buildClaimThetaTransaction` | Claims theta (time-decay share) for writer. |
65
+ | `buildRepayPoolLoanFromCollateralInstruction` | Repays pool loan from collateral (short/pool). |
66
+ | `buildRepayPoolLoanInstruction` | Repays pool loan with external funds (short/pool). |
83
67
 
84
- // Close LONG
85
- const closeLong = await buildCloseLongToPoolTransaction(closeLongParams);
68
+ ### OMLP (Lending)
86
69
 
87
- // Exercise LONG
88
- const exercise = buildOptionExerciseTransaction({ optionAccount, positionAccount, /* ... */ });
89
- ```
70
+ | Function | Description |
71
+ |----------|-------------|
72
+ | `buildDepositToPositionTransaction` | Deposits liquidity to OMLP. |
73
+ | `buildWithdrawFromPositionTransaction` | Withdraws liquidity. |
74
+ | `withdrawAllFromPosition` | Withdraws full position (omlp/service). |
75
+ | `withdrawInterestFromPosition` | Withdraws accrued interest only (omlp/service). |
90
76
 
91
- ### SHORT
77
+ Borrow/repay for writers: use `buildOptionMintTransactionWithDerivation` (with vault/poolLoan) and `buildRepayPoolLoanFromCollateralInstruction` or `buildUnwindWriterUnsoldWithLoanRepayment`.
92
78
 
93
- ```ts
94
- import {
95
- buildOptionMintTransaction,
96
- buildUnwindWriterUnsoldTransaction,
97
- buildSyncWriterPositionTransaction,
98
- buildSettleMakerCollateralTransaction,
99
- buildClaimPremiumTransaction,
100
- buildCloseOptionTransaction,
101
- } from "@epicentral/sos-sdk";
102
- import { OptionType } from "@epicentral/sos-sdk";
79
+ ### WSOL / Token Helpers
103
80
 
104
- // Mint (open SHORT)
105
- const built = await buildOptionMintTransaction({
106
- optionType: OptionType.Call,
107
- strikePrice,
108
- expirationDate,
109
- quantity,
110
- underlyingAsset,
111
- underlyingSymbol,
112
- makerCollateralAmount,
113
- borrowedAmount,
114
- maker,
115
- makerCollateralAccount,
116
- underlyingMint,
117
- });
81
+ | Function | Description |
82
+ |----------|-------------|
83
+ | `getWrapSOLInstructions` | Wraps SOL to WSOL. |
84
+ | `getUnwrapSOLInstructions` | Unwraps WSOL to SOL. |
85
+ | `getCreateAssociatedTokenIdempotentInstructionWithAddress` | Creates ATA if missing (idempotent). |
86
+ | `NATIVE_MINT` | WSOL mint address. |
118
87
 
119
- // Unwind / Sync / Settle
120
- const unwind = await buildUnwindWriterUnsoldTransaction(unwindParams);
121
- const sync = buildSyncWriterPositionTransaction(syncParams);
122
- const settle = await buildSettleMakerCollateralTransaction(settleParams);
88
+ ## Unwind with Loan Repayment
123
89
 
124
- // Claim premium / close option
125
- const claim = await buildClaimPremiumTransaction({ optionPool, makerPaymentAccount, premiumVault, maker });
126
- const close = buildCloseOptionTransaction({ optionAccount, optionMint, makerOptionAccount, maker });
127
- ```
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
+
92
+ Use **`buildUnwindWriterUnsoldWithLoanRepayment`** so that:
93
+
94
+ 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).
128
98
 
129
- ### Pool
99
+ If there are no active pool loans for that vault, the API still works and passes empty `remaining_accounts`.
100
+
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
+
103
+ ## Usage Examples
104
+
105
+ ### Buy from pool (with derivation)
130
106
 
131
107
  ```ts
132
108
  import {
133
- buildDepositToPoolTransaction,
134
- buildWithdrawFromPoolTransaction,
135
- buildBorrowFromPoolTransaction,
136
- buildRepayPoolLoanTransaction,
109
+ buildBuyFromPoolTransactionWithDerivation,
110
+ resolveOptionAccounts,
111
+ OptionType,
137
112
  } from "@epicentral/sos-sdk";
138
113
 
139
- const deposit = await buildDepositToPoolTransaction(depositToPoolParams);
140
- const withdraw = await buildWithdrawFromPoolTransaction(withdrawFromPoolParams);
141
- const borrow = await buildBorrowFromPoolTransaction(borrowFromPoolParams);
142
- const repay = await buildRepayPoolLoanTransaction(repayPoolLoanParams);
114
+ const tx = await buildBuyFromPoolTransactionWithDerivation({
115
+ underlyingAsset: "...",
116
+ optionType: OptionType.Call,
117
+ strikePrice: 100_000,
118
+ expirationDate: BigInt(1735689600),
119
+ buyer: walletAddress,
120
+ buyerPaymentAccount: buyerUsdcAta,
121
+ priceUpdate: pythPriceFeed,
122
+ quantity: 1_000_000,
123
+ premiumAmount: 50_000,
124
+ rpc,
125
+ });
143
126
  ```
144
127
 
145
- ### OMLP
128
+ ### Unwind with loan repayment
146
129
 
147
130
  ```ts
148
131
  import {
149
- buildDepositToPositionTransaction,
150
- buildWithdrawFromPositionTransaction,
132
+ buildUnwindWriterUnsoldWithLoanRepayment,
133
+ OptionType,
151
134
  } from "@epicentral/sos-sdk";
152
135
 
153
- const deposit = await buildDepositToPositionTransaction({
154
- vault,
155
- lenderTokenAccount,
156
- vaultTokenAccount,
157
- lender,
158
- amount,
159
- });
160
- const withdraw = await buildWithdrawFromPositionTransaction({
161
- vault,
162
- vaultTokenAccount,
163
- lenderTokenAccount,
164
- lender,
165
- amount,
136
+ const tx = await buildUnwindWriterUnsoldWithLoanRepayment({
137
+ underlyingAsset: "...",
138
+ optionType: OptionType.Call,
139
+ strikePrice: 100_000,
140
+ expirationDate: BigInt(1735689600),
141
+ writer: walletAddress,
142
+ unwindQty: 500_000,
143
+ rpc,
166
144
  });
167
145
  ```
168
146
 
169
- ### Fetch accounts
147
+ ## Types and Exports
170
148
 
171
- ```ts
172
- import { fetchOptionAccount, fetchOptionPool, fetchVault } from "@epicentral/sos-sdk";
149
+ Key types exported from the package:
173
150
 
174
- const option = await fetchOptionAccount(rpc, optionAddress);
175
- const pool = await fetchOptionPool(rpc, optionPoolAddress);
176
- const vault = await fetchVault(rpc, vaultAddress);
177
- ```
151
+ - `OptionType` Call or Put.
152
+ - `BuiltTransaction` `{ instructions: Instruction[] }`.
153
+ - `AddressLike` `string | Address`.
154
+ - `KitRpc` – RPC client type for fetches.
155
+ - `RemainingAccountInput` – `{ address, isWritable, isSigner? }`.
156
+
157
+ PDAs, fetchers, and builders are exported from the package root.
158
+
159
+ ## Program Compatibility
160
+
161
+ The SDK targets the Solana Option Standard program. Use `PROGRAM_ID` (or `getProgramId()`) from the package for the program address. Pass `programId` in builder params when using a different deployment.
package/accounts/list.ts CHANGED
@@ -27,6 +27,8 @@ import type { AddressLike, KitRpc } from "../client/types";
27
27
 
28
28
  const DISCRIMINATOR_OFFSET = 0n;
29
29
  const OWNER_OFFSET = 8n;
30
+ /** WriterPosition layout: discriminator(8) + writer_authority(32) + option_pool(32). Used for getProgramAccounts memcmp. */
31
+ const WRITER_POSITION_OPTION_POOL_OFFSET = 40n;
30
32
  const ACTIVE_POOL_LOAN_STATUS = 1;
31
33
 
32
34
  type ListedAccount<T> = {
@@ -68,13 +70,24 @@ function ownerFilter(owner: AddressLike) {
68
70
  } as const;
69
71
  }
70
72
 
73
+ function optionPoolFilter(optionPool: AddressLike) {
74
+ return {
75
+ memcmp: {
76
+ offset: WRITER_POSITION_OPTION_POOL_OFFSET,
77
+ encoding: "base58",
78
+ bytes: toAddress(optionPool),
79
+ },
80
+ } as const;
81
+ }
82
+
71
83
  async function fetchAndDecodeProgramAccounts<T>(
72
84
  rpc: KitRpc,
73
85
  decoder: { decode: (value: Uint8Array) => T },
74
- filters: ReadonlyArray<unknown>
86
+ filters: ReadonlyArray<unknown>,
87
+ programAddress: AddressLike = PROGRAM_ID
75
88
  ): Promise<Array<ListedAccount<T>>> {
76
89
  const response = await rpc
77
- .getProgramAccounts(PROGRAM_ID, {
90
+ .getProgramAccounts(toAddress(programAddress), {
78
91
  encoding: "base64",
79
92
  filters: filters as never,
80
93
  })
@@ -105,6 +118,27 @@ export async function fetchWriterPositionsByWriter(
105
118
  ]);
106
119
  }
107
120
 
121
+ /**
122
+ * Fetches all WriterPosition accounts for a single option pool.
123
+ * WriterPosition layout: option_pool at offset 40, 32 bytes (memcmp filter).
124
+ */
125
+ export async function fetchWriterPositionsForPool(
126
+ rpc: KitRpc,
127
+ optionPool: AddressLike,
128
+ programId?: AddressLike
129
+ ): Promise<Array<ListedAccount<WriterPosition>>> {
130
+ return fetchAndDecodeProgramAccounts(
131
+ rpc,
132
+ getWriterPositionDecoder(),
133
+ [
134
+ discriminatorFilter(WRITER_POSITION_DISCRIMINATOR),
135
+ optionPoolFilter(optionPool),
136
+ { dataSize: BigInt(getWriterPositionSize()) },
137
+ ],
138
+ programId ?? PROGRAM_ID
139
+ );
140
+ }
141
+
108
142
  export async function fetchPositionAccountsByBuyer(
109
143
  rpc: KitRpc,
110
144
  buyer: AddressLike
package/accounts/pdas.ts CHANGED
@@ -258,6 +258,39 @@ export async function deriveEscrowAuthorityPda(
258
258
  });
259
259
  }
260
260
 
261
+ /**
262
+ * Derives the PoolLoan PDA using the canonical seeds: vault, maker, nonce.
263
+ * Matches the program's derivation in omlp_context.rs (BorrowFromPool).
264
+ *
265
+ * @param vault - OMLP vault PDA (from deriveVaultPda)
266
+ * @param maker - Writer/borrower pubkey
267
+ * @param nonce - Loan nonce (u64)
268
+ * @param programId - Optional program ID
269
+ */
270
+ export async function derivePoolLoanPdaFromVault(
271
+ vault: AddressLike,
272
+ maker: AddressLike,
273
+ nonce: bigint | number,
274
+ programId: AddressLike = PROGRAM_ID
275
+ ): Promise<readonly [Address, number]> {
276
+ const addressEncoder = getAddressEncoder();
277
+ return getProgramDerivedAddress({
278
+ programAddress: toAddress(programId),
279
+ seeds: [
280
+ new TextEncoder().encode("pool_loan"),
281
+ addressEncoder.encode(toAddress(vault)),
282
+ addressEncoder.encode(toAddress(maker)),
283
+ u64ToLeBytes(nonce),
284
+ ],
285
+ });
286
+ }
287
+
288
+ /**
289
+ * Derives the PoolLoan PDA using writer position and nonce.
290
+ *
291
+ * @deprecated This derivation does not match the program. Use {@link derivePoolLoanPdaFromVault}
292
+ * with (vault, maker, nonce) instead. Program seeds are: pool_loan, vault, maker, nonce (u64 le).
293
+ */
261
294
  export async function derivePoolLoanPda(
262
295
  writerPosition: AddressLike,
263
296
  nonce: bigint | number,
package/index.ts CHANGED
@@ -17,6 +17,9 @@ export * from "./shared/transactions";
17
17
  export * from "./long/builders";
18
18
  export * from "./long/exercise";
19
19
  export * from "./long/quotes";
20
+ export {
21
+ getBuyFromPoolRemainingAccounts,
22
+ } from "./long/remaining-accounts";
20
23
 
21
24
  export * from "./short/builders";
22
25
  export * from "./short/claim-theta";
@@ -0,0 +1,31 @@
1
+ import type { RemainingAccountInput } from "../shared/remaining-accounts";
2
+ import type { AddressLike, KitRpc } from "../client/types";
3
+ import { PROGRAM_ID } from "../client/program";
4
+ import { fetchWriterPositionsForPool } from "../accounts/list";
5
+
6
+ /**
7
+ * Returns remaining_accounts for the buy_from_pool instruction: WriterPosition
8
+ * accounts for the given pool, sorted by unsoldQty ascending (smallest first),
9
+ * in the shape expected by buildBuyFromPoolTransaction and
10
+ * buildBuyFromPoolTransactionWithDerivation.
11
+ */
12
+ export async function getBuyFromPoolRemainingAccounts(
13
+ rpc: KitRpc,
14
+ optionPool: AddressLike,
15
+ programId?: AddressLike
16
+ ): Promise<RemainingAccountInput[]> {
17
+ const positions = await fetchWriterPositionsForPool(
18
+ rpc,
19
+ optionPool,
20
+ programId ?? PROGRAM_ID
21
+ );
22
+ const sorted = [...positions].sort((a, b) => {
23
+ const aQty = a.data.unsoldQty;
24
+ const bQty = b.data.unsoldQty;
25
+ return aQty < bQty ? -1 : aQty > bQty ? 1 : 0;
26
+ });
27
+ return sorted.map(({ address }) => ({
28
+ address,
29
+ isWritable: true,
30
+ }));
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicentral/sos-sdk",
3
- "version": "0.2.12",
3
+ "version": "0.3.0-alpha.1",
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",
@@ -23,6 +23,9 @@
23
23
  "decimal.js": "^10.4.3"
24
24
  },
25
25
  "scripts": {
26
- "typecheck": "tsc --project tsconfig.json --noEmit"
26
+ "typecheck": "tsc --project tsconfig.json --noEmit",
27
+ "publish-beta": "dotenv -e .env -- pnpm publish --access public --tag beta",
28
+ "publish-alpha": "dotenv -e .env -- pnpm publish --access public --tag alpha",
29
+ "deprecate": "dotenv -e .env -- pnpm exec npm deprecate"
27
30
  }
28
31
  }
package/short/builders.ts CHANGED
@@ -8,11 +8,14 @@ import {
8
8
  import type { Instruction } from "@solana/kit";
9
9
  import { toAddress } from "../client/program";
10
10
  import type { AddressLike, BuiltTransaction, KitRpc } from "../client/types";
11
+ import { fetchVault } from "../accounts/fetchers";
12
+ import { fetchPoolLoansByMaker } from "../accounts/list";
11
13
  import { resolveOptionAccounts } from "../accounts/resolve-option";
12
14
  import {
13
15
  deriveAssociatedTokenAddress,
14
16
  deriveMakerCollateralSharePda,
15
17
  deriveMetadataPda,
18
+ deriveVaultPda,
16
19
  deriveWriterPositionPda,
17
20
  } from "../accounts/pdas";
18
21
  import { assertNonNegativeAmount, assertPositiveAmount } from "../shared/amounts";
@@ -335,9 +338,90 @@ export interface BuildUnwindWriterUnsoldTransactionWithDerivationParams {
335
338
  programId?: AddressLike;
336
339
  omlpVault?: AddressLike;
337
340
  feeWallet?: AddressLike;
341
+ /**
342
+ * When repaying pool loans: [Vault PDA, PoolLoan₁, PoolLoan₂, ...] (all writable).
343
+ * Prefer {@link buildUnwindWriterUnsoldWithLoanRepayment} to build this automatically.
344
+ */
338
345
  remainingAccounts?: RemainingAccountInput[];
339
346
  }
340
347
 
348
+ export interface BuildUnwindWriterUnsoldWithLoanRepaymentParams {
349
+ underlyingAsset: AddressLike;
350
+ optionType: OptionType;
351
+ strikePrice: number;
352
+ expirationDate: bigint | number;
353
+ writer: AddressLike;
354
+ unwindQty: bigint | number;
355
+ rpc: KitRpc;
356
+ programId?: AddressLike;
357
+ /** Override when pool fetch is not used; otherwise resolved from option pool. */
358
+ underlyingMint?: AddressLike;
359
+ }
360
+
361
+ /**
362
+ * Builds an unwind_writer_unsold transaction that also repays any active pool loans
363
+ * 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).
365
+ *
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.
369
+ */
370
+ export async function buildUnwindWriterUnsoldWithLoanRepayment(
371
+ params: BuildUnwindWriterUnsoldWithLoanRepaymentParams
372
+ ): Promise<BuiltTransaction> {
373
+ const resolved = await resolveOptionAccounts({
374
+ underlyingAsset: params.underlyingAsset,
375
+ optionType: params.optionType,
376
+ strikePrice: params.strikePrice,
377
+ expirationDate: params.expirationDate,
378
+ programId: params.programId,
379
+ rpc: params.rpc,
380
+ });
381
+
382
+ const underlyingMint = params.underlyingMint ?? resolved.underlyingMint;
383
+ invariant(
384
+ !!underlyingMint,
385
+ "underlyingMint is required; ensure rpc is provided and option pool is initialized, or pass underlyingMint."
386
+ );
387
+
388
+ const [vaultPda] = await deriveVaultPda(underlyingMint, params.programId);
389
+ const vaultPdaStr = toAddress(vaultPda);
390
+
391
+ const [loans, vault] = await Promise.all([
392
+ fetchPoolLoansByMaker(params.rpc, params.writer),
393
+ fetchVault(params.rpc, vaultPda),
394
+ ]);
395
+
396
+ const vaultLoans = loans.filter(
397
+ (item) => toAddress(item.data.vault) === vaultPdaStr
398
+ );
399
+
400
+ const remainingAccounts: RemainingAccountInput[] = [
401
+ { address: vaultPda, isWritable: true },
402
+ ...vaultLoans.map((item) => ({ address: item.address, isWritable: true })),
403
+ ];
404
+
405
+ const omlpVault = await deriveAssociatedTokenAddress(vaultPda, underlyingMint);
406
+ const feeWallet = vault
407
+ ? await deriveAssociatedTokenAddress(vault.feeWallet, underlyingMint)
408
+ : undefined;
409
+
410
+ return buildUnwindWriterUnsoldTransactionWithDerivation({
411
+ underlyingAsset: params.underlyingAsset,
412
+ optionType: params.optionType,
413
+ strikePrice: params.strikePrice,
414
+ expirationDate: params.expirationDate,
415
+ writer: params.writer,
416
+ unwindQty: params.unwindQty,
417
+ rpc: params.rpc,
418
+ programId: params.programId,
419
+ omlpVault,
420
+ feeWallet,
421
+ remainingAccounts,
422
+ });
423
+ }
424
+
341
425
  export async function buildUnwindWriterUnsoldTransactionWithDerivation(
342
426
  params: BuildUnwindWriterUnsoldTransactionWithDerivationParams
343
427
  ): Promise<BuiltTransaction> {