@epicentral/sos-sdk 0.6.1-alpha → 0.7.0-alpha

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.

Potentially problematic release.


This version of @epicentral/sos-sdk might be problematic. Click here for more details.

package/long/builders.ts CHANGED
@@ -24,7 +24,13 @@ import {
24
24
  } from "../wsol/instructions";
25
25
  import { fetchMarketDataAccount, fetchOptionPool } from "../accounts/fetchers";
26
26
  import { getBuyFromPoolRemainingAccounts } from "./remaining-accounts";
27
+ import { applySlippageBps } from "./quotes";
28
+ import {
29
+ buildSwitchboardCrank,
30
+ prependSwitchboardCrank,
31
+ } from "../oracle/switchboard";
27
32
  import { fetchWriterPositionsForPool } from "../accounts/list";
33
+ import { getGlobalTradeConfig } from "../shared/trade-config";
28
34
  import bs58 from "bs58";
29
35
 
30
36
  export interface BuildBuyFromPoolParams {
@@ -143,6 +149,9 @@ export interface BuildBuyFromPoolTransactionWithDerivationParams {
143
149
  buyerPosition?: AddressLike;
144
150
  buyerOptionAccount?: AddressLike;
145
151
  remainingAccounts?: RemainingAccountInput[];
152
+ disableSwitchboardCrank?: boolean;
153
+ switchboardCrossbarUrl?: string;
154
+ switchboardNumSignatures?: number;
146
155
  }
147
156
 
148
157
  const DEFAULT_MARKET_ORDER_SLIPPAGE_BUFFER_BASE_UNITS = 500_000n;
@@ -224,7 +233,7 @@ export async function buildBuyFromPoolTransactionWithDerivation(
224
233
  Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
225
234
  );
226
235
 
227
- return buildBuyFromPoolTransaction({
236
+ const actionTx = await buildBuyFromPoolTransaction({
228
237
  optionPool: resolved.optionPool,
229
238
  optionAccount: resolved.optionAccount,
230
239
  longMint: resolved.longMint,
@@ -241,6 +250,20 @@ export async function buildBuyFromPoolTransactionWithDerivation(
241
250
  buyerOptionAccount,
242
251
  remainingAccounts: params.remainingAccounts,
243
252
  });
253
+
254
+ if (params.disableSwitchboardCrank) {
255
+ return actionTx;
256
+ }
257
+
258
+ const crank = await buildSwitchboardCrank({
259
+ rpc: params.rpc,
260
+ payer: params.buyer,
261
+ switchboardFeed,
262
+ marketData: resolved.marketData,
263
+ crossbarUrl: params.switchboardCrossbarUrl,
264
+ numSignatures: params.switchboardNumSignatures,
265
+ });
266
+ return prependSwitchboardCrank(crank, actionTx);
244
267
  }
245
268
 
246
269
  export interface BuildBuyFromPoolMarketOrderParams
@@ -311,10 +334,16 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
311
334
  `This may indicate data staleness - please refresh and retry.`
312
335
  );
313
336
 
314
- const slippageBuffer = normalizeMarketOrderSlippageBuffer(
315
- params,
316
- refetchedPool.underlyingMint
317
- );
337
+ const globalTradeConfig = getGlobalTradeConfig();
338
+ const hasExplicitSlippageBuffer =
339
+ params.slippageBufferBaseUnits !== undefined ||
340
+ params.slippageBufferLamports !== undefined;
341
+ const slippageBuffer = hasExplicitSlippageBuffer
342
+ ? normalizeMarketOrderSlippageBuffer(params, refetchedPool.underlyingMint)
343
+ : globalTradeConfig.slippageBps !== undefined
344
+ ? applySlippageBps(params.quotedPremiumTotal, globalTradeConfig.slippageBps) -
345
+ BigInt(params.quotedPremiumTotal)
346
+ : normalizeMarketOrderSlippageBuffer(params, refetchedPool.underlyingMint);
318
347
  const maxPremiumAmount = BigInt(params.quotedPremiumTotal) + slippageBuffer;
319
348
  assertPositiveAmount(maxPremiumAmount, "maxPremiumAmount");
320
349
 
@@ -329,7 +358,7 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
329
358
  Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
330
359
  );
331
360
 
332
- return buildBuyFromPoolTransaction({
361
+ const actionTx = await buildBuyFromPoolTransaction({
333
362
  optionPool: resolved.optionPool,
334
363
  optionAccount: resolved.optionAccount,
335
364
  longMint: resolved.longMint,
@@ -346,6 +375,20 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
346
375
  buyerOptionAccount,
347
376
  remainingAccounts,
348
377
  });
378
+
379
+ if (params.disableSwitchboardCrank) {
380
+ return actionTx;
381
+ }
382
+
383
+ const crank = await buildSwitchboardCrank({
384
+ rpc: params.rpc,
385
+ payer: params.buyer,
386
+ switchboardFeed,
387
+ marketData: resolved.marketData,
388
+ crossbarUrl: params.switchboardCrossbarUrl,
389
+ numSignatures: params.switchboardNumSignatures,
390
+ });
391
+ return prependSwitchboardCrank(crank, actionTx);
349
392
  }
350
393
 
351
394
  export async function buildCloseLongToPoolInstruction(
@@ -438,6 +481,9 @@ export interface BuildCloseLongToPoolTransactionWithDerivationParams {
438
481
  */
439
482
  unwrapPayoutSol?: boolean;
440
483
  remainingAccounts?: RemainingAccountInput[];
484
+ disableSwitchboardCrank?: boolean;
485
+ switchboardCrossbarUrl?: string;
486
+ switchboardNumSignatures?: number;
441
487
  }
442
488
 
443
489
  export async function buildCloseLongToPoolTransactionWithDerivation(
@@ -485,7 +531,7 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
485
531
  Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
486
532
  );
487
533
 
488
- return buildCloseLongToPoolTransaction({
534
+ const actionTx = await buildCloseLongToPoolTransaction({
489
535
  optionPool: resolved.optionPool,
490
536
  optionAccount: resolved.optionAccount,
491
537
  collateralPool: resolved.collateralPool,
@@ -507,4 +553,18 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
507
553
  unwrapPayoutSol,
508
554
  remainingAccounts: params.remainingAccounts,
509
555
  });
556
+
557
+ if (params.disableSwitchboardCrank) {
558
+ return actionTx;
559
+ }
560
+
561
+ const crank = await buildSwitchboardCrank({
562
+ rpc: params.rpc,
563
+ payer: params.buyer,
564
+ switchboardFeed,
565
+ marketData: resolved.marketData,
566
+ crossbarUrl: params.switchboardCrossbarUrl,
567
+ numSignatures: params.switchboardNumSignatures,
568
+ });
569
+ return prependSwitchboardCrank(crank, actionTx);
510
570
  }
package/long/exercise.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import { getOptionExerciseInstruction } from "../generated/instructions";
2
2
  import type { Instruction } from "@solana/kit";
3
3
  import { toAddress } from "../client/program";
4
- import type { AddressLike, BuiltTransaction } from "../client/types";
4
+ import type { AddressLike, BuiltTransaction, KitRpc } from "../client/types";
5
+ import {
6
+ buildSwitchboardCrank,
7
+ prependSwitchboardCrank,
8
+ } from "../oracle/switchboard";
5
9
 
6
10
  export interface BuildOptionExerciseParams {
7
11
  optionAccount: AddressLike;
@@ -16,6 +20,10 @@ export interface BuildOptionExerciseParams {
16
20
  escrowAuthority: AddressLike;
17
21
  buyer: AddressLike;
18
22
  tokenProgram?: AddressLike;
23
+ rpc?: KitRpc;
24
+ disableSwitchboardCrank?: boolean;
25
+ switchboardCrossbarUrl?: string;
26
+ switchboardNumSignatures?: number;
19
27
  }
20
28
 
21
29
  /**
@@ -48,7 +56,18 @@ export function buildOptionExerciseInstruction(
48
56
  */
49
57
  export function buildOptionExerciseTransaction(
50
58
  params: BuildOptionExerciseParams
51
- ): BuiltTransaction {
59
+ ): Promise<BuiltTransaction> {
52
60
  const instruction = buildOptionExerciseInstruction(params);
53
- return { instructions: [instruction] };
61
+ const actionTx = { instructions: [instruction] };
62
+ if (params.disableSwitchboardCrank || !params.rpc) {
63
+ return Promise.resolve(actionTx);
64
+ }
65
+
66
+ return buildSwitchboardCrank({
67
+ rpc: params.rpc,
68
+ payer: params.buyer,
69
+ switchboardFeed: params.switchboardFeed,
70
+ crossbarUrl: params.switchboardCrossbarUrl,
71
+ numSignatures: params.switchboardNumSignatures,
72
+ }).then((crank) => prependSwitchboardCrank(crank, actionTx));
54
73
  }
package/omlp/builders.ts CHANGED
@@ -4,9 +4,13 @@ import {
4
4
  } from "../generated/instructions";
5
5
  import type { Instruction } from "@solana/kit";
6
6
  import { toAddress } from "../client/program";
7
- import type { AddressLike, BuiltTransaction } from "../client/types";
7
+ import type { AddressLike, BuiltTransaction, KitRpc } from "../client/types";
8
8
  import { assertPositiveAmount } from "../shared/amounts";
9
9
  import { getCloseAccountInstruction, NATIVE_MINT } from "../wsol/instructions";
10
+ import {
11
+ buildSwitchboardCrank,
12
+ prependSwitchboardCrank,
13
+ } from "../oracle/switchboard";
10
14
 
11
15
  export interface BuildDepositToPositionParams {
12
16
  vault: AddressLike;
@@ -15,6 +19,12 @@ export interface BuildDepositToPositionParams {
15
19
  lender: AddressLike;
16
20
  amount: bigint | number;
17
21
  position?: AddressLike;
22
+ rpc?: KitRpc;
23
+ switchboardFeed?: AddressLike;
24
+ marketData?: AddressLike;
25
+ disableSwitchboardCrank?: boolean;
26
+ switchboardCrossbarUrl?: string;
27
+ switchboardNumSignatures?: number;
18
28
  }
19
29
 
20
30
  export interface BuildWithdrawFromPositionParams {
@@ -26,6 +36,12 @@ export interface BuildWithdrawFromPositionParams {
26
36
  position?: AddressLike;
27
37
  unwrapSol?: boolean;
28
38
  vaultMint?: AddressLike;
39
+ rpc?: KitRpc;
40
+ switchboardFeed?: AddressLike;
41
+ marketData?: AddressLike;
42
+ disableSwitchboardCrank?: boolean;
43
+ switchboardCrossbarUrl?: string;
44
+ switchboardNumSignatures?: number;
29
45
  }
30
46
 
31
47
  export async function buildDepositToPositionInstruction(
@@ -49,7 +65,24 @@ export async function buildDepositToPositionTransaction(
49
65
  params: BuildDepositToPositionParams
50
66
  ): Promise<BuiltTransaction> {
51
67
  const instruction = await buildDepositToPositionInstruction(params);
52
- return { instructions: [instruction] };
68
+ const actionTx = { instructions: [instruction] };
69
+ if (
70
+ params.disableSwitchboardCrank ||
71
+ !params.rpc ||
72
+ (!params.switchboardFeed && !params.marketData)
73
+ ) {
74
+ return actionTx;
75
+ }
76
+
77
+ const crank = await buildSwitchboardCrank({
78
+ rpc: params.rpc,
79
+ payer: params.lender,
80
+ switchboardFeed: params.switchboardFeed,
81
+ marketData: params.marketData,
82
+ crossbarUrl: params.switchboardCrossbarUrl,
83
+ numSignatures: params.switchboardNumSignatures,
84
+ });
85
+ return prependSwitchboardCrank(crank, actionTx);
53
86
  }
54
87
 
55
88
  export async function buildWithdrawFromPositionInstruction(
@@ -90,5 +123,22 @@ export async function buildWithdrawFromPositionTransaction(
90
123
  );
91
124
  }
92
125
 
93
- return { instructions };
126
+ const actionTx = { instructions };
127
+ if (
128
+ params.disableSwitchboardCrank ||
129
+ !params.rpc ||
130
+ (!params.switchboardFeed && !params.marketData)
131
+ ) {
132
+ return actionTx;
133
+ }
134
+
135
+ const crank = await buildSwitchboardCrank({
136
+ rpc: params.rpc,
137
+ payer: params.lender,
138
+ switchboardFeed: params.switchboardFeed,
139
+ marketData: params.marketData,
140
+ crossbarUrl: params.switchboardCrossbarUrl,
141
+ numSignatures: params.switchboardNumSignatures,
142
+ });
143
+ return prependSwitchboardCrank(crank, actionTx);
94
144
  }
@@ -1,10 +1,17 @@
1
- import type { Address } from "@solana/kit";
1
+ import type { Address, Instruction } from "@solana/kit";
2
+ import { fromLegacyTransactionInstruction } from "@solana/compat";
3
+ import { CrossbarClient } from "@switchboard-xyz/common";
2
4
  import bs58 from "bs58";
3
5
  import { toAddress } from "../client/program";
4
- import type { AddressLike, KitRpc } from "../client/types";
6
+ import type { AddressLike, BuiltTransaction, KitRpc } from "../client/types";
5
7
  import { fetchMarketDataAccount } from "../accounts/fetchers";
6
8
  import { invariant } from "../shared/errors";
7
9
 
10
+ const DEVNET_GENESIS_HASH = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
11
+ const MAINNET_BETA_GENESIS_HASH = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
12
+
13
+ export type SwitchboardNetwork = "devnet" | "mainnet";
14
+
8
15
  export async function resolveSwitchboardFeedFromMarketData(
9
16
  rpc: KitRpc,
10
17
  marketData: AddressLike
@@ -54,3 +61,84 @@ export async function buildSwitchboardPullFeedUpdate<
54
61
  lookupTables: luts ?? [],
55
62
  };
56
63
  }
64
+
65
+ export interface BuildSwitchboardCrankParams {
66
+ rpc: KitRpc;
67
+ payer: AddressLike;
68
+ switchboardFeed?: AddressLike;
69
+ marketData?: AddressLike;
70
+ network?: SwitchboardNetwork;
71
+ crossbarUrl?: string;
72
+ numSignatures?: number;
73
+ }
74
+
75
+ export interface SwitchboardCrankResult {
76
+ instructions: Instruction<string>[];
77
+ addressLookupTableAddresses: AddressLike[];
78
+ }
79
+
80
+ export async function inferSwitchboardNetwork(
81
+ rpc: KitRpc
82
+ ): Promise<SwitchboardNetwork> {
83
+ const genesisHash = await rpc.getGenesisHash().send();
84
+ if (genesisHash === DEVNET_GENESIS_HASH) {
85
+ return "devnet";
86
+ }
87
+ if (genesisHash === MAINNET_BETA_GENESIS_HASH) {
88
+ return "mainnet";
89
+ }
90
+ throw new Error(
91
+ `Unable to infer Switchboard network from genesis hash: ${genesisHash}`
92
+ );
93
+ }
94
+
95
+ export async function buildSwitchboardCrank(
96
+ params: BuildSwitchboardCrankParams
97
+ ): Promise<SwitchboardCrankResult> {
98
+ const resolvedFeed =
99
+ params.switchboardFeed ??
100
+ (params.marketData
101
+ ? await resolveSwitchboardFeedFromMarketData(params.rpc, params.marketData)
102
+ : undefined);
103
+
104
+ invariant(
105
+ !!resolvedFeed,
106
+ "switchboardFeed or marketData is required to build Switchboard crank instructions."
107
+ );
108
+
109
+ const network = params.network ?? (await inferSwitchboardNetwork(params.rpc));
110
+ const crossbar = params.crossbarUrl
111
+ ? new CrossbarClient(params.crossbarUrl)
112
+ : CrossbarClient.default();
113
+ const updates = await crossbar.fetchSolanaUpdates(
114
+ network,
115
+ [toAddress(resolvedFeed)],
116
+ toAddress(params.payer),
117
+ params.numSignatures
118
+ );
119
+ const update = updates[0];
120
+
121
+ const instructions =
122
+ update?.pullIxns?.map((instruction) =>
123
+ fromLegacyTransactionInstruction(instruction)
124
+ ) ?? [];
125
+ const addressLookupTableAddresses = update?.lookupTables ?? [];
126
+
127
+ return {
128
+ instructions,
129
+ addressLookupTableAddresses,
130
+ };
131
+ }
132
+
133
+ export function prependSwitchboardCrank(
134
+ crank: SwitchboardCrankResult,
135
+ action: BuiltTransaction
136
+ ): BuiltTransaction {
137
+ return {
138
+ instructions: [...crank.instructions, ...action.instructions],
139
+ addressLookupTableAddresses: [
140
+ ...(crank.addressLookupTableAddresses ?? []),
141
+ ...(action.addressLookupTableAddresses ?? []),
142
+ ],
143
+ };
144
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicentral/sos-sdk",
3
- "version": "0.6.1-alpha",
3
+ "version": "0.7.0-alpha",
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",
@@ -18,7 +18,10 @@
18
18
  "@solana-program/address-lookup-table": "^0.11.0",
19
19
  "@solana-program/compute-budget": "^0.13.0",
20
20
  "@solana-program/system": "^0.11.0",
21
+ "@solana/compat": "^6.1.0",
21
22
  "@solana/kit": "^6.1.0",
23
+ "@switchboard-xyz/common": "^5.7.0",
24
+ "@switchboard-xyz/on-demand": "^3.9.0",
22
25
  "bs58": "^6.0.0",
23
26
  "decimal.js": "^10.4.3"
24
27
  },
@@ -0,0 +1,27 @@
1
+ export interface TradeConfig {
2
+ slippageBps?: number;
3
+ computeUnitLimit?: number;
4
+ computeUnitPriceMicroLamports?: number;
5
+ }
6
+
7
+ let globalTradeConfig: TradeConfig = {};
8
+
9
+ export function setGlobalTradeConfig(config: TradeConfig): void {
10
+ globalTradeConfig = { ...config };
11
+ }
12
+
13
+ export function updateGlobalTradeConfig(config: Partial<TradeConfig>): void {
14
+ globalTradeConfig = { ...globalTradeConfig, ...config };
15
+ }
16
+
17
+ export function getGlobalTradeConfig(): TradeConfig {
18
+ return { ...globalTradeConfig };
19
+ }
20
+
21
+ export function resetGlobalTradeConfig(): void {
22
+ globalTradeConfig = {};
23
+ }
24
+
25
+ export function resolveTradeConfig(overrides?: Partial<TradeConfig>): TradeConfig {
26
+ return { ...globalTradeConfig, ...overrides };
27
+ }
@@ -21,6 +21,11 @@ import {
21
21
  import type { Instruction } from "@solana/kit";
22
22
  import { toAddress } from "../client/program";
23
23
  import type { AddressLike, BuiltTransaction, KitRpc } from "../client/types";
24
+ import {
25
+ getLookupTableAddressForNetwork,
26
+ type LookupTableNetwork,
27
+ } from "../client/lookup-table";
28
+ import { getGlobalTradeConfig } from "./trade-config";
24
29
 
25
30
  export interface SendBuiltTransactionParams extends BuiltTransaction {
26
31
  rpc: KitRpc;
@@ -29,7 +34,18 @@ export interface SendBuiltTransactionParams extends BuiltTransaction {
29
34
  commitment?: "processed" | "confirmed" | "finalized";
30
35
  computeUnitLimit?: number;
31
36
  computeUnitPriceMicroLamports?: number;
37
+ /**
38
+ * Address Lookup Table addresses to compress the transaction.
39
+ * REQUIRED for option_mint and other large transactions to avoid
40
+ * "encoding overruns Uint8Array" (Solana's 1232-byte tx limit).
41
+ * Use getLookupTableAddressForNetwork(network) or pass network to auto-include.
42
+ */
32
43
  addressLookupTableAddresses?: AddressLike[];
44
+ /**
45
+ * When set, automatically includes the option program's lookup table for this network.
46
+ * Use this when sending option_mint, buy_from_pool, or other large transactions.
47
+ */
48
+ network?: LookupTableNetwork;
33
49
  }
34
50
 
35
51
  /**
@@ -41,23 +57,38 @@ export async function sendBuiltTransaction(
41
57
  params: SendBuiltTransactionParams
42
58
  ): Promise<string> {
43
59
  const commitment = params.commitment ?? "confirmed";
60
+ const globalTradeConfig = getGlobalTradeConfig();
44
61
  const { value: latestBlockhash } = await params.rpc.getLatestBlockhash().send();
45
62
 
46
63
  const computeBudgetInstructions: Instruction<string>[] = [];
47
- if (params.computeUnitLimit !== undefined) {
64
+ const computeUnitLimit =
65
+ params.computeUnitLimit ?? globalTradeConfig.computeUnitLimit;
66
+ if (computeUnitLimit !== undefined) {
48
67
  computeBudgetInstructions.push(
49
- getSetComputeUnitLimitInstruction({ units: params.computeUnitLimit })
68
+ getSetComputeUnitLimitInstruction({ units: computeUnitLimit })
50
69
  );
51
70
  }
52
- if (params.computeUnitPriceMicroLamports !== undefined) {
71
+ const computeUnitPriceMicroLamports =
72
+ params.computeUnitPriceMicroLamports ??
73
+ globalTradeConfig.computeUnitPriceMicroLamports;
74
+ if (computeUnitPriceMicroLamports !== undefined) {
53
75
  computeBudgetInstructions.push(
54
76
  getSetComputeUnitPriceInstruction({
55
- microLamports: params.computeUnitPriceMicroLamports,
77
+ microLamports: computeUnitPriceMicroLamports,
56
78
  })
57
79
  );
58
80
  }
59
81
  const allInstructions = [...computeBudgetInstructions, ...params.instructions];
60
82
 
83
+ // Resolve address lookup tables: explicit list, or from network for option program
84
+ let addressLookupTableAddresses = params.addressLookupTableAddresses ?? [];
85
+ if (params.network) {
86
+ const programAlt = getLookupTableAddressForNetwork(params.network);
87
+ if (programAlt && !addressLookupTableAddresses.some((a) => String(a) === String(programAlt))) {
88
+ addressLookupTableAddresses = [programAlt, ...addressLookupTableAddresses];
89
+ }
90
+ }
91
+
61
92
  let txMessage = pipe(
62
93
  createTransactionMessage({ version: 0 }),
63
94
  (tx) => setTransactionMessageFeePayerSigner(params.feePayer, tx),
@@ -65,12 +96,9 @@ export async function sendBuiltTransaction(
65
96
  (tx) => appendTransactionMessageInstructions(allInstructions, tx)
66
97
  );
67
98
 
68
- if (
69
- params.addressLookupTableAddresses &&
70
- params.addressLookupTableAddresses.length > 0
71
- ) {
99
+ if (addressLookupTableAddresses.length > 0) {
72
100
  const addressesByAddressLookupTable: AddressesByLookupTableAddress = {};
73
- for (const altAddress of params.addressLookupTableAddresses) {
101
+ for (const altAddress of addressLookupTableAddresses) {
74
102
  const resolvedAddress = toAddress(altAddress);
75
103
  const { data } = await fetchAddressLookupTable(params.rpc, resolvedAddress);
76
104
  addressesByAddressLookupTable[resolvedAddress] = data.addresses;
package/short/builders.ts CHANGED
@@ -31,6 +31,12 @@ import {
31
31
  getWrapSOLInstructions,
32
32
  } from "../wsol/instructions";
33
33
  import { preflightUnwindWriterUnsold } from "./preflight";
34
+ import {
35
+ buildSwitchboardCrank,
36
+ prependSwitchboardCrank,
37
+ } from "../oracle/switchboard";
38
+ import { applySlippageBps } from "../long/quotes";
39
+ import { getGlobalTradeConfig } from "../shared/trade-config";
34
40
  import bs58 from "bs58";
35
41
 
36
42
  export interface BuildOptionMintParams {
@@ -48,6 +54,7 @@ export interface BuildOptionMintParams {
48
54
  collateralMint?: AddressLike;
49
55
  makerCollateralAmount: bigint | number;
50
56
  borrowedAmount: bigint | number;
57
+ maxRequiredCollateralAmount?: bigint | number;
51
58
  maker: AddressLike;
52
59
  makerCollateralAccount: AddressLike;
53
60
  underlyingMint: AddressLike;
@@ -133,6 +140,16 @@ export async function buildOptionMintInstruction(
133
140
  invariant(params.underlyingSymbol.length > 0, "underlyingSymbol is required.");
134
141
 
135
142
  const borrowedAmount = BigInt(params.borrowedAmount);
143
+ const globalTradeConfig = getGlobalTradeConfig();
144
+ const maxRequiredCollateralAmount =
145
+ params.maxRequiredCollateralAmount !== undefined
146
+ ? BigInt(params.maxRequiredCollateralAmount)
147
+ : globalTradeConfig.slippageBps !== undefined
148
+ ? applySlippageBps(
149
+ BigInt(params.makerCollateralAmount) + borrowedAmount,
150
+ globalTradeConfig.slippageBps
151
+ )
152
+ : BigInt(params.makerCollateralAmount) + borrowedAmount;
136
153
  if (borrowedAmount > 0n) {
137
154
  invariant(!!params.vault, "vault is required when borrowedAmount > 0");
138
155
  invariant(
@@ -209,6 +226,7 @@ export async function buildOptionMintInstruction(
209
226
  collateralMintArg: toAddress(params.collateralMint ?? params.underlyingMint),
210
227
  makerCollateralAmount: params.makerCollateralAmount,
211
228
  borrowedAmount: params.borrowedAmount,
229
+ maxRequiredCollateralAmount,
212
230
  });
213
231
 
214
232
  return appendRemainingAccounts(kitInstruction, params.remainingAccounts);
@@ -251,6 +269,7 @@ export interface BuildOptionMintTransactionWithDerivationParams {
251
269
  collateralMint?: AddressLike;
252
270
  makerCollateralAmount: bigint | number;
253
271
  borrowedAmount: bigint | number;
272
+ maxRequiredCollateralAmount?: bigint | number;
254
273
  maker: AddressLike;
255
274
  /**
256
275
  * Optional. When omitted, the SDK derives the maker's collateral ATA for collateralMint
@@ -268,6 +287,9 @@ export interface BuildOptionMintTransactionWithDerivationParams {
268
287
  escrowTokenAccount?: AddressLike;
269
288
  poolLoan?: AddressLike;
270
289
  remainingAccounts?: RemainingAccountInput[];
290
+ disableSwitchboardCrank?: boolean;
291
+ switchboardCrossbarUrl?: string;
292
+ switchboardNumSignatures?: number;
271
293
  }
272
294
 
273
295
  export async function buildOptionMintTransactionWithDerivation(
@@ -357,9 +379,23 @@ export async function buildOptionMintTransactionWithDerivation(
357
379
  makerCollateralAccount
358
380
  );
359
381
 
360
- return {
382
+ const actionTx = {
361
383
  instructions: [createAtaIx, ...tx.instructions],
362
384
  };
385
+
386
+ if (params.disableSwitchboardCrank) {
387
+ return actionTx;
388
+ }
389
+
390
+ const crank = await buildSwitchboardCrank({
391
+ rpc: params.rpc,
392
+ payer: params.maker,
393
+ switchboardFeed,
394
+ marketData: resolved.marketData,
395
+ crossbarUrl: params.switchboardCrossbarUrl,
396
+ numSignatures: params.switchboardNumSignatures,
397
+ });
398
+ return prependSwitchboardCrank(crank, actionTx);
363
399
  }
364
400
 
365
401
  export async function buildUnwindWriterUnsoldInstruction(
@@ -480,8 +516,12 @@ export async function buildUnwindWriterUnsoldWithLoanRepayment(
480
516
  ]);
481
517
 
482
518
  const vaultLoans = loans
483
- .filter((item) => toAddress(item.data.vault) === vaultPdaStr)
484
- .slice(0, MAX_POOL_LOANS_PER_UNWIND);
519
+ .filter((item) => toAddress(item.data.vault) === vaultPdaStr);
520
+
521
+ invariant(
522
+ vaultLoans.length <= MAX_POOL_LOANS_PER_UNWIND,
523
+ `Too many active pool loans for unwind: ${vaultLoans.length}. Max supported in one unwind is ${MAX_POOL_LOANS_PER_UNWIND}.`
524
+ );
485
525
 
486
526
  const remainingAccounts: RemainingAccountInput[] = vaultLoans.map((item) => ({
487
527
  address: item.address,
@@ -515,8 +555,8 @@ export async function buildUnwindWriterUnsoldWithLoanRepayment(
515
555
  : 0n;
516
556
 
517
557
  invariant(
518
- preflight.canRepayFully,
519
- `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)}`
558
+ preflight.canRepayRequestedSlice,
559
+ `Unwind cannot repay the requested slice: 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)}`
520
560
  );
521
561
  if (isWsolPath && lamportsToWrap > 0n && !params.includeWrapForShortfall) {
522
562
  invariant(