@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
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLiquidateWriterPositionRescueInstructionAsync,
|
|
3
|
+
getOptionMintInstructionAsync,
|
|
4
|
+
getSettleMakerCollateralInstructionAsync,
|
|
5
|
+
getSyncWriterPositionInstruction,
|
|
6
|
+
getUnwindWriterUnsoldInstructionAsync,
|
|
7
|
+
type OptionType,
|
|
8
|
+
} from "../generated";
|
|
9
|
+
import type { Instruction, TransactionSigner } from "@solana/kit";
|
|
10
|
+
import { toAddress } from "../client/program";
|
|
11
|
+
import type { AddressLike, BuiltTransaction, KitRpc } from "../client/types";
|
|
12
|
+
import { fetchMarketDataAccount, fetchVault, fetchWriterPosition } from "../accounts/fetchers";
|
|
13
|
+
import { fetchPoolLoansByMaker } from "../accounts/list";
|
|
14
|
+
import { resolveOptionAccounts } from "../accounts/resolve-option";
|
|
15
|
+
import {
|
|
16
|
+
deriveAssociatedTokenAddress,
|
|
17
|
+
deriveMakerCollateralSharePda,
|
|
18
|
+
deriveMetadataPda,
|
|
19
|
+
deriveVaultPda,
|
|
20
|
+
deriveWriterPositionPda,
|
|
21
|
+
} from "../accounts/pdas";
|
|
22
|
+
import { assertNonNegativeAmount, assertPositiveAmount } from "../shared/amounts";
|
|
23
|
+
import { invariant } from "../shared/errors";
|
|
24
|
+
import {
|
|
25
|
+
appendRemainingAccounts,
|
|
26
|
+
type RemainingAccountInput,
|
|
27
|
+
} from "../shared/remaining-accounts";
|
|
28
|
+
import {
|
|
29
|
+
getCloseAccountInstruction,
|
|
30
|
+
getCreateAssociatedTokenIdempotentInstructionWithAddress,
|
|
31
|
+
} from "../wsol/instructions";
|
|
32
|
+
import { preflightUnwindWriterUnsold, writerPositionHasOngoingPoolLoanDebt } from "./preflight";
|
|
33
|
+
import {
|
|
34
|
+
buildSwitchboardQuoteInstruction,
|
|
35
|
+
feedIdBytesToHex,
|
|
36
|
+
inferSwitchboardNetwork,
|
|
37
|
+
INSTRUCTIONS_SYSVAR_ADDRESS,
|
|
38
|
+
SLOT_HASHES_SYSVAR_ADDRESS,
|
|
39
|
+
SWITCHBOARD_DEFAULT_DEVNET_QUEUE,
|
|
40
|
+
} from "../oracle/switchboard";
|
|
41
|
+
import { applySlippageBps } from "../long/quotes";
|
|
42
|
+
import { getGlobalTradeConfig } from "../shared/trade-config";
|
|
43
|
+
|
|
44
|
+
export interface BuildOptionMintParams {
|
|
45
|
+
optionType: OptionType;
|
|
46
|
+
strikePrice: number;
|
|
47
|
+
expirationDate: bigint | number;
|
|
48
|
+
quantity: bigint | number;
|
|
49
|
+
underlyingAsset: AddressLike;
|
|
50
|
+
underlyingSymbol: string;
|
|
51
|
+
/**
|
|
52
|
+
* Collateral mint (e.g., USDC, BTC, SOL) - Writer's choice for backing the position.
|
|
53
|
+
* Can differ from underlying asset - enables multi-collateral settlement.
|
|
54
|
+
* OMLP vault routing is based on this mint. Defaults to underlyingMint if not provided.
|
|
55
|
+
*/
|
|
56
|
+
collateralMint?: AddressLike;
|
|
57
|
+
makerCollateralAmount: bigint | number;
|
|
58
|
+
borrowedAmount: bigint | number;
|
|
59
|
+
maxRequiredCollateralAmount?: bigint | number;
|
|
60
|
+
maker: AddressLike;
|
|
61
|
+
makerCollateralAccount: AddressLike;
|
|
62
|
+
underlyingMint: AddressLike;
|
|
63
|
+
/** Queue account required by direct Switchboard quote verification. */
|
|
64
|
+
switchboardQueue?: AddressLike;
|
|
65
|
+
longMetadataAccount?: AddressLike;
|
|
66
|
+
shortMetadataAccount?: AddressLike;
|
|
67
|
+
optionAccount?: AddressLike;
|
|
68
|
+
longMint?: AddressLike;
|
|
69
|
+
shortMint?: AddressLike;
|
|
70
|
+
mintAuthority?: AddressLike;
|
|
71
|
+
makerLongAccount?: AddressLike;
|
|
72
|
+
makerShortAccount?: AddressLike;
|
|
73
|
+
marketData?: AddressLike;
|
|
74
|
+
optionPool?: AddressLike;
|
|
75
|
+
escrowLongAccount?: AddressLike;
|
|
76
|
+
premiumVault?: AddressLike;
|
|
77
|
+
collateralPool?: AddressLike;
|
|
78
|
+
collateralVault?: AddressLike;
|
|
79
|
+
writerPosition?: AddressLike;
|
|
80
|
+
vault?: AddressLike;
|
|
81
|
+
vaultTokenAccount?: AddressLike;
|
|
82
|
+
escrowState?: AddressLike;
|
|
83
|
+
escrowAuthority?: AddressLike;
|
|
84
|
+
escrowTokenAccount?: AddressLike;
|
|
85
|
+
poolLoan?: AddressLike;
|
|
86
|
+
/**
|
|
87
|
+
* When true (default), appends an SPL CloseAccount instruction after option_mint to close the
|
|
88
|
+
* maker's LONG token account (reclaim rent). The program transfers all LONG to escrow, so the
|
|
89
|
+
* maker's LONG ATA is left with zero balance and can be closed in the same transaction.
|
|
90
|
+
*/
|
|
91
|
+
closeMakerLongAccount?: boolean;
|
|
92
|
+
remainingAccounts?: RemainingAccountInput[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Max PoolLoans per unwind to stay under 64-account tx limit (~18 named + ~20 loans) */
|
|
96
|
+
const MAX_POOL_LOANS_PER_UNWIND = 20;
|
|
97
|
+
|
|
98
|
+
export interface BuildUnwindWriterUnsoldParams {
|
|
99
|
+
optionPool: AddressLike;
|
|
100
|
+
optionAccount: AddressLike;
|
|
101
|
+
/** Market data PDA (`["market_data", underlying_asset]`). */
|
|
102
|
+
marketData: AddressLike;
|
|
103
|
+
longMint: AddressLike;
|
|
104
|
+
shortMint: AddressLike;
|
|
105
|
+
escrowLongAccount: AddressLike;
|
|
106
|
+
writerShortAccount: AddressLike;
|
|
107
|
+
collateralVault: AddressLike;
|
|
108
|
+
/** Pool premium vault (source of theta-first debt repayment + leftover theta release). */
|
|
109
|
+
premiumVault: AddressLike;
|
|
110
|
+
writerCollateralAccount: AddressLike;
|
|
111
|
+
writer: AddressLike;
|
|
112
|
+
unwindQty: bigint | number;
|
|
113
|
+
collateralPool?: AddressLike;
|
|
114
|
+
writerPosition?: AddressLike;
|
|
115
|
+
omlpVaultState?: AddressLike;
|
|
116
|
+
omlpVault?: AddressLike;
|
|
117
|
+
feeWallet?: AddressLike;
|
|
118
|
+
remainingAccounts?: RemainingAccountInput[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface BuildSyncWriterPositionParams {
|
|
122
|
+
optionPool: AddressLike;
|
|
123
|
+
optionAccount: AddressLike;
|
|
124
|
+
writerPosition: AddressLike;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface BuildSettleMakerCollateralParams {
|
|
128
|
+
optionAccount: AddressLike;
|
|
129
|
+
collateralVault: AddressLike;
|
|
130
|
+
makerCollateralAccount: AddressLike;
|
|
131
|
+
/** OMLP Vault token account (destination for principal+interest). */
|
|
132
|
+
omlpVault: AddressLike;
|
|
133
|
+
/** OMLP Vault state PDA (mut: updates `total_loans`, `total_liquidity`, `acc_interest_per_share_fp`). */
|
|
134
|
+
omlpVaultState: AddressLike;
|
|
135
|
+
poolLoan: AddressLike;
|
|
136
|
+
maker: AddressLike;
|
|
137
|
+
makerCollateralShare?: AddressLike;
|
|
138
|
+
collateralPool?: AddressLike;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function buildOptionMintInstruction(
|
|
142
|
+
params: BuildOptionMintParams
|
|
143
|
+
): Promise<Instruction<string>> {
|
|
144
|
+
assertPositiveAmount(params.quantity, "quantity");
|
|
145
|
+
assertNonNegativeAmount(params.makerCollateralAmount, "makerCollateralAmount");
|
|
146
|
+
assertNonNegativeAmount(params.borrowedAmount, "borrowedAmount");
|
|
147
|
+
invariant(params.strikePrice > 0, "strikePrice must be greater than zero.");
|
|
148
|
+
invariant(params.underlyingSymbol.length > 0, "underlyingSymbol is required.");
|
|
149
|
+
|
|
150
|
+
const borrowedAmount = BigInt(params.borrowedAmount);
|
|
151
|
+
const globalTradeConfig = getGlobalTradeConfig();
|
|
152
|
+
const maxRequiredCollateralAmount =
|
|
153
|
+
params.maxRequiredCollateralAmount !== undefined
|
|
154
|
+
? BigInt(params.maxRequiredCollateralAmount)
|
|
155
|
+
: globalTradeConfig.slippageBps !== undefined
|
|
156
|
+
? applySlippageBps(
|
|
157
|
+
BigInt(params.makerCollateralAmount) + borrowedAmount,
|
|
158
|
+
globalTradeConfig.slippageBps
|
|
159
|
+
)
|
|
160
|
+
: BigInt(params.makerCollateralAmount) + borrowedAmount;
|
|
161
|
+
if (borrowedAmount > 0n) {
|
|
162
|
+
invariant(!!params.vault, "vault is required when borrowedAmount > 0");
|
|
163
|
+
invariant(
|
|
164
|
+
!!params.vaultTokenAccount,
|
|
165
|
+
"vaultTokenAccount is required when borrowedAmount > 0"
|
|
166
|
+
);
|
|
167
|
+
invariant(!!params.escrowState, "escrowState is required when borrowedAmount > 0");
|
|
168
|
+
invariant(
|
|
169
|
+
!!params.escrowAuthority,
|
|
170
|
+
"escrowAuthority is required when borrowedAmount > 0"
|
|
171
|
+
);
|
|
172
|
+
invariant(
|
|
173
|
+
!!params.escrowTokenAccount,
|
|
174
|
+
"escrowTokenAccount is required when borrowedAmount > 0"
|
|
175
|
+
);
|
|
176
|
+
invariant(!!params.poolLoan, "poolLoan is required when borrowedAmount > 0");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const [derivedLongMetadata, derivedShortMetadata] = await Promise.all([
|
|
180
|
+
params.longMint ? deriveMetadataPda(params.longMint) : Promise.resolve(undefined),
|
|
181
|
+
params.shortMint ? deriveMetadataPda(params.shortMint) : Promise.resolve(undefined),
|
|
182
|
+
]);
|
|
183
|
+
const longMetadata = params.longMetadataAccount ?? derivedLongMetadata?.[0];
|
|
184
|
+
const shortMetadata = params.shortMetadataAccount ?? derivedShortMetadata?.[0];
|
|
185
|
+
|
|
186
|
+
invariant(
|
|
187
|
+
!!longMetadata && !!shortMetadata,
|
|
188
|
+
"longMetadataAccount and shortMetadataAccount are required (or provide longMint/shortMint to derive)."
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const kitInstruction = await getOptionMintInstructionAsync({
|
|
192
|
+
optionAccount: params.optionAccount ? toAddress(params.optionAccount) : undefined,
|
|
193
|
+
longMint: params.longMint ? toAddress(params.longMint) : undefined,
|
|
194
|
+
shortMint: params.shortMint ? toAddress(params.shortMint) : undefined,
|
|
195
|
+
mintAuthority: params.mintAuthority ? toAddress(params.mintAuthority) : undefined,
|
|
196
|
+
makerLongAccount: params.makerLongAccount
|
|
197
|
+
? toAddress(params.makerLongAccount)
|
|
198
|
+
: undefined,
|
|
199
|
+
makerShortAccount: params.makerShortAccount
|
|
200
|
+
? toAddress(params.makerShortAccount)
|
|
201
|
+
: undefined,
|
|
202
|
+
longMetadataAccount: toAddress(longMetadata!),
|
|
203
|
+
shortMetadataAccount: toAddress(shortMetadata!),
|
|
204
|
+
marketData: params.marketData ? toAddress(params.marketData) : undefined,
|
|
205
|
+
underlyingMint: toAddress(params.underlyingMint),
|
|
206
|
+
collateralMint: toAddress(params.collateralMint ?? params.underlyingMint),
|
|
207
|
+
optionPool: params.optionPool ? toAddress(params.optionPool) : undefined,
|
|
208
|
+
escrowLongAccount: params.escrowLongAccount
|
|
209
|
+
? toAddress(params.escrowLongAccount)
|
|
210
|
+
: undefined,
|
|
211
|
+
premiumVault: params.premiumVault ? toAddress(params.premiumVault) : undefined,
|
|
212
|
+
collateralPool: params.collateralPool ? toAddress(params.collateralPool) : undefined,
|
|
213
|
+
collateralVault: params.collateralVault ? toAddress(params.collateralVault) : undefined,
|
|
214
|
+
makerCollateralAccount: toAddress(params.makerCollateralAccount),
|
|
215
|
+
writerPosition: params.writerPosition ? toAddress(params.writerPosition) : undefined,
|
|
216
|
+
vault: params.vault ? toAddress(params.vault) : undefined,
|
|
217
|
+
vaultTokenAccount: params.vaultTokenAccount
|
|
218
|
+
? toAddress(params.vaultTokenAccount)
|
|
219
|
+
: undefined,
|
|
220
|
+
escrowState: params.escrowState ? toAddress(params.escrowState) : undefined,
|
|
221
|
+
escrowAuthority: params.escrowAuthority ? toAddress(params.escrowAuthority) : undefined,
|
|
222
|
+
escrowTokenAccount: params.escrowTokenAccount
|
|
223
|
+
? toAddress(params.escrowTokenAccount)
|
|
224
|
+
: undefined,
|
|
225
|
+
poolLoan: params.poolLoan ? toAddress(params.poolLoan) : undefined,
|
|
226
|
+
maker: toAddress(params.maker) as any,
|
|
227
|
+
optionType: params.optionType,
|
|
228
|
+
strikePrice: params.strikePrice,
|
|
229
|
+
expirationDate: params.expirationDate,
|
|
230
|
+
quantity: params.quantity,
|
|
231
|
+
underlyingAsset: toAddress(params.underlyingAsset),
|
|
232
|
+
underlyingSymbol: params.underlyingSymbol,
|
|
233
|
+
collateralMintArg: toAddress(params.collateralMint ?? params.underlyingMint),
|
|
234
|
+
makerCollateralAmount: params.makerCollateralAmount,
|
|
235
|
+
borrowedAmount: params.borrowedAmount,
|
|
236
|
+
maxRequiredCollateralAmount,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const quoteVerificationAccounts: RemainingAccountInput[] = [
|
|
240
|
+
{
|
|
241
|
+
address: params.switchboardQueue ?? SWITCHBOARD_DEFAULT_DEVNET_QUEUE,
|
|
242
|
+
isWritable: false,
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
address: SLOT_HASHES_SYSVAR_ADDRESS,
|
|
246
|
+
isWritable: false,
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
address: INSTRUCTIONS_SYSVAR_ADDRESS,
|
|
250
|
+
isWritable: false,
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
return appendRemainingAccounts(kitInstruction, [
|
|
255
|
+
...quoteVerificationAccounts,
|
|
256
|
+
...(params.remainingAccounts ?? []),
|
|
257
|
+
]);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export async function buildOptionMintTransaction(
|
|
261
|
+
params: BuildOptionMintParams
|
|
262
|
+
): Promise<BuiltTransaction> {
|
|
263
|
+
const instruction = await buildOptionMintInstruction(params);
|
|
264
|
+
const instructions: Instruction<string>[] = [instruction];
|
|
265
|
+
|
|
266
|
+
const shouldCloseMakerLong =
|
|
267
|
+
params.closeMakerLongAccount !== false && params.makerLongAccount != null;
|
|
268
|
+
if (shouldCloseMakerLong) {
|
|
269
|
+
instructions.push(
|
|
270
|
+
getCloseAccountInstruction(
|
|
271
|
+
params.makerLongAccount!,
|
|
272
|
+
params.maker,
|
|
273
|
+
params.maker
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return { instructions };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface BuildOptionMintTransactionWithDerivationParams {
|
|
282
|
+
underlyingAsset: AddressLike;
|
|
283
|
+
optionType: OptionType;
|
|
284
|
+
strikePrice: number;
|
|
285
|
+
expirationDate: bigint | number;
|
|
286
|
+
quantity: bigint | number;
|
|
287
|
+
underlyingMint: AddressLike;
|
|
288
|
+
underlyingSymbol: string;
|
|
289
|
+
/**
|
|
290
|
+
* Collateral mint (e.g., USDC, BTC, SOL) - Writer's choice for backing the position.
|
|
291
|
+
* Can differ from underlying asset - enables multi-collateral settlement.
|
|
292
|
+
* OMLP vault routing is based on this mint. Defaults to underlyingMint if not provided.
|
|
293
|
+
*/
|
|
294
|
+
collateralMint?: AddressLike;
|
|
295
|
+
makerCollateralAmount: bigint | number;
|
|
296
|
+
borrowedAmount: bigint | number;
|
|
297
|
+
maxRequiredCollateralAmount?: bigint | number;
|
|
298
|
+
maker: AddressLike;
|
|
299
|
+
/**
|
|
300
|
+
* Optional. When omitted, the SDK derives the maker's collateral ATA for collateralMint
|
|
301
|
+
* (or underlyingMint if collateralMint is not provided).
|
|
302
|
+
*/
|
|
303
|
+
makerCollateralAccount?: AddressLike;
|
|
304
|
+
rpc: KitRpc;
|
|
305
|
+
rpcEndpoint?: string;
|
|
306
|
+
programId?: AddressLike;
|
|
307
|
+
vault?: AddressLike;
|
|
308
|
+
vaultTokenAccount?: AddressLike;
|
|
309
|
+
escrowState?: AddressLike;
|
|
310
|
+
escrowAuthority?: AddressLike;
|
|
311
|
+
escrowTokenAccount?: AddressLike;
|
|
312
|
+
poolLoan?: AddressLike;
|
|
313
|
+
remainingAccounts?: RemainingAccountInput[];
|
|
314
|
+
disableSwitchboardCrank?: boolean;
|
|
315
|
+
switchboardCrossbarUrl?: string;
|
|
316
|
+
switchboardNumSignatures?: number;
|
|
317
|
+
/**
|
|
318
|
+
* 0-based index of this Switchboard quote ix inside the **final** transaction after the
|
|
319
|
+
* wallet prepends compute-budget instructions (see `Queue.fetchQuoteIx` `instructionIdx`).
|
|
320
|
+
* Kit `sendInstructions` prepends **only** `SetComputeUnitLimit` before these ixs → default **1**.
|
|
321
|
+
* Use **0** if nothing is prepended; **2** if both limit and priority fee compute-budget ixs run first.
|
|
322
|
+
*/
|
|
323
|
+
switchboardQuoteInstructionIndex?: number;
|
|
324
|
+
/**
|
|
325
|
+
* When true, omits the collateral ATA create-idempotent instruction from the mint transaction.
|
|
326
|
+
* Send that ix in a prior transaction (e.g. with leverage setup) so quote + option_mint stays
|
|
327
|
+
* under Solana's 1232-byte versioned-transaction limit when OMLP accounts are present.
|
|
328
|
+
*/
|
|
329
|
+
skipCreateCollateralAta?: boolean;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export async function buildOptionMintTransactionWithDerivation(
|
|
333
|
+
params: BuildOptionMintTransactionWithDerivationParams
|
|
334
|
+
): Promise<BuiltTransaction> {
|
|
335
|
+
const borrowedAmount = BigInt(params.borrowedAmount);
|
|
336
|
+
if (borrowedAmount > 0n) {
|
|
337
|
+
invariant(!!params.vault, "vault is required when borrowedAmount > 0");
|
|
338
|
+
invariant(
|
|
339
|
+
!!params.vaultTokenAccount,
|
|
340
|
+
"vaultTokenAccount is required when borrowedAmount > 0"
|
|
341
|
+
);
|
|
342
|
+
invariant(!!params.escrowState, "escrowState is required when borrowedAmount > 0");
|
|
343
|
+
invariant(
|
|
344
|
+
!!params.escrowAuthority,
|
|
345
|
+
"escrowAuthority is required when borrowedAmount > 0"
|
|
346
|
+
);
|
|
347
|
+
invariant(
|
|
348
|
+
!!params.escrowTokenAccount,
|
|
349
|
+
"escrowTokenAccount is required when borrowedAmount > 0"
|
|
350
|
+
);
|
|
351
|
+
invariant(!!params.poolLoan, "poolLoan is required when borrowedAmount > 0");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const resolved = await resolveOptionAccounts({
|
|
355
|
+
underlyingAsset: params.underlyingAsset,
|
|
356
|
+
optionType: params.optionType,
|
|
357
|
+
strikePrice: params.strikePrice,
|
|
358
|
+
expirationDate: params.expirationDate,
|
|
359
|
+
programId: params.programId,
|
|
360
|
+
rpc: params.rpc,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const underlyingMint = resolved.underlyingMint ?? params.underlyingMint;
|
|
364
|
+
const collateralMint = params.collateralMint ?? underlyingMint;
|
|
365
|
+
const existingCollateralMint = resolved.collateralPoolData?.collateralMint;
|
|
366
|
+
if (
|
|
367
|
+
existingCollateralMint &&
|
|
368
|
+
toAddress(existingCollateralMint) !== toAddress(collateralMint)
|
|
369
|
+
) {
|
|
370
|
+
invariant(
|
|
371
|
+
false,
|
|
372
|
+
`Collateral pool mint mismatch. Option market is already collateralized with ${toAddress(
|
|
373
|
+
existingCollateralMint
|
|
374
|
+
)}, but request used ${toAddress(collateralMint)}.`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const collateralVault =
|
|
379
|
+
resolved.collateralVault ??
|
|
380
|
+
(await deriveAssociatedTokenAddress(resolved.collateralPool, collateralMint, true));
|
|
381
|
+
|
|
382
|
+
const [makerLongAccount, makerShortAccount] = await Promise.all([
|
|
383
|
+
deriveAssociatedTokenAddress(params.maker, resolved.longMint),
|
|
384
|
+
deriveAssociatedTokenAddress(params.maker, resolved.shortMint),
|
|
385
|
+
]);
|
|
386
|
+
const makerCollateralAccount = params.makerCollateralAccount
|
|
387
|
+
? toAddress(params.makerCollateralAccount)
|
|
388
|
+
: await deriveAssociatedTokenAddress(params.maker, collateralMint);
|
|
389
|
+
const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
|
|
390
|
+
invariant(
|
|
391
|
+
!!marketDataAccount,
|
|
392
|
+
"Market data account not found for resolved option market."
|
|
393
|
+
);
|
|
394
|
+
const switchboardFeedId = feedIdBytesToHex(
|
|
395
|
+
Uint8Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
const [writerPositionPdaForIx] = await deriveWriterPositionPda(
|
|
399
|
+
resolved.optionPool,
|
|
400
|
+
params.maker,
|
|
401
|
+
params.programId
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
// option_mint + borrow: `sync_collateral_pool_debt` requires
|
|
405
|
+
// snapshot.active_loan_count == writer.active_loan_count
|
|
406
|
+
// after the writer block (+1 for this borrow). The named `pool_loan` is the new loan from
|
|
407
|
+
// TX A. gPA can return more PoolLoans than `writer.active_loan_count` (on-chain desync
|
|
408
|
+
// or stale actives). We pass exactly `activeLoanCount` pre-ix prior loans, excluding the
|
|
409
|
+
// new `pool_loan`, matching `writer_position` (and preferring same vault) with canonical
|
|
410
|
+
// `toAddress` equality. Prefer *higher* `nonce` first so we do not pick a stale low-nonce
|
|
411
|
+
// account that the sync path skips (discriminator / borrow_mut), which yields under-count
|
|
412
|
+
// and 6102.
|
|
413
|
+
let priorActivePoolLoans: RemainingAccountInput[] = [];
|
|
414
|
+
if (borrowedAmount > 0n) {
|
|
415
|
+
const wp = toAddress(writerPositionPdaForIx);
|
|
416
|
+
const newLoan = toAddress(params.poolLoan!);
|
|
417
|
+
const vaultPda = toAddress(params.vault!);
|
|
418
|
+
const [writerPos, activeLoans] = await Promise.all([
|
|
419
|
+
fetchWriterPosition(params.rpc, writerPositionPdaForIx),
|
|
420
|
+
fetchPoolLoansByMaker(params.rpc, params.maker),
|
|
421
|
+
]);
|
|
422
|
+
const priorCount = writerPos?.activeLoanCount ?? 0;
|
|
423
|
+
const forWriter = (l: (typeof activeLoans)[0]) =>
|
|
424
|
+
toAddress(l.data.writerPosition) === wp && toAddress(l.address) !== newLoan;
|
|
425
|
+
const sortByNonceDesc = (
|
|
426
|
+
a: (typeof activeLoans)[0],
|
|
427
|
+
b: (typeof activeLoans)[0]
|
|
428
|
+
) => {
|
|
429
|
+
if (a.data.nonce > b.data.nonce) return -1;
|
|
430
|
+
if (a.data.nonce < b.data.nonce) return 1;
|
|
431
|
+
return String(a.address).localeCompare(String(b.address));
|
|
432
|
+
};
|
|
433
|
+
const withVault = activeLoans
|
|
434
|
+
.filter(
|
|
435
|
+
(l) => forWriter(l) && toAddress(l.data.vault) === vaultPda
|
|
436
|
+
)
|
|
437
|
+
.sort(sortByNonceDesc);
|
|
438
|
+
const withoutVault = activeLoans.filter(forWriter).sort(sortByNonceDesc);
|
|
439
|
+
const candidates =
|
|
440
|
+
withVault.length >= priorCount ? withVault : withoutVault;
|
|
441
|
+
priorActivePoolLoans = candidates
|
|
442
|
+
.slice(0, priorCount)
|
|
443
|
+
.map((l) => ({ address: l.address, isWritable: true }));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const tx = await buildOptionMintTransaction({
|
|
447
|
+
...params,
|
|
448
|
+
underlyingAsset: params.underlyingAsset,
|
|
449
|
+
underlyingMint,
|
|
450
|
+
collateralMint,
|
|
451
|
+
makerCollateralAccount,
|
|
452
|
+
optionAccount: resolved.optionAccount,
|
|
453
|
+
longMint: resolved.longMint,
|
|
454
|
+
shortMint: resolved.shortMint,
|
|
455
|
+
mintAuthority: resolved.mintAuthority,
|
|
456
|
+
makerLongAccount,
|
|
457
|
+
makerShortAccount,
|
|
458
|
+
marketData: resolved.marketData,
|
|
459
|
+
optionPool: resolved.optionPool,
|
|
460
|
+
escrowLongAccount: resolved.escrowLongAccount,
|
|
461
|
+
premiumVault: resolved.premiumVault,
|
|
462
|
+
collateralPool: resolved.collateralPool,
|
|
463
|
+
collateralVault,
|
|
464
|
+
writerPosition: writerPositionPdaForIx,
|
|
465
|
+
vault: params.vault,
|
|
466
|
+
vaultTokenAccount: params.vaultTokenAccount,
|
|
467
|
+
escrowState: params.escrowState,
|
|
468
|
+
escrowAuthority: params.escrowAuthority,
|
|
469
|
+
escrowTokenAccount: params.escrowTokenAccount,
|
|
470
|
+
poolLoan: params.poolLoan,
|
|
471
|
+
switchboardQueue: SWITCHBOARD_DEFAULT_DEVNET_QUEUE,
|
|
472
|
+
remainingAccounts: [...priorActivePoolLoans, ...(params.remainingAccounts ?? [])],
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
const createAtaIx =
|
|
476
|
+
params.skipCreateCollateralAta === true
|
|
477
|
+
? null
|
|
478
|
+
: await getCreateAssociatedTokenIdempotentInstructionWithAddress(
|
|
479
|
+
params.maker,
|
|
480
|
+
params.maker,
|
|
481
|
+
collateralMint,
|
|
482
|
+
makerCollateralAccount
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
const actionTx = {
|
|
486
|
+
instructions:
|
|
487
|
+
createAtaIx !== null ? [createAtaIx, ...tx.instructions] : [...tx.instructions],
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
if (params.disableSwitchboardCrank) {
|
|
491
|
+
return actionTx;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
invariant(
|
|
495
|
+
!!params.rpcEndpoint,
|
|
496
|
+
"rpcEndpoint is required to fetch Switchboard quote instructions."
|
|
497
|
+
);
|
|
498
|
+
const quoteNetwork = await inferSwitchboardNetwork(params.rpc);
|
|
499
|
+
const quote = await buildSwitchboardQuoteInstruction({
|
|
500
|
+
rpcEndpoint: params.rpcEndpoint,
|
|
501
|
+
feedIdHex: switchboardFeedId,
|
|
502
|
+
network: quoteNetwork,
|
|
503
|
+
crossbarUrl: params.switchboardCrossbarUrl,
|
|
504
|
+
numSignatures: params.switchboardNumSignatures,
|
|
505
|
+
instructionIdx: params.switchboardQuoteInstructionIndex ?? 1,
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
return {
|
|
509
|
+
instructions: [quote.instruction, ...actionTx.instructions],
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export async function buildUnwindWriterUnsoldInstruction(
|
|
514
|
+
params: BuildUnwindWriterUnsoldParams
|
|
515
|
+
): Promise<Instruction<string>> {
|
|
516
|
+
assertPositiveAmount(params.unwindQty, "unwindQty");
|
|
517
|
+
|
|
518
|
+
const kitInstruction = await getUnwindWriterUnsoldInstructionAsync(
|
|
519
|
+
{
|
|
520
|
+
optionPool: toAddress(params.optionPool),
|
|
521
|
+
optionAccount: toAddress(params.optionAccount),
|
|
522
|
+
marketData: toAddress(params.marketData),
|
|
523
|
+
collateralPool: params.collateralPool ? toAddress(params.collateralPool) : undefined,
|
|
524
|
+
writerPosition: params.writerPosition ? toAddress(params.writerPosition) : undefined,
|
|
525
|
+
longMint: toAddress(params.longMint),
|
|
526
|
+
shortMint: toAddress(params.shortMint),
|
|
527
|
+
escrowLongAccount: toAddress(params.escrowLongAccount),
|
|
528
|
+
writerShortAccount: toAddress(params.writerShortAccount),
|
|
529
|
+
collateralVault: toAddress(params.collateralVault),
|
|
530
|
+
premiumVault: toAddress(params.premiumVault),
|
|
531
|
+
writerCollateralAccount: toAddress(params.writerCollateralAccount),
|
|
532
|
+
omlpVaultState: params.omlpVaultState ? toAddress(params.omlpVaultState) : undefined,
|
|
533
|
+
omlpVault: params.omlpVault ? toAddress(params.omlpVault) : undefined,
|
|
534
|
+
feeWallet: params.feeWallet ? toAddress(params.feeWallet) : undefined,
|
|
535
|
+
writer: toAddress(params.writer) as any,
|
|
536
|
+
unwindQty: params.unwindQty,
|
|
537
|
+
} as any
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
return appendRemainingAccounts(kitInstruction, params.remainingAccounts);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export async function buildUnwindWriterUnsoldTransaction(
|
|
544
|
+
params: BuildUnwindWriterUnsoldParams
|
|
545
|
+
): Promise<BuiltTransaction> {
|
|
546
|
+
const instruction = await buildUnwindWriterUnsoldInstruction(params);
|
|
547
|
+
return { instructions: [instruction] };
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export interface BuildUnwindWriterUnsoldTransactionWithDerivationParams {
|
|
551
|
+
underlyingAsset: AddressLike;
|
|
552
|
+
optionType: OptionType;
|
|
553
|
+
strikePrice: number;
|
|
554
|
+
expirationDate: bigint | number;
|
|
555
|
+
writer: AddressLike;
|
|
556
|
+
unwindQty: bigint | number;
|
|
557
|
+
rpc: KitRpc;
|
|
558
|
+
programId?: AddressLike;
|
|
559
|
+
omlpVaultState?: AddressLike;
|
|
560
|
+
omlpVault?: AddressLike;
|
|
561
|
+
feeWallet?: AddressLike;
|
|
562
|
+
/**
|
|
563
|
+
* When repaying pool loans: [PoolLoan₁, PoolLoan₂, ...] (all writable).
|
|
564
|
+
* omlpVaultState, omlpVault, feeWallet must also be passed.
|
|
565
|
+
* Prefer {@link buildUnwindWriterUnsoldWithLoanRepayment} to build this automatically.
|
|
566
|
+
*/
|
|
567
|
+
remainingAccounts?: RemainingAccountInput[];
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export interface BuildUnwindWriterUnsoldWithLoanRepaymentParams {
|
|
571
|
+
underlyingAsset: AddressLike;
|
|
572
|
+
optionType: OptionType;
|
|
573
|
+
strikePrice: number;
|
|
574
|
+
expirationDate: bigint | number;
|
|
575
|
+
writer: AddressLike;
|
|
576
|
+
unwindQty: bigint | number;
|
|
577
|
+
rpc: KitRpc;
|
|
578
|
+
programId?: AddressLike;
|
|
579
|
+
/** Override when pool fetch is not used; otherwise resolved from option pool. */
|
|
580
|
+
underlyingMint?: AddressLike;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Builds an unwind_writer_unsold transaction that also repays any active pool loans
|
|
585
|
+
* for the option's underlying vault. When a writer unwinds an unsold short that had
|
|
586
|
+
* borrowed from OMLP, the program repays lenders from the collateral vault (burn,
|
|
587
|
+
* repay, then return collateral to writer) in one instruction.
|
|
588
|
+
*
|
|
589
|
+
* Passes omlpVaultState (Vault PDA), omlpVault, feeWallet as named accounts.
|
|
590
|
+
* remaining_accounts: [PoolLoan₁, PoolLoan₂, ...] only (capped at 20).
|
|
591
|
+
* If no active pool loans exist, still passes vault accounts so the API works for all unwinds.
|
|
592
|
+
*/
|
|
593
|
+
export async function buildUnwindWriterUnsoldWithLoanRepayment(
|
|
594
|
+
params: BuildUnwindWriterUnsoldWithLoanRepaymentParams
|
|
595
|
+
): Promise<BuiltTransaction> {
|
|
596
|
+
const resolved = await resolveOptionAccounts({
|
|
597
|
+
underlyingAsset: params.underlyingAsset,
|
|
598
|
+
optionType: params.optionType,
|
|
599
|
+
strikePrice: params.strikePrice,
|
|
600
|
+
expirationDate: params.expirationDate,
|
|
601
|
+
programId: params.programId,
|
|
602
|
+
rpc: params.rpc,
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
const underlyingMint = params.underlyingMint ?? resolved.underlyingMint;
|
|
606
|
+
invariant(
|
|
607
|
+
!!underlyingMint,
|
|
608
|
+
"underlyingMint is required; ensure rpc is provided and option pool is initialized, or pass underlyingMint."
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
const [vaultPda] = await deriveVaultPda(underlyingMint, params.programId);
|
|
612
|
+
const vaultPdaStr = toAddress(vaultPda);
|
|
613
|
+
|
|
614
|
+
const [writerPositionAddress] = await deriveWriterPositionPda(
|
|
615
|
+
resolved.optionPool,
|
|
616
|
+
params.writer,
|
|
617
|
+
params.programId
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
const [loans, vault, writerPosition] = await Promise.all([
|
|
621
|
+
fetchPoolLoansByMaker(params.rpc, params.writer),
|
|
622
|
+
fetchVault(params.rpc, vaultPda),
|
|
623
|
+
fetchWriterPosition(params.rpc, writerPositionAddress),
|
|
624
|
+
]);
|
|
625
|
+
|
|
626
|
+
invariant(!!writerPosition, "Writer position not found for unwind.");
|
|
627
|
+
|
|
628
|
+
const wpAddr = toAddress(writerPositionAddress);
|
|
629
|
+
const vaultLoansAll = loans.filter(
|
|
630
|
+
(item) => toAddress(item.data.vault) === vaultPdaStr && Number(item.data.status) === 1
|
|
631
|
+
);
|
|
632
|
+
const matchingLoans = writerPositionHasOngoingPoolLoanDebt(writerPosition)
|
|
633
|
+
? vaultLoansAll.filter((item) => toAddress(item.data.writerPosition) === wpAddr)
|
|
634
|
+
: [];
|
|
635
|
+
|
|
636
|
+
// Mirror preflight: pick exactly `activeLoanCount` loans (top-N by nonce DESC) and verify
|
|
637
|
+
// their principal sum matches `writer.borrowed_principal`. Orphan status==1 PoolLoans
|
|
638
|
+
// not tracked by the writer would otherwise inflate `snapshot.active_loan_count` and
|
|
639
|
+
// trip the on-chain MaintenanceLoansIncomplete invariant.
|
|
640
|
+
const sortByNonceDesc = (
|
|
641
|
+
a: (typeof matchingLoans)[number],
|
|
642
|
+
b: (typeof matchingLoans)[number]
|
|
643
|
+
) => {
|
|
644
|
+
if (a.data.nonce > b.data.nonce) return -1;
|
|
645
|
+
if (a.data.nonce < b.data.nonce) return 1;
|
|
646
|
+
return String(a.address).localeCompare(String(b.address));
|
|
647
|
+
};
|
|
648
|
+
const vaultLoans = writerPositionHasOngoingPoolLoanDebt(writerPosition)
|
|
649
|
+
? matchingLoans.slice().sort(sortByNonceDesc).slice(0, writerPosition.activeLoanCount)
|
|
650
|
+
: [];
|
|
651
|
+
|
|
652
|
+
if (writerPositionHasOngoingPoolLoanDebt(writerPosition)) {
|
|
653
|
+
invariant(
|
|
654
|
+
vaultLoans.length === writerPosition.activeLoanCount,
|
|
655
|
+
`Expected ${writerPosition.activeLoanCount} active pool loan(s) for this writer position; only found ${matchingLoans.length} matching. Refresh positions.`
|
|
656
|
+
);
|
|
657
|
+
const selectedPrincipalSum = vaultLoans.reduce(
|
|
658
|
+
(sum, item) => sum + BigInt(item.data.principal),
|
|
659
|
+
0n
|
|
660
|
+
);
|
|
661
|
+
const writerBorrowedPrincipal = BigInt(writerPosition.borrowedPrincipal);
|
|
662
|
+
invariant(
|
|
663
|
+
selectedPrincipalSum === writerBorrowedPrincipal,
|
|
664
|
+
`Active pool loan principal mismatch: top-${writerPosition.activeLoanCount} loans sum to ${selectedPrincipalSum} but writer.borrowed_principal=${writerBorrowedPrincipal}. On-chain state is inconsistent (orphan PoolLoans). Use the keeper rescue path.`
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
invariant(
|
|
669
|
+
vaultLoans.length <= MAX_POOL_LOANS_PER_UNWIND,
|
|
670
|
+
`Too many active pool loans for unwind: ${vaultLoans.length}. Max supported in one unwind is ${MAX_POOL_LOANS_PER_UNWIND}.`
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
const remainingAccounts: RemainingAccountInput[] = vaultLoans.map((item) => ({
|
|
674
|
+
address: item.address,
|
|
675
|
+
isWritable: true,
|
|
676
|
+
}));
|
|
677
|
+
|
|
678
|
+
const omlpVault = await deriveAssociatedTokenAddress(vaultPda, underlyingMint);
|
|
679
|
+
const feeWallet = vault
|
|
680
|
+
? await deriveAssociatedTokenAddress(vault.feeWallet, underlyingMint)
|
|
681
|
+
: undefined;
|
|
682
|
+
|
|
683
|
+
// Theta-hedge model: if proportional debt cannot be covered by
|
|
684
|
+
// `theta_available + proportional_collateral`, the on-chain path reverts
|
|
685
|
+
// with `LiquidationInsufficientCollateralForDebt`. Writer-wallet fallback
|
|
686
|
+
// has been removed; the keeper-gated rescue ix handles insolvent
|
|
687
|
+
// positions instead.
|
|
688
|
+
const preflight = await preflightUnwindWriterUnsold({
|
|
689
|
+
underlyingAsset: params.underlyingAsset,
|
|
690
|
+
optionType: params.optionType,
|
|
691
|
+
strikePrice: params.strikePrice,
|
|
692
|
+
expirationDate: params.expirationDate,
|
|
693
|
+
writer: params.writer,
|
|
694
|
+
unwindQty: params.unwindQty,
|
|
695
|
+
rpc: params.rpc,
|
|
696
|
+
programId: params.programId,
|
|
697
|
+
underlyingMint,
|
|
698
|
+
});
|
|
699
|
+
invariant(
|
|
700
|
+
preflight.canRepayRequestedSlice,
|
|
701
|
+
`Unwind cannot repay the requested slice from collateral vault + theta. required=${preflight.summary.proportionalTotalOwed} collateral_available=${preflight.summary.collateralVaultAvailable}. Use liquidate_writer_position_rescue if the position is insolvent.`
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
return await buildUnwindWriterUnsoldTransactionWithDerivation({
|
|
705
|
+
underlyingAsset: params.underlyingAsset,
|
|
706
|
+
optionType: params.optionType,
|
|
707
|
+
strikePrice: params.strikePrice,
|
|
708
|
+
expirationDate: params.expirationDate,
|
|
709
|
+
writer: params.writer,
|
|
710
|
+
unwindQty: preflight.effectiveUnwindQty,
|
|
711
|
+
rpc: params.rpc,
|
|
712
|
+
programId: params.programId,
|
|
713
|
+
omlpVaultState: vaultPda,
|
|
714
|
+
omlpVault,
|
|
715
|
+
feeWallet,
|
|
716
|
+
remainingAccounts,
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export async function buildUnwindWriterUnsoldTransactionWithDerivation(
|
|
721
|
+
params: BuildUnwindWriterUnsoldTransactionWithDerivationParams
|
|
722
|
+
): Promise<BuiltTransaction> {
|
|
723
|
+
const resolved = await resolveOptionAccounts({
|
|
724
|
+
underlyingAsset: params.underlyingAsset,
|
|
725
|
+
optionType: params.optionType,
|
|
726
|
+
strikePrice: params.strikePrice,
|
|
727
|
+
expirationDate: params.expirationDate,
|
|
728
|
+
programId: params.programId,
|
|
729
|
+
rpc: params.rpc,
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
invariant(
|
|
733
|
+
!!resolved.escrowLongAccount &&
|
|
734
|
+
!!resolved.premiumVault &&
|
|
735
|
+
!!resolved.collateralVault &&
|
|
736
|
+
!!resolved.underlyingMint,
|
|
737
|
+
"Option pool and collateral pool must exist; ensure rpc is provided and pools are initialized."
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
const [writerShortAccount, writerCollateralAccount, writerPosition] =
|
|
741
|
+
await Promise.all([
|
|
742
|
+
deriveAssociatedTokenAddress(params.writer, resolved.shortMint),
|
|
743
|
+
deriveAssociatedTokenAddress(params.writer, resolved.underlyingMint),
|
|
744
|
+
deriveWriterPositionPda(resolved.optionPool, params.writer, params.programId),
|
|
745
|
+
]);
|
|
746
|
+
|
|
747
|
+
return buildUnwindWriterUnsoldTransaction({
|
|
748
|
+
optionPool: resolved.optionPool,
|
|
749
|
+
optionAccount: resolved.optionAccount,
|
|
750
|
+
marketData: resolved.marketData,
|
|
751
|
+
longMint: resolved.longMint,
|
|
752
|
+
shortMint: resolved.shortMint,
|
|
753
|
+
escrowLongAccount: resolved.escrowLongAccount!,
|
|
754
|
+
writerShortAccount,
|
|
755
|
+
collateralVault: resolved.collateralVault!,
|
|
756
|
+
premiumVault: resolved.premiumVault!,
|
|
757
|
+
writerCollateralAccount,
|
|
758
|
+
writer: params.writer,
|
|
759
|
+
unwindQty: params.unwindQty,
|
|
760
|
+
collateralPool: resolved.collateralPool,
|
|
761
|
+
writerPosition: writerPosition[0],
|
|
762
|
+
omlpVaultState: params.omlpVaultState,
|
|
763
|
+
omlpVault: params.omlpVault,
|
|
764
|
+
feeWallet: params.feeWallet,
|
|
765
|
+
remainingAccounts: params.remainingAccounts,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
export function buildSyncWriterPositionInstruction(
|
|
770
|
+
params: BuildSyncWriterPositionParams
|
|
771
|
+
): Instruction<string> {
|
|
772
|
+
const kitInstruction = getSyncWriterPositionInstruction({
|
|
773
|
+
optionPool: toAddress(params.optionPool),
|
|
774
|
+
optionAccount: toAddress(params.optionAccount),
|
|
775
|
+
writerPosition: toAddress(params.writerPosition),
|
|
776
|
+
});
|
|
777
|
+
return kitInstruction;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
export function buildSyncWriterPositionTransaction(
|
|
781
|
+
params: BuildSyncWriterPositionParams
|
|
782
|
+
): BuiltTransaction {
|
|
783
|
+
const instruction = buildSyncWriterPositionInstruction(params);
|
|
784
|
+
return { instructions: [instruction] };
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
export async function buildSettleMakerCollateralInstruction(
|
|
788
|
+
params: BuildSettleMakerCollateralParams
|
|
789
|
+
): Promise<Instruction<string>> {
|
|
790
|
+
const makerCollateralShare =
|
|
791
|
+
params.makerCollateralShare ??
|
|
792
|
+
(params.collateralPool
|
|
793
|
+
? (await deriveMakerCollateralSharePda(params.collateralPool, params.maker))[0]
|
|
794
|
+
: undefined);
|
|
795
|
+
|
|
796
|
+
invariant(
|
|
797
|
+
!!makerCollateralShare,
|
|
798
|
+
"makerCollateralShare is required (or provide collateralPool + maker to derive)."
|
|
799
|
+
);
|
|
800
|
+
|
|
801
|
+
const kitInstruction = await getSettleMakerCollateralInstructionAsync({
|
|
802
|
+
optionAccount: toAddress(params.optionAccount),
|
|
803
|
+
collateralPool: params.collateralPool ? toAddress(params.collateralPool) : undefined,
|
|
804
|
+
makerCollateralShare: toAddress(makerCollateralShare),
|
|
805
|
+
collateralVault: toAddress(params.collateralVault),
|
|
806
|
+
makerCollateralAccount: toAddress(params.makerCollateralAccount),
|
|
807
|
+
omlpVault: toAddress(params.omlpVault),
|
|
808
|
+
omlpVaultState: toAddress(params.omlpVaultState),
|
|
809
|
+
poolLoan: toAddress(params.poolLoan),
|
|
810
|
+
});
|
|
811
|
+
return kitInstruction;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
export async function buildSettleMakerCollateralTransaction(
|
|
815
|
+
params: BuildSettleMakerCollateralParams
|
|
816
|
+
): Promise<BuiltTransaction> {
|
|
817
|
+
const instruction = await buildSettleMakerCollateralInstruction(params);
|
|
818
|
+
return { instructions: [instruction] };
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Parameters for the permissioned rescue liquidation path. Gated on-chain
|
|
823
|
+
* to `Vault::keeper`; the SDK does not re-verify keeper identity, but the
|
|
824
|
+
* transaction will revert with `RescueUnauthorized` if the signer's pubkey
|
|
825
|
+
* does not match the vault's keeper.
|
|
826
|
+
*/
|
|
827
|
+
export interface BuildLiquidateWriterPositionRescueParams {
|
|
828
|
+
optionPool: AddressLike;
|
|
829
|
+
optionAccount: AddressLike;
|
|
830
|
+
/** Market data PDA (`["market_data", underlying_asset]`). */
|
|
831
|
+
marketData: AddressLike;
|
|
832
|
+
collateralPool: AddressLike;
|
|
833
|
+
writerPosition: AddressLike;
|
|
834
|
+
longMint: AddressLike;
|
|
835
|
+
escrowLongAccount: AddressLike;
|
|
836
|
+
/** OMLP Vault state PDA (mut: decrements total_loans, writes bad_debt_lamports). */
|
|
837
|
+
omlpVault: AddressLike;
|
|
838
|
+
collateralVault: AddressLike;
|
|
839
|
+
premiumVault: AddressLike;
|
|
840
|
+
omlpVaultTokenAccount: AddressLike;
|
|
841
|
+
feeWallet: AddressLike;
|
|
842
|
+
/** Vault keeper — must sign and match `Vault::keeper`. */
|
|
843
|
+
keeper: TransactionSigner<string>;
|
|
844
|
+
/** Every active PoolLoan for this writer/vault (SDK: loadWriterLoans). */
|
|
845
|
+
remainingAccounts?: RemainingAccountInput[];
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
export async function buildLiquidateWriterPositionRescueInstruction(
|
|
849
|
+
params: BuildLiquidateWriterPositionRescueParams
|
|
850
|
+
): Promise<Instruction<string>> {
|
|
851
|
+
const kitInstruction = await getLiquidateWriterPositionRescueInstructionAsync({
|
|
852
|
+
optionPool: toAddress(params.optionPool),
|
|
853
|
+
optionAccount: toAddress(params.optionAccount),
|
|
854
|
+
marketData: toAddress(params.marketData),
|
|
855
|
+
collateralPool: toAddress(params.collateralPool),
|
|
856
|
+
writerPosition: toAddress(params.writerPosition),
|
|
857
|
+
longMint: toAddress(params.longMint),
|
|
858
|
+
escrowLongAccount: toAddress(params.escrowLongAccount),
|
|
859
|
+
omlpVault: toAddress(params.omlpVault),
|
|
860
|
+
collateralVault: toAddress(params.collateralVault),
|
|
861
|
+
premiumVault: toAddress(params.premiumVault),
|
|
862
|
+
omlpVaultTokenAccount: toAddress(params.omlpVaultTokenAccount),
|
|
863
|
+
feeWallet: toAddress(params.feeWallet),
|
|
864
|
+
keeper: params.keeper as any,
|
|
865
|
+
});
|
|
866
|
+
return appendRemainingAccounts(kitInstruction, params.remainingAccounts);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
export async function buildLiquidateWriterPositionRescueTransaction(
|
|
870
|
+
params: BuildLiquidateWriterPositionRescueParams
|
|
871
|
+
): Promise<BuiltTransaction> {
|
|
872
|
+
const instruction = await buildLiquidateWriterPositionRescueInstruction(params);
|
|
873
|
+
return { instructions: [instruction] };
|
|
874
|
+
}
|