@epicentral/sos-sdk 0.12.2-beta → 0.13.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.
@@ -48,6 +48,7 @@ export type UpdateImpliedVolatilityInstruction<
48
48
  TAccountOptionAccount extends string | AccountMeta<string> = string,
49
49
  TAccountOptionPool extends string | AccountMeta<string> = string,
50
50
  TAccountMarketData extends string | AccountMeta<string> = string,
51
+ TAccountVolatilityQuoteAccount extends string | AccountMeta<string> = string,
51
52
  TAccountKeeper extends string | AccountMeta<string> = string,
52
53
  TRemainingAccounts extends readonly AccountMeta<string>[] = [],
53
54
  > = Instruction<TProgram> &
@@ -63,6 +64,9 @@ export type UpdateImpliedVolatilityInstruction<
63
64
  TAccountMarketData extends string
64
65
  ? ReadonlyAccount<TAccountMarketData>
65
66
  : TAccountMarketData,
67
+ TAccountVolatilityQuoteAccount extends string
68
+ ? ReadonlyAccount<TAccountVolatilityQuoteAccount>
69
+ : TAccountVolatilityQuoteAccount,
66
70
  TAccountKeeper extends string
67
71
  ? ReadonlySignerAccount<TAccountKeeper> &
68
72
  AccountSignerMeta<TAccountKeeper>
@@ -107,6 +111,7 @@ export type UpdateImpliedVolatilityInput<
107
111
  TAccountOptionAccount extends string = string,
108
112
  TAccountOptionPool extends string = string,
109
113
  TAccountMarketData extends string = string,
114
+ TAccountVolatilityQuoteAccount extends string = string,
110
115
  TAccountKeeper extends string = string,
111
116
  > = {
112
117
  /** The option account to update IV for */
@@ -115,6 +120,7 @@ export type UpdateImpliedVolatilityInput<
115
120
  optionPool: Address<TAccountOptionPool>;
116
121
  /** Market data - provides baseline historical volatility */
117
122
  marketData: Address<TAccountMarketData>;
123
+ volatilityQuoteAccount: Address<TAccountVolatilityQuoteAccount>;
118
124
  /** Keeper/anyone can call this instruction (permissionless update) */
119
125
  keeper: TransactionSigner<TAccountKeeper>;
120
126
  };
@@ -123,6 +129,7 @@ export function getUpdateImpliedVolatilityInstruction<
123
129
  TAccountOptionAccount extends string,
124
130
  TAccountOptionPool extends string,
125
131
  TAccountMarketData extends string,
132
+ TAccountVolatilityQuoteAccount extends string,
126
133
  TAccountKeeper extends string,
127
134
  TProgramAddress extends Address = typeof OPTION_PROGRAM_PROGRAM_ADDRESS,
128
135
  >(
@@ -130,6 +137,7 @@ export function getUpdateImpliedVolatilityInstruction<
130
137
  TAccountOptionAccount,
131
138
  TAccountOptionPool,
132
139
  TAccountMarketData,
140
+ TAccountVolatilityQuoteAccount,
133
141
  TAccountKeeper
134
142
  >,
135
143
  config?: { programAddress?: TProgramAddress },
@@ -138,6 +146,7 @@ export function getUpdateImpliedVolatilityInstruction<
138
146
  TAccountOptionAccount,
139
147
  TAccountOptionPool,
140
148
  TAccountMarketData,
149
+ TAccountVolatilityQuoteAccount,
141
150
  TAccountKeeper
142
151
  > {
143
152
  // Program address.
@@ -149,6 +158,10 @@ export function getUpdateImpliedVolatilityInstruction<
149
158
  optionAccount: { value: input.optionAccount ?? null, isWritable: true },
150
159
  optionPool: { value: input.optionPool ?? null, isWritable: false },
151
160
  marketData: { value: input.marketData ?? null, isWritable: false },
161
+ volatilityQuoteAccount: {
162
+ value: input.volatilityQuoteAccount ?? null,
163
+ isWritable: false,
164
+ },
152
165
  keeper: { value: input.keeper ?? null, isWritable: false },
153
166
  };
154
167
  const accounts = originalAccounts as Record<
@@ -162,6 +175,7 @@ export function getUpdateImpliedVolatilityInstruction<
162
175
  getAccountMeta(accounts.optionAccount),
163
176
  getAccountMeta(accounts.optionPool),
164
177
  getAccountMeta(accounts.marketData),
178
+ getAccountMeta(accounts.volatilityQuoteAccount),
165
179
  getAccountMeta(accounts.keeper),
166
180
  ],
