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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,7 +47,9 @@ Additional modules:
47
47
 
48
48
  | Function | Description |
49
49
  |----------|-------------|
50
+ | `buildBuyFromPoolMarketOrderTransactionWithDerivation` | High-level market-order buy builder (refetches pool + remaining accounts, applies premium cap buffer). |
50
51
  | `buildBuyFromPoolTransactionWithDerivation` | Builds buy-from-pool transaction; resolves accounts from option identity. |
52
+ | `preflightBuyFromPoolMarketOrder` | Buy preflight helper for liquidity + remaining-account coverage checks. |
51
53
  | `buildCloseLongToPoolTransactionWithDerivation` | Builds close-long-to-pool transaction. |
52
54
  | `getBuyFromPoolRemainingAccounts` | Builds remaining_accounts for buy (writer positions, etc.). |
53
55
 
@@ -148,16 +150,31 @@ const tx = await buildUnwindWriterUnsoldWithLoanRepayment({
148
150
 
149
151
  ## Usage Examples
150
152
 
151
- ### Buy from pool (with derivation)
153
+ ### Buy From Pool (market order, high-level)
152
154
 
153
155
  ```ts
154
156
  import {
155
- buildBuyFromPoolTransactionWithDerivation,
156
- resolveOptionAccounts,
157
+ buildBuyFromPoolMarketOrderTransactionWithDerivation,
158
+ preflightBuyFromPoolMarketOrder,
157
159
  OptionType,
158
160
  } from "@epicentral/sos-sdk";
159
161
 
160
- const tx = await buildBuyFromPoolTransactionWithDerivation({
162
+ const preflight = await preflightBuyFromPoolMarketOrder({
163
+ underlyingAsset: "...",
164
+ optionType: OptionType.Call,
165
+ strikePrice: 100_000,
166
+ expirationDate: BigInt(1735689600),
167
+ quantity: 1_000_000,
168
+ rpc,
169
+ quotedPremiumTotal: 50_000,
170
+ slippageBufferBaseUnits: 500_000n,
171
+ });
172
+
173
+ if (!preflight.canBuy) {
174
+ throw new Error(preflight.reason ?? "Buy preflight failed");
175
+ }
176
+
177
+ const tx = await buildBuyFromPoolMarketOrderTransactionWithDerivation({
161
178
  underlyingAsset: "...",
162
179
  optionType: OptionType.Call,
163
180
  strikePrice: 100_000,
@@ -166,11 +183,30 @@ const tx = await buildBuyFromPoolTransactionWithDerivation({
166
183
  buyerPaymentAccount: buyerUsdcAta,
167
184
  priceUpdate: pythPriceFeed,
168
185
  quantity: 1_000_000,
169
- premiumAmount: 50_000,
186
+ quotedPremiumTotal: 50_000,
187
+ slippageBufferBaseUnits: 500_000n,
170
188
  rpc,
171
189
  });
172
190
  ```
173
191
 
192
+ ### Buy premium semantics (market orders)
193
+
194
+ - `premiumAmount` / `max_premium_amount` is a **max premium cap**, not an exact premium target.
195
+ - Program computes premium on-chain at execution time and fails with `SlippageToleranceExceeded` if computed premium exceeds the cap.
196
+ - High-level market builder computes cap as `quotedPremiumTotal + buffer`:
197
+ - Canonical: `slippageBufferBaseUnits`
198
+ - Convenience for SOL/WSOL: `slippageBufferLamports`
199
+ - Default buffer: `500_000` base units (0.0005 SOL lamports)
200
+
201
+ ### Buy liquidity errors (6041)
202
+
203
+ - `InsufficientPoolLiquidity` can happen when:
204
+ - `option_pool.total_available < quantity`, or
205
+ - remaining writer-position accounts cannot cover full quantity in the smallest-first fill loop.
206
+ - Recommended client flow:
207
+ 1. Run `preflightBuyFromPoolMarketOrder` for UX gating.
208
+ 2. Build via `buildBuyFromPoolMarketOrderTransactionWithDerivation` so pool + remaining accounts are refetched immediately before build.
209
+
174
210
  ### Unwind with loan repayment
175
211
 
176
212
  ```ts
@@ -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("HsoWoDfW4yXVaXE31tEz167cjQn79TeXYxggSrXvRvri"),
7
+ devnet: address("B7kctbAQDEHT8gJcqRB3Bjkv4EkxMCZecy2H6EQNLQPo"),
8
8
  mainnet: null,
9
9
  };
10
10
 
package/index.ts CHANGED
@@ -16,6 +16,7 @@ export * from "./shared/transactions";
16
16
 
17
17
  export * from "./long/builders";
18
18
  export * from "./long/exercise";
19
+ export * from "./long/preflight";
19
20
  export * from "./long/quotes";
20
21
  export {
21
22
  getBuyFromPoolRemainingAccounts,
package/long/builders.ts CHANGED
@@ -10,14 +10,16 @@ import {
10
10
  deriveAssociatedTokenAddress,
11
11
  deriveBuyerPositionPda,
12
12
  } from "../accounts/pdas";
13
- import { assertPositiveAmount } from "../shared/amounts";
13
+ import { assertNonNegativeAmount, assertPositiveAmount } from "../shared/amounts";
14
14
  import { invariant } from "../shared/errors";
15
15
  import {
16
16
  appendRemainingAccounts,
17
17
  type RemainingAccountInput,
18
18
  } from "../shared/remaining-accounts";
19
19
  import type { OptionType } from "../generated/types";
20
- import { getCreateAssociatedTokenIdempotentInstructionWithAddress } from "../wsol/instructions";
20
+ import { getCreateAssociatedTokenIdempotentInstructionWithAddress, NATIVE_MINT } from "../wsol/instructions";
21
+ import { fetchOptionPool } from "../accounts/fetchers";
22
+ import { getBuyFromPoolRemainingAccounts } from "./remaining-accounts";
21
23
 
22
24
  export interface BuildBuyFromPoolParams {
23
25
  optionPool: AddressLike;
@@ -127,6 +129,42 @@ export interface BuildBuyFromPoolTransactionWithDerivationParams {
127
129
  remainingAccounts?: RemainingAccountInput[];
128
130
  }
129
131
 
132
+ const DEFAULT_MARKET_ORDER_SLIPPAGE_BUFFER_BASE_UNITS = 500_000n;
133
+
134
+ interface MarketOrderBufferLikeParams {
135
+ slippageBufferBaseUnits?: bigint | number;
136
+ slippageBufferLamports?: bigint | number;
137
+ }
138
+
139
+ function normalizeMarketOrderSlippageBuffer(
140
+ params: MarketOrderBufferLikeParams,
141
+ underlyingMint: AddressLike
142
+ ): bigint {
143
+ const hasBaseUnits = params.slippageBufferBaseUnits !== undefined;
144
+ const hasLamports = params.slippageBufferLamports !== undefined;
145
+
146
+ invariant(
147
+ !(hasBaseUnits && hasLamports),
148
+ "Provide only one of slippageBufferBaseUnits or slippageBufferLamports."
149
+ );
150
+
151
+ if (hasBaseUnits) {
152
+ assertNonNegativeAmount(params.slippageBufferBaseUnits!, "slippageBufferBaseUnits");
153
+ return BigInt(params.slippageBufferBaseUnits!);
154
+ }
155
+
156
+ if (hasLamports) {
157
+ assertNonNegativeAmount(params.slippageBufferLamports!, "slippageBufferLamports");
158
+ invariant(
159
+ String(toAddress(underlyingMint)) === String(NATIVE_MINT),
160
+ "slippageBufferLamports is only supported for SOL/WSOL underlyings. Use slippageBufferBaseUnits for other assets."
161
+ );
162
+ return BigInt(params.slippageBufferLamports!);
163
+ }
164
+
165
+ return DEFAULT_MARKET_ORDER_SLIPPAGE_BUFFER_BASE_UNITS;
166
+ }
167
+
130
168
  export async function buildBuyFromPoolTransactionWithDerivation(
131
169
  params: BuildBuyFromPoolTransactionWithDerivationParams
132
170
  ): Promise<BuiltTransaction> {
@@ -178,6 +216,82 @@ export async function buildBuyFromPoolTransactionWithDerivation(
178
216
  });
179
217
  }
180
218
 
219
+ export interface BuildBuyFromPoolMarketOrderParams
220
+ extends Omit<
221
+ BuildBuyFromPoolTransactionWithDerivationParams,
222
+ "premiumAmount" | "remainingAccounts"
223
+ >,
224
+ MarketOrderBufferLikeParams {
225
+ quotedPremiumTotal: bigint | number;
226
+ }
227
+
228
+ /**
229
+ * High-level market-order buy builder.
230
+ * Refetches option pool and remaining writer-position accounts right before
231
+ * build and sets max premium = quotedPremiumTotal + slippage buffer.
232
+ */
233
+ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
234
+ params: BuildBuyFromPoolMarketOrderParams
235
+ ): Promise<BuiltTransaction> {
236
+ assertPositiveAmount(params.quantity, "quantity");
237
+ assertPositiveAmount(params.quotedPremiumTotal, "quotedPremiumTotal");
238
+
239
+ const resolved = await resolveOptionAccounts({
240
+ underlyingAsset: params.underlyingAsset,
241
+ optionType: params.optionType,
242
+ strikePrice: params.strikePrice,
243
+ expirationDate: params.expirationDate,
244
+ programId: params.programId,
245
+ rpc: params.rpc,
246
+ });
247
+
248
+ const [refetchedPool, remainingAccounts, buyerPosition, buyerOptionAccount] =
249
+ await Promise.all([
250
+ fetchOptionPool(params.rpc, resolved.optionPool),
251
+ getBuyFromPoolRemainingAccounts(params.rpc, resolved.optionPool, params.programId),
252
+ params.buyerPosition
253
+ ? Promise.resolve(params.buyerPosition)
254
+ : deriveBuyerPositionPda(
255
+ params.buyer,
256
+ resolved.optionAccount,
257
+ params.programId
258
+ ).then(([addr]) => addr),
259
+ params.buyerOptionAccount
260
+ ? Promise.resolve(params.buyerOptionAccount)
261
+ : deriveAssociatedTokenAddress(params.buyer, resolved.longMint),
262
+ ]);
263
+
264
+ invariant(
265
+ !!refetchedPool,
266
+ "Option pool must exist; ensure rpc is provided and pool is initialized."
267
+ );
268
+
269
+ const slippageBuffer = normalizeMarketOrderSlippageBuffer(
270
+ params,
271
+ refetchedPool.underlyingMint
272
+ );
273
+ const maxPremiumAmount = BigInt(params.quotedPremiumTotal) + slippageBuffer;
274
+ assertPositiveAmount(maxPremiumAmount, "maxPremiumAmount");
275
+
276
+ return buildBuyFromPoolTransaction({
277
+ optionPool: resolved.optionPool,
278
+ optionAccount: resolved.optionAccount,
279
+ longMint: resolved.longMint,
280
+ underlyingMint: refetchedPool.underlyingMint,
281
+ marketData: resolved.marketData,
282
+ priceUpdate: params.priceUpdate,
283
+ buyer: params.buyer,
284
+ buyerPaymentAccount: params.buyerPaymentAccount,
285
+ escrowLongAccount: refetchedPool.escrowLongAccount,
286
+ premiumVault: refetchedPool.premiumVault,
287
+ quantity: params.quantity,
288
+ premiumAmount: maxPremiumAmount,
289
+ buyerPosition,
290
+ buyerOptionAccount,
291
+ remainingAccounts,
292
+ });
293
+ }
294
+
181
295
  export async function buildCloseLongToPoolInstruction(
182
296
  params: BuildCloseLongToPoolParams
183
297
  ): Promise<Instruction<string>> {
@@ -0,0 +1,114 @@
1
+ import type { AddressLike, KitRpc } from "../client/types";
2
+ import type { OptionType } from "../generated/types";
3
+ import { fetchWriterPositionsForPool } from "../accounts/list";
4
+ import { resolveOptionAccounts } from "../accounts/resolve-option";
5
+ import { fetchOptionPool } from "../accounts/fetchers";
6
+ import { assertNonNegativeAmount, assertPositiveAmount } from "../shared/amounts";
7
+ import { invariant } from "../shared/errors";
8
+
9
+ function toBigInt(value: bigint | number): bigint {
10
+ return typeof value === "bigint" ? value : BigInt(value);
11
+ }
12
+
13
+ export interface PreflightBuyFromPoolMarketOrderParams {
14
+ underlyingAsset: AddressLike;
15
+ optionType: OptionType;
16
+ strikePrice: number;
17
+ expirationDate: bigint | number;
18
+ quantity: bigint | number;
19
+ rpc: KitRpc;
20
+ programId?: AddressLike;
21
+ quotedPremiumTotal?: bigint | number;
22
+ slippageBufferBaseUnits?: bigint | number;
23
+ }
24
+
25
+ export interface BuyFromPoolMarketOrderPremiumSummary {
26
+ quotedPremiumTotal: bigint;
27
+ slippageBufferBaseUnits: bigint;
28
+ maxPremiumAmount: bigint;
29
+ }
30
+
31
+ export interface PreflightBuyFromPoolMarketOrderResult {
32
+ canBuy: boolean;
33
+ reason?: string;
34
+ poolTotalAvailable: bigint;
35
+ requestedQuantity: bigint;
36
+ remainingAccountsCount: number;
37
+ remainingUnsoldAggregate: bigint;
38
+ premium?: BuyFromPoolMarketOrderPremiumSummary;
39
+ }
40
+
41
+ export async function preflightBuyFromPoolMarketOrder(
42
+ params: PreflightBuyFromPoolMarketOrderParams
43
+ ): Promise<PreflightBuyFromPoolMarketOrderResult> {
44
+ assertPositiveAmount(params.quantity, "quantity");
45
+ const requestedQuantity = toBigInt(params.quantity);
46
+
47
+ const resolved = await resolveOptionAccounts({
48
+ underlyingAsset: params.underlyingAsset,
49
+ optionType: params.optionType,
50
+ strikePrice: params.strikePrice,
51
+ expirationDate: params.expirationDate,
52
+ programId: params.programId,
53
+ rpc: params.rpc,
54
+ });
55
+
56
+ const [optionPool, writerPositions] = await Promise.all([
57
+ fetchOptionPool(params.rpc, resolved.optionPool),
58
+ fetchWriterPositionsForPool(params.rpc, resolved.optionPool, params.programId),
59
+ ]);
60
+
61
+ invariant(
62
+ !!optionPool,
63
+ "Option pool must exist; ensure rpc is provided and pool is initialized."
64
+ );
65
+
66
+ const availableWriterPositions = writerPositions.filter(
67
+ ({ data }) => toBigInt(data.unsoldQty) > 0n
68
+ );
69
+ const remainingUnsoldAggregate = availableWriterPositions.reduce(
70
+ (acc, { data }) => acc + toBigInt(data.unsoldQty),
71
+ 0n
72
+ );
73
+ const poolTotalAvailable = toBigInt(optionPool.totalAvailable);
74
+
75
+ const hasPoolLiquidity = poolTotalAvailable >= requestedQuantity;
76
+ const hasWriterCoverage = remainingUnsoldAggregate >= requestedQuantity;
77
+
78
+ let reason: string | undefined;
79
+ if (!hasPoolLiquidity) {
80
+ reason = "Pool total_available is less than requested quantity.";
81
+ } else if (!hasWriterCoverage) {
82
+ reason =
83
+ "Remaining writer-position liquidity is insufficient to fully fill requested quantity.";
84
+ }
85
+
86
+ const result: PreflightBuyFromPoolMarketOrderResult = {
87
+ canBuy: hasPoolLiquidity && hasWriterCoverage,
88
+ reason,
89
+ poolTotalAvailable,
90
+ requestedQuantity,
91
+ remainingAccountsCount: availableWriterPositions.length,
92
+ remainingUnsoldAggregate,
93
+ };
94
+
95
+ if (params.quotedPremiumTotal !== undefined) {
96
+ assertPositiveAmount(params.quotedPremiumTotal, "quotedPremiumTotal");
97
+ if (params.slippageBufferBaseUnits !== undefined) {
98
+ assertNonNegativeAmount(params.slippageBufferBaseUnits, "slippageBufferBaseUnits");
99
+ }
100
+ const quotedPremiumTotal = toBigInt(params.quotedPremiumTotal);
101
+ const slippageBufferBaseUnits =
102
+ params.slippageBufferBaseUnits !== undefined
103
+ ? toBigInt(params.slippageBufferBaseUnits)
104
+ : 0n;
105
+
106
+ result.premium = {
107
+ quotedPremiumTotal,
108
+ slippageBufferBaseUnits,
109
+ maxPremiumAmount: quotedPremiumTotal + slippageBufferBaseUnits,
110
+ };
111
+ }
112
+
113
+ return result;
114
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicentral/sos-sdk",
3
- "version": "0.4.0-alpha.1",
3
+ "version": "0.4.0-alpha.2",
4
4
  "private": false,
5
5
  "description": "Solana Option Standard SDK. The frontend-first SDK for Native Options Trading on Solana. Created by Epicentral Labs.",
6
6
  "type": "module",