@epicentral/sos-sdk 0.9.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -0
- package/AGENTS.md +7 -0
- package/LICENSE +21 -0
- package/README.md +568 -0
- package/accounts/fetchers.ts +196 -0
- package/accounts/list.ts +184 -0
- package/accounts/pdas.ts +325 -0
- package/accounts/resolve-option.ts +104 -0
- package/client/lookup-table.ts +114 -0
- package/client/program.ts +13 -0
- package/client/types.ts +9 -0
- package/generated/accounts/collateralPool.ts +217 -0
- package/generated/accounts/config.ts +156 -0
- package/generated/accounts/escrowState.ts +183 -0
- package/generated/accounts/index.ts +20 -0
- package/generated/accounts/lenderPosition.ts +211 -0
- package/generated/accounts/makerCollateralShare.ts +229 -0
- package/generated/accounts/marketDataAccount.ts +176 -0
- package/generated/accounts/optionAccount.ts +247 -0
- package/generated/accounts/optionPool.ts +285 -0
- package/generated/accounts/poolLoan.ts +232 -0
- package/generated/accounts/positionAccount.ts +201 -0
- package/generated/accounts/vault.ts +366 -0
- package/generated/accounts/writerPosition.ts +327 -0
- package/generated/errors/index.ts +9 -0
- package/generated/errors/optionProgram.ts +476 -0
- package/generated/index.ts +13 -0
- package/generated/instructions/acceptAdmin.ts +230 -0
- package/generated/instructions/autoExerciseAllExpired.ts +685 -0
- package/generated/instructions/autoExerciseExpired.ts +754 -0
- package/generated/instructions/borrowFromPool.ts +619 -0
- package/generated/instructions/buyFromPool.ts +761 -0
- package/generated/instructions/closeLongToPool.ts +762 -0
- package/generated/instructions/closeOption.ts +235 -0
- package/generated/instructions/createEscrowV2.ts +518 -0
- package/generated/instructions/depositCollateral.ts +624 -0
- package/generated/instructions/depositToPosition.ts +429 -0
- package/generated/instructions/index.ts +47 -0
- package/generated/instructions/initCollateralPool.ts +513 -0
- package/generated/instructions/initConfig.ts +279 -0
- package/generated/instructions/initOptionPool.ts +587 -0
- package/generated/instructions/initializeMarketData.ts +359 -0
- package/generated/instructions/liquidateWriterPosition.ts +750 -0
- package/generated/instructions/liquidateWriterPositionRescue.ts +623 -0
- package/generated/instructions/omlpCreateVault.ts +553 -0
- package/generated/instructions/omlpUpdateFeeWallet.ts +473 -0
- package/generated/instructions/omlpUpdateInterestModel.ts +322 -0
- package/generated/instructions/omlpUpdateLiquidationThreshold.ts +304 -0
- package/generated/instructions/omlpUpdateMaintenanceBuffer.ts +304 -0
- package/generated/instructions/omlpUpdateMaxBorrowCap.ts +304 -0
- package/generated/instructions/omlpUpdateMaxLeverage.ts +304 -0
- package/generated/instructions/omlpUpdateProtocolFee.ts +304 -0
- package/generated/instructions/omlpUpdateSupplyLimit.ts +304 -0
- package/generated/instructions/optionExercise.ts +617 -0
- package/generated/instructions/optionMint.ts +1373 -0
- package/generated/instructions/optionValidate.ts +302 -0
- package/generated/instructions/repayPoolLoan.ts +558 -0
- package/generated/instructions/repayPoolLoanFromCollateral.ts +514 -0
- package/generated/instructions/repayPoolLoanFromWallet.ts +542 -0
- package/generated/instructions/settleMakerCollateral.ts +509 -0
- package/generated/instructions/syncWriterPosition.ts +206 -0
- package/generated/instructions/transferAdmin.ts +245 -0
- package/generated/instructions/unwindWriterUnsold.ts +764 -0
- package/generated/instructions/updateImpliedVolatility.ts +226 -0
- package/generated/instructions/updateMarketData.ts +315 -0
- package/generated/instructions/withdrawFromPosition.ts +405 -0
- package/generated/instructions/writeToPool.ts +619 -0
- package/generated/programs/index.ts +9 -0
- package/generated/programs/optionProgram.ts +1144 -0
- package/generated/shared/index.ts +164 -0
- package/generated/types/impliedVolatilityUpdated.ts +73 -0
- package/generated/types/index.ts +28 -0
- package/generated/types/liquidationExecuted.ts +73 -0
- package/generated/types/liquidationRescueEvent.ts +82 -0
- package/generated/types/marketDataInitialized.ts +61 -0
- package/generated/types/marketDataUpdated.ts +69 -0
- package/generated/types/optionClosed.ts +56 -0
- package/generated/types/optionExercised.ts +62 -0
- package/generated/types/optionExpired.ts +49 -0
- package/generated/types/optionMinted.ts +78 -0
- package/generated/types/optionType.ts +38 -0
- package/generated/types/optionValidated.ts +82 -0
- package/generated/types/poolLoanCreated.ts +74 -0
- package/generated/types/poolLoanRepaid.ts +74 -0
- package/generated/types/positionDeposited.ts +73 -0
- package/generated/types/positionWithdrawn.ts +81 -0
- package/generated/types/protocolFeeUpdated.ts +69 -0
- package/generated/types/vaultCreated.ts +60 -0
- package/generated/types/vaultFeeWalletUpdated.ts +67 -0
- package/generated/types/vaultInterestModelUpdated.ts +77 -0
- package/generated/types/vaultLiquidationThresholdUpdated.ts +69 -0
- package/index.ts +68 -0
- package/long/builders.ts +690 -0
- package/long/exercise.ts +123 -0
- package/long/preflight.ts +214 -0
- package/long/quotes.ts +48 -0
- package/long/remaining-accounts.ts +111 -0
- package/omlp/builders.ts +94 -0
- package/omlp/service.ts +136 -0
- package/oracle/switchboard.ts +315 -0
- package/package.json +34 -0
- package/shared/amounts.ts +53 -0
- package/shared/balances.ts +57 -0
- package/shared/errors.ts +12 -0
- package/shared/remaining-accounts.ts +41 -0
- package/shared/trade-config.ts +27 -0
- package/shared/transactions.ts +121 -0
- package/short/builders.ts +874 -0
- package/short/close-option.ts +34 -0
- package/short/pool.ts +189 -0
- package/short/preflight.ts +619 -0
- package/tsconfig.json +13 -0
- package/wsol/instructions.ts +247 -0
package/long/builders.ts
ADDED
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getBuyFromPoolInstructionAsync,
|
|
3
|
+
getCloseLongToPoolInstructionAsync,
|
|
4
|
+
} from "../generated/instructions";
|
|
5
|
+
import type { Instruction } from "@solana/kit";
|
|
6
|
+
import { toAddress } from "../client/program";
|
|
7
|
+
import type { AddressLike, BuiltTransaction, KitRpc } from "../client/types";
|
|
8
|
+
import { resolveOptionAccounts } from "../accounts/resolve-option";
|
|
9
|
+
import {
|
|
10
|
+
deriveAssociatedTokenAddress,
|
|
11
|
+
deriveBuyerPositionPda,
|
|
12
|
+
} from "../accounts/pdas";
|
|
13
|
+
import { assertNonNegativeAmount, assertPositiveAmount } from "../shared/amounts";
|
|
14
|
+
import { invariant } from "../shared/errors";
|
|
15
|
+
import {
|
|
16
|
+
appendRemainingAccounts,
|
|
17
|
+
type RemainingAccountInput,
|
|
18
|
+
} from "../shared/remaining-accounts";
|
|
19
|
+
import type { OptionType } from "../generated/types";
|
|
20
|
+
import {
|
|
21
|
+
getCloseAccountInstruction,
|
|
22
|
+
getCreateAssociatedTokenIdempotentInstructionWithAddress,
|
|
23
|
+
NATIVE_MINT,
|
|
24
|
+
} from "../wsol/instructions";
|
|
25
|
+
import { fetchMarketDataAccount, fetchOptionPool } from "../accounts/fetchers";
|
|
26
|
+
import {
|
|
27
|
+
getBuyFromPoolRemainingAccounts,
|
|
28
|
+
getCloseLongToPoolRemainingAccounts,
|
|
29
|
+
} from "./remaining-accounts";
|
|
30
|
+
import { applySlippageBps } from "./quotes";
|
|
31
|
+
import {
|
|
32
|
+
buildSwitchboardQuoteInstruction,
|
|
33
|
+
feedIdBytesToHex,
|
|
34
|
+
getDefaultSwitchboardQueueAddress,
|
|
35
|
+
inferSwitchboardNetwork,
|
|
36
|
+
prependSwitchboardQuote,
|
|
37
|
+
} from "../oracle/switchboard";
|
|
38
|
+
import { fetchWriterPositionsForPool } from "../accounts/list";
|
|
39
|
+
import { getGlobalTradeConfig } from "../shared/trade-config";
|
|
40
|
+
|
|
41
|
+
export interface BuildBuyFromPoolParams {
|
|
42
|
+
optionPool: AddressLike;
|
|
43
|
+
optionAccount: AddressLike;
|
|
44
|
+
longMint: AddressLike;
|
|
45
|
+
underlyingMint: AddressLike;
|
|
46
|
+
marketData: AddressLike;
|
|
47
|
+
/** Program `switchboard_queue` — use {@link getDefaultSwitchboardQueueAddress} for the cluster. */
|
|
48
|
+
switchboardQueue: AddressLike;
|
|
49
|
+
buyer: AddressLike;
|
|
50
|
+
buyerPaymentAccount: AddressLike;
|
|
51
|
+
escrowLongAccount: AddressLike;
|
|
52
|
+
premiumVault: AddressLike;
|
|
53
|
+
quantity: bigint | number;
|
|
54
|
+
premiumAmount: bigint | number;
|
|
55
|
+
buyerPosition?: AddressLike;
|
|
56
|
+
buyerOptionAccount?: AddressLike;
|
|
57
|
+
remainingAccounts?: RemainingAccountInput[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface BuildCloseLongToPoolParams {
|
|
61
|
+
optionPool: AddressLike;
|
|
62
|
+
optionAccount: AddressLike;
|
|
63
|
+
collateralPool: AddressLike;
|
|
64
|
+
underlyingMint: AddressLike;
|
|
65
|
+
longMint: AddressLike;
|
|
66
|
+
escrowLongAccount: AddressLike;
|
|
67
|
+
premiumVault: AddressLike;
|
|
68
|
+
marketData: AddressLike;
|
|
69
|
+
switchboardQueue: AddressLike;
|
|
70
|
+
buyer: AddressLike;
|
|
71
|
+
buyerLongAccount: AddressLike;
|
|
72
|
+
buyerPayoutAccount: AddressLike;
|
|
73
|
+
collateralVault: AddressLike;
|
|
74
|
+
quantity: bigint | number;
|
|
75
|
+
minPayoutAmount: bigint | number;
|
|
76
|
+
buyerPosition?: AddressLike;
|
|
77
|
+
/**
|
|
78
|
+
* When true, appends an SPL CloseAccount to close the buyer's LONG token account after close_long_to_pool (reclaim rent).
|
|
79
|
+
* Set to true only when closing the entire position; for partial closes the LONG ATA still holds remaining tokens.
|
|
80
|
+
*/
|
|
81
|
+
closeLongTokenAccount?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* When true and underlying is WSOL, appends an SPL CloseAccount to unwrap the payout ATA so the buyer receives native SOL.
|
|
84
|
+
* Ignored when underlyingMint is not WSOL.
|
|
85
|
+
*/
|
|
86
|
+
unwrapPayoutSol?: boolean;
|
|
87
|
+
remainingAccounts?: RemainingAccountInput[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function buildBuyFromPoolInstruction(
|
|
91
|
+
params: BuildBuyFromPoolParams
|
|
92
|
+
): Promise<Instruction<string>> {
|
|
93
|
+
assertPositiveAmount(params.quantity, "quantity");
|
|
94
|
+
assertPositiveAmount(params.premiumAmount, "premiumAmount");
|
|
95
|
+
|
|
96
|
+
const kitInstruction = await getBuyFromPoolInstructionAsync({
|
|
97
|
+
optionPool: toAddress(params.optionPool),
|
|
98
|
+
optionAccount: toAddress(params.optionAccount),
|
|
99
|
+
longMint: toAddress(params.longMint),
|
|
100
|
+
underlyingMint: toAddress(params.underlyingMint),
|
|
101
|
+
marketData: toAddress(params.marketData),
|
|
102
|
+
switchboardQueue: toAddress(params.switchboardQueue),
|
|
103
|
+
buyer: toAddress(params.buyer) as any,
|
|
104
|
+
buyerPosition: params.buyerPosition ? toAddress(params.buyerPosition) : undefined,
|
|
105
|
+
buyerOptionAccount: params.buyerOptionAccount
|
|
106
|
+
? toAddress(params.buyerOptionAccount)
|
|
107
|
+
: undefined,
|
|
108
|
+
buyerPaymentAccount: toAddress(params.buyerPaymentAccount),
|
|
109
|
+
escrowLongAccount: toAddress(params.escrowLongAccount),
|
|
110
|
+
premiumVault: toAddress(params.premiumVault),
|
|
111
|
+
quantity: params.quantity,
|
|
112
|
+
premiumAmount: params.premiumAmount,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return appendRemainingAccounts(kitInstruction, params.remainingAccounts);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Builds a buy-from-pool transaction. The returned transaction may include a
|
|
120
|
+
* leading create-ATA-idempotent instruction for the buyer's option account so
|
|
121
|
+
* first-time buyers succeed without a separate setup step.
|
|
122
|
+
*/
|
|
123
|
+
export async function buildBuyFromPoolTransaction(
|
|
124
|
+
params: BuildBuyFromPoolParams
|
|
125
|
+
): Promise<BuiltTransaction> {
|
|
126
|
+
const buyerOptionAccountAddress = params.buyerOptionAccount
|
|
127
|
+
? toAddress(params.buyerOptionAccount)
|
|
128
|
+
: await deriveAssociatedTokenAddress(params.buyer, params.longMint);
|
|
129
|
+
|
|
130
|
+
const createAtaIx =
|
|
131
|
+
await getCreateAssociatedTokenIdempotentInstructionWithAddress(
|
|
132
|
+
params.buyer,
|
|
133
|
+
params.buyer,
|
|
134
|
+
params.longMint,
|
|
135
|
+
buyerOptionAccountAddress
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const buyFromPoolIx = await buildBuyFromPoolInstruction(params);
|
|
139
|
+
return { instructions: [createAtaIx, buyFromPoolIx] };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface BuildBuyFromPoolTransactionWithDerivationParams {
|
|
143
|
+
underlyingAsset: AddressLike;
|
|
144
|
+
optionType: OptionType;
|
|
145
|
+
strikePrice: number;
|
|
146
|
+
expirationDate: bigint | number;
|
|
147
|
+
buyer: AddressLike;
|
|
148
|
+
buyerPaymentAccount: AddressLike;
|
|
149
|
+
/** When `disableSwitchboardCrank` is true, optional explicit queue (else devnet/mainnet default). */
|
|
150
|
+
switchboardQueue?: AddressLike;
|
|
151
|
+
quantity: bigint | number;
|
|
152
|
+
premiumAmount: bigint | number;
|
|
153
|
+
rpc: KitRpc;
|
|
154
|
+
/** Required unless `disableSwitchboardCrank` — HTTP RPC URL for Switchboard `fetchQuoteIx`. */
|
|
155
|
+
rpcEndpoint?: string;
|
|
156
|
+
programId?: AddressLike;
|
|
157
|
+
buyerPosition?: AddressLike;
|
|
158
|
+
buyerOptionAccount?: AddressLike;
|
|
159
|
+
remainingAccounts?: RemainingAccountInput[];
|
|
160
|
+
/** When false (default), prepends Switchboard quote ix and uses its queue in the program ix. */
|
|
161
|
+
disableSwitchboardCrank?: boolean;
|
|
162
|
+
switchboardCrossbarUrl?: string;
|
|
163
|
+
switchboardNumSignatures?: number;
|
|
164
|
+
/**
|
|
165
|
+
* 0-based index of the Switchboard quote ix in the **final** serialized transaction (after the wallet prepends compute-budget ixs).
|
|
166
|
+
* OPX `sendInstructions` prepends SetComputeUnitLimit + SetComputeUnitPrice by default → **2**.
|
|
167
|
+
* Use **1** if only SetComputeUnitLimit is prepended (e.g. `omitComputeUnitPriceInstruction`); **0** if no CU prepend.
|
|
168
|
+
*/
|
|
169
|
+
switchboardQuoteInstructionIndex?: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const DEFAULT_MARKET_ORDER_SLIPPAGE_BUFFER_BASE_UNITS = 500_000n;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Bps added to the *cap* for `max_premium_amount` (stacked on user `slippageBps` / buffer paths).
|
|
176
|
+
* Primary alignment for 6042 is UI `quotedPremiumTotal` (see solana-opx pool buy + Switchboard spot vs UI spot).
|
|
177
|
+
* This is a small extra cushion for f64/rounding in `option-program` `pool.rs`.
|
|
178
|
+
*/
|
|
179
|
+
const POOL_BUY_MAX_PREMIUM_ONCHAIN_PAD_BPS = 100;
|
|
180
|
+
|
|
181
|
+
interface MarketOrderBufferLikeParams {
|
|
182
|
+
slippageBufferBaseUnits?: bigint | number;
|
|
183
|
+
slippageBufferLamports?: bigint | number;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function normalizeMarketOrderSlippageBuffer(
|
|
187
|
+
params: MarketOrderBufferLikeParams,
|
|
188
|
+
underlyingMint: AddressLike
|
|
189
|
+
): bigint {
|
|
190
|
+
const hasBaseUnits = params.slippageBufferBaseUnits !== undefined;
|
|
191
|
+
const hasLamports = params.slippageBufferLamports !== undefined;
|
|
192
|
+
|
|
193
|
+
invariant(
|
|
194
|
+
!(hasBaseUnits && hasLamports),
|
|
195
|
+
"Provide only one of slippageBufferBaseUnits or slippageBufferLamports."
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (hasBaseUnits) {
|
|
199
|
+
assertNonNegativeAmount(params.slippageBufferBaseUnits!, "slippageBufferBaseUnits");
|
|
200
|
+
return BigInt(params.slippageBufferBaseUnits!);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (hasLamports) {
|
|
204
|
+
assertNonNegativeAmount(params.slippageBufferLamports!, "slippageBufferLamports");
|
|
205
|
+
invariant(
|
|
206
|
+
String(toAddress(underlyingMint)) === String(NATIVE_MINT),
|
|
207
|
+
"slippageBufferLamports is only supported for SOL/WSOL underlyings. Use slippageBufferBaseUnits for other assets."
|
|
208
|
+
);
|
|
209
|
+
return BigInt(params.slippageBufferLamports!);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return DEFAULT_MARKET_ORDER_SLIPPAGE_BUFFER_BASE_UNITS;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function buildBuyFromPoolTransactionWithDerivation(
|
|
216
|
+
params: BuildBuyFromPoolTransactionWithDerivationParams
|
|
217
|
+
): Promise<BuiltTransaction> {
|
|
218
|
+
const resolved = await resolveOptionAccounts({
|
|
219
|
+
underlyingAsset: params.underlyingAsset,
|
|
220
|
+
optionType: params.optionType,
|
|
221
|
+
strikePrice: params.strikePrice,
|
|
222
|
+
expirationDate: params.expirationDate,
|
|
223
|
+
programId: params.programId,
|
|
224
|
+
rpc: params.rpc,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
invariant(
|
|
228
|
+
!!resolved.escrowLongAccount &&
|
|
229
|
+
!!resolved.premiumVault &&
|
|
230
|
+
!!resolved.underlyingMint,
|
|
231
|
+
"Option pool must exist; ensure rpc is provided and pool is initialized."
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const [buyerPosition, buyerOptionAccount] = await Promise.all([
|
|
235
|
+
params.buyerPosition
|
|
236
|
+
? Promise.resolve(params.buyerPosition)
|
|
237
|
+
: deriveBuyerPositionPda(
|
|
238
|
+
params.buyer,
|
|
239
|
+
resolved.optionAccount,
|
|
240
|
+
params.programId
|
|
241
|
+
).then(([addr]) => addr),
|
|
242
|
+
params.buyerOptionAccount
|
|
243
|
+
? Promise.resolve(params.buyerOptionAccount)
|
|
244
|
+
: deriveAssociatedTokenAddress(params.buyer, resolved.longMint),
|
|
245
|
+
]);
|
|
246
|
+
|
|
247
|
+
const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
|
|
248
|
+
invariant(
|
|
249
|
+
!!marketDataAccount,
|
|
250
|
+
"Market data account not found for resolved option market."
|
|
251
|
+
);
|
|
252
|
+
const feedIdHex = feedIdBytesToHex(
|
|
253
|
+
Uint8Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
|
|
254
|
+
);
|
|
255
|
+
const network = await inferSwitchboardNetwork(params.rpc);
|
|
256
|
+
|
|
257
|
+
if (params.disableSwitchboardCrank === true) {
|
|
258
|
+
const switchboardQueue = params.switchboardQueue
|
|
259
|
+
? toAddress(params.switchboardQueue)
|
|
260
|
+
: getDefaultSwitchboardQueueAddress(network);
|
|
261
|
+
return buildBuyFromPoolTransaction({
|
|
262
|
+
optionPool: resolved.optionPool,
|
|
263
|
+
optionAccount: resolved.optionAccount,
|
|
264
|
+
longMint: resolved.longMint,
|
|
265
|
+
underlyingMint: resolved.underlyingMint!,
|
|
266
|
+
marketData: resolved.marketData,
|
|
267
|
+
switchboardQueue,
|
|
268
|
+
buyer: params.buyer,
|
|
269
|
+
buyerPaymentAccount: params.buyerPaymentAccount,
|
|
270
|
+
escrowLongAccount: resolved.escrowLongAccount!,
|
|
271
|
+
premiumVault: resolved.premiumVault!,
|
|
272
|
+
quantity: params.quantity,
|
|
273
|
+
premiumAmount: params.premiumAmount,
|
|
274
|
+
buyerPosition,
|
|
275
|
+
buyerOptionAccount,
|
|
276
|
+
remainingAccounts: params.remainingAccounts,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
invariant(
|
|
281
|
+
!!params.rpcEndpoint,
|
|
282
|
+
"rpcEndpoint is required to fetch Switchboard quote instructions (or set disableSwitchboardCrank)."
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const quote = await buildSwitchboardQuoteInstruction({
|
|
286
|
+
rpcEndpoint: params.rpcEndpoint,
|
|
287
|
+
feedIdHex,
|
|
288
|
+
network,
|
|
289
|
+
crossbarUrl: params.switchboardCrossbarUrl,
|
|
290
|
+
numSignatures: params.switchboardNumSignatures,
|
|
291
|
+
instructionIdx: params.switchboardQuoteInstructionIndex ?? 2,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const actionTx = await buildBuyFromPoolTransaction({
|
|
295
|
+
optionPool: resolved.optionPool,
|
|
296
|
+
optionAccount: resolved.optionAccount,
|
|
297
|
+
longMint: resolved.longMint,
|
|
298
|
+
underlyingMint: resolved.underlyingMint!,
|
|
299
|
+
marketData: resolved.marketData,
|
|
300
|
+
switchboardQueue: getDefaultSwitchboardQueueAddress(network),
|
|
301
|
+
buyer: params.buyer,
|
|
302
|
+
buyerPaymentAccount: params.buyerPaymentAccount,
|
|
303
|
+
escrowLongAccount: resolved.escrowLongAccount!,
|
|
304
|
+
premiumVault: resolved.premiumVault!,
|
|
305
|
+
quantity: params.quantity,
|
|
306
|
+
premiumAmount: params.premiumAmount,
|
|
307
|
+
buyerPosition,
|
|
308
|
+
buyerOptionAccount,
|
|
309
|
+
remainingAccounts: params.remainingAccounts,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
return prependSwitchboardQuote(quote, actionTx);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export interface BuildBuyFromPoolMarketOrderParams
|
|
316
|
+
extends Omit<
|
|
317
|
+
BuildBuyFromPoolTransactionWithDerivationParams,
|
|
318
|
+
"premiumAmount" | "remainingAccounts"
|
|
319
|
+
>,
|
|
320
|
+
MarketOrderBufferLikeParams {
|
|
321
|
+
quotedPremiumTotal: bigint | number;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* High-level market-order buy builder.
|
|
326
|
+
* Refetches option pool and remaining writer-position accounts right before
|
|
327
|
+
* build and sets max premium = quotedPremiumTotal + slippage buffer.
|
|
328
|
+
*/
|
|
329
|
+
export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
330
|
+
params: BuildBuyFromPoolMarketOrderParams
|
|
331
|
+
): Promise<BuiltTransaction> {
|
|
332
|
+
assertPositiveAmount(params.quantity, "quantity");
|
|
333
|
+
assertPositiveAmount(params.quotedPremiumTotal, "quotedPremiumTotal");
|
|
334
|
+
|
|
335
|
+
const resolved = await resolveOptionAccounts({
|
|
336
|
+
underlyingAsset: params.underlyingAsset,
|
|
337
|
+
optionType: params.optionType,
|
|
338
|
+
strikePrice: params.strikePrice,
|
|
339
|
+
expirationDate: params.expirationDate,
|
|
340
|
+
programId: params.programId,
|
|
341
|
+
rpc: params.rpc,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const [refetchedPool, remainingAccounts, buyerPosition, buyerOptionAccount] =
|
|
345
|
+
await Promise.all([
|
|
346
|
+
fetchOptionPool(params.rpc, resolved.optionPool),
|
|
347
|
+
getBuyFromPoolRemainingAccounts(params.rpc, resolved.optionPool, params.programId),
|
|
348
|
+
params.buyerPosition
|
|
349
|
+
? Promise.resolve(params.buyerPosition)
|
|
350
|
+
: deriveBuyerPositionPda(
|
|
351
|
+
params.buyer,
|
|
352
|
+
resolved.optionAccount,
|
|
353
|
+
params.programId
|
|
354
|
+
).then(([addr]) => addr),
|
|
355
|
+
params.buyerOptionAccount
|
|
356
|
+
? Promise.resolve(params.buyerOptionAccount)
|
|
357
|
+
: deriveAssociatedTokenAddress(params.buyer, resolved.longMint),
|
|
358
|
+
]);
|
|
359
|
+
|
|
360
|
+
invariant(
|
|
361
|
+
!!refetchedPool,
|
|
362
|
+
"Option pool must exist; ensure rpc is provided and pool is initialized."
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
// Build-time coverage assertion: verify active writer liquidity >= requested quantity
|
|
366
|
+
// This catches data staleness between preflight and build
|
|
367
|
+
const quantity = BigInt(params.quantity);
|
|
368
|
+
const writerPositions = await fetchWriterPositionsForPool(
|
|
369
|
+
params.rpc,
|
|
370
|
+
resolved.optionPool,
|
|
371
|
+
params.programId
|
|
372
|
+
);
|
|
373
|
+
const activeUnsoldTotal = writerPositions
|
|
374
|
+
.filter((p) => !p.data.isSettled && !p.data.isLiquidated && p.data.unsoldQty > 0n)
|
|
375
|
+
.reduce((sum, p) => sum + p.data.unsoldQty, 0n);
|
|
376
|
+
|
|
377
|
+
invariant(
|
|
378
|
+
activeUnsoldTotal >= quantity,
|
|
379
|
+
`Insufficient active writer liquidity: available=${activeUnsoldTotal}, requested=${quantity}. ` +
|
|
380
|
+
`This may indicate data staleness - please refresh and retry.`
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const globalTradeConfig = getGlobalTradeConfig();
|
|
384
|
+
const hasExplicitSlippageBuffer =
|
|
385
|
+
params.slippageBufferBaseUnits !== undefined ||
|
|
386
|
+
params.slippageBufferLamports !== undefined;
|
|
387
|
+
const quotePremium = BigInt(params.quotedPremiumTotal);
|
|
388
|
+
const onchainPad = (q: bigint) =>
|
|
389
|
+
(q * BigInt(POOL_BUY_MAX_PREMIUM_ONCHAIN_PAD_BPS)) / 10_000n;
|
|
390
|
+
const slippageBuffer = hasExplicitSlippageBuffer
|
|
391
|
+
? normalizeMarketOrderSlippageBuffer(params, refetchedPool.underlyingMint) +
|
|
392
|
+
onchainPad(quotePremium)
|
|
393
|
+
: globalTradeConfig.slippageBps !== undefined
|
|
394
|
+
? applySlippageBps(
|
|
395
|
+
quotePremium,
|
|
396
|
+
globalTradeConfig.slippageBps + POOL_BUY_MAX_PREMIUM_ONCHAIN_PAD_BPS
|
|
397
|
+
) - quotePremium
|
|
398
|
+
: normalizeMarketOrderSlippageBuffer(params, refetchedPool.underlyingMint) +
|
|
399
|
+
onchainPad(quotePremium);
|
|
400
|
+
const maxPremiumAmount = quotePremium + slippageBuffer;
|
|
401
|
+
assertPositiveAmount(maxPremiumAmount, "maxPremiumAmount");
|
|
402
|
+
|
|
403
|
+
const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
|
|
404
|
+
invariant(
|
|
405
|
+
!!marketDataAccount,
|
|
406
|
+
"Market data account not found for resolved option market."
|
|
407
|
+
);
|
|
408
|
+
const feedIdHex = feedIdBytesToHex(
|
|
409
|
+
Uint8Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
|
|
410
|
+
);
|
|
411
|
+
const network = await inferSwitchboardNetwork(params.rpc);
|
|
412
|
+
|
|
413
|
+
if (params.disableSwitchboardCrank === true) {
|
|
414
|
+
const switchboardQueue = params.switchboardQueue
|
|
415
|
+
? toAddress(params.switchboardQueue)
|
|
416
|
+
: getDefaultSwitchboardQueueAddress(network);
|
|
417
|
+
return buildBuyFromPoolTransaction({
|
|
418
|
+
optionPool: resolved.optionPool,
|
|
419
|
+
optionAccount: resolved.optionAccount,
|
|
420
|
+
longMint: resolved.longMint,
|
|
421
|
+
underlyingMint: refetchedPool.underlyingMint,
|
|
422
|
+
marketData: resolved.marketData,
|
|
423
|
+
switchboardQueue,
|
|
424
|
+
buyer: params.buyer,
|
|
425
|
+
buyerPaymentAccount: params.buyerPaymentAccount,
|
|
426
|
+
escrowLongAccount: refetchedPool.escrowLongAccount,
|
|
427
|
+
premiumVault: refetchedPool.premiumVault,
|
|
428
|
+
quantity: params.quantity,
|
|
429
|
+
premiumAmount: maxPremiumAmount,
|
|
430
|
+
buyerPosition,
|
|
431
|
+
buyerOptionAccount,
|
|
432
|
+
remainingAccounts,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
invariant(
|
|
437
|
+
!!params.rpcEndpoint,
|
|
438
|
+
"rpcEndpoint is required to fetch Switchboard quote instructions (or set disableSwitchboardCrank)."
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
const quote = await buildSwitchboardQuoteInstruction({
|
|
442
|
+
rpcEndpoint: params.rpcEndpoint,
|
|
443
|
+
feedIdHex,
|
|
444
|
+
network,
|
|
445
|
+
crossbarUrl: params.switchboardCrossbarUrl,
|
|
446
|
+
numSignatures: params.switchboardNumSignatures,
|
|
447
|
+
instructionIdx: params.switchboardQuoteInstructionIndex ?? 2,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const actionTx = await buildBuyFromPoolTransaction({
|
|
451
|
+
optionPool: resolved.optionPool,
|
|
452
|
+
optionAccount: resolved.optionAccount,
|
|
453
|
+
longMint: resolved.longMint,
|
|
454
|
+
underlyingMint: refetchedPool.underlyingMint,
|
|
455
|
+
marketData: resolved.marketData,
|
|
456
|
+
switchboardQueue: getDefaultSwitchboardQueueAddress(network),
|
|
457
|
+
buyer: params.buyer,
|
|
458
|
+
buyerPaymentAccount: params.buyerPaymentAccount,
|
|
459
|
+
escrowLongAccount: refetchedPool.escrowLongAccount,
|
|
460
|
+
premiumVault: refetchedPool.premiumVault,
|
|
461
|
+
quantity: params.quantity,
|
|
462
|
+
premiumAmount: maxPremiumAmount,
|
|
463
|
+
buyerPosition,
|
|
464
|
+
buyerOptionAccount,
|
|
465
|
+
remainingAccounts,
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
return prependSwitchboardQuote(quote, actionTx);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export async function buildCloseLongToPoolInstruction(
|
|
472
|
+
params: BuildCloseLongToPoolParams
|
|
473
|
+
): Promise<Instruction<string>> {
|
|
474
|
+
assertPositiveAmount(params.quantity, "quantity");
|
|
475
|
+
invariant(
|
|
476
|
+
BigInt(params.minPayoutAmount) >= 0n,
|
|
477
|
+
"minPayoutAmount must be greater than or equal to zero."
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
const kitInstruction = await getCloseLongToPoolInstructionAsync({
|
|
481
|
+
optionPool: toAddress(params.optionPool),
|
|
482
|
+
optionAccount: toAddress(params.optionAccount),
|
|
483
|
+
collateralPool: toAddress(params.collateralPool),
|
|
484
|
+
underlyingMint: toAddress(params.underlyingMint),
|
|
485
|
+
longMint: toAddress(params.longMint),
|
|
486
|
+
escrowLongAccount: toAddress(params.escrowLongAccount),
|
|
487
|
+
premiumVault: toAddress(params.premiumVault),
|
|
488
|
+
marketData: toAddress(params.marketData),
|
|
489
|
+
switchboardQueue: toAddress(params.switchboardQueue),
|
|
490
|
+
buyer: toAddress(params.buyer) as any,
|
|
491
|
+
buyerLongAccount: toAddress(params.buyerLongAccount),
|
|
492
|
+
buyerPayoutAccount: toAddress(params.buyerPayoutAccount),
|
|
493
|
+
collateralVault: toAddress(params.collateralVault),
|
|
494
|
+
buyerPosition: params.buyerPosition ? toAddress(params.buyerPosition) : undefined,
|
|
495
|
+
quantity: params.quantity,
|
|
496
|
+
minPayoutAmount: params.minPayoutAmount,
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
return appendRemainingAccounts(kitInstruction, params.remainingAccounts);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export async function buildCloseLongToPoolTransaction(
|
|
503
|
+
params: BuildCloseLongToPoolParams
|
|
504
|
+
): Promise<BuiltTransaction> {
|
|
505
|
+
const instruction = await buildCloseLongToPoolInstruction(params);
|
|
506
|
+
const instructions = [instruction];
|
|
507
|
+
|
|
508
|
+
if (params.closeLongTokenAccount === true) {
|
|
509
|
+
instructions.push(
|
|
510
|
+
getCloseAccountInstruction(
|
|
511
|
+
params.buyerLongAccount,
|
|
512
|
+
params.buyer,
|
|
513
|
+
params.buyer
|
|
514
|
+
)
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const shouldUnwrapPayout =
|
|
519
|
+
params.unwrapPayoutSol === true &&
|
|
520
|
+
toAddress(params.underlyingMint) === toAddress(NATIVE_MINT);
|
|
521
|
+
if (shouldUnwrapPayout) {
|
|
522
|
+
instructions.push(
|
|
523
|
+
getCloseAccountInstruction(
|
|
524
|
+
params.buyerPayoutAccount,
|
|
525
|
+
params.buyer,
|
|
526
|
+
params.buyer
|
|
527
|
+
)
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return { instructions };
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export interface BuildCloseLongToPoolTransactionWithDerivationParams {
|
|
535
|
+
underlyingAsset: AddressLike;
|
|
536
|
+
optionType: OptionType;
|
|
537
|
+
strikePrice: number;
|
|
538
|
+
expirationDate: bigint | number;
|
|
539
|
+
buyer: AddressLike;
|
|
540
|
+
buyerLongAccount: AddressLike;
|
|
541
|
+
buyerPayoutAccount: AddressLike;
|
|
542
|
+
switchboardQueue?: AddressLike;
|
|
543
|
+
quantity: bigint | number;
|
|
544
|
+
minPayoutAmount: bigint | number;
|
|
545
|
+
rpc: KitRpc;
|
|
546
|
+
rpcEndpoint?: string;
|
|
547
|
+
programId?: AddressLike;
|
|
548
|
+
buyerPosition?: AddressLike;
|
|
549
|
+
/**
|
|
550
|
+
* When true (default), appends CloseAccount for the buyer's LONG token account after close_long_to_pool.
|
|
551
|
+
* Set to false when doing a partial close (LONG ATA still holds remaining tokens).
|
|
552
|
+
*/
|
|
553
|
+
closeLongTokenAccount?: boolean;
|
|
554
|
+
/**
|
|
555
|
+
* When true (default for WSOL underlying), appends CloseAccount to unwrap payout WSOL ATA to native SOL.
|
|
556
|
+
* Only applies when option underlying is WSOL.
|
|
557
|
+
*/
|
|
558
|
+
unwrapPayoutSol?: boolean;
|
|
559
|
+
remainingAccounts?: RemainingAccountInput[];
|
|
560
|
+
disableSwitchboardCrank?: boolean;
|
|
561
|
+
switchboardCrossbarUrl?: string;
|
|
562
|
+
switchboardNumSignatures?: number;
|
|
563
|
+
/**
|
|
564
|
+
* 0-based index of the Switchboard quote ix in the **final** tx after the wallet prepends compute-budget ixs.
|
|
565
|
+
* OPX `sendInstructions` prepends only `SetComputeUnitLimit` before program ixs → default **1** (quote after CU limit).
|
|
566
|
+
*/
|
|
567
|
+
switchboardQuoteInstructionIndex?: number;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
571
|
+
params: BuildCloseLongToPoolTransactionWithDerivationParams
|
|
572
|
+
): Promise<BuiltTransaction> {
|
|
573
|
+
const resolved = await resolveOptionAccounts({
|
|
574
|
+
underlyingAsset: params.underlyingAsset,
|
|
575
|
+
optionType: params.optionType,
|
|
576
|
+
strikePrice: params.strikePrice,
|
|
577
|
+
expirationDate: params.expirationDate,
|
|
578
|
+
programId: params.programId,
|
|
579
|
+
rpc: params.rpc,
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
invariant(
|
|
583
|
+
!!resolved.escrowLongAccount &&
|
|
584
|
+
!!resolved.premiumVault &&
|
|
585
|
+
!!resolved.collateralVault &&
|
|
586
|
+
!!resolved.underlyingMint,
|
|
587
|
+
"Option pool and collateral pool must exist; ensure rpc is provided and pools are initialized."
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
const buyerPosition = params.buyerPosition
|
|
591
|
+
? params.buyerPosition
|
|
592
|
+
: (await deriveBuyerPositionPda(
|
|
593
|
+
params.buyer,
|
|
594
|
+
resolved.optionAccount,
|
|
595
|
+
params.programId
|
|
596
|
+
))[0];
|
|
597
|
+
|
|
598
|
+
const isWsolUnderlying =
|
|
599
|
+
toAddress(resolved.underlyingMint!) === toAddress(NATIVE_MINT);
|
|
600
|
+
const closeLongTokenAccount =
|
|
601
|
+
params.closeLongTokenAccount !== false;
|
|
602
|
+
const unwrapPayoutSol =
|
|
603
|
+
params.unwrapPayoutSol !== false && isWsolUnderlying;
|
|
604
|
+
|
|
605
|
+
// close_long_to_pool requires a complete set of active WriterPositions so the
|
|
606
|
+
// program can run the strict Hamilton completeness check. Auto-populate if
|
|
607
|
+
// the caller didn't supply remaining accounts explicitly.
|
|
608
|
+
const remainingAccounts =
|
|
609
|
+
params.remainingAccounts ??
|
|
610
|
+
(await getCloseLongToPoolRemainingAccounts(
|
|
611
|
+
params.rpc,
|
|
612
|
+
resolved.optionPool,
|
|
613
|
+
params.programId
|
|
614
|
+
));
|
|
615
|
+
|
|
616
|
+
const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
|
|
617
|
+
invariant(
|
|
618
|
+
!!marketDataAccount,
|
|
619
|
+
"Market data account not found for resolved option market."
|
|
620
|
+
);
|
|
621
|
+
const feedIdHex = feedIdBytesToHex(
|
|
622
|
+
Uint8Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
|
|
623
|
+
);
|
|
624
|
+
const network = await inferSwitchboardNetwork(params.rpc);
|
|
625
|
+
|
|
626
|
+
if (params.disableSwitchboardCrank === true) {
|
|
627
|
+
const switchboardQueue = params.switchboardQueue
|
|
628
|
+
? toAddress(params.switchboardQueue)
|
|
629
|
+
: getDefaultSwitchboardQueueAddress(network);
|
|
630
|
+
return buildCloseLongToPoolTransaction({
|
|
631
|
+
optionPool: resolved.optionPool,
|
|
632
|
+
optionAccount: resolved.optionAccount,
|
|
633
|
+
collateralPool: resolved.collateralPool,
|
|
634
|
+
underlyingMint: resolved.underlyingMint!,
|
|
635
|
+
longMint: resolved.longMint,
|
|
636
|
+
escrowLongAccount: resolved.escrowLongAccount!,
|
|
637
|
+
premiumVault: resolved.premiumVault!,
|
|
638
|
+
marketData: resolved.marketData,
|
|
639
|
+
switchboardQueue,
|
|
640
|
+
buyer: params.buyer,
|
|
641
|
+
buyerLongAccount: params.buyerLongAccount,
|
|
642
|
+
buyerPayoutAccount: params.buyerPayoutAccount,
|
|
643
|
+
collateralVault: resolved.collateralVault!,
|
|
644
|
+
quantity: params.quantity,
|
|
645
|
+
minPayoutAmount: params.minPayoutAmount,
|
|
646
|
+
buyerPosition,
|
|
647
|
+
closeLongTokenAccount,
|
|
648
|
+
unwrapPayoutSol,
|
|
649
|
+
remainingAccounts,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
invariant(
|
|
654
|
+
!!params.rpcEndpoint,
|
|
655
|
+
"rpcEndpoint is required to fetch Switchboard quote instructions (or set disableSwitchboardCrank)."
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
const quote = await buildSwitchboardQuoteInstruction({
|
|
659
|
+
rpcEndpoint: params.rpcEndpoint,
|
|
660
|
+
feedIdHex,
|
|
661
|
+
network,
|
|
662
|
+
crossbarUrl: params.switchboardCrossbarUrl,
|
|
663
|
+
numSignatures: params.switchboardNumSignatures,
|
|
664
|
+
instructionIdx: params.switchboardQuoteInstructionIndex ?? 1,
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
const actionTx = await buildCloseLongToPoolTransaction({
|
|
668
|
+
optionPool: resolved.optionPool,
|
|
669
|
+
optionAccount: resolved.optionAccount,
|
|
670
|
+
collateralPool: resolved.collateralPool,
|
|
671
|
+
underlyingMint: resolved.underlyingMint!,
|
|
672
|
+
longMint: resolved.longMint,
|
|
673
|
+
escrowLongAccount: resolved.escrowLongAccount!,
|
|
674
|
+
premiumVault: resolved.premiumVault!,
|
|
675
|
+
marketData: resolved.marketData,
|
|
676
|
+
switchboardQueue: getDefaultSwitchboardQueueAddress(network),
|
|
677
|
+
buyer: params.buyer,
|
|
678
|
+
buyerLongAccount: params.buyerLongAccount,
|
|
679
|
+
buyerPayoutAccount: params.buyerPayoutAccount,
|
|
680
|
+
collateralVault: resolved.collateralVault!,
|
|
681
|
+
quantity: params.quantity,
|
|
682
|
+
minPayoutAmount: params.minPayoutAmount,
|
|
683
|
+
buyerPosition,
|
|
684
|
+
closeLongTokenAccount,
|
|
685
|
+
unwrapPayoutSol,
|
|
686
|
+
remainingAccounts,
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
return prependSwitchboardQuote(quote, actionTx);
|
|
690
|
+
}
|