@epicentral/sos-sdk 0.12.1-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.
- package/generated/accounts/marketDataAccount.ts +13 -1
- package/generated/instructions/buyFromPool.ts +43 -15
- package/generated/instructions/closeLongToPool.ts +40 -12
- package/generated/instructions/index.ts +1 -0
- package/generated/instructions/initializeMarketData.ts +13 -0
- package/generated/instructions/liquidateWriterPosition.ts +39 -11
- package/generated/instructions/migrateMarketDataVolOracle.ts +248 -0
- package/generated/instructions/optionMint.ts +50 -22
- package/generated/instructions/updateImpliedVolatility.ts +19 -2
- package/generated/programs/optionProgram.ts +16 -0
- package/index.ts +6 -0
- package/long/builders.ts +78 -17
- package/oracle/volatility-quote.ts +65 -0
- package/package.json +7 -1
- package/shared/option-program-parser.ts +6 -0
- package/short/builders.ts +93 -0
- package/short/preflight.ts +3 -2
|
@@ -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[
|
|
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 <
|
|
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;
|
|
@@ -110,10 +115,12 @@ export interface BuildCloseLongToPoolParams {
|
|
|
110
115
|
*/
|
|
111
116
|
closeLongTokenAccount?: boolean;
|
|
112
117
|
/**
|
|
113
|
-
* When true and
|
|
114
|
-
*
|
|
118
|
+
* When true and settlement is physical (collateral mint == underlying) with WSOL underlying,
|
|
119
|
+
* appends CloseAccount to unwrap the payout WSOL ATA to native SOL.
|
|
115
120
|
*/
|
|
116
121
|
unwrapPayoutSol?: boolean;
|
|
122
|
+
/** When false, skips idempotent create-ATA for payout accounts (default: true). */
|
|
123
|
+
createPayoutAtas?: boolean;
|
|
117
124
|
remainingAccounts?: RemainingAccountInput[];
|
|
118
125
|
}
|
|
119
126
|
|
|
@@ -129,6 +136,7 @@ export async function buildBuyFromPoolInstruction(
|
|
|
129
136
|
longMint: toAddress(params.longMint),
|
|
130
137
|
underlyingMint: toAddress(params.underlyingMint),
|
|
131
138
|
marketData: toAddress(params.marketData),
|
|
139
|
+
volatilityQuoteAccount: toAddress(params.volatilityQuoteAccount),
|
|
132
140
|
switchboardQueue: toAddress(params.switchboardQueue),
|
|
133
141
|
buyer: toAddress(params.buyer) as any,
|
|
134
142
|
buyerPosition: params.buyerPosition ? toAddress(params.buyerPosition) : undefined,
|
|
@@ -215,6 +223,8 @@ export interface BuildBuyFromPoolTransactionWithDerivationParams {
|
|
|
215
223
|
* Use **1** if only SetComputeUnitLimit is prepended (e.g. `omitComputeUnitPriceInstruction`); **0** if no CU prepend.
|
|
216
224
|
*/
|
|
217
225
|
switchboardQuoteInstructionIndex?: number;
|
|
226
|
+
/** Optional override — derived from on-chain market data when omitted. */
|
|
227
|
+
volatilityQuoteAccount?: AddressLike;
|
|
218
228
|
}
|
|
219
229
|
|
|
220
230
|
const DEFAULT_MARKET_ORDER_SLIPPAGE_BUFFER_BASE_UNITS = 500_000n;
|
|
@@ -301,12 +311,22 @@ export async function buildBuyFromPoolTransactionWithDerivation(
|
|
|
301
311
|
}),
|
|
302
312
|
]);
|
|
303
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
|
+
|
|
304
323
|
const buyParams = {
|
|
305
324
|
optionPool: resolved.optionPool,
|
|
306
325
|
optionAccount: resolved.optionAccount,
|
|
307
326
|
longMint: resolved.longMint,
|
|
308
327
|
underlyingMint: resolved.underlyingMint!,
|
|
309
328
|
marketData: resolved.marketData,
|
|
329
|
+
volatilityQuoteAccount,
|
|
310
330
|
buyer: params.buyer,
|
|
311
331
|
paymentMint: paymentAccounts.paymentMint,
|
|
312
332
|
buyerPaymentAccount: paymentAccounts.buyerPaymentAccount,
|
|
@@ -319,11 +339,6 @@ export async function buildBuyFromPoolTransactionWithDerivation(
|
|
|
319
339
|
remainingAccounts: params.remainingAccounts,
|
|
320
340
|
};
|
|
321
341
|
|
|
322
|
-
const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
|
|
323
|
-
invariant(
|
|
324
|
-
!!marketDataAccount,
|
|
325
|
-
"Market data account not found for resolved option market."
|
|
326
|
-
);
|
|
327
342
|
const feedIdHex = feedIdBytesToHex(
|
|
328
343
|
Uint8Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
|
|
329
344
|
);
|
|
@@ -458,12 +473,22 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
458
473
|
const maxPremiumAmount = quotePremium + slippageBuffer;
|
|
459
474
|
assertPositiveAmount(maxPremiumAmount, "maxPremiumAmount");
|
|
460
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
|
+
|
|
461
485
|
const marketBuyParams = {
|
|
462
486
|
optionPool: resolved.optionPool,
|
|
463
487
|
optionAccount: resolved.optionAccount,
|
|
464
488
|
longMint: resolved.longMint,
|
|
465
489
|
underlyingMint: refetchedPool.underlyingMint,
|
|
466
490
|
marketData: resolved.marketData,
|
|
491
|
+
volatilityQuoteAccount,
|
|
467
492
|
buyer: params.buyer,
|
|
468
493
|
paymentMint: paymentAccounts.paymentMint,
|
|
469
494
|
buyerPaymentAccount: paymentAccounts.buyerPaymentAccount,
|
|
@@ -476,11 +501,6 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
476
501
|
remainingAccounts,
|
|
477
502
|
};
|
|
478
503
|
|
|
479
|
-
const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
|
|
480
|
-
invariant(
|
|
481
|
-
!!marketDataAccount,
|
|
482
|
-
"Market data account not found for resolved option market."
|
|
483
|
-
);
|
|
484
504
|
const feedIdHex = feedIdBytesToHex(
|
|
485
505
|
Uint8Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
|
|
486
506
|
);
|
|
@@ -539,6 +559,7 @@ export async function buildCloseLongToPoolInstruction(
|
|
|
539
559
|
escrowLongAccount: toAddress(params.escrowLongAccount),
|
|
540
560
|
premiumVault: toAddress(params.premiumVault),
|
|
541
561
|
marketData: toAddress(params.marketData),
|
|
562
|
+
volatilityQuoteAccount: toAddress(params.volatilityQuoteAccount),
|
|
542
563
|
switchboardQueue: toAddress(params.switchboardQueue),
|
|
543
564
|
buyer: toAddress(params.buyer) as any,
|
|
544
565
|
buyerLongAccount: toAddress(params.buyerLongAccount),
|
|
@@ -554,10 +575,36 @@ export async function buildCloseLongToPoolInstruction(
|
|
|
554
575
|
}
|
|
555
576
|
|
|
556
577
|
export async function buildCloseLongToPoolTransaction(
|
|
557
|
-
params: BuildCloseLongToPoolParams
|
|
578
|
+
params: BuildCloseLongToPoolParams & { createPayoutAtas?: boolean }
|
|
558
579
|
): Promise<BuiltTransaction> {
|
|
580
|
+
const collateralMint = params.collateralMint ?? params.underlyingMint;
|
|
581
|
+
const isPhysicalSettlement =
|
|
582
|
+
toAddress(collateralMint) === toAddress(params.underlyingMint);
|
|
583
|
+
const instructions: Instruction<string>[] = [];
|
|
584
|
+
|
|
585
|
+
if (params.createPayoutAtas !== false) {
|
|
586
|
+
instructions.push(
|
|
587
|
+
await getCreateAssociatedTokenIdempotentInstructionWithAddress(
|
|
588
|
+
params.buyer,
|
|
589
|
+
params.buyer,
|
|
590
|
+
toAddress(collateralMint),
|
|
591
|
+
toAddress(params.buyerPayoutAccount)
|
|
592
|
+
)
|
|
593
|
+
);
|
|
594
|
+
if (!isPhysicalSettlement) {
|
|
595
|
+
instructions.push(
|
|
596
|
+
await getCreateAssociatedTokenIdempotentInstructionWithAddress(
|
|
597
|
+
params.buyer,
|
|
598
|
+
params.buyer,
|
|
599
|
+
toAddress(params.underlyingMint),
|
|
600
|
+
toAddress(params.buyerUnderlyingPayoutAccount)
|
|
601
|
+
)
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
559
606
|
const instruction = await buildCloseLongToPoolInstruction(params);
|
|
560
|
-
|
|
607
|
+
instructions.push(instruction);
|
|
561
608
|
|
|
562
609
|
if (params.closeLongTokenAccount === true) {
|
|
563
610
|
instructions.push(
|
|
@@ -609,10 +656,12 @@ export interface BuildCloseLongToPoolTransactionWithDerivationParams {
|
|
|
609
656
|
*/
|
|
610
657
|
closeLongTokenAccount?: boolean;
|
|
611
658
|
/**
|
|
612
|
-
* When true
|
|
613
|
-
*
|
|
659
|
+
* When true, unwraps physical-settlement WSOL payout to native SOL after close.
|
|
660
|
+
* Defaults to true only for physical WSOL pools; cash-settled pools default to false.
|
|
614
661
|
*/
|
|
615
662
|
unwrapPayoutSol?: boolean;
|
|
663
|
+
/** When false, skips idempotent create-ATA for payout accounts (default: true). */
|
|
664
|
+
createPayoutAtas?: boolean;
|
|
616
665
|
remainingAccounts?: RemainingAccountInput[];
|
|
617
666
|
disableSwitchboardCrank?: boolean;
|
|
618
667
|
switchboardCrossbarUrl?: string;
|
|
@@ -622,6 +671,8 @@ export interface BuildCloseLongToPoolTransactionWithDerivationParams {
|
|
|
622
671
|
* OPX `sendInstructions` prepends only `SetComputeUnitLimit` before program ixs → default **1** (quote after CU limit).
|
|
623
672
|
*/
|
|
624
673
|
switchboardQuoteInstructionIndex?: number;
|
|
674
|
+
/** Optional override — derived from on-chain market data when omitted. */
|
|
675
|
+
volatilityQuoteAccount?: AddressLike;
|
|
625
676
|
}
|
|
626
677
|
|
|
627
678
|
export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
@@ -665,10 +716,13 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
|
665
716
|
|
|
666
717
|
const isWsolUnderlying =
|
|
667
718
|
toAddress(resolved.underlyingMint!) === toAddress(NATIVE_MINT);
|
|
719
|
+
const isPhysicalSettlement =
|
|
720
|
+
toAddress(collateralMint) === toAddress(resolved.underlyingMint!);
|
|
668
721
|
const closeLongTokenAccount =
|
|
669
722
|
params.closeLongTokenAccount !== false;
|
|
670
723
|
const unwrapPayoutSol =
|
|
671
|
-
params.unwrapPayoutSol
|
|
724
|
+
params.unwrapPayoutSol ??
|
|
725
|
+
(isWsolUnderlying && isPhysicalSettlement);
|
|
672
726
|
|
|
673
727
|
// close_long_to_pool requires a complete set of active WriterPositions so the
|
|
674
728
|
// program can run the strict Hamilton completeness check. Auto-populate if
|
|
@@ -686,6 +740,9 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
|
686
740
|
!!marketDataAccount,
|
|
687
741
|
"Market data account not found for resolved option market."
|
|
688
742
|
);
|
|
743
|
+
const volatilityQuoteAccount =
|
|
744
|
+
params.volatilityQuoteAccount ??
|
|
745
|
+
deriveVolatilityQuoteAddressFromMarketData(marketDataAccount);
|
|
689
746
|
const feedIdHex = feedIdBytesToHex(
|
|
690
747
|
Uint8Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
|
|
691
748
|
);
|
|
@@ -705,6 +762,7 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
|
705
762
|
escrowLongAccount: resolved.escrowLongAccount!,
|
|
706
763
|
premiumVault: resolved.premiumVault!,
|
|
707
764
|
marketData: resolved.marketData,
|
|
765
|
+
volatilityQuoteAccount,
|
|
708
766
|
switchboardQueue,
|
|
709
767
|
buyer: params.buyer,
|
|
710
768
|
buyerLongAccount: params.buyerLongAccount,
|
|
@@ -716,6 +774,7 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
|
716
774
|
buyerPosition,
|
|
717
775
|
closeLongTokenAccount,
|
|
718
776
|
unwrapPayoutSol,
|
|
777
|
+
createPayoutAtas: params.createPayoutAtas,
|
|
719
778
|
remainingAccounts,
|
|
720
779
|
});
|
|
721
780
|
}
|
|
@@ -744,6 +803,7 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
|
744
803
|
escrowLongAccount: resolved.escrowLongAccount!,
|
|
745
804
|
premiumVault: resolved.premiumVault!,
|
|
746
805
|
marketData: resolved.marketData,
|
|
806
|
+
volatilityQuoteAccount,
|
|
747
807
|
switchboardQueue: getDefaultSwitchboardQueueAddress(network),
|
|
748
808
|
buyer: params.buyer,
|
|
749
809
|
buyerLongAccount: params.buyerLongAccount,
|
|
@@ -755,6 +815,7 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
|
755
815
|
buyerPosition,
|
|
756
816
|
closeLongTokenAccount,
|
|
757
817
|
unwrapPayoutSol,
|
|
818
|
+
createPayoutAtas: params.createPayoutAtas,
|
|
758
819
|
remainingAccounts,
|
|
759
820
|
});
|
|
760
821
|
|
|
@@ -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.
|
|
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
|