@epicentral/sos-sdk 0.9.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -0
- package/AGENTS.md +7 -0
- package/LICENSE +21 -0
- package/README.md +568 -0
- package/accounts/fetchers.ts +196 -0
- package/accounts/list.ts +184 -0
- package/accounts/pdas.ts +325 -0
- package/accounts/resolve-option.ts +104 -0
- package/client/lookup-table.ts +114 -0
- package/client/program.ts +13 -0
- package/client/types.ts +9 -0
- package/generated/accounts/collateralPool.ts +217 -0
- package/generated/accounts/config.ts +156 -0
- package/generated/accounts/escrowState.ts +183 -0
- package/generated/accounts/index.ts +20 -0
- package/generated/accounts/lenderPosition.ts +211 -0
- package/generated/accounts/makerCollateralShare.ts +229 -0
- package/generated/accounts/marketDataAccount.ts +176 -0
- package/generated/accounts/optionAccount.ts +247 -0
- package/generated/accounts/optionPool.ts +285 -0
- package/generated/accounts/poolLoan.ts +232 -0
- package/generated/accounts/positionAccount.ts +201 -0
- package/generated/accounts/vault.ts +366 -0
- package/generated/accounts/writerPosition.ts +327 -0
- package/generated/errors/index.ts +9 -0
- package/generated/errors/optionProgram.ts +476 -0
- package/generated/index.ts +13 -0
- package/generated/instructions/acceptAdmin.ts +230 -0
- package/generated/instructions/autoExerciseAllExpired.ts +685 -0
- package/generated/instructions/autoExerciseExpired.ts +754 -0
- package/generated/instructions/borrowFromPool.ts +619 -0
- package/generated/instructions/buyFromPool.ts +761 -0
- package/generated/instructions/closeLongToPool.ts +762 -0
- package/generated/instructions/closeOption.ts +235 -0
- package/generated/instructions/createEscrowV2.ts +518 -0
- package/generated/instructions/depositCollateral.ts +624 -0
- package/generated/instructions/depositToPosition.ts +429 -0
- package/generated/instructions/index.ts +47 -0
- package/generated/instructions/initCollateralPool.ts +513 -0
- package/generated/instructions/initConfig.ts +279 -0
- package/generated/instructions/initOptionPool.ts +587 -0
- package/generated/instructions/initializeMarketData.ts +359 -0
- package/generated/instructions/liquidateWriterPosition.ts +750 -0
- package/generated/instructions/liquidateWriterPositionRescue.ts +623 -0
- package/generated/instructions/omlpCreateVault.ts +553 -0
- package/generated/instructions/omlpUpdateFeeWallet.ts +473 -0
- package/generated/instructions/omlpUpdateInterestModel.ts +322 -0
- package/generated/instructions/omlpUpdateLiquidationThreshold.ts +304 -0
- package/generated/instructions/omlpUpdateMaintenanceBuffer.ts +304 -0
- package/generated/instructions/omlpUpdateMaxBorrowCap.ts +304 -0
- package/generated/instructions/omlpUpdateMaxLeverage.ts +304 -0
- package/generated/instructions/omlpUpdateProtocolFee.ts +304 -0
- package/generated/instructions/omlpUpdateSupplyLimit.ts +304 -0
- package/generated/instructions/optionExercise.ts +617 -0
- package/generated/instructions/optionMint.ts +1373 -0
- package/generated/instructions/optionValidate.ts +302 -0
- package/generated/instructions/repayPoolLoan.ts +558 -0
- package/generated/instructions/repayPoolLoanFromCollateral.ts +514 -0
- package/generated/instructions/repayPoolLoanFromWallet.ts +542 -0
- package/generated/instructions/settleMakerCollateral.ts +509 -0
- package/generated/instructions/syncWriterPosition.ts +206 -0
- package/generated/instructions/transferAdmin.ts +245 -0
- package/generated/instructions/unwindWriterUnsold.ts +764 -0
- package/generated/instructions/updateImpliedVolatility.ts +226 -0
- package/generated/instructions/updateMarketData.ts +315 -0
- package/generated/instructions/withdrawFromPosition.ts +405 -0
- package/generated/instructions/writeToPool.ts +619 -0
- package/generated/programs/index.ts +9 -0
- package/generated/programs/optionProgram.ts +1144 -0
- package/generated/shared/index.ts +164 -0
- package/generated/types/impliedVolatilityUpdated.ts +73 -0
- package/generated/types/index.ts +28 -0
- package/generated/types/liquidationExecuted.ts +73 -0
- package/generated/types/liquidationRescueEvent.ts +82 -0
- package/generated/types/marketDataInitialized.ts +61 -0
- package/generated/types/marketDataUpdated.ts +69 -0
- package/generated/types/optionClosed.ts +56 -0
- package/generated/types/optionExercised.ts +62 -0
- package/generated/types/optionExpired.ts +49 -0
- package/generated/types/optionMinted.ts +78 -0
- package/generated/types/optionType.ts +38 -0
- package/generated/types/optionValidated.ts +82 -0
- package/generated/types/poolLoanCreated.ts +74 -0
- package/generated/types/poolLoanRepaid.ts +74 -0
- package/generated/types/positionDeposited.ts +73 -0
- package/generated/types/positionWithdrawn.ts +81 -0
- package/generated/types/protocolFeeUpdated.ts +69 -0
- package/generated/types/vaultCreated.ts +60 -0
- package/generated/types/vaultFeeWalletUpdated.ts +67 -0
- package/generated/types/vaultInterestModelUpdated.ts +77 -0
- package/generated/types/vaultLiquidationThresholdUpdated.ts +69 -0
- package/index.ts +68 -0
- package/long/builders.ts +690 -0
- package/long/exercise.ts +123 -0
- package/long/preflight.ts +214 -0
- package/long/quotes.ts +48 -0
- package/long/remaining-accounts.ts +111 -0
- package/omlp/builders.ts +94 -0
- package/omlp/service.ts +136 -0
- package/oracle/switchboard.ts +315 -0
- package/package.json +34 -0
- package/shared/amounts.ts +53 -0
- package/shared/balances.ts +57 -0
- package/shared/errors.ts +12 -0
- package/shared/remaining-accounts.ts +41 -0
- package/shared/trade-config.ts +27 -0
- package/shared/transactions.ts +121 -0
- package/short/builders.ts +874 -0
- package/short/close-option.ts +34 -0
- package/short/pool.ts +189 -0
- package/short/preflight.ts +619 -0
- package/tsconfig.json +13 -0
- package/wsol/instructions.ts +247 -0
package/.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
NPM_TOKEN=EnterNpmTokenHere
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Multi-repo context
|
|
2
|
+
|
|
3
|
+
This repo is the **canonical** `@epicentral/sos-sdk` package (npm). Full workspace instructions (how **option-program**, **sos-sdk**, and **solana-opx** relate, versioning, sync workflow) live in **[`AGENTS.md`](../solana-opx/AGENTS.md)** when repos are siblings, or open `solana-opx/AGENTS.md` from the Cursor multi-root workspace.
|
|
4
|
+
|
|
5
|
+
Package manager for this repo: **`npm`**.
|
|
6
|
+
|
|
7
|
+
When SDK changes must be consumed by OPX: see **`solana-opx/.cursor/rules/program-sdk-opx-sync.mdc`** — consumers using `file:../sos-sdk` need **`pnpm install`** in **solana-opx** after pulls.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Epicentral Labs, DAO LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
# @epicentral/sos-sdk
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @epicentral/sos-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer dependencies: `@solana/kit` (and your RPC client). The SDK uses `KitRpc` for account resolution and fetches.
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
The SDK supports two main flows:
|
|
16
|
+
|
|
17
|
+
- **Long (buyer)** – Buy options from the pool, close positions, exercise.
|
|
18
|
+
- **Short (writer)** – Mint options (write), unwind unsold, settle collateral, claim theta.
|
|
19
|
+
|
|
20
|
+
Additional modules:
|
|
21
|
+
|
|
22
|
+
- **OMLP** – Option Maker Liquidity Pool. Lenders deposit; writers borrow to collateralize shorts.
|
|
23
|
+
- **WSOL** – Helpers for wrapping/unwrapping SOL and creating token accounts.
|
|
24
|
+
|
|
25
|
+
## High-Level Functions and Instructions
|
|
26
|
+
|
|
27
|
+
### Accounts, PDAs, and Fetchers
|
|
28
|
+
|
|
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. |
|
|
45
|
+
|
|
46
|
+
### Long (Buyer) Flows
|
|
47
|
+
|
|
48
|
+
| Function | Description |
|
|
49
|
+
|----------|-------------|
|
|
50
|
+
| `buildBuyFromPoolMarketOrderTransactionWithDerivation` | High-level market-order buy builder (refetches pool + remaining accounts, applies premium cap buffer). |
|
|
51
|
+
| `buildBuyFromPoolTransactionWithDerivation` | Builds buy-from-pool transaction; resolves accounts from option identity. |
|
|
52
|
+
| `preflightBuyFromPoolMarketOrder` | Buy preflight helper for liquidity + remaining-account coverage checks. |
|
|
53
|
+
| `preflightCloseLongToPool` | Close-long preflight: verifies `sum(active WriterPosition.sold_qty) == OptionPool.total_sold_qty` so the on-chain Hamilton completeness check will pass. |
|
|
54
|
+
| `buildCloseLongToPoolTransactionWithDerivation` | Builds close-long-to-pool transaction; auto-populates the complete active WriterPosition set, appends CloseAccount for buyer LONG ATA, and unwraps WSOL payout when underlying is SOL. |
|
|
55
|
+
| `getBuyFromPoolRemainingAccounts` | Builds `remaining_accounts` for `buy_from_pool`: active WriterPositions sorted by `(createdAt asc, pubkey asc)` (FIFO; matches the on-chain ordering check). |
|
|
56
|
+
| `getCloseLongToPoolRemainingAccounts` | Builds `remaining_accounts` for `close_long_to_pool`: **every** active WriterPosition for the pool (required for the Hamilton sum-check). |
|
|
57
|
+
|
|
58
|
+
### Short (Writer) Flows
|
|
59
|
+
|
|
60
|
+
| Function | Description |
|
|
61
|
+
|----------|-------------|
|
|
62
|
+
| `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.). |
|
|
63
|
+
| `buildUnwindWriterUnsoldTransactionWithDerivation` | Builds unwind unsold transaction. |
|
|
64
|
+
| `buildUnwindWriterUnsoldWithLoanRepayment` | **Unwind + repay pool loans in one tx.** Use when closing unsold shorts that borrowed from OMLP. |
|
|
65
|
+
| `buildSyncWriterPositionTransaction` | Syncs writer position with pool accumulators. |
|
|
66
|
+
| `buildSettleMakerCollateralTransaction` | Settles maker collateral after buyer closes (repays principal + accrued interest to OMLP from collateral vault first, then returns remainder to maker). |
|
|
67
|
+
| `buildCloseOptionTransaction` | Closes option token account. |
|
|
68
|
+
| `buildClaimThetaTransaction` | Claims theta (time-decay share) for writer. |
|
|
69
|
+
| `buildRepayPoolLoanFromCollateralInstruction` | Repays pool loan from collateral (short/pool). |
|
|
70
|
+
| `buildRepayPoolLoanInstruction` | Repays pool loan with external funds (short/pool). |
|
|
71
|
+
| `buildRepayPoolLoanFromWalletInstruction` | Repays pool loan from maker's wallet (stuck loan recovery). |
|
|
72
|
+
|
|
73
|
+
### Seller Close Signers
|
|
74
|
+
|
|
75
|
+
- `buildUnwindWriterUnsoldWithLoanRepayment` / `buildUnwindWriterUnsoldTransactionWithDerivation`
|
|
76
|
+
- Requires `writer` transaction signer.
|
|
77
|
+
- On-chain transfers for lender repayment and collateral return are authorized by program PDAs (`collateral_pool` / `option_pool`) where applicable.
|
|
78
|
+
- `buildSettleMakerCollateralTransaction`
|
|
79
|
+
- No maker transaction signer is required by this instruction format.
|
|
80
|
+
- On-chain repayment (`collateral_vault` -> `omlp_vault`) and maker return are signed by the `collateral_pool` PDA.
|
|
81
|
+
- Lender repayment is sourced from collateral vault funds, not maker wallet funds.
|
|
82
|
+
|
|
83
|
+
### OMLP (Lending)
|
|
84
|
+
|
|
85
|
+
| Function | Description |
|
|
86
|
+
|----------|-------------|
|
|
87
|
+
| `buildDepositToPositionTransaction` | Deposits liquidity to OMLP. |
|
|
88
|
+
| `buildWithdrawFromPositionTransaction` | Withdraws liquidity; supports optional same-tx WSOL unwrap via `unwrapSol` + `vaultMint`. |
|
|
89
|
+
| `withdrawAllFromPosition` | Withdraws full position (principal + proportional interest, including pending index accrual, capped by pool liquidity). |
|
|
90
|
+
| `withdrawInterestFromPosition` | Withdraws interest only (realized + pending index accrual, capped by pool liquidity). |
|
|
91
|
+
|
|
92
|
+
Borrow/repay for writers: use `buildOptionMintTransactionWithDerivation` (with vault/poolLoan) and `buildRepayPoolLoanFromCollateralInstruction` or `buildUnwindWriterUnsoldWithLoanRepayment`.
|
|
93
|
+
|
|
94
|
+
**PoolLoan ↔ WriterPosition:** Each `PoolLoan` stores `writer_position` (the `WriterPosition` PDA for that option pool + maker). `borrow_from_pool` records it; `option_mint` checks it matches the mint’s writer account. Clients should filter loans with `loan.writerPosition === writerPositionAddress` (same vault, active status). Standard repay instructions require `optionPool` and `writerPosition` accounts that match the loan.
|
|
95
|
+
|
|
96
|
+
The Codama **`generated/instructions`** module also exposes **admin vault tuning** IXs gated by protocol `config.admin`: besides fee / leverage / supply / borrow-cap updates, you can rotate `Vault::fee_wallet` (`omlpUpdateFeeWallet`), change liquidation threshold (`omlpUpdateLiquidationThreshold`), and update the interest-rate kink curve (`omlpUpdateInterestModel`).
|
|
97
|
+
|
|
98
|
+
### Transaction size and Address Lookup Tables
|
|
99
|
+
|
|
100
|
+
Option mint (and buy/close) transactions exceed Solana's 1232-byte limit without compression. If you see **"encoding overruns Uint8Array"** when minting, pass `network` to `sendBuiltTransaction`:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
await sendBuiltTransaction({
|
|
104
|
+
instructions: tx.instructions,
|
|
105
|
+
rpc,
|
|
106
|
+
rpcSubscriptions,
|
|
107
|
+
feePayer: walletSigner,
|
|
108
|
+
network: "devnet", // or "mainnet" — auto-includes lookup table when configured
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Or pass `addressLookupTableAddresses: [getLookupTableAddressForNetwork("devnet")]` when non-null. Mainnet lookup table is `null` by default; create one via `yarn update:lookuptable` for mainnet.
|
|
113
|
+
|
|
114
|
+
### Token account closing (option mint and close long)
|
|
115
|
+
|
|
116
|
+
- **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.
|
|
117
|
+
- **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:
|
|
118
|
+
- **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).
|
|
119
|
+
- **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.
|
|
120
|
+
|
|
121
|
+
### Partial Close: Buyers
|
|
122
|
+
|
|
123
|
+
`close_long_to_pool` already supports any partial quantity on-chain. To close fewer contracts than the full position:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { buildCloseLongToPoolTransactionWithDerivation, preflightCloseLongToPool } from "@epicentral/sos-sdk";
|
|
127
|
+
|
|
128
|
+
const fullPositionQty = 5; // contracts held
|
|
129
|
+
const closeQty = 2; // contracts to sell back
|
|
130
|
+
|
|
131
|
+
const preflight = await preflightCloseLongToPool({
|
|
132
|
+
underlyingAsset, optionType, strikePrice, expirationDate,
|
|
133
|
+
quantity: BigInt(Math.floor(closeQty * 1e6)),
|
|
134
|
+
rpc, programId,
|
|
135
|
+
});
|
|
136
|
+
if (!preflight.canClose) throw new Error(preflight.reason);
|
|
137
|
+
|
|
138
|
+
const built = await buildCloseLongToPoolTransactionWithDerivation({
|
|
139
|
+
underlyingAsset, optionType, strikePrice, expirationDate,
|
|
140
|
+
buyer, buyerLongAccount, buyerPayoutAccount,
|
|
141
|
+
quantity: BigInt(Math.floor(closeQty * 1e6)),
|
|
142
|
+
minPayoutAmount: 0n,
|
|
143
|
+
// Keep the LONG ATA open — it still holds remaining tokens after the partial close.
|
|
144
|
+
closeLongTokenAccount: false,
|
|
145
|
+
rpc, rpcEndpoint,
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
For the **final close** (closing the last remaining contracts), use the default `closeLongTokenAccount: true` so the buyer's LONG ATA is closed and rent is reclaimed.
|
|
150
|
+
|
|
151
|
+
### Partial Unwind: Writers
|
|
152
|
+
|
|
153
|
+
`unwind_writer_unsold` supports any quantity ≤ `unsoldQty`. To unwind fewer contracts than the full unsold position:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { buildUnwindWriterUnsoldWithLoanRepayment, preflightUnwindWriterUnsold } from "@epicentral/sos-sdk";
|
|
157
|
+
|
|
158
|
+
const fullUnsoldQty = 10_000_000n; // 10 contracts in base units
|
|
159
|
+
const partialUnwindQty = 3_000_000n; // 3 contracts to unwind
|
|
160
|
+
|
|
161
|
+
const preflight = await preflightUnwindWriterUnsold({
|
|
162
|
+
underlyingAsset, optionType, strikePrice, expirationDate,
|
|
163
|
+
writer, unwindQty: partialUnwindQty, rpc, programId,
|
|
164
|
+
});
|
|
165
|
+
if (!preflight.canUnwind) throw new Error(preflight.reason);
|
|
166
|
+
// Use preflight.effectiveUnwindQty (may be capped by available qty)
|
|
167
|
+
const built = await buildUnwindWriterUnsoldWithLoanRepayment({
|
|
168
|
+
underlyingAsset, optionType, strikePrice, expirationDate,
|
|
169
|
+
writer, unwindQty: preflight.effectiveUnwindQty, rpc, programId,
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Writer sold exposure:** Writers can only unwind `unsoldQty`. Sold contracts (filled by buyers) are not unwindable until buyers sell back via `close_long_to_pool`, which converts sold inventory back to unsold. On-chain `unwind_writer_unsold` enforces `unwindQty ≤ unsoldQty` and repays loan principal, interest, and fees proportionally to the unwind slice.
|
|
174
|
+
|
|
175
|
+
### OMLP withdraw behavior
|
|
176
|
+
|
|
177
|
+
- Interest is allocated proportionally via the vault interest-per-share index.
|
|
178
|
+
- On-chain `withdraw_from_position` syncs pending interest before transferring funds, so a lender withdrawal automatically includes their proportional earned interest when available.
|
|
179
|
+
- `withdrawAllFromPosition` and `withdrawInterestFromPosition` compute pending interest from `accInterestPerShareFp` and `interestIndexSnapshotFp`, then cap by `poolAvailable = totalLiquidity - totalLoans`.
|
|
180
|
+
- Optional WSOL unwrap in the same transaction:
|
|
181
|
+
- Set `unwrapSol: true` and provide `vaultMint`.
|
|
182
|
+
- If `vaultMint === NATIVE_MINT`, SDK appends a `CloseAccount` after withdraw to unwrap WSOL ATA to native SOL.
|
|
183
|
+
- For non-WSOL mints, the same builder remains token-agnostic and does not append unwrap instructions.
|
|
184
|
+
|
|
185
|
+
### WSOL / Token Helpers
|
|
186
|
+
|
|
187
|
+
| Function | Description |
|
|
188
|
+
|----------|-------------|
|
|
189
|
+
| `getWrapSOLInstructions` | Wraps SOL to WSOL. |
|
|
190
|
+
| `getUnwrapSOLInstructions` | Unwraps WSOL to SOL. |
|
|
191
|
+
| `getCreateAssociatedTokenIdempotentInstructionWithAddress` | Creates ATA if missing (idempotent). |
|
|
192
|
+
| `NATIVE_MINT` | WSOL mint address. |
|
|
193
|
+
|
|
194
|
+
### Oracle / Switchboard
|
|
195
|
+
|
|
196
|
+
| Function | Description |
|
|
197
|
+
|----------|-------------|
|
|
198
|
+
| `resolveSwitchboardFeedFromMarketData` | Resolves Switchboard feed address from market data account. |
|
|
199
|
+
| `resolveSwitchboardFeedIdFromMarketData` | Resolves Switchboard feed ID (hex) from market data account. |
|
|
200
|
+
| `buildSwitchboardQuoteInstruction` | Builds a direct quote instruction (ed25519 verify ix) for quote-path transactions. |
|
|
201
|
+
| `buildSwitchboardPullFeedUpdate` | Low-level helper that fetches Switchboard pull-feed update instructions. |
|
|
202
|
+
| `buildSwitchboardQuoteWeb3JsInstruction` | Same as `buildSwitchboardQuoteInstruction` but returns a legacy web3.js instruction (Anchor scripts). |
|
|
203
|
+
| `buildSwitchboardCrank` | Returns Kit-native crank instructions plus lookup tables for the configured feed (legacy / non-oracle paths). |
|
|
204
|
+
| `prependSwitchboardCrank` | Prepends crank instructions/ALTs to a built SDK transaction. |
|
|
205
|
+
| `prependSwitchboardQuote` | Prepends the verified quote ix at transaction index `0`. |
|
|
206
|
+
| `getDefaultSwitchboardQueueAddress` | Returns the default Switchboard on-demand queue for `devnet` or `mainnet` (use as program `switchboard_queue`). |
|
|
207
|
+
|
|
208
|
+
**Devnet / on-demand quote:** Trade and validation flows prepend `buildSwitchboardQuoteInstruction` (Crossbar / `Queue.fetchQuoteIx`), not a crank update. Pass **`getDefaultSwitchboardQueueAddress(network)`** (or `SWITCHBOARD_DEFAULT_DEVNET_QUEUE` on devnet) as **`switchboard_queue`** on program instructions — do not thread **`quote.queueAddress`** from the quote result; the default queue is the single obvious source of truth and matches Switchboard’s default queue for the cluster. `buildSwitchboardCrank` remains for legacy or non-oracle paths.
|
|
209
|
+
|
|
210
|
+
Long-open / long-close / `option_mint` derivation builders prepend `buildSwitchboardQuoteInstruction` by default (`rpcEndpoint` required). OMLP deposit/withdraw may still use crank prepends where those instructions do not consume oracle accounts.
|
|
211
|
+
|
|
212
|
+
See [Frontend Switchboard Integration](../../docs/FRONTEND_SWITCHBOARD_INTEGRATION.md) for full setup and usage.
|
|
213
|
+
|
|
214
|
+
### Global Trade Config
|
|
215
|
+
|
|
216
|
+
Use the shared trade config helpers to set SDK-wide defaults for slippage and compute-budget settings:
|
|
217
|
+
|
|
218
|
+
- `setGlobalTradeConfig`
|
|
219
|
+
- `updateGlobalTradeConfig`
|
|
220
|
+
- `getGlobalTradeConfig`
|
|
221
|
+
- `resetGlobalTradeConfig`
|
|
222
|
+
|
|
223
|
+
These defaults are used by the high-level builders unless an action-specific override is provided. `option_mint` also forwards a max-collateral bound on-chain so signer delays cannot silently widen required collateral past the configured tolerance.
|
|
224
|
+
|
|
225
|
+
## Multi-Collateral Settlement
|
|
226
|
+
|
|
227
|
+
The SDK supports universal multi-collateral settlement, allowing writers to use ANY supported asset as collateral for options (not just the underlying). This enables:
|
|
228
|
+
|
|
229
|
+
- **Capital Efficiency**: Writers use whatever assets they hold (USDC, BTC, SOL, BONK, etc.)
|
|
230
|
+
- **No Forced Conversions**: No swap fees or slippage to get the "correct" collateral
|
|
231
|
+
- **Lender Flexibility**: Lend any supported asset, earn yield in that asset
|
|
232
|
+
|
|
233
|
+
### How it works
|
|
234
|
+
|
|
235
|
+
When minting an option, specify `collateralMint` to choose the backing asset:
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import {
|
|
239
|
+
buildOptionMintTransactionWithDerivation,
|
|
240
|
+
OptionType,
|
|
241
|
+
} from "@epicentral/sos-sdk";
|
|
242
|
+
|
|
243
|
+
// Write SOL calls backed by USDC
|
|
244
|
+
const tx = await buildOptionMintTransactionWithDerivation({
|
|
245
|
+
underlyingAsset: SOL_MINT,
|
|
246
|
+
optionType: OptionType.Call,
|
|
247
|
+
strikePrice: 150.0,
|
|
248
|
+
expirationDate: BigInt(1735689600),
|
|
249
|
+
quantity: 1_000_000, // 1 contract
|
|
250
|
+
underlyingMint: SOL_MINT,
|
|
251
|
+
underlyingSymbol: "SOL",
|
|
252
|
+
collateralMint: USDC_MINT, // Back with USDC instead of SOL
|
|
253
|
+
makerCollateralAmount: 780_000_000, // $780 (10% of $7,800)
|
|
254
|
+
borrowedAmount: 7_020_000_000, // $7,020 (90% borrowed)
|
|
255
|
+
maker: walletAddress,
|
|
256
|
+
rpc,
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Key points:**
|
|
261
|
+
- `collateralMint` defaults to `underlyingMint` if not provided (backwards compatible)
|
|
262
|
+
- OMLP vault routing is based on `collateralMint` - the vault must exist for the collateral asset
|
|
263
|
+
- At settlement, buyers receive payout in the collateral currency that backed the position
|
|
264
|
+
- The `WriterPosition` account tracks `collateralMint` and `settlementMint` for each position
|
|
265
|
+
|
|
266
|
+
### Collateral Calculation
|
|
267
|
+
|
|
268
|
+
Use `calculateRequiredCollateral` to estimate collateral needs before minting:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import { calculateRequiredCollateral } from "@epicentral/sos-sdk";
|
|
272
|
+
|
|
273
|
+
const required = calculateRequiredCollateral(
|
|
274
|
+
1_000_000n, // 1 contract in base units
|
|
275
|
+
150.0, // $150 strike price
|
|
276
|
+
145.23, // Current spot price (USD)
|
|
277
|
+
6 // USDC decimals
|
|
278
|
+
);
|
|
279
|
+
// Returns: USDC base units needed
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Unwind with Loan Repayment (theta-hedge model)
|
|
283
|
+
|
|
284
|
+
When a writer unwinds an unsold short that had borrowed from the OMLP pool, the program repays proportionally to the unwind ratio inside `unwind_writer_unsold` under a **theta-first, full-repay-or-revert** model.
|
|
285
|
+
|
|
286
|
+
**Proportional Repayment (partial unwinds):**
|
|
287
|
+
- Unwind ratio = `unwind_qty / written_qty`
|
|
288
|
+
- Principal, interest, and protocol fees are sliced by the unwind ratio against the pool's full, accrued debt snapshot.
|
|
289
|
+
|
|
290
|
+
**Repayment waterfall (on-chain):**
|
|
291
|
+
1. `writer_position.realize_theta(pool.acc_theta_per_oi_fp)` — pending theta becomes `theta_balance`.
|
|
292
|
+
2. `theta_to_debt = min(theta_balance, proportional_debt)` paid from `premium_vault` (only when `premium_vault.mint == omlp_vault.mint`).
|
|
293
|
+
3. `collateral_to_debt = proportional_debt - theta_to_debt` paid from `collateral_vault`. If the writer's proportional collateral share cannot cover it, the instruction reverts with `LiquidationInsufficientCollateralForDebt` — keepers route the position to `liquidate_writer_position_rescue`.
|
|
294
|
+
4. Leftover theta (`theta_balance - theta_to_debt`) is transferred to the writer from `premium_vault`.
|
|
295
|
+
5. Unlocked collateral (`proportional_share - collateral_to_debt`) is transferred to the writer from `collateral_vault`.
|
|
296
|
+
|
|
297
|
+
> **There is no wallet fallback path.** Writers do not top up from their own wallet during unwind; the protocol always closes the book via theta + collateral (or routes to the keeper rescue instruction on insolvency).
|
|
298
|
+
|
|
299
|
+
**Collateral Return:**
|
|
300
|
+
- Proportional collateral share = `(collateral_deposited * unwind_qty) / written_qty`
|
|
301
|
+
- Returnable collateral = `proportional_share - collateral_to_debt`
|
|
302
|
+
- Insolvent writer (proportional_share < collateral_to_debt) ⇒ `LiquidationInsufficientCollateralForDebt` (6099)
|
|
303
|
+
- Drained vault (collateral_vault.amount < collateral_to_debt) ⇒ `InsufficientCollateralVault` (6090)
|
|
304
|
+
|
|
305
|
+
Use **`buildUnwindWriterUnsoldWithLoanRepayment`** so that:
|
|
306
|
+
|
|
307
|
+
1. Active pool loans for the option's underlying vault are fetched.
|
|
308
|
+
2. `omlpVaultState` (Vault PDA), `omlpVault`, `feeWallet`, and `premiumVault` are passed as named accounts.
|
|
309
|
+
3. `remaining_accounts` = **[PoolLoan₁, PoolLoan₂, ...]** only (capped at 20 loans per tx).
|
|
310
|
+
4. One transaction burns LONG+SHORT, repays lenders theta-first, pays protocol fees, and returns leftover theta + unlocked collateral to the writer.
|
|
311
|
+
|
|
312
|
+
Use **`preflightUnwindWriterUnsold`** before building the transaction to get:
|
|
313
|
+
|
|
314
|
+
- Per-loan principal / interest / protocol-fee breakdown.
|
|
315
|
+
- **Proportional obligations** for the slice (principal, interest, fees, total owed).
|
|
316
|
+
- **Theta hedge breakdown**: `thetaAvailable`, `thetaToDebt`, `collateralToDebt`, `premiumMintMatchesCollateralMint`.
|
|
317
|
+
- **Collateral return**: `proportionalCollateralShare`, `returnableCollateral`.
|
|
318
|
+
- **Shortfalls**: `collateralClaimShortfall` (writer underwater — rescue required), `collateralVaultShortfall` (vault drained), `needsRescue`.
|
|
319
|
+
- `canRepayRequestedSlice` — `true` when the slice will settle without rescue.
|
|
320
|
+
- Deprecated compatibility fields: `canRepayFully` mirrors `canRepayRequestedSlice`; `writerRepaymentAccount` resolves to the writer's default underlying ATA but is **not used on-chain** anymore.
|
|
321
|
+
|
|
322
|
+
If there are no active pool loans for that vault, the API still works and passes empty `remaining_accounts`.
|
|
323
|
+
|
|
324
|
+
**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.
|
|
325
|
+
|
|
326
|
+
**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. **Note: this is for the standalone repay flow, not unwind** — unwind itself has no wallet fallback.
|
|
327
|
+
|
|
328
|
+
### Recommended Preflight + Unwind
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import {
|
|
332
|
+
preflightUnwindWriterUnsold,
|
|
333
|
+
buildUnwindWriterUnsoldWithLoanRepayment,
|
|
334
|
+
} from "@epicentral/sos-sdk";
|
|
335
|
+
|
|
336
|
+
const preflight = await preflightUnwindWriterUnsold({
|
|
337
|
+
underlyingAsset,
|
|
338
|
+
optionType,
|
|
339
|
+
strikePrice,
|
|
340
|
+
expirationDate,
|
|
341
|
+
writer,
|
|
342
|
+
unwindQty,
|
|
343
|
+
rpc,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
if (preflight.summary.needsRescue) {
|
|
347
|
+
// Writer is underwater — route to keeper rescue path instead of unwind.
|
|
348
|
+
// See buildLiquidateWriterPositionRescueInstruction.
|
|
349
|
+
throw new Error(preflight.reason);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const tx = await buildUnwindWriterUnsoldWithLoanRepayment({
|
|
353
|
+
underlyingAsset,
|
|
354
|
+
optionType,
|
|
355
|
+
strikePrice,
|
|
356
|
+
expirationDate,
|
|
357
|
+
writer,
|
|
358
|
+
unwindQty,
|
|
359
|
+
rpc,
|
|
360
|
+
});
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Error codes to watch (see `generated/errors/optionProgram.ts`):
|
|
364
|
+
|
|
365
|
+
- `MaintenanceBufferBreached` (6098) — position breaches the OMLP maintenance buffer at call time.
|
|
366
|
+
- `LiquidationInsufficientCollateralForDebt` (6099) — writer is underwater; use the rescue ix.
|
|
367
|
+
- `ThetaClaimDisabled` (6100) — legacy client still calling `claim_theta` (removed).
|
|
368
|
+
- `MaintenanceLoansIncomplete` (6101) — `remaining_accounts` missing active pool loans.
|
|
369
|
+
- `RescueUnauthorized` (6102) — rescue ix caller is not the vault keeper.
|
|
370
|
+
- `InvalidLoanRepayment` (6103) — repayment exceeded synced balances (slot race).
|
|
371
|
+
- `RescuePreconditionsNotMet` (6104) — rescue called on a solvent position.
|
|
372
|
+
|
|
373
|
+
## Usage Examples
|
|
374
|
+
|
|
375
|
+
### Buy From Pool (market order, high-level)
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
import {
|
|
379
|
+
buildBuyFromPoolMarketOrderTransactionWithDerivation,
|
|
380
|
+
preflightBuyFromPoolMarketOrder,
|
|
381
|
+
OptionType,
|
|
382
|
+
} from "@epicentral/sos-sdk";
|
|
383
|
+
|
|
384
|
+
const preflight = await preflightBuyFromPoolMarketOrder({
|
|
385
|
+
underlyingAsset: "...",
|
|
386
|
+
optionType: OptionType.Call,
|
|
387
|
+
strikePrice: 100_000,
|
|
388
|
+
expirationDate: BigInt(1735689600),
|
|
389
|
+
quantity: 1_000_000,
|
|
390
|
+
rpc,
|
|
391
|
+
quotedPremiumTotal: 50_000,
|
|
392
|
+
slippageBufferBaseUnits: 500_000n,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
if (!preflight.canBuy) {
|
|
396
|
+
throw new Error(preflight.reason ?? "Buy preflight failed");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const tx = await buildBuyFromPoolMarketOrderTransactionWithDerivation({
|
|
400
|
+
underlyingAsset: "...",
|
|
401
|
+
optionType: OptionType.Call,
|
|
402
|
+
strikePrice: 100_000,
|
|
403
|
+
expirationDate: BigInt(1735689600),
|
|
404
|
+
buyer: walletAddress,
|
|
405
|
+
// Must be the buyer's ATA for option_pool.underlying_mint (same mint as `quotedPremiumTotal` base units).
|
|
406
|
+
buyerPaymentAccount: buyerUnderlyingAta,
|
|
407
|
+
quantity: 1_000_000,
|
|
408
|
+
quotedPremiumTotal: 50_000,
|
|
409
|
+
slippageBufferBaseUnits: 500_000n,
|
|
410
|
+
rpc,
|
|
411
|
+
rpcEndpoint: "https://api.devnet.solana.com",
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Buy premium semantics (market orders)
|
|
416
|
+
|
|
417
|
+
- Premium is paid in the **option pool underlying** token; `buyer_payment_account` must match that mint (see program `buy_from_pool`).
|
|
418
|
+
- `premiumAmount` / `max_premium_amount` is a **max premium cap**, not an exact premium target.
|
|
419
|
+
- Program computes premium on-chain at execution time and fails with `SlippageToleranceExceeded` if computed premium exceeds the cap.
|
|
420
|
+
- High-level market builder computes cap as `quotedPremiumTotal + buffer`:
|
|
421
|
+
- Canonical: `slippageBufferBaseUnits`
|
|
422
|
+
- Convenience for SOL/WSOL: `slippageBufferLamports`
|
|
423
|
+
- Default buffer: `500_000` **underlying** base units when using global trade-config slippage (not lamports unless underlying is SOL/WSOL)
|
|
424
|
+
|
|
425
|
+
### Buy liquidity errors (6041 split into 6042/6043)
|
|
426
|
+
|
|
427
|
+
The program uses distinct error codes for liquidity failures:
|
|
428
|
+
|
|
429
|
+
- `InsufficientPoolAggregateLiquidity` (6042) – `option_pool.total_available < quantity`
|
|
430
|
+
- `InsufficientWriterPositionLiquidity` (6043) – remaining writer-position accounts cannot cover full quantity in the FIFO fill loop
|
|
431
|
+
- `BuyRemainingAccountsUnsorted` (6096) – `remaining_accounts` not sorted by `(createdAt asc, pubkey asc)`
|
|
432
|
+
|
|
433
|
+
`option_pool.total_available` is the on-chain aggregate of LONG in pool escrow that may be sold via `buy_from_pool` (increments on writer deposit and on buyer `close_long_to_pool` recycle, decrements on buy and on unwind/liquidation). Client UX often combines it with writer `unsold_qty` (e.g. `min`). `buy_from_pool` fills writers strictly FIFO — the SDK sorts by `(createdAt asc, pubkey asc)` in `getBuyFromPoolRemainingAccounts` and the program verifies the same ordering on-chain.
|
|
434
|
+
|
|
435
|
+
**Recommended client flow:**
|
|
436
|
+
1. Run `preflightBuyFromPoolMarketOrder` for UX gating (checks both pool and active writer liquidity).
|
|
437
|
+
2. Build via `buildBuyFromPoolMarketOrderTransactionWithDerivation` – it refetches pool + remaining accounts (FIFO-sorted) and asserts active writer liquidity >= requested quantity before building.
|
|
438
|
+
|
|
439
|
+
### Close-long errors (Hamilton recycle)
|
|
440
|
+
|
|
441
|
+
`close_long_to_pool` returns closed LONG tokens to the pool escrow and redistributes the recovered `sold_qty` back to writers as `unsold_qty` using a Hamilton largest-remainder allocation. The program enforces a **strict completeness** check against the full active writer set:
|
|
442
|
+
|
|
443
|
+
- `CloseLongWritersIncomplete` (6095) – `sum(passed WriterPosition.sold_qty) != OptionPool.total_sold_qty`
|
|
444
|
+
- `CloseLongNoSoldInventory` (6096) – `OptionPool.total_sold_qty == 0` or no WriterPositions passed (also raised when close quantity exceeds `total_open_interest_qty`, which is a pre-check in `close_long_to_pool` that keeps the post-close utilization used by marginal IV non-negative)
|
|
445
|
+
- `CloseLongDuplicateWriter` (6097) – duplicate `WriterPosition` pubkeys in `remaining_accounts`
|
|
446
|
+
|
|
447
|
+
Use `getCloseLongToPoolRemainingAccounts` to build the complete active set, or let `buildCloseLongToPoolTransactionWithDerivation` populate it automatically. Run `preflightCloseLongToPool` to catch data-staleness mismatches before submit.
|
|
448
|
+
|
|
449
|
+
### Pool pricing: marginal IV + lender-senior payout cap
|
|
450
|
+
|
|
451
|
+
**`buy_from_pool` / `close_long_to_pool` price against marginal utilization, not stored IV.** Premium / payout BS inputs use σ evaluated at the pool state *after* the trade:
|
|
452
|
+
|
|
453
|
+
| Leg | σ utilization |
|
|
454
|
+
|-----|--------------|
|
|
455
|
+
| `buy_from_pool` | `(total_open_interest_qty + quantity) / total_deposited` |
|
|
456
|
+
| `close_long_to_pool` | `(total_open_interest_qty − quantity) / total_deposited` |
|
|
457
|
+
| `OptionAccount.implied_volatility` | refreshed to **post-state** σ after counter updates (indexer display) |
|
|
458
|
+
|
|
459
|
+
Stored `implied_volatility` is the right number for option-chain displays, but **not** the right σ to quote the current trade. Callers quoting previews client-side should mirror this using `calculateKinkImpliedVolatility(hv, totalDeposited, totalOpenInterestQty ± tradeQty)` in OPX (`kink-implied-volatility.ts`). This closes the same-slot round-trip arbitrage where buying pulled σ up but an immediate close was previously priced against the **same pre-trade stored σ** — buyers now always pay σ_high (post-buy OI) and receive σ_low (post-close OI), so churn loses money with unchanged oracle and time.
|
|
460
|
+
|
|
461
|
+
**`close_long_to_pool` caps buyer payouts at maker equity.** The collateral-leg budget is now the `min` of:
|
|
462
|
+
|
|
463
|
+
1. `total_maker_contribution − total_interest_owed` (stakeholder maker-equity formula; `total_interest_owed` includes fees via `sync_collateral_pool_debt`).
|
|
464
|
+
2. `total_collateral − (total_borrowed + total_interest_owed)` (principal-safe budget).
|
|
465
|
+
|
|
466
|
+
Both are saturating. `total_collateral` continues to back cash-covered / solvency checks, but it is **not** the buyer payout budget — that protection prevents buyers from draining the lender-owned sleeve of the collateral vault.
|
|
467
|
+
|
|
468
|
+
Preflight / `min_payout_amount` simulation impact:
|
|
469
|
+
|
|
470
|
+
- The previous `max_payable = premium_capacity + total_collateral` upper bound is no longer the chain-truth capacity. Callers that simulate exact proceeds must use the new cap.
|
|
471
|
+
- Low-risk heuristic for UX: approximate `collateral_capacity ≈ min(total_maker_contribution − total_interest_owed, total_collateral − total_borrowed − total_interest_owed)`; feed the result into the same `compute_close_payout_split` formula the program uses for the premium-first split.
|
|
472
|
+
- For pools without borrow (`total_borrowed == 0`), the cap collapses to `total_maker_contribution − 0 = total_maker_contribution` (fees/interest are zero too), which matches the previous behavior.
|
|
473
|
+
|
|
474
|
+
Full write-up, golden vectors, and parity matrix live in `option-program/docs/PRICING_PARITY.md` — **"Marginal IV for pool buy/close"** and **"Lender seniority: buyer payouts must not use borrowed funds"**.
|
|
475
|
+
|
|
476
|
+
### Framework deserialization errors (`#3003`)
|
|
477
|
+
|
|
478
|
+
If simulation fails with `custom program error: #3003`, this usually means account deserialization failed before business logic (`60xx`) ran.
|
|
479
|
+
|
|
480
|
+
Check these first:
|
|
481
|
+
|
|
482
|
+
- `buyer_position` account shape/size (`146` bytes expected).
|
|
483
|
+
- `market_data` account shape/size (`128` bytes expected).
|
|
484
|
+
- Price-sensitive instructions use **verified quotes**: pass `rpcEndpoint` so the SDK can prepend `buildSwitchboardQuoteInstruction` at ix `0` (unless `disableSwitchboardCrank`). Ensure `market_data.switchboard_feed_id` holds the 32-byte feed ID hash.
|
|
485
|
+
- For `option_mint`, the three oracle accounts are passed via **remaining_accounts** (queue, SlotHashes, Instructions sysvars); other updated instructions take them as named accounts.
|
|
486
|
+
- `write_to_pool`, `unwind_writer_unsold`, and `liquidate_writer_position_rescue` include the **`market_data` PDA** (read-only, after `option_account`) for in-tx IV refresh; `resolveOptionAccounts` / `deriveMarketDataPda` supply it.
|
|
487
|
+
- Account list/order matches the generated instruction layout.
|
|
488
|
+
|
|
489
|
+
This is different from liquidity failures (`6042/6043`) and should be debugged as an account wiring/layout issue.
|
|
490
|
+
|
|
491
|
+
### Oracle inputs (asset-agnostic)
|
|
492
|
+
|
|
493
|
+
- Keep oracle handling universal across assets.
|
|
494
|
+
- Use the market-configured `switchboard_feed_id` as source-of-truth.
|
|
495
|
+
- On-chain verification uses the feed ID in the Switchboard quote at transaction index `0`; the program checks queue + sysvars + quote proof.
|
|
496
|
+
- Avoid hardcoding a single feed/account address in shared SDK integration flows.
|
|
497
|
+
|
|
498
|
+
### Price update freshness (required for accurate payouts)
|
|
499
|
+
|
|
500
|
+
The program verifies a **Switchboard quote instruction at tx index 0** and reads the price for the configured `switchboard_feed_id` for:
|
|
501
|
+
|
|
502
|
+
- **Buy / close / validate / exercise / liquidation / auto-exercise:** Black-Scholes or intrinsic math uses that verified spot.
|
|
503
|
+
|
|
504
|
+
The SDK prepends `buildSwitchboardQuoteInstruction` by default for these builders (same pattern as `option_mint`).
|
|
505
|
+
|
|
506
|
+
- **Mainnet / devnet:** fetch the quote immediately before submit; quotes age out after a bounded slot window (`SWITCHBOARD_QUOTE_MAX_AGE_SLOTS` on-chain).
|
|
507
|
+
|
|
508
|
+
**Note:** Pull-feed-only crank prepends are deprecated for these program paths; use the quote + named oracle accounts (or `remaining_accounts` for `option_mint`).
|
|
509
|
+
|
|
510
|
+
### Unwind with loan repayment
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
import {
|
|
514
|
+
buildUnwindWriterUnsoldWithLoanRepayment,
|
|
515
|
+
OptionType,
|
|
516
|
+
} from "@epicentral/sos-sdk";
|
|
517
|
+
|
|
518
|
+
const tx = await buildUnwindWriterUnsoldWithLoanRepayment({
|
|
519
|
+
underlyingAsset: "...",
|
|
520
|
+
optionType: OptionType.Call,
|
|
521
|
+
strikePrice: 100_000,
|
|
522
|
+
expirationDate: BigInt(1735689600),
|
|
523
|
+
writer: walletAddress,
|
|
524
|
+
unwindQty: 500_000,
|
|
525
|
+
rpc,
|
|
526
|
+
});
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
## Types and Exports
|
|
530
|
+
|
|
531
|
+
Key types exported from the package:
|
|
532
|
+
|
|
533
|
+
- `OptionType` – Call or Put.
|
|
534
|
+
- `BuiltTransaction` – `{ instructions: Instruction[] }`.
|
|
535
|
+
- `AddressLike` – `string | Address`.
|
|
536
|
+
- `KitRpc` – RPC client type for fetches.
|
|
537
|
+
- `RemainingAccountInput` – `{ address, isWritable, isSigner? }`.
|
|
538
|
+
|
|
539
|
+
PDAs, fetchers, and builders are exported from the package root.
|
|
540
|
+
|
|
541
|
+
## Program Compatibility
|
|
542
|
+
|
|
543
|
+
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.
|
|
544
|
+
|
|
545
|
+
## Collateral Calculation Helper
|
|
546
|
+
|
|
547
|
+
The SDK exports `calculateRequiredCollateral` for pre-flight collateral estimation:
|
|
548
|
+
|
|
549
|
+
```ts
|
|
550
|
+
import { calculateRequiredCollateral } from "@epicentral/sos-sdk";
|
|
551
|
+
|
|
552
|
+
const required = calculateRequiredCollateral(
|
|
553
|
+
1_000_000n, // 1 contract in base units
|
|
554
|
+
150.0, // $150 strike price
|
|
555
|
+
145.23, // Current spot price (USD)
|
|
556
|
+
9 // Token decimals (9 for SOL)
|
|
557
|
+
);
|
|
558
|
+
// Returns: token base units needed (e.g., 103_280_000_000 lamports for ~103.28 SOL)
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**Formula:**
|
|
562
|
+
```
|
|
563
|
+
contracts = quantity / 1_000_000
|
|
564
|
+
usd_value = contracts * 100 * strike_price
|
|
565
|
+
collateral = (usd_value / spot_price) * 10^token_decimals
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
This matches the on-chain formula in `Vault::calculate_required_collateral` and can be used to display required collateral to users before submitting an `option_mint` transaction.
|