167
181
  data: getUpdateImpliedVolatilityInstructionDataEncoder().encode({}),
@@ -171,6 +185,7 @@ export function getUpdateImpliedVolatilityInstruction<
171
185
  TAccountOptionAccount,
172
186
  TAccountOptionPool,
173
187
  TAccountMarketData,
188
+ TAccountVolatilityQuoteAccount,
174
189
  TAccountKeeper
175
190
  >);
176
191
  }
@@ -187,8 +202,9 @@ export type ParsedUpdateImpliedVolatilityInstruction<
187
202
  optionPool: TAccountMetas[1];
188
203
  /** Market data - provides baseline historical volatility */
189
204
  marketData: TAccountMetas[2];
205
+ volatilityQuoteAccount: TAccountMetas[3];
190
206
  /** Keeper/anyone can call this instruction (permissionless update) */
191
- keeper: TAccountMetas[3];
207
+ keeper: TAccountMetas[4];
192
208
  };
193
209
  data: UpdateImpliedVolatilityInstructionData;
194
210
  };
@@ -201,7 +217,7 @@ export function parseUpdateImpliedVolatilityInstruction<
201
217
  InstructionWithAccounts<TAccountMetas> &
202
218
  InstructionWithData<ReadonlyUint8Array>,
203
219
  ): ParsedUpdateImpliedVolatilityInstruction<TProgram, TAccountMetas> {
204
- if (instruction.accounts.length < 4) {
220
+ if (instruction.accounts.length < 5) {
205
221
  // TODO: Coded error.
206
222
  throw new Error("Not enough accounts");
207
223
  }
@@ -217,6 +233,7 @@ export function parseUpdateImpliedVolatilityInstruction<
217
233
  optionAccount: getNextAccount(),
218
234
  optionPool: getNextAccount(),
219
235
  marketData: getNextAccount(),
236
+ volatilityQuoteAccount: getNextAccount(),
220
237
  keeper: getNextAccount(),
221
238
  },
222
239
  data: getUpdateImpliedVolatilityInstructionDataDecoder().decode(
@@ -34,6 +34,7 @@ import {
34
34
  type ParsedLiquidateWriterPositionInstruction,
35
35
  type ParsedLiquidateWriterPositionRescueInstruction,
36
36
  type ParsedMigrateCollateralPoolV1ToV2Instruction,
37
+ type ParsedMigrateMarketDataVolOracleInstruction,
37
38
  type ParsedOmlpCreateVaultInstruction,
38
39
  type ParsedOmlpUpdateFeeWalletInstruction,
39
40
  type ParsedOmlpUpdateInterestModelInstruction,
@@ -265,6 +266,7 @@ export enum OptionProgramInstruction {
265
266
  LiquidateWriterPosition,
266
267
  LiquidateWriterPositionRescue,
267
268
  MigrateCollateralPoolV1ToV2,
269
+ MigrateMarketDataVolOracle,
268
270
  OmlpCreateVault,
269
271
  OmlpUpdateFeeWallet,
270
272
  OmlpUpdateInterestModel,
@@ -516,6 +518,17 @@ export function identifyOptionProgramInstruction(
516
518
  ) {
517
519
  return OptionProgramInstruction.MigrateCollateralPoolV1ToV2;
518
520
  }
521
+ if (
522
+ containsBytes(
523
+ data,
524
+ fixEncoderSize(getBytesEncoder(), 8).encode(
525
+ new Uint8Array([230, 102, 218, 98, 194, 231, 97, 241]),
526
+ ),
527
+ 0,
528
+ )
529
+ ) {
530
+ return OptionProgramInstruction.MigrateMarketDataVolOracle;
531
+ }
519
532
  if (
520
533
  containsBytes(
521
534
  data,
@@ -859,6 +872,9 @@ export type ParsedOptionProgramInstruction<
859
872
  | ({
860
873
  instructionType: OptionProgramInstruction.MigrateCollateralPoolV1ToV2;
861
874
  } & ParsedMigrateCollateralPoolV1ToV2Instruction<TProgram>)
875
+ | ({
876
+ instructionType: OptionProgramInstruction.MigrateMarketDataVolOracle;
877
+ } & ParsedMigrateMarketDataVolOracleInstruction<TProgram>)
862
878
  | ({
863
879
  instructionType: OptionProgramInstruction.OmlpCreateVault;
864
880
  } & ParsedOmlpCreateVaultInstruction<TProgram>)
package/index.ts CHANGED
@@ -64,6 +64,12 @@ export {
64
64
  type SwitchboardNetwork,
65
65
  } from "./oracle/switchboard";
66
66
 
67
+ export {
68
+ deriveVolatilityQuoteAddress,
69
+ deriveVolatilityQuoteAddressFromMarketData,
70
+ isVolOracleEnabled,
71
+ } from "./oracle/volatility-quote";
72
+
67
73
  export {
68
74
  getWrapSOLInstructions,
69
75
  getUnwrapSOLInstructions,
package/long/builders.ts CHANGED
@@ -36,6 +36,7 @@ import {
36
36
  inferSwitchboardNetwork,
37
37
  prependSwitchboardQuote,
38
38
  } from "../oracle/switchboard";
39
+ import { deriveVolatilityQuoteAddressFromMarketData } from "../oracle/volatility-quote";
39
40
  import { fetchWriterPositionsForPool } from "../accounts/list";
40
41
  import { getGlobalTradeConfig } from "../shared/trade-config";
41
42
 
@@ -45,6 +46,8 @@ export interface BuildBuyFromPoolParams {
45
46
  longMint: AddressLike;
46
47
  underlyingMint: AddressLike;
47
48
  marketData: AddressLike;
49
+ /** Authority-updated Switchboard HV quote PDA (see deriveVolatilityQuoteAddressFromMarketData). */
50
+ volatilityQuoteAccount: AddressLike;
48
51
  /** Program `switchboard_queue` — use {@link getDefaultSwitchboardQueueAddress} for the cluster. */
49
52
  switchboardQueue: AddressLike;
50
53
  buyer: AddressLike;
@@ -92,6 +95,8 @@ export interface BuildCloseLongToPoolParams {
92
95
  escrowLongAccount: AddressLike;
93
96
  premiumVault: AddressLike;
94
97
  marketData: AddressLike;
98
+ /** Authority-updated Switchboard HV quote PDA (see deriveVolatilityQuoteAddressFromMarketData). */
99
+ volatilityQuoteAccount: AddressLike;
95
100
  switchboardQueue: AddressLike;
96
101
  buyer: AddressLike;
97
102
  buyerLongAccount: AddressLike;
@@ -131,6 +136,7 @@ export async function buildBuyFromPoolInstruction(
131
136
  longMint: toAddress(params.longMint),
132
137
  underlyingMint: toAddress(params.underlyingMint),
133
138
  marketData: toAddress(params.marketData),
139
+ volatilityQuoteAccount: toAddress(params.volatilityQuoteAccount),
134
140
  switchboardQueue: toAddress(params.switchboardQueue),
135
141
  buyer: toAddress(params.buyer) as any,
136
142
  buyerPosition: params.buyerPosition ? toAddress(params.buyerPosition) : undefined,
@@ -217,6 +223,8 @@ export interface BuildBuyFromPoolTransactionWithDerivationParams {
217
223
  * Use **1** if only SetComputeUnitLimit is prepended (e.g. `omitComputeUnitPriceInstruction`); **0** if no CU prepend.
218
224
  */
219
225
  switchboardQuoteInstructionIndex?: number;
226
+ /** Optional override — derived from on-chain market data when omitted. */
227
+ volatilityQuoteAccount?: AddressLike;
220
228
  }
221
229
 
222
230
  const DEFAULT_MARKET_ORDER_SLIPPAGE_BUFFER_BASE_UNITS = 500_000n;
@@ -303,12 +311,22 @@ export async function buildBuyFromPoolTransactionWithDerivation(
303
311
  }),
304
312
  ]);
305
313
 
314
+ const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
315
+ invariant(
316
+ !!marketDataAccount,
317
+ "Market data account not found for resolved option market."
318
+ );
319
+ const volatilityQuoteAccount =
320
+ params.volatilityQuoteAccount ??
321
+ deriveVolatilityQuoteAddressFromMarketData(marketDataAccount);
322
+
306
323
  const buyParams = {
307
324
  optionPool: resolved.optionPool,
308
325
  optionAccount: resolved.optionAccount,
309
326
  longMint: resolved.longMint,
310
327
  underlyingMint: resolved.underlyingMint!,
311
328
  marketData: resolved.marketData,
329
+ volatilityQuoteAccount,
312
330
  buyer: params.buyer,
313
331
  paymentMint: paymentAccounts.paymentMint,
314
332
  buyerPaymentAccount: paymentAccounts.buyerPaymentAccount,
@@ -321,11 +339,6 @@ export async function buildBuyFromPoolTransactionWithDerivation(
321
339
  remainingAccounts: params.remainingAccounts,
322
340
  };
323
341
 
324
- const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
325
- invariant(
326
- !!marketDataAccount,
327
- "Market data account not found for resolved option market."
328
- );
329
342
  const feedIdHex = feedIdBytesToHex(
330
343
  Uint8Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
331
344
  );
@@ -460,12 +473,22 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
460
473
  const maxPremiumAmount = quotePremium + slippageBuffer;
461
474
  assertPositiveAmount(maxPremiumAmount, "maxPremiumAmount");
462
475
 
476
+ const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
477
+ invariant(
478
+ !!marketDataAccount,
479
+ "Market data account not found for resolved option market."
480
+ );
481
+ const volatilityQuoteAccount =
482
+ params.volatilityQuoteAccount ??
483
+ deriveVolatilityQuoteAddressFromMarketData(marketDataAccount);
484
+
463
485
  const marketBuyParams = {
464
486
  optionPool: resolved.optionPool,
465
487
  optionAccount: resolved.optionAccount,
466
488
  longMint: resolved.longMint,
467
489
  underlyingMint: refetchedPool.underlyingMint,
468
490
  marketData: resolved.marketData,
491
+ volatilityQuoteAccount,
469
492
  buyer: params.buyer,
470
493
  paymentMint: paymentAccounts.paymentMint,
471
494
  buyerPaymentAccount: paymentAccounts.buyerPaymentAccount,
@@ -478,11 +501,6 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
478
501
  remainingAccounts,
479
502
  };
480
503
 
481
- const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
482
- invariant(
483
- !!marketDataAccount,
484
- "Market data account not found for resolved option market."
485
- );
486
504
  const feedIdHex = feedIdBytesToHex(
487
505
  Uint8Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
488
506
  );
@@ -541,6 +559,7 @@ export async function buildCloseLongToPoolInstruction(
541
559
  escrowLongAccount: toAddress(params.escrowLongAccount),
542
560
  premiumVault: toAddress(params.premiumVault),
543
561
  marketData: toAddress(params.marketData),
562
+ volatilityQuoteAccount: toAddress(params.volatilityQuoteAccount),
544
563
  switchboardQueue: toAddress(params.switchboardQueue),
545
564
  buyer: toAddress(params.buyer) as any,
546
565
  buyerLongAccount: toAddress(params.buyerLongAccount),
@@ -652,6 +671,8 @@ export interface BuildCloseLongToPoolTransactionWithDerivationParams {
652
671
  * OPX `sendInstructions` prepends only `SetComputeUnitLimit` before program ixs → default **1** (quote after CU limit).
653
672
  */
654
673
  switchboardQuoteInstructionIndex?: number;
674
+ /** Optional override — derived from on-chain market data when omitted. */
675
+ volatilityQuoteAccount?: AddressLike;
655
676
  }
656
677
 
657
678
  export async function buildCloseLongToPoolTransactionWithDerivation(
@@ -719,6 +740,9 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
719
740
  !!marketDataAccount,
720
741
  "Market data account not found for resolved option market."
721
742
  );
743
+ const volatilityQuoteAccount =
744
+ params.volatilityQuoteAccount ??
745
+ deriveVolatilityQuoteAddressFromMarketData(marketDataAccount);
722
746
  const feedIdHex = feedIdBytesToHex(
723
747
  Uint8Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
724
748
  );
@@ -738,6 +762,7 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
738
762
  escrowLongAccount: resolved.escrowLongAccount!,
739
763
  premiumVault: resolved.premiumVault!,
740
764
  marketData: resolved.marketData,
765
+ volatilityQuoteAccount,
741
766
  switchboardQueue,
742
767
  buyer: params.buyer,
743
768
  buyerLongAccount: params.buyerLongAccount,
@@ -778,6 +803,7 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
778
803
  escrowLongAccount: resolved.escrowLongAccount!,
779
804
  premiumVault: resolved.premiumVault!,
780
805
  marketData: resolved.marketData,
806
+ volatilityQuoteAccount,
781
807
  switchboardQueue: getDefaultSwitchboardQueueAddress(network),
782
808
  buyer: params.buyer,
783
809
  buyerLongAccount: params.buyerLongAccount,
@@ -0,0 +1,65 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import { address, type Address } from "@solana/kit";
3
+ import { toAddress } from "../client/program";
4
+ import type { AddressLike } from "../client/types";
5
+ import type { MarketDataAccount } from "../generated/accounts/marketDataAccount";
6
+
7
+ const ZERO_PUBKEY = "11111111111111111111111111111111";
8
+ const QUOTE_PROGRAM_ID = new PublicKey("orac1eFjzWL5R3RbbdMV68K9H6TaCVVcL6LjvQQWAbz");
9
+ const AUTHORITY_QUOTE_SCHEME_TAG = Buffer.from("AUTH");
10
+
11
+ function feedIdBytes(feedId: MarketDataAccount["switchboardVolatilityFeedId"]): Uint8Array {
12
+ return Uint8Array.from(feedId);
13
+ }
14
+
15
+ function feedIdIsZero(feedId: MarketDataAccount["switchboardVolatilityFeedId"]): boolean {
16
+ const bytes = feedIdBytes(feedId);
17
+ return bytes.length === 32 && bytes.every((b) => b === 0);
18
+ }
19
+
20
+ export function isVolOracleEnabled(
21
+ marketData: Pick<
22
+ MarketDataAccount,
23
+ "switchboardVolatilityFeedId" | "volatilityQuoteAuthority"
24
+ >
25
+ ): boolean {
26
+ return (
27
+ !feedIdIsZero(marketData.switchboardVolatilityFeedId) &&
28
+ String(marketData.volatilityQuoteAuthority) !== ZERO_PUBKEY
29
+ );
30
+ }
31
+
32
+ /**
33
+ * Derives the Switchboard authority quote PDA for an HV feed.
34
+ */
35
+ export function deriveVolatilityQuoteAddress(
36
+ authority: AddressLike,
37
+ switchboardVolatilityFeedId: MarketDataAccount["switchboardVolatilityFeedId"]
38
+ ): Address {
39
+ const authorityPk = new PublicKey(String(toAddress(authority)));
40
+ const feedHash = Buffer.from(feedIdBytes(switchboardVolatilityFeedId));
41
+ const [quoteAccount] = PublicKey.findProgramAddressSync(
42
+ [AUTHORITY_QUOTE_SCHEME_TAG, authorityPk.toBuffer(), feedHash],
43
+ QUOTE_PROGRAM_ID
44
+ );
45
+ return address(quoteAccount.toBase58());
46
+ }
47
+
48
+ /**
49
+ * When vol oracle is disabled on market data, returns the system program as a
50
+ * read-only placeholder (the program skips quote reads in that case).
51
+ */
52
+ export function deriveVolatilityQuoteAddressFromMarketData(
53
+ marketData: Pick<
54
+ MarketDataAccount,
55
+ "volatilityQuoteAuthority" | "switchboardVolatilityFeedId"
56
+ >
57
+ ): Address {
58
+ if (!isVolOracleEnabled(marketData)) {
59
+ return address(ZERO_PUBKEY);
60
+ }
61
+ return deriveVolatilityQuoteAddress(
62
+ marketData.volatilityQuoteAuthority,
63
+ marketData.switchboardVolatilityFeedId
64
+ );
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicentral/sos-sdk",
3
- "version": "0.12.2-beta",
3
+ "version": "0.13.0-beta",
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",
@@ -20,11 +20,17 @@
20
20
  "@solana-program/system": "^0.11.0",
21
21
  "@solana/compat": "^6.1.0",
22
22
  "@solana/kit": "^6.1.0",
23
+ "@solana/web3.js": "^1.98.0",
23
24
  "@switchboard-xyz/common": "^5.7.0",
24
25
  "@switchboard-xyz/on-demand": "^3.9.0",
25
26
  "bs58": "^6.0.0",
26
27
  "decimal.js": "^10.4.3"
27
28
  },
29
+ "overrides": {
30
+ "@switchboard-xyz/on-demand": {
31
+ "@switchboard-xyz/common": "$@switchboard-xyz/common"
32
+ }
33
+ },
28
34
  "scripts": {
29
35
  "typecheck": "tsc --project tsconfig.json --noEmit",
30
36
  "publish-beta": "dotenv -e .env -- pnpm publish --access public --tag beta",
@@ -26,6 +26,7 @@ import {
26
26
  parseLiquidateWriterPositionInstruction,
27
27
  parseLiquidateWriterPositionRescueInstruction,
28
28
  parseMigrateCollateralPoolV1ToV2Instruction,
29
+ parseMigrateMarketDataVolOracleInstruction,
29
30
  parseOmlpCreateVaultInstruction,
30
31
  parseOmlpUpdateFeeWalletInstruction,
31
32
  parseOmlpUpdateInterestModelInstruction,
@@ -179,6 +180,11 @@ export function parseOptionProgramInstruction<
179
180
  parseLiquidateWriterPositionRescueInstruction(instruction),
180
181
  instructionType,
181
182
  );
183
+ case OptionProgramInstruction.MigrateMarketDataVolOracle:
184
+ return withInstructionType(
185
+ parseMigrateMarketDataVolOracleInstruction(instruction),
186
+ instructionType,
187
+ );
182
188
  case OptionProgramInstruction.MigrateCollateralPoolV1ToV2:
183
189
  return withInstructionType(
184
190
  parseMigrateCollateralPoolV1ToV2Instruction(instruction),
package/short/builders.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  getClaimBuyerSettlementInstructionAsync,
3
3
  getClaimMakerSettlementInstructionAsync,
4
+ getLiquidateWriterPositionInstructionAsync,
4
5
  getLiquidateWriterPositionRescueInstructionAsync,
5
6
  getOptionMintInstructionAsync,
6
7
  getPrepareBuyerSettlementInstructionAsync,
@@ -42,6 +43,7 @@ import {
42
43
  SLOT_HASHES_SYSVAR_ADDRESS,
43
44
  SWITCHBOARD_DEFAULT_DEVNET_QUEUE,
44
45
  } from "../oracle/switchboard";
46
+ import { deriveVolatilityQuoteAddressFromMarketData } from "../oracle/volatility-quote";
45
47
  import { applySlippageBps } from "../long/quotes";
46
48
  import { getGlobalTradeConfig } from "../shared/trade-config";
47
49
 
@@ -75,6 +77,8 @@ export interface BuildOptionMintParams {
75
77
  makerLongAccount?: AddressLike;
76
78
  makerShortAccount?: AddressLike;
77
79
  marketData?: AddressLike;
80
+ /** Authority-updated Switchboard HV quote PDA (derived from market data when omitted). */
81
+ volatilityQuoteAccount?: AddressLike;
78
82
  optionPool?: AddressLike;
79
83
  escrowLongAccount?: AddressLike;
80
84
  premiumVault?: AddressLike;
@@ -240,6 +244,10 @@ export async function buildOptionMintInstruction(
240
244
  !!longMetadata && !!shortMetadata,
241
245
  "longMetadataAccount and shortMetadataAccount are required (or provide longMint/shortMint to derive)."
242
246
  );
247
+ invariant(
248
+ !!params.volatilityQuoteAccount,
249
+ "volatilityQuoteAccount is required (use buildOptionMintTransactionWithDerivation or deriveVolatilityQuoteAddressFromMarketData)."
250
+ );
243
251
 
244
252
  const kitInstruction = await getOptionMintInstructionAsync(
245
253
  {
@@ -256,6 +264,7 @@ export async function buildOptionMintInstruction(
256
264
  longMetadataAccount: toAddress(longMetadata!),
257
265
  shortMetadataAccount: toAddress(shortMetadata!),
258
266
  marketData: params.marketData ? toAddress(params.marketData) : undefined,
267
+ volatilityQuoteAccount: toAddress(params.volatilityQuoteAccount!),
259
268
  underlyingMint: toAddress(params.underlyingMint),
260
269
  collateralMint: toAddress(params.collateralMint ?? params.underlyingMint),
261
270
  optionPool: params.optionPool ? toAddress(params.optionPool) : undefined,
@@ -383,6 +392,8 @@ export interface BuildOptionMintTransactionWithDerivationParams {
383
392
  * under Solana's 1232-byte versioned-transaction limit when OMLP accounts are present.
384
393
  */
385
394
  skipCreateCollateralAta?: boolean;
395
+ /** Optional override — derived from on-chain market data when omitted. */
396
+ volatilityQuoteAccount?: AddressLike;
386
397
  }
387
398
 
388
399
  export async function buildOptionMintTransactionWithDerivation(
@@ -447,6 +458,9 @@ export async function buildOptionMintTransactionWithDerivation(
447
458
  !!marketDataAccount,
448
459
  "Market data account not found for resolved option market."
449
460
  );
461
+ const volatilityQuoteAccount =
462
+ params.volatilityQuoteAccount ??
463
+ deriveVolatilityQuoteAddressFromMarketData(marketDataAccount);
450
464
  const switchboardFeedId = feedIdBytesToHex(
451
465
  Uint8Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
452
466
  );
@@ -514,6 +528,7 @@ export async function buildOptionMintTransactionWithDerivation(
514
528
  makerLongAccount,
515
529
  makerShortAccount,
516
530
  marketData: resolved.marketData,
531
+ volatilityQuoteAccount,
517
532
  optionPool: resolved.optionPool,
518
533
  escrowLongAccount: resolved.escrowLongAccount,
519
534
  premiumVault: resolved.premiumVault,
@@ -957,6 +972,84 @@ export async function buildClaimMakerSettlementInstruction(
957
972
  });
958
973
  }
959
974
 
975
+ export interface BuildLiquidateWriterPositionParams {
976
+ optionPool: AddressLike;
977
+ optionAccount: AddressLike;
978
+ /** Market data PDA (`["market_data", underlying_asset]`). */
979
+ marketData: AddressLike;
980
+ /** Authority-updated Switchboard HV quote PDA (derived from market data when omitted). */
981
+ volatilityQuoteAccount?: AddressLike;
982
+ collateralPool: AddressLike;
983
+ writerPosition: AddressLike;
984
+ longMint: AddressLike;
985
+ escrowLongAccount: AddressLike;
986
+ underlyingMint: AddressLike;
987
+ switchboardQueue: AddressLike;
988
+ /** OMLP Vault state PDA (mut: decrements total_loans, etc.). */
989
+ omlpVault: AddressLike;
990
+ collateralVault: AddressLike;
991
+ premiumVault: AddressLike;
992
+ omlpVaultTokenAccount: AddressLike;
993
+ feeWallet: AddressLike;
994
+ /** Permissionless liquidator signer. */
995
+ liquidator: TransactionSigner<string>;
996
+ /** Every active PoolLoan for this writer/vault (SDK: loadWriterLoans). */
997
+ remainingAccounts?: RemainingAccountInput[];
998
+ rpc?: KitRpc;
999
+ }
1000
+
1001
+ export async function buildLiquidateWriterPositionInstruction(
1002
+ params: BuildLiquidateWriterPositionParams
1003
+ ): Promise<Instruction<string>> {
1004
+ let volatilityQuoteAccount = params.volatilityQuoteAccount;
1005
+ if (!volatilityQuoteAccount) {
1006
+ invariant(!!params.rpc, "rpc is required when volatilityQuoteAccount is omitted");
1007
+ const marketDataAccount = await fetchMarketDataAccount(params.rpc!, params.marketData);
1008
+ invariant(
1009
+ !!marketDataAccount,
1010
+ "Market data account not found for resolved option market."
1011
+ );
1012
+ volatilityQuoteAccount = deriveVolatilityQuoteAddressFromMarketData(marketDataAccount);
1013
+ }
1014
+
1015
+ const kitInstruction = await getLiquidateWriterPositionInstructionAsync({
1016
+ optionPool: toAddress(params.optionPool),
1017
+ optionAccount: toAddress(params.optionAccount),
1018
+ marketData: toAddress(params.marketData),
1019
+ volatilityQuoteAccount: toAddress(volatilityQuoteAccount),
1020
+ collateralPool: toAddress(params.collateralPool),
1021
+ writerPosition: toAddress(params.writerPosition),
1022
+ longMint: toAddress(params.longMint),
1023
+ escrowLongAccount: toAddress(params.escrowLongAccount),
1024
+ underlyingMint: toAddress(params.underlyingMint),
1025
+ switchboardQueue: toAddress(params.switchboardQueue),
1026
+ omlpVault: toAddress(params.omlpVault),
1027
+ collateralVault: toAddress(params.collateralVault),
1028
+ premiumVault: toAddress(params.premiumVault),
1029
+ omlpVaultTokenAccount: toAddress(params.omlpVaultTokenAccount),
1030
+ feeWallet: toAddress(params.feeWallet),
1031
+ liquidator: params.liquidator as any,
1032
+ });
1033
+ return appendRemainingAccounts(kitInstruction, [
1034
+ {
1035
+ address: SLOT_HASHES_SYSVAR_ADDRESS,
1036
+ isWritable: false,
1037
+ },
1038
+ {
1039
+ address: INSTRUCTIONS_SYSVAR_ADDRESS,
1040
+ isWritable: false,
1041
+ },
1042
+ ...(params.remainingAccounts ?? []),
1043
+ ]);
1044
+ }
1045
+
1046
+ export async function buildLiquidateWriterPositionTransaction(
1047
+ params: BuildLiquidateWriterPositionParams
1048
+ ): Promise<BuiltTransaction> {
1049
+ const instruction = await buildLiquidateWriterPositionInstruction(params);
1050
+ return { instructions: [instruction] };
1051
+ }
1052
+
960
1053
  /**
961
1054
  * Parameters for the permissioned rescue liquidation path. Gated on-chain
962
1055
  * to `Vault::keeper`; the SDK does not re-verify keeper identity, but the