@epicentral/sos-sdk 0.5.0-alpha.1 → 0.5.0-alpha.10
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 +49 -10
- package/client/lookup-table.ts +1 -1
- package/generated/programs/optionProgram.ts +2 -2
- package/long/builders.ts +60 -2
- package/omlp/builders.ts +22 -2
- package/omlp/service.ts +41 -4
- package/package.json +1 -1
- package/short/builders.ts +42 -8
- package/short/preflight.ts +34 -4
package/README.md
CHANGED
|
@@ -50,14 +50,14 @@ Additional modules:
|
|
|
50
50
|
| `buildBuyFromPoolMarketOrderTransactionWithDerivation` | High-level market-order buy builder (refetches pool + remaining accounts, applies premium cap buffer). |
|
|
51
51
|
| `buildBuyFromPoolTransactionWithDerivation` | Builds buy-from-pool transaction; resolves accounts from option identity. |
|
|
52
52
|
| `preflightBuyFromPoolMarketOrder` | Buy preflight helper for liquidity + remaining-account coverage checks. |
|
|
53
|
-
| `buildCloseLongToPoolTransactionWithDerivation` | Builds close-long-to-pool transaction. |
|
|
53
|
+
| `buildCloseLongToPoolTransactionWithDerivation` | Builds close-long-to-pool transaction; by default appends CloseAccount for buyer LONG ATA and unwraps WSOL payout when underlying is SOL. |
|
|
54
54
|
| `getBuyFromPoolRemainingAccounts` | Builds remaining_accounts for buy (writer positions, etc.). |
|
|
55
55
|
|
|
56
56
|
### Short (Writer) Flows
|
|
57
57
|
|
|
58
58
|
| Function | Description |
|
|
59
59
|
|----------|-------------|
|
|
60
|
-
| `buildOptionMintTransactionWithDerivation` | Builds option mint (write) transaction. Supports multi-collateral: use `collateralMint` to back positions with any supported asset (USDC, BTC, SOL, etc.). |
|
|
60
|
+
| `buildOptionMintTransactionWithDerivation` | Builds option mint (write) transaction. By default appends CloseAccount for the maker's LONG token account after mint (reclaim rent). Supports multi-collateral: use `collateralMint` to back positions with any supported asset (USDC, BTC, SOL, etc.). |
|
|
61
61
|
| `buildUnwindWriterUnsoldTransactionWithDerivation` | Builds unwind unsold transaction. |
|
|
62
62
|
| `buildUnwindWriterUnsoldWithLoanRepayment` | **Unwind + repay pool loans in one tx.** Use when closing unsold shorts that borrowed from OMLP. |
|
|
63
63
|
| `buildSyncWriterPositionTransaction` | Syncs writer position with pool accumulators. |
|
|
@@ -73,12 +73,29 @@ Additional modules:
|
|
|
73
73
|
| Function | Description |
|
|
74
74
|
|----------|-------------|
|
|
75
75
|
| `buildDepositToPositionTransaction` | Deposits liquidity to OMLP. |
|
|
76
|
-
| `buildWithdrawFromPositionTransaction` | Withdraws liquidity
|
|
77
|
-
| `withdrawAllFromPosition` | Withdraws full position (
|
|
78
|
-
| `withdrawInterestFromPosition` | Withdraws
|
|
76
|
+
| `buildWithdrawFromPositionTransaction` | Withdraws liquidity; supports optional same-tx WSOL unwrap via `unwrapSol` + `vaultMint`. |
|
|
77
|
+
| `withdrawAllFromPosition` | Withdraws full position (principal + proportional interest, including pending index accrual, capped by pool liquidity). |
|
|
78
|
+
| `withdrawInterestFromPosition` | Withdraws interest only (realized + pending index accrual, capped by pool liquidity). |
|
|
79
79
|
|
|
80
80
|
Borrow/repay for writers: use `buildOptionMintTransactionWithDerivation` (with vault/poolLoan) and `buildRepayPoolLoanFromCollateralInstruction` or `buildUnwindWriterUnsoldWithLoanRepayment`.
|
|
81
81
|
|
|
82
|
+
### Token account closing (option mint and close long)
|
|
83
|
+
|
|
84
|
+
- **Option mint (seller/writer):** After `option_mint`, all LONG tokens go to the pool escrow; the maker's LONG ATA is left with zero balance. The SDK **automatically appends an SPL CloseAccount instruction** (when `closeMakerLongAccount` is not set to `false`) so the maker reclaims rent. Use `buildOptionMintTransaction` or `buildOptionMintTransactionWithDerivation`; pass `closeMakerLongAccount: false` to skip closing the LONG ATA.
|
|
85
|
+
- **Close long (buyer):** When the buyer closes or exercises early via `close_long_to_pool`, LONG tokens are returned to the pool and payout is sent to the buyer's payout ATA. The SDK can:
|
|
86
|
+
- **Close the buyer's LONG token account** after the close instruction so rent is reclaimed. Use `closeLongTokenAccount: true` (default for `buildCloseLongToPoolTransactionWithDerivation`); set to `false` for **partial** closes (the LONG ATA still holds remaining tokens).
|
|
87
|
+
- **Unwrap WSOL payout** when the option underlying is SOL: append CloseAccount on the payout ATA so the buyer receives native SOL. Use `unwrapPayoutSol: true` (default for WSOL in the derivation builder); set to `false` to keep payout as WSOL.
|
|
88
|
+
|
|
89
|
+
### OMLP withdraw behavior
|
|
90
|
+
|
|
91
|
+
- Interest is allocated proportionally via the vault interest-per-share index.
|
|
92
|
+
- On-chain `withdraw_from_position` syncs pending interest before transferring funds, so a lender withdrawal automatically includes their proportional earned interest when available.
|
|
93
|
+
- `withdrawAllFromPosition` and `withdrawInterestFromPosition` compute pending interest from `accInterestPerShareFp` and `interestIndexSnapshotFp`, then cap by `poolAvailable = totalLiquidity - totalLoans`.
|
|
94
|
+
- Optional WSOL unwrap in the same transaction:
|
|
95
|
+
- Set `unwrapSol: true` and provide `vaultMint`.
|
|
96
|
+
- If `vaultMint === NATIVE_MINT`, SDK appends a `CloseAccount` after withdraw to unwrap WSOL ATA to native SOL.
|
|
97
|
+
- For non-WSOL mints, the same builder remains token-agnostic and does not append unwrap instructions.
|
|
98
|
+
|
|
82
99
|
### WSOL / Token Helpers
|
|
83
100
|
|
|
84
101
|
| Function | Description |
|
|
@@ -179,7 +196,8 @@ Use **`preflightUnwindWriterUnsold`** before building the transaction to get:
|
|
|
179
196
|
- **Collateral return calculation** (proportional share, returnable amount).
|
|
180
197
|
- Collateral-vault available, wallet fallback required, and shortfall.
|
|
181
198
|
- **Top-up UX fields:** `collateralVaultShortfall`, `needsWalletTopUp`.
|
|
182
|
-
-
|
|
199
|
+
- WSOL repay metadata: `solTopUpRequired`, `topUpRequiredForRepay`, `nativeSolAvailable`.
|
|
200
|
+
- `canRepayFully`, which now reflects effective repay solvency (including native SOL top-up capacity for WSOL paths).
|
|
183
201
|
|
|
184
202
|
If there are no active pool loans for that vault, the API still works and passes empty `remaining_accounts`.
|
|
185
203
|
|
|
@@ -205,10 +223,6 @@ const preflight = await preflightUnwindWriterUnsold({
|
|
|
205
223
|
rpc,
|
|
206
224
|
});
|
|
207
225
|
|
|
208
|
-
if (!preflight.canRepayFully) {
|
|
209
|
-
throw new Error(`Unwind blocked. Shortfall: ${preflight.summary.shortfall.toString()}`);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
226
|
const tx = await buildUnwindWriterUnsoldWithLoanRepayment({
|
|
213
227
|
underlyingAsset,
|
|
214
228
|
optionType,
|
|
@@ -217,9 +231,15 @@ const tx = await buildUnwindWriterUnsoldWithLoanRepayment({
|
|
|
217
231
|
writer,
|
|
218
232
|
unwindQty,
|
|
219
233
|
rpc,
|
|
234
|
+
includeWrapForShortfall: true, // for WSOL paths, auto-wrap net top-up when needed
|
|
235
|
+
writerSigner: walletSigner, // required when wrapping is needed
|
|
220
236
|
});
|
|
221
237
|
```
|
|
222
238
|
|
|
239
|
+
Notes:
|
|
240
|
+
- For WSOL underlyings, the builder wraps only the net required amount: `max(0, walletFallbackRequired - walletFallbackAvailable)`.
|
|
241
|
+
- If repayment is still insolvent after considering vault + fallback + native SOL top-up capacity, the builder throws an actionable insolvency error.
|
|
242
|
+
|
|
223
243
|
## Usage Examples
|
|
224
244
|
|
|
225
245
|
### Buy From Pool (market order, high-level)
|
|
@@ -283,6 +303,25 @@ The program uses distinct error codes for liquidity failures:
|
|
|
283
303
|
1. Run `preflightBuyFromPoolMarketOrder` for UX gating (checks both pool and active writer liquidity).
|
|
284
304
|
2. Build via `buildBuyFromPoolMarketOrderTransactionWithDerivation` – it refetches pool + remaining accounts and asserts active writer liquidity >= requested quantity before building.
|
|
285
305
|
|
|
306
|
+
### Framework deserialization errors (`#3003`)
|
|
307
|
+
|
|
308
|
+
If simulation fails with `custom program error: #3003`, this usually means account deserialization failed before business logic (`60xx`) ran.
|
|
309
|
+
|
|
310
|
+
Check these first:
|
|
311
|
+
|
|
312
|
+
- `buyer_position` account shape/size (`146` bytes expected).
|
|
313
|
+
- `market_data` account shape/size (`128` bytes expected).
|
|
314
|
+
- `priceUpdate` is a valid Pyth Receiver `PriceUpdateV2` account.
|
|
315
|
+
- Account list/order matches the generated instruction layout.
|
|
316
|
+
|
|
317
|
+
This is different from liquidity failures (`6042/6043`) and should be debugged as an account wiring/layout issue.
|
|
318
|
+
|
|
319
|
+
### Oracle inputs (asset-agnostic)
|
|
320
|
+
|
|
321
|
+
- Keep oracle handling universal across assets.
|
|
322
|
+
- Provide `priceUpdate` for the selected underlying and allow program-side validation against `market_data.pyth_feed_id`.
|
|
323
|
+
- Avoid hardcoding a single feed/account address in shared SDK integration flows.
|
|
324
|
+
|
|
286
325
|
### Unwind with loan repayment
|
|
287
326
|
|
|
288
327
|
```ts
|
package/client/lookup-table.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { PROGRAM_ID } from "./program";
|
|
|
4
4
|
import type { KitRpc } from "./types";
|
|
5
5
|
|
|
6
6
|
export const LOOKUP_TABLE_ADDRESSES: Record<"devnet" | "mainnet", Address | null> = {
|
|
7
|
-
devnet: address("
|
|
7
|
+
devnet: address("7aGuGaRRdDA4KtsaWSCvFvAexv4sg8A8kkFKXuvsB58v"),
|
|
8
8
|
mainnet: null,
|
|
9
9
|
};
|
|
10
10
|
|
|
@@ -88,7 +88,7 @@ import {
|
|
|
88
88
|
} from "../instructions";
|
|
89
89
|
|
|
90
90
|
export const OPTION_PROGRAM_PROGRAM_ADDRESS =
|
|
91
|
-
"
|
|
91
|
+
"Box8aCPTes6zAdgAh2e25Wo64PbFfoi9T4ToiZsfetVo" as Address<"Box8aCPTes6zAdgAh2e25Wo64PbFfoi9T4ToiZsfetVo">;
|
|
92
92
|
|
|
93
93
|
export enum OptionProgramAccount {
|
|
94
94
|
CollateralPool,
|
|
@@ -679,7 +679,7 @@ export function identifyOptionProgramInstruction(
|
|
|
679
679
|
}
|
|
680
680
|
|
|
681
681
|
export type ParsedOptionProgramInstruction<
|
|
682
|
-
TProgram extends string = "
|
|
682
|
+
TProgram extends string = "Box8aCPTes6zAdgAh2e25Wo64PbFfoi9T4ToiZsfetVo",
|
|
683
683
|
> =
|
|
684
684
|
| ({
|
|
685
685
|
instructionType: OptionProgramInstruction.AcceptAdmin;
|
package/long/builders.ts
CHANGED
|
@@ -17,7 +17,11 @@ import {
|
|
|
17
17
|
type RemainingAccountInput,
|
|
18
18
|
} from "../shared/remaining-accounts";
|
|
19
19
|
import type { OptionType } from "../generated/types";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
getCloseAccountInstruction,
|
|
22
|
+
getCreateAssociatedTokenIdempotentInstructionWithAddress,
|
|
23
|
+
NATIVE_MINT,
|
|
24
|
+
} from "../wsol/instructions";
|
|
21
25
|
import { fetchOptionPool } from "../accounts/fetchers";
|
|
22
26
|
import { getBuyFromPoolRemainingAccounts } from "./remaining-accounts";
|
|
23
27
|
import { fetchWriterPositionsForPool } from "../accounts/list";
|
|
@@ -58,6 +62,16 @@ export interface BuildCloseLongToPoolParams {
|
|
|
58
62
|
minPayoutAmount: bigint | number;
|
|
59
63
|
buyerPosition?: AddressLike;
|
|
60
64
|
omlpVault?: AddressLike;
|
|
65
|
+
/**
|
|
66
|
+
* When true, appends an SPL CloseAccount to close the buyer's LONG token account after close_long_to_pool (reclaim rent).
|
|
67
|
+
* Set to true only when closing the entire position; for partial closes the LONG ATA still holds remaining tokens.
|
|
68
|
+
*/
|
|
69
|
+
closeLongTokenAccount?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* When true and underlying is WSOL, appends an SPL CloseAccount to unwrap the payout ATA so the buyer receives native SOL.
|
|
72
|
+
* Ignored when underlyingMint is not WSOL.
|
|
73
|
+
*/
|
|
74
|
+
unwrapPayoutSol?: boolean;
|
|
61
75
|
remainingAccounts?: RemainingAccountInput[];
|
|
62
76
|
}
|
|
63
77
|
|
|
@@ -347,7 +361,32 @@ export async function buildCloseLongToPoolTransaction(
|
|
|
347
361
|
params: BuildCloseLongToPoolParams
|
|
348
362
|
): Promise<BuiltTransaction> {
|
|
349
363
|
const instruction = await buildCloseLongToPoolInstruction(params);
|
|
350
|
-
|
|
364
|
+
const instructions = [instruction];
|
|
365
|
+
|
|
366
|
+
if (params.closeLongTokenAccount === true) {
|
|
367
|
+
instructions.push(
|
|
368
|
+
getCloseAccountInstruction(
|
|
369
|
+
params.buyerLongAccount,
|
|
370
|
+
params.buyer,
|
|
371
|
+
params.buyer
|
|
372
|
+
)
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const shouldUnwrapPayout =
|
|
377
|
+
params.unwrapPayoutSol === true &&
|
|
378
|
+
toAddress(params.underlyingMint) === toAddress(NATIVE_MINT);
|
|
379
|
+
if (shouldUnwrapPayout) {
|
|
380
|
+
instructions.push(
|
|
381
|
+
getCloseAccountInstruction(
|
|
382
|
+
params.buyerPayoutAccount,
|
|
383
|
+
params.buyer,
|
|
384
|
+
params.buyer
|
|
385
|
+
)
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return { instructions };
|
|
351
390
|
}
|
|
352
391
|
|
|
353
392
|
export interface BuildCloseLongToPoolTransactionWithDerivationParams {
|
|
@@ -365,6 +404,16 @@ export interface BuildCloseLongToPoolTransactionWithDerivationParams {
|
|
|
365
404
|
programId?: AddressLike;
|
|
366
405
|
buyerPosition?: AddressLike;
|
|
367
406
|
omlpVault?: AddressLike;
|
|
407
|
+
/**
|
|
408
|
+
* When true (default), appends CloseAccount for the buyer's LONG token account after close_long_to_pool.
|
|
409
|
+
* Set to false when doing a partial close (LONG ATA still holds remaining tokens).
|
|
410
|
+
*/
|
|
411
|
+
closeLongTokenAccount?: boolean;
|
|
412
|
+
/**
|
|
413
|
+
* When true (default for WSOL underlying), appends CloseAccount to unwrap payout WSOL ATA to native SOL.
|
|
414
|
+
* Only applies when option underlying is WSOL.
|
|
415
|
+
*/
|
|
416
|
+
unwrapPayoutSol?: boolean;
|
|
368
417
|
remainingAccounts?: RemainingAccountInput[];
|
|
369
418
|
}
|
|
370
419
|
|
|
@@ -396,6 +445,13 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
|
396
445
|
params.programId
|
|
397
446
|
))[0];
|
|
398
447
|
|
|
448
|
+
const isWsolUnderlying =
|
|
449
|
+
toAddress(resolved.underlyingMint!) === toAddress(NATIVE_MINT);
|
|
450
|
+
const closeLongTokenAccount =
|
|
451
|
+
params.closeLongTokenAccount !== false;
|
|
452
|
+
const unwrapPayoutSol =
|
|
453
|
+
params.unwrapPayoutSol !== false && isWsolUnderlying;
|
|
454
|
+
|
|
399
455
|
return buildCloseLongToPoolTransaction({
|
|
400
456
|
optionPool: resolved.optionPool,
|
|
401
457
|
optionAccount: resolved.optionAccount,
|
|
@@ -414,6 +470,8 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
|
414
470
|
minPayoutAmount: params.minPayoutAmount,
|
|
415
471
|
buyerPosition,
|
|
416
472
|
omlpVault: params.omlpVault,
|
|
473
|
+
closeLongTokenAccount,
|
|
474
|
+
unwrapPayoutSol,
|
|
417
475
|
remainingAccounts: params.remainingAccounts,
|
|
418
476
|
});
|
|
419
477
|
}
|
package/omlp/builders.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { Instruction } from "@solana/kit";
|
|
|
6
6
|
import { toAddress } from "../client/program";
|
|
7
7
|
import type { AddressLike, BuiltTransaction } from "../client/types";
|
|
8
8
|
import { assertPositiveAmount } from "../shared/amounts";
|
|
9
|
+
import { getCloseAccountInstruction, NATIVE_MINT } from "../wsol/instructions";
|
|
9
10
|
|
|
10
11
|
export interface BuildDepositToPositionParams {
|
|
11
12
|
vault: AddressLike;
|
|
@@ -23,6 +24,8 @@ export interface BuildWithdrawFromPositionParams {
|
|
|
23
24
|
lender: AddressLike;
|
|
24
25
|
amount: bigint | number;
|
|
25
26
|
position?: AddressLike;
|
|
27
|
+
unwrapSol?: boolean;
|
|
28
|
+
vaultMint?: AddressLike;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export async function buildDepositToPositionInstruction(
|
|
@@ -69,6 +72,23 @@ export async function buildWithdrawFromPositionInstruction(
|
|
|
69
72
|
export async function buildWithdrawFromPositionTransaction(
|
|
70
73
|
params: BuildWithdrawFromPositionParams
|
|
71
74
|
): Promise<BuiltTransaction> {
|
|
72
|
-
const
|
|
73
|
-
|
|
75
|
+
const withdrawInstruction = await buildWithdrawFromPositionInstruction(params);
|
|
76
|
+
const instructions: Instruction<string>[] = [withdrawInstruction];
|
|
77
|
+
|
|
78
|
+
const shouldUnwrapSol =
|
|
79
|
+
params.unwrapSol === true &&
|
|
80
|
+
params.vaultMint !== undefined &&
|
|
81
|
+
toAddress(params.vaultMint) === toAddress(NATIVE_MINT);
|
|
82
|
+
|
|
83
|
+
if (shouldUnwrapSol) {
|
|
84
|
+
instructions.push(
|
|
85
|
+
getCloseAccountInstruction(
|
|
86
|
+
params.lenderTokenAccount,
|
|
87
|
+
params.lender,
|
|
88
|
+
params.lender
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { instructions };
|
|
74
94
|
}
|
package/omlp/service.ts
CHANGED
|
@@ -12,10 +12,25 @@ import {
|
|
|
12
12
|
type BuildWithdrawFromPositionParams,
|
|
13
13
|
} from "./builders";
|
|
14
14
|
|
|
15
|
+
const INTEREST_FP_SCALE = 1_000_000_000_000n;
|
|
16
|
+
|
|
15
17
|
function positiveDiff(a: bigint, b: bigint): bigint {
|
|
16
18
|
return a > b ? a - b : 0n;
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
function calculatePendingInterest(
|
|
22
|
+
deposited: bigint,
|
|
23
|
+
vaultAccInterestPerShareFp: bigint,
|
|
24
|
+
positionInterestIndexSnapshotFp: bigint
|
|
25
|
+
): bigint {
|
|
26
|
+
const deltaFp = positiveDiff(
|
|
27
|
+
vaultAccInterestPerShareFp,
|
|
28
|
+
positionInterestIndexSnapshotFp
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return (deposited * deltaFp) / INTEREST_FP_SCALE;
|
|
32
|
+
}
|
|
33
|
+
|
|
19
34
|
export async function depositToPosition(
|
|
20
35
|
params: BuildDepositToPositionParams
|
|
21
36
|
) {
|
|
@@ -50,14 +65,23 @@ export async function withdrawAllFromPosition(
|
|
|
50
65
|
position.totalInterestEarned,
|
|
51
66
|
position.interestClaimed
|
|
52
67
|
);
|
|
53
|
-
const
|
|
68
|
+
const pendingInterest = calculatePendingInterest(
|
|
69
|
+
position.deposited,
|
|
70
|
+
vault.accInterestPerShareFp,
|
|
71
|
+
position.interestIndexSnapshotFp
|
|
72
|
+
);
|
|
73
|
+
const userMax = position.deposited + unclaimedInterest + pendingInterest;
|
|
54
74
|
const poolAvailable = positiveDiff(vault.totalLiquidity, vault.totalLoans);
|
|
55
75
|
const amount = userMax < poolAvailable ? userMax : poolAvailable;
|
|
56
76
|
if (amount <= 0n) {
|
|
57
77
|
throw new Error("No withdrawable balance available right now.");
|
|
58
78
|
}
|
|
59
79
|
|
|
60
|
-
const built = await buildWithdrawFromPositionTransaction({
|
|
80
|
+
const built = await buildWithdrawFromPositionTransaction({
|
|
81
|
+
...params,
|
|
82
|
+
amount,
|
|
83
|
+
vaultMint: vault.mint,
|
|
84
|
+
});
|
|
61
85
|
return { instructions: built.instructions, amount };
|
|
62
86
|
}
|
|
63
87
|
|
|
@@ -83,13 +107,26 @@ export async function withdrawInterestFromPosition(
|
|
|
83
107
|
position.totalInterestEarned,
|
|
84
108
|
position.interestClaimed
|
|
85
109
|
);
|
|
110
|
+
const pendingInterest = calculatePendingInterest(
|
|
111
|
+
position.deposited,
|
|
112
|
+
vault.accInterestPerShareFp,
|
|
113
|
+
position.interestIndexSnapshotFp
|
|
114
|
+
);
|
|
115
|
+
const totalClaimableInterest = unclaimedInterest + pendingInterest;
|
|
86
116
|
const poolAvailable = positiveDiff(vault.totalLiquidity, vault.totalLoans);
|
|
87
|
-
const amount =
|
|
117
|
+
const amount =
|
|
118
|
+
totalClaimableInterest < poolAvailable
|
|
119
|
+
? totalClaimableInterest
|
|
120
|
+
: poolAvailable;
|
|
88
121
|
if (amount <= 0n) {
|
|
89
122
|
throw new Error("No claimable interest available right now.");
|
|
90
123
|
}
|
|
91
124
|
|
|
92
|
-
const built = await buildWithdrawFromPositionTransaction({
|
|
125
|
+
const built = await buildWithdrawFromPositionTransaction({
|
|
126
|
+
...params,
|
|
127
|
+
amount,
|
|
128
|
+
vaultMint: vault.mint,
|
|
129
|
+
});
|
|
93
130
|
return { instructions: built.instructions, amount };
|
|
94
131
|
}
|
|
95
132
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epicentral/sos-sdk",
|
|
3
|
-
"version": "0.5.0-alpha.
|
|
3
|
+
"version": "0.5.0-alpha.10",
|
|
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
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type RemainingAccountInput,
|
|
26
26
|
} from "../shared/remaining-accounts";
|
|
27
27
|
import {
|
|
28
|
+
getCloseAccountInstruction,
|
|
28
29
|
NATIVE_MINT,
|
|
29
30
|
getCreateAssociatedTokenIdempotentInstructionWithAddress,
|
|
30
31
|
getWrapSOLInstructions,
|
|
@@ -75,6 +76,12 @@ export interface BuildOptionMintParams {
|
|
|
75
76
|
escrowAuthority?: AddressLike;
|
|
76
77
|
escrowTokenAccount?: AddressLike;
|
|
77
78
|
poolLoan?: AddressLike;
|
|
79
|
+
/**
|
|
80
|
+
* When true (default), appends an SPL CloseAccount instruction after option_mint to close the
|
|
81
|
+
* maker's LONG token account (reclaim rent). The program transfers all LONG to escrow, so the
|
|
82
|
+
* maker's LONG ATA is left with zero balance and can be closed in the same transaction.
|
|
83
|
+
*/
|
|
84
|
+
closeMakerLongAccount?: boolean;
|
|
78
85
|
remainingAccounts?: RemainingAccountInput[];
|
|
79
86
|
}
|
|
80
87
|
|
|
@@ -213,7 +220,21 @@ export async function buildOptionMintTransaction(
|
|
|
213
220
|
params: BuildOptionMintParams
|
|
214
221
|
): Promise<BuiltTransaction> {
|
|
215
222
|
const instruction = await buildOptionMintInstruction(params);
|
|
216
|
-
|
|
223
|
+
const instructions: Instruction<string>[] = [instruction];
|
|
224
|
+
|
|
225
|
+
const shouldCloseMakerLong =
|
|
226
|
+
params.closeMakerLongAccount !== false && params.makerLongAccount != null;
|
|
227
|
+
if (shouldCloseMakerLong) {
|
|
228
|
+
instructions.push(
|
|
229
|
+
getCloseAccountInstruction(
|
|
230
|
+
params.makerLongAccount!,
|
|
231
|
+
params.maker,
|
|
232
|
+
params.maker
|
|
233
|
+
)
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return { instructions };
|
|
217
238
|
}
|
|
218
239
|
|
|
219
240
|
export interface BuildOptionMintTransactionWithDerivationParams {
|
|
@@ -468,6 +489,7 @@ export async function buildUnwindWriterUnsoldWithLoanRepayment(
|
|
|
468
489
|
const writerRepaymentAccount =
|
|
469
490
|
params.writerRepaymentAccount ??
|
|
470
491
|
(await deriveAssociatedTokenAddress(params.writer, underlyingMint));
|
|
492
|
+
const writerDefaultRepaymentAta = await deriveAssociatedTokenAddress(params.writer, underlyingMint);
|
|
471
493
|
|
|
472
494
|
const preflight = await preflightUnwindWriterUnsold({
|
|
473
495
|
underlyingAsset: params.underlyingAsset,
|
|
@@ -480,10 +502,22 @@ export async function buildUnwindWriterUnsoldWithLoanRepayment(
|
|
|
480
502
|
programId: params.programId,
|
|
481
503
|
underlyingMint,
|
|
482
504
|
});
|
|
505
|
+
const isWsolPath = toAddress(underlyingMint) === toAddress(NATIVE_MINT);
|
|
506
|
+
const lamportsToWrap =
|
|
507
|
+
preflight.summary.walletFallbackRequired > preflight.summary.walletFallbackAvailable
|
|
508
|
+
? preflight.summary.walletFallbackRequired - preflight.summary.walletFallbackAvailable
|
|
509
|
+
: 0n;
|
|
510
|
+
|
|
483
511
|
invariant(
|
|
484
512
|
preflight.canRepayFully,
|
|
485
|
-
`Unwind cannot fully repay loans:
|
|
513
|
+
`Unwind cannot fully repay loans: required=${preflight.summary.proportionalTotalOwed} available_now=${preflight.summary.collateralVaultAvailable + preflight.summary.walletFallbackAvailable} native_sol_available=${preflight.summary.nativeSolAvailable} remaining_shortfall=${preflight.summary.proportionalTotalOwed - (preflight.summary.collateralVaultAvailable + preflight.summary.walletFallbackAvailable + preflight.summary.nativeSolAvailable)}`
|
|
486
514
|
);
|
|
515
|
+
if (isWsolPath && lamportsToWrap > 0n && !params.includeWrapForShortfall) {
|
|
516
|
+
invariant(
|
|
517
|
+
false,
|
|
518
|
+
`Unwind requires WSOL top-up of ${lamportsToWrap} lamports. Rebuild with includeWrapForShortfall=true and writerSigner.`
|
|
519
|
+
);
|
|
520
|
+
}
|
|
487
521
|
|
|
488
522
|
const unwindTx = await buildUnwindWriterUnsoldTransactionWithDerivation({
|
|
489
523
|
underlyingAsset: params.underlyingAsset,
|
|
@@ -501,11 +535,11 @@ export async function buildUnwindWriterUnsoldWithLoanRepayment(
|
|
|
501
535
|
remainingAccounts,
|
|
502
536
|
});
|
|
503
537
|
|
|
504
|
-
if (
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
538
|
+
if (params.includeWrapForShortfall && isWsolPath && lamportsToWrap > 0n) {
|
|
539
|
+
invariant(
|
|
540
|
+
toAddress(writerRepaymentAccount) === toAddress(writerDefaultRepaymentAta),
|
|
541
|
+
"WSOL auto-wrap requires writerRepaymentAccount to be the writer WSOL ATA."
|
|
542
|
+
);
|
|
509
543
|
invariant(
|
|
510
544
|
!!params.writerSigner,
|
|
511
545
|
"writerSigner is required when includeWrapForShortfall=true for WSOL shortfall top-up."
|
|
@@ -513,7 +547,7 @@ export async function buildUnwindWriterUnsoldWithLoanRepayment(
|
|
|
513
547
|
const wrapInstructions = await getWrapSOLInstructions({
|
|
514
548
|
payer: params.writerSigner,
|
|
515
549
|
owner: params.writer,
|
|
516
|
-
lamports:
|
|
550
|
+
lamports: lamportsToWrap,
|
|
517
551
|
});
|
|
518
552
|
return {
|
|
519
553
|
instructions: [...wrapInstructions, ...unwindTx.instructions],
|
package/short/preflight.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { fetchPoolLoansByMaker } from "../accounts/list";
|
|
|
6
6
|
import { deriveAssociatedTokenAddress, deriveVaultPda, deriveWriterPositionPda } from "../accounts/pdas";
|
|
7
7
|
import { resolveOptionAccounts } from "../accounts/resolve-option";
|
|
8
8
|
import { invariant } from "../shared/errors";
|
|
9
|
+
import { NATIVE_MINT } from "../wsol/instructions";
|
|
9
10
|
|
|
10
11
|
const TOKEN_ACCOUNT_AMOUNT_OFFSET = 64;
|
|
11
12
|
const BPS_DENOMINATOR = 10_000n;
|
|
@@ -78,6 +79,10 @@ export interface UnwindPreflightSummary {
|
|
|
78
79
|
/** For top-up UX: explicit shortfall fields */
|
|
79
80
|
collateralVaultShortfall: bigint;
|
|
80
81
|
needsWalletTopUp: boolean;
|
|
82
|
+
/** WSOL-only top-up metadata */
|
|
83
|
+
solTopUpRequired: bigint;
|
|
84
|
+
topUpRequiredForRepay: boolean;
|
|
85
|
+
nativeSolAvailable: bigint;
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
export interface UnwindPreflightResult {
|
|
@@ -111,9 +116,11 @@ export async function preflightUnwindWriterUnsold(
|
|
|
111
116
|
const underlyingMint = params.underlyingMint ?? resolved.underlyingMint;
|
|
112
117
|
const [vaultPda] = await deriveVaultPda(underlyingMint, params.programId);
|
|
113
118
|
const vaultPdaAddress = toAddress(vaultPda);
|
|
119
|
+
const writerAddress = toAddress(params.writer);
|
|
120
|
+
const writerDefaultRepaymentAta = await deriveAssociatedTokenAddress(params.writer, underlyingMint);
|
|
114
121
|
const writerRepaymentAccount =
|
|
115
122
|
params.writerRepaymentAccount ??
|
|
116
|
-
|
|
123
|
+
writerDefaultRepaymentAta;
|
|
117
124
|
const writerRepaymentAddress = toAddress(writerRepaymentAccount);
|
|
118
125
|
const [writerPositionAddress] = await deriveWriterPositionPda(
|
|
119
126
|
resolved.optionPool,
|
|
@@ -162,6 +169,9 @@ export async function preflightUnwindWriterUnsold(
|
|
|
162
169
|
shortfall: 0n,
|
|
163
170
|
collateralVaultShortfall: 0n,
|
|
164
171
|
needsWalletTopUp: false,
|
|
172
|
+
solTopUpRequired: 0n,
|
|
173
|
+
topUpRequiredForRepay: false,
|
|
174
|
+
nativeSolAvailable: 0n,
|
|
165
175
|
},
|
|
166
176
|
};
|
|
167
177
|
}
|
|
@@ -192,6 +202,9 @@ export async function preflightUnwindWriterUnsold(
|
|
|
192
202
|
shortfall: 0n,
|
|
193
203
|
collateralVaultShortfall: 0n,
|
|
194
204
|
needsWalletTopUp: false,
|
|
205
|
+
solTopUpRequired: 0n,
|
|
206
|
+
topUpRequiredForRepay: false,
|
|
207
|
+
nativeSolAvailable: 0n,
|
|
195
208
|
},
|
|
196
209
|
};
|
|
197
210
|
}
|
|
@@ -242,10 +255,15 @@ export async function preflightUnwindWriterUnsold(
|
|
|
242
255
|
{ principal: 0n, interest: 0n, fees: 0n, owed: 0n }
|
|
243
256
|
);
|
|
244
257
|
|
|
245
|
-
const
|
|
258
|
+
const isWsolRepaymentPath = toAddress(underlyingMint) === toAddress(NATIVE_MINT);
|
|
259
|
+
const canTopUpByWrapping =
|
|
260
|
+
isWsolRepaymentPath && writerRepaymentAddress === toAddress(writerDefaultRepaymentAta);
|
|
261
|
+
const [collateralVaultAvailable, walletFallbackAvailable, nativeBalanceResponse] = await Promise.all([
|
|
246
262
|
fetchTokenAmount(params.rpc, resolved.collateralVault!),
|
|
247
263
|
fetchTokenAmount(params.rpc, writerRepaymentAddress),
|
|
264
|
+
canTopUpByWrapping ? params.rpc.getBalance(writerAddress).send() : Promise.resolve({ value: 0n }),
|
|
248
265
|
]);
|
|
266
|
+
const nativeSolAvailable = nativeBalanceResponse.value;
|
|
249
267
|
|
|
250
268
|
// Calculate proportional obligations for partial unwinds
|
|
251
269
|
const writtenQty = toBigInt(writerPosition.writtenQty);
|
|
@@ -270,6 +288,12 @@ export async function preflightUnwindWriterUnsold(
|
|
|
270
288
|
proportionalTotalOwed > collateralVaultAvailable ? proportionalTotalOwed - collateralVaultAvailable : 0n;
|
|
271
289
|
const totalAvailable = collateralVaultAvailable + walletFallbackAvailable;
|
|
272
290
|
const shortfall = proportionalTotalOwed > totalAvailable ? proportionalTotalOwed - totalAvailable : 0n;
|
|
291
|
+
const solTopUpRequired =
|
|
292
|
+
walletFallbackRequired > walletFallbackAvailable ? walletFallbackRequired - walletFallbackAvailable : 0n;
|
|
293
|
+
const topUpRequiredForRepay = solTopUpRequired > 0n;
|
|
294
|
+
const effectiveTotalAvailable = totalAvailable + (canTopUpByWrapping ? nativeSolAvailable : 0n);
|
|
295
|
+
const effectiveShortfall =
|
|
296
|
+
proportionalTotalOwed > effectiveTotalAvailable ? proportionalTotalOwed - effectiveTotalAvailable : 0n;
|
|
273
297
|
|
|
274
298
|
// For top-up UX: explicit collateral vault shortfall
|
|
275
299
|
const collateralVaultShortfall = returnableCollateral > collateralVaultAvailable
|
|
@@ -279,8 +303,11 @@ export async function preflightUnwindWriterUnsold(
|
|
|
279
303
|
|
|
280
304
|
return {
|
|
281
305
|
canUnwind: true,
|
|
282
|
-
canRepayFully:
|
|
283
|
-
reason:
|
|
306
|
+
canRepayFully: effectiveShortfall === 0n,
|
|
307
|
+
reason:
|
|
308
|
+
effectiveShortfall === 0n
|
|
309
|
+
? undefined
|
|
310
|
+
: "Insufficient combined collateral vault + wallet fallback funds (including SOL top-up capacity for WSOL)",
|
|
284
311
|
writerPositionAddress: String(writerPositionAddress),
|
|
285
312
|
writerRepaymentAccount: String(writerRepaymentAddress),
|
|
286
313
|
collateralVaultAddress: String(resolved.collateralVault),
|
|
@@ -303,6 +330,9 @@ export async function preflightUnwindWriterUnsold(
|
|
|
303
330
|
shortfall,
|
|
304
331
|
collateralVaultShortfall,
|
|
305
332
|
needsWalletTopUp,
|
|
333
|
+
solTopUpRequired,
|
|
334
|
+
topUpRequiredForRepay,
|
|
335
|
+
nativeSolAvailable,
|
|
306
336
|
},
|
|
307
337
|
};
|
|
308
338
|
}
|