@epicentral/sos-sdk 0.6.2-alpha → 0.7.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -5
- package/client/lookup-table.ts +1 -1
- package/client/types.ts +1 -0
- package/generated/accounts/vault.ts +13 -1
- package/generated/instructions/index.ts +1 -0
- package/generated/instructions/omlpCreateVault.ts +6 -0
- package/generated/instructions/omlpUpdateMaxBorrowCap.ts +304 -0
- package/generated/instructions/optionMint.ts +6 -0
- package/generated/programs/optionProgram.ts +18 -2
- package/index.ts +7 -0
- package/long/builders.ts +67 -7
- package/long/exercise.ts +22 -3
- package/omlp/builders.ts +53 -3
- package/oracle/switchboard.ts +90 -2
- package/package.json +4 -1
- package/shared/trade-config.ts +27 -0
- package/shared/transactions.ts +11 -4
- package/short/builders.ts +45 -5
- package/short/preflight.ts +16 -13
package/README.md
CHANGED
|
@@ -136,10 +136,25 @@ Or pass `addressLookupTableAddresses: [getLookupTableAddressForNetwork("devnet")
|
|
|
136
136
|
| Function | Description |
|
|
137
137
|
|----------|-------------|
|
|
138
138
|
| `resolveSwitchboardFeedFromMarketData` | Resolves Switchboard feed address from market data account. |
|
|
139
|
-
| `buildSwitchboardPullFeedUpdate` |
|
|
139
|
+
| `buildSwitchboardPullFeedUpdate` | Low-level helper that fetches Switchboard pull-feed update instructions. |
|
|
140
|
+
| `buildSwitchboardCrank` | Returns Kit-native crank instructions plus lookup tables for the configured feed. |
|
|
141
|
+
| `prependSwitchboardCrank` | Prepends crank instructions/ALTs to a built SDK transaction. |
|
|
142
|
+
|
|
143
|
+
Price-sensitive option builders and OMLP deposit/withdraw builders now prepend Switchboard crank instructions by default, so frontends can keep using the same Kit transaction pipeline without managing a separate oracle update step.
|
|
140
144
|
|
|
141
145
|
See [Frontend Switchboard Integration](../../docs/FRONTEND_SWITCHBOARD_INTEGRATION.md) for full setup and usage.
|
|
142
146
|
|
|
147
|
+
### Global Trade Config
|
|
148
|
+
|
|
149
|
+
Use the shared trade config helpers to set SDK-wide defaults for slippage and compute-budget settings:
|
|
150
|
+
|
|
151
|
+
- `setGlobalTradeConfig`
|
|
152
|
+
- `updateGlobalTradeConfig`
|
|
153
|
+
- `getGlobalTradeConfig`
|
|
154
|
+
- `resetGlobalTradeConfig`
|
|
155
|
+
|
|
156
|
+
These defaults are used by the high-level builders unless an action-specific override is provided. `option_mint` also forwards a max-collateral bound on-chain so signer delays cannot silently widen required collateral past the configured tolerance.
|
|
157
|
+
|
|
143
158
|
## Multi-Collateral Settlement
|
|
144
159
|
|
|
145
160
|
The SDK supports universal multi-collateral settlement, allowing writers to use ANY supported asset as collateral for options (not just the underlying). This enables:
|
|
@@ -215,7 +230,7 @@ When a writer unwinds an unsold short that had borrowed from the OMLP pool, the
|
|
|
215
230
|
**Collateral Return:**
|
|
216
231
|
- Proportional collateral share = `(collateral_deposited * unwind_qty) / written_qty`
|
|
217
232
|
- Returnable collateral = `proportional_share - amount_already_repaid_from_vault`
|
|
218
|
-
- If vault lacks sufficient post-repayment balance, fails with `InsufficientCollateralVault` (
|
|
233
|
+
- If vault lacks sufficient post-repayment balance, fails with `InsufficientCollateralVault` (6092)
|
|
219
234
|
|
|
220
235
|
Use **`buildUnwindWriterUnsoldWithLoanRepayment`** so that:
|
|
221
236
|
|
|
@@ -232,7 +247,8 @@ Use **`preflightUnwindWriterUnsold`** before building the transaction to get:
|
|
|
232
247
|
- Collateral-vault available, wallet fallback required, and shortfall.
|
|
233
248
|
- **Top-up UX fields:** `collateralVaultShortfall`, `needsWalletTopUp`.
|
|
234
249
|
- WSOL repay metadata: `solTopUpRequired`, `topUpRequiredForRepay`, `nativeSolAvailable`.
|
|
235
|
-
- `
|
|
250
|
+
- `canRepayRequestedSlice`, which reflects solvency for the proportional unwind slice (including native SOL top-up capacity for WSOL paths).
|
|
251
|
+
- Deprecated compatibility field: `canRepayFully` mirrors the slice-based result for older callers.
|
|
236
252
|
|
|
237
253
|
If there are no active pool loans for that vault, the API still works and passes empty `remaining_accounts`.
|
|
238
254
|
|
|
@@ -333,7 +349,7 @@ The program uses distinct error codes for liquidity failures:
|
|
|
333
349
|
- `InsufficientPoolAggregateLiquidity` (6042) – `option_pool.total_available < quantity`
|
|
334
350
|
- `InsufficientWriterPositionLiquidity` (6043) – remaining writer-position accounts cannot cover full quantity in the smallest-first fill loop
|
|
335
351
|
|
|
336
|
-
|
|
352
|
+
`option_pool.total_available` is now treated as writer-backed unsold inventory. `close_long_to_pool` no longer inflates the aggregate counter by re-adding buyer-closed LONGs, which removes the false-liquidity drift behind `InsufficientWriterPositionLiquidity`.
|
|
337
353
|
|
|
338
354
|
**Recommended client flow:**
|
|
339
355
|
1. Run `preflightBuyFromPoolMarketOrder` for UX gating (checks both pool and active writer liquidity).
|
|
@@ -365,7 +381,7 @@ The program uses the **Switchboard pull feed account** you pass in (or that the
|
|
|
365
381
|
- **Buy:** Premium computation (Black-Scholes).
|
|
366
382
|
- **Close:** Payout computation (mark-to-market). If the price is stale, the close payout will not reflect the current option value; the buyer may receive back only their premium instead of profit.
|
|
367
383
|
|
|
368
|
-
|
|
384
|
+
The SDK now prepends Switchboard crank instructions by default for mint, buy, close, unwind, exercise/settle/liquidate, and OMLP deposit/withdraw builders. Keep feeds configured correctly, but frontends no longer need a separate crank transaction in the standard path.
|
|
369
385
|
|
|
370
386
|
- **Mainnet:** keep feed updates fresh enough to satisfy the feed's configured `max_staleness`.
|
|
371
387
|
- **Devnet:** ensure your keeper/update pipeline runs before user trade flows; payouts reflect the feed's staleness config.
|
package/client/lookup-table.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { PROGRAM_ID } from "./program";
|
|
|
4
4
|
import type { KitRpc } from "./types";
|
|
5
5
|
|
|
6
6
|
export const LOOKUP_TABLE_ADDRESSES: Record<"devnet" | "mainnet", Address | null> = {
|
|
7
|
-
devnet: address("
|
|
7
|
+
devnet: address("CxJn2cyXF8wRr4Cg7GKhSbGS7VW8wAnz3C4uQq537kXr"),
|
|
8
8
|
mainnet: null,
|
|
9
9
|
};
|
|
10
10
|
|
package/client/types.ts
CHANGED
|
@@ -111,6 +111,11 @@ export type Vault = {
|
|
|
111
111
|
* Set by admin, enforced on deposits. 0 = unlimited.
|
|
112
112
|
*/
|
|
113
113
|
supplyLimit: bigint;
|
|
114
|
+
/**
|
|
115
|
+
* Maximum aggregate borrow cap as a share of total liquidity (basis points).
|
|
116
|
+
* Example: 9000 = at most 90% of liquidity may be loaned out.
|
|
117
|
+
*/
|
|
118
|
+
maxBorrowCapBps: number;
|
|
114
119
|
/**
|
|
115
120
|
* Cumulative interest earned per deposited unit (fixed-point)
|
|
116
121
|
* Increases whenever loans are repaid/liquidated
|
|
@@ -177,6 +182,11 @@ export type VaultArgs = {
|
|
|
177
182
|
* Set by admin, enforced on deposits. 0 = unlimited.
|
|
178
183
|
*/
|
|
179
184
|
supplyLimit: number | bigint;
|
|
185
|
+
/**
|
|
186
|
+
* Maximum aggregate borrow cap as a share of total liquidity (basis points).
|
|
187
|
+
* Example: 9000 = at most 90% of liquidity may be loaned out.
|
|
188
|
+
*/
|
|
189
|
+
maxBorrowCapBps: number;
|
|
180
190
|
/**
|
|
181
191
|
* Cumulative interest earned per deposited unit (fixed-point)
|
|
182
192
|
* Increases whenever loans are repaid/liquidated
|
|
@@ -208,6 +218,7 @@ export function getVaultEncoder(): FixedSizeEncoder<VaultArgs> {
|
|
|
208
218
|
["rateSlope1Bps", getU16Encoder()],
|
|
209
219
|
["rateSlope2Bps", getU16Encoder()],
|
|
210
220
|
["supplyLimit", getU64Encoder()],
|
|
221
|
+
["maxBorrowCapBps", getU16Encoder()],
|
|
211
222
|
["accInterestPerShareFp", getU128Encoder()],
|
|
212
223
|
["lastInterestUpdateSlot", getU64Encoder()],
|
|
213
224
|
]),
|
|
@@ -236,6 +247,7 @@ export function getVaultDecoder(): FixedSizeDecoder<Vault> {
|
|
|
236
247
|
["rateSlope1Bps", getU16Decoder()],
|
|
237
248
|
["rateSlope2Bps", getU16Decoder()],
|
|
238
249
|
["supplyLimit", getU64Decoder()],
|
|
250
|
+
["maxBorrowCapBps", getU16Decoder()],
|
|
239
251
|
["accInterestPerShareFp", getU128Decoder()],
|
|
240
252
|
["lastInterestUpdateSlot", getU64Decoder()],
|
|
241
253
|
]);
|
|
@@ -300,5 +312,5 @@ export async function fetchAllMaybeVault(
|
|
|
300
312
|
}
|
|
301
313
|
|
|
302
314
|
export function getVaultSize(): number {
|
|
303
|
-
return
|
|
315
|
+
return 193;
|
|
304
316
|
}
|
|
@@ -23,6 +23,7 @@ export * from "./initializeMarketData";
|
|
|
23
23
|
export * from "./initOptionPool";
|
|
24
24
|
export * from "./liquidateWriterPosition";
|
|
25
25
|
export * from "./omlpCreateVault";
|
|
26
|
+
export * from "./omlpUpdateMaxBorrowCap";
|
|
26
27
|
export * from "./omlpUpdateMaxLeverage";
|
|
27
28
|
export * from "./omlpUpdateProtocolFee";
|
|
28
29
|
export * from "./omlpUpdateSupplyLimit";
|
|
@@ -107,6 +107,7 @@ export type OmlpCreateVaultInstruction<
|
|
|
107
107
|
export type OmlpCreateVaultInstructionData = {
|
|
108
108
|
discriminator: ReadonlyUint8Array;
|
|
109
109
|
liqThresholdBps: number;
|
|
110
|
+
maxBorrowCapBps: number;
|
|
110
111
|
maxLeverageMultiplier: number;
|
|
111
112
|
baseRateBps: number;
|
|
112
113
|
optimalUtilizationBps: number;
|
|
@@ -117,6 +118,7 @@ export type OmlpCreateVaultInstructionData = {
|
|
|
117
118
|
|
|
118
119
|
export type OmlpCreateVaultInstructionDataArgs = {
|
|
119
120
|
liqThresholdBps: number;
|
|
121
|
+
maxBorrowCapBps: number;
|
|
120
122
|
maxLeverageMultiplier: number;
|
|
121
123
|
baseRateBps: number;
|
|
122
124
|
optimalUtilizationBps: number;
|
|
@@ -130,6 +132,7 @@ export function getOmlpCreateVaultInstructionDataEncoder(): FixedSizeEncoder<Oml
|
|
|
130
132
|
getStructEncoder([
|
|
131
133
|
["discriminator", fixEncoderSize(getBytesEncoder(), 8)],
|
|
132
134
|
["liqThresholdBps", getU16Encoder()],
|
|
135
|
+
["maxBorrowCapBps", getU16Encoder()],
|
|
133
136
|
["maxLeverageMultiplier", getU16Encoder()],
|
|
134
137
|
["baseRateBps", getU16Encoder()],
|
|
135
138
|
["optimalUtilizationBps", getU16Encoder()],
|
|
@@ -145,6 +148,7 @@ export function getOmlpCreateVaultInstructionDataDecoder(): FixedSizeDecoder<Oml
|
|
|
145
148
|
return getStructDecoder([
|
|
146
149
|
["discriminator", fixDecoderSize(getBytesDecoder(), 8)],
|
|
147
150
|
["liqThresholdBps", getU16Decoder()],
|
|
151
|
+
["maxBorrowCapBps", getU16Decoder()],
|
|
148
152
|
["maxLeverageMultiplier", getU16Decoder()],
|
|
149
153
|
["baseRateBps", getU16Decoder()],
|
|
150
154
|
["optimalUtilizationBps", getU16Decoder()],
|
|
@@ -189,6 +193,7 @@ export type OmlpCreateVaultAsyncInput<
|
|
|
189
193
|
associatedTokenProgram?: Address<TAccountAssociatedTokenProgram>;
|
|
190
194
|
systemProgram?: Address<TAccountSystemProgram>;
|
|
191
195
|
liqThresholdBps: OmlpCreateVaultInstructionDataArgs["liqThresholdBps"];
|
|
196
|
+
maxBorrowCapBps: OmlpCreateVaultInstructionDataArgs["maxBorrowCapBps"];
|
|
192
197
|
maxLeverageMultiplier: OmlpCreateVaultInstructionDataArgs["maxLeverageMultiplier"];
|
|
193
198
|
baseRateBps: OmlpCreateVaultInstructionDataArgs["baseRateBps"];
|
|
194
199
|
optimalUtilizationBps: OmlpCreateVaultInstructionDataArgs["optimalUtilizationBps"];
|
|
@@ -369,6 +374,7 @@ export type OmlpCreateVaultInput<
|
|
|
369
374
|
associatedTokenProgram?: Address<TAccountAssociatedTokenProgram>;
|
|
370
375
|
systemProgram?: Address<TAccountSystemProgram>;
|
|
371
376
|
liqThresholdBps: OmlpCreateVaultInstructionDataArgs["liqThresholdBps"];
|
|
377
|
+
maxBorrowCapBps: OmlpCreateVaultInstructionDataArgs["maxBorrowCapBps"];
|
|
372
378
|
maxLeverageMultiplier: OmlpCreateVaultInstructionDataArgs["maxLeverageMultiplier"];
|
|
373
379
|
baseRateBps: OmlpCreateVaultInstructionDataArgs["baseRateBps"];
|
|
374
380
|
optimalUtilizationBps: OmlpCreateVaultInstructionDataArgs["optimalUtilizationBps"];
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This code was AUTOGENERATED using the Codama library.
|
|
3
|
+
* Please DO NOT EDIT THIS FILE, instead use visitors
|
|
4
|
+
* to add features, then rerun Codama to update it.
|
|
5
|
+
*
|
|
6
|
+
* @see https://github.com/codama-idl/codama
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
combineCodec,
|
|
11
|
+
fixDecoderSize,
|
|
12
|
+
fixEncoderSize,
|
|
13
|
+
getBytesDecoder,
|
|
14
|
+
getBytesEncoder,
|
|
15
|
+
getProgramDerivedAddress,
|
|
16
|
+
getStructDecoder,
|
|
17
|
+
getStructEncoder,
|
|
18
|
+
getU16Decoder,
|
|
19
|
+
getU16Encoder,
|
|
20
|
+
transformEncoder,
|
|
21
|
+
type AccountMeta,
|
|
22
|
+
type AccountSignerMeta,
|
|
23
|
+
type Address,
|
|
24
|
+
type FixedSizeCodec,
|
|
25
|
+
type FixedSizeDecoder,
|
|
26
|
+
type FixedSizeEncoder,
|
|
27
|
+
type Instruction,
|
|
28
|
+
type InstructionWithAccounts,
|
|
29
|
+
type InstructionWithData,
|
|
30
|
+
type ReadonlyAccount,
|
|
31
|
+
type ReadonlySignerAccount,
|
|
32
|
+
type ReadonlyUint8Array,
|
|
33
|
+
type TransactionSigner,
|
|
34
|
+
type WritableAccount,
|
|
35
|
+
} from "@solana/kit";
|
|
36
|
+
import { OPTION_PROGRAM_PROGRAM_ADDRESS } from "../programs";
|
|
37
|
+
import { getAccountMetaFactory, type ResolvedAccount } from "../shared";
|
|
38
|
+
|
|
39
|
+
export const OMLP_UPDATE_MAX_BORROW_CAP_DISCRIMINATOR = new Uint8Array([
|
|
40
|
+
156, 196, 135, 28, 127, 162, 101, 52,
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
export function getOmlpUpdateMaxBorrowCapDiscriminatorBytes() {
|
|
44
|
+
return fixEncoderSize(getBytesEncoder(), 8).encode(
|
|
45
|
+
OMLP_UPDATE_MAX_BORROW_CAP_DISCRIMINATOR,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type OmlpUpdateMaxBorrowCapInstruction<
|
|
50
|
+
TProgram extends string = typeof OPTION_PROGRAM_PROGRAM_ADDRESS,
|
|
51
|
+
TAccountConfig extends string | AccountMeta<string> = string,
|
|
52
|
+
TAccountVault extends string | AccountMeta<string> = string,
|
|
53
|
+
TAccountAdmin extends string | AccountMeta<string> = string,
|
|
54
|
+
TRemainingAccounts extends readonly AccountMeta<string>[] = [],
|
|
55
|
+
> = Instruction<TProgram> &
|
|
56
|
+
InstructionWithData<ReadonlyUint8Array> &
|
|
57
|
+
InstructionWithAccounts<
|
|
58
|
+
[
|
|
59
|
+
TAccountConfig extends string
|
|
60
|
+
? ReadonlyAccount<TAccountConfig>
|
|
61
|
+
: TAccountConfig,
|
|
62
|
+
TAccountVault extends string
|
|
63
|
+
? WritableAccount<TAccountVault>
|
|
64
|
+
: TAccountVault,
|
|
65
|
+
TAccountAdmin extends string
|
|
66
|
+
? ReadonlySignerAccount<TAccountAdmin> &
|
|
67
|
+
AccountSignerMeta<TAccountAdmin>
|
|
68
|
+
: TAccountAdmin,
|
|
69
|
+
...TRemainingAccounts,
|
|
70
|
+
]
|
|
71
|
+
>;
|
|
72
|
+
|
|
73
|
+
export type OmlpUpdateMaxBorrowCapInstructionData = {
|
|
74
|
+
discriminator: ReadonlyUint8Array;
|
|
75
|
+
newMaxBorrowCapBps: number;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type OmlpUpdateMaxBorrowCapInstructionDataArgs = {
|
|
79
|
+
newMaxBorrowCapBps: number;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export function getOmlpUpdateMaxBorrowCapInstructionDataEncoder(): FixedSizeEncoder<OmlpUpdateMaxBorrowCapInstructionDataArgs> {
|
|
83
|
+
return transformEncoder(
|
|
84
|
+
getStructEncoder([
|
|
85
|
+
["discriminator", fixEncoderSize(getBytesEncoder(), 8)],
|
|
86
|
+
["newMaxBorrowCapBps", getU16Encoder()],
|
|
87
|
+
]),
|
|
88
|
+
(value) => ({
|
|
89
|
+
...value,
|
|
90
|
+
discriminator: OMLP_UPDATE_MAX_BORROW_CAP_DISCRIMINATOR,
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function getOmlpUpdateMaxBorrowCapInstructionDataDecoder(): FixedSizeDecoder<OmlpUpdateMaxBorrowCapInstructionData> {
|
|
96
|
+
return getStructDecoder([
|
|
97
|
+
["discriminator", fixDecoderSize(getBytesDecoder(), 8)],
|
|
98
|
+
["newMaxBorrowCapBps", getU16Decoder()],
|
|
99
|
+
]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getOmlpUpdateMaxBorrowCapInstructionDataCodec(): FixedSizeCodec<
|
|
103
|
+
OmlpUpdateMaxBorrowCapInstructionDataArgs,
|
|
104
|
+
OmlpUpdateMaxBorrowCapInstructionData
|
|
105
|
+
> {
|
|
106
|
+
return combineCodec(
|
|
107
|
+
getOmlpUpdateMaxBorrowCapInstructionDataEncoder(),
|
|
108
|
+
getOmlpUpdateMaxBorrowCapInstructionDataDecoder(),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export type OmlpUpdateMaxBorrowCapAsyncInput<
|
|
113
|
+
TAccountConfig extends string = string,
|
|
114
|
+
TAccountVault extends string = string,
|
|
115
|
+
TAccountAdmin extends string = string,
|
|
116
|
+
> = {
|
|
117
|
+
/** Protocol config - stores admin authority */
|
|
118
|
+
config?: Address<TAccountConfig>;
|
|
119
|
+
vault: Address<TAccountVault>;
|
|
120
|
+
/** Admin signer - must match config.admin */
|
|
121
|
+
admin: TransactionSigner<TAccountAdmin>;
|
|
122
|
+
newMaxBorrowCapBps: OmlpUpdateMaxBorrowCapInstructionDataArgs["newMaxBorrowCapBps"];
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export async function getOmlpUpdateMaxBorrowCapInstructionAsync<
|
|
126
|
+
TAccountConfig extends string,
|
|
127
|
+
TAccountVault extends string,
|
|
128
|
+
TAccountAdmin extends string,
|
|
129
|
+
TProgramAddress extends Address = typeof OPTION_PROGRAM_PROGRAM_ADDRESS,
|
|
130
|
+
>(
|
|
131
|
+
input: OmlpUpdateMaxBorrowCapAsyncInput<
|
|
132
|
+
TAccountConfig,
|
|
133
|
+
TAccountVault,
|
|
134
|
+
TAccountAdmin
|
|
135
|
+
>,
|
|
136
|
+
config?: { programAddress?: TProgramAddress },
|
|
137
|
+
): Promise<
|
|
138
|
+
OmlpUpdateMaxBorrowCapInstruction<
|
|
139
|
+
TProgramAddress,
|
|
140
|
+
TAccountConfig,
|
|
141
|
+
TAccountVault,
|
|
142
|
+
TAccountAdmin
|
|
143
|
+
>
|
|
144
|
+
> {
|
|
145
|
+
// Program address.
|
|
146
|
+
const programAddress =
|
|
147
|
+
config?.programAddress ?? OPTION_PROGRAM_PROGRAM_ADDRESS;
|
|
148
|
+
|
|
149
|
+
// Original accounts.
|
|
150
|
+
const originalAccounts = {
|
|
151
|
+
config: { value: input.config ?? null, isWritable: false },
|
|
152
|
+
vault: { value: input.vault ?? null, isWritable: true },
|
|
153
|
+
admin: { value: input.admin ?? null, isWritable: false },
|
|
154
|
+
};
|
|
155
|
+
const accounts = originalAccounts as Record<
|
|
156
|
+
keyof typeof originalAccounts,
|
|
157
|
+
ResolvedAccount
|
|
158
|
+
>;
|
|
159
|
+
|
|
160
|
+
// Original args.
|
|
161
|
+
const args = { ...input };
|
|
162
|
+
|
|
163
|
+
// Resolve default values.
|
|
164
|
+
if (!accounts.config.value) {
|
|
165
|
+
accounts.config.value = await getProgramDerivedAddress({
|
|
166
|
+
programAddress,
|
|
167
|
+
seeds: [
|
|
168
|
+
getBytesEncoder().encode(new Uint8Array([99, 111, 110, 102, 105, 103])),
|
|
169
|
+
],
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const getAccountMeta = getAccountMetaFactory(programAddress, "programId");
|
|
174
|
+
return Object.freeze({
|
|
175
|
+
accounts: [
|
|
176
|
+
getAccountMeta(accounts.config),
|
|
177
|
+
getAccountMeta(accounts.vault),
|
|
178
|
+
getAccountMeta(accounts.admin),
|
|
179
|
+
],
|
|
180
|
+
data: getOmlpUpdateMaxBorrowCapInstructionDataEncoder().encode(
|
|
181
|
+
args as OmlpUpdateMaxBorrowCapInstructionDataArgs,
|
|
182
|
+
),
|
|
183
|
+
programAddress,
|
|
184
|
+
} as OmlpUpdateMaxBorrowCapInstruction<
|
|
185
|
+
TProgramAddress,
|
|
186
|
+
TAccountConfig,
|
|
187
|
+
TAccountVault,
|
|
188
|
+
TAccountAdmin
|
|
189
|
+
>);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export type OmlpUpdateMaxBorrowCapInput<
|
|
193
|
+
TAccountConfig extends string = string,
|
|
194
|
+
TAccountVault extends string = string,
|
|
195
|
+
TAccountAdmin extends string = string,
|
|
196
|
+
> = {
|
|
197
|
+
/** Protocol config - stores admin authority */
|
|
198
|
+
config: Address<TAccountConfig>;
|
|
199
|
+
vault: Address<TAccountVault>;
|
|
200
|
+
/** Admin signer - must match config.admin */
|
|
201
|
+
admin: TransactionSigner<TAccountAdmin>;
|
|
202
|
+
newMaxBorrowCapBps: OmlpUpdateMaxBorrowCapInstructionDataArgs["newMaxBorrowCapBps"];
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export function getOmlpUpdateMaxBorrowCapInstruction<
|
|
206
|
+
TAccountConfig extends string,
|
|
207
|
+
TAccountVault extends string,
|
|
208
|
+
TAccountAdmin extends string,
|
|
209
|
+
TProgramAddress extends Address = typeof OPTION_PROGRAM_PROGRAM_ADDRESS,
|
|
210
|
+
>(
|
|
211
|
+
input: OmlpUpdateMaxBorrowCapInput<
|
|
212
|
+
TAccountConfig,
|
|
213
|
+
TAccountVault,
|
|
214
|
+
TAccountAdmin
|
|
215
|
+
>,
|
|
216
|
+
config?: { programAddress?: TProgramAddress },
|
|
217
|
+
): OmlpUpdateMaxBorrowCapInstruction<
|
|
218
|
+
TProgramAddress,
|
|
219
|
+
TAccountConfig,
|
|
220
|
+
TAccountVault,
|
|
221
|
+
TAccountAdmin
|
|
222
|
+
> {
|
|
223
|
+
// Program address.
|
|
224
|
+
const programAddress =
|
|
225
|
+
config?.programAddress ?? OPTION_PROGRAM_PROGRAM_ADDRESS;
|
|
226
|
+
|
|
227
|
+
// Original accounts.
|
|
228
|
+
const originalAccounts = {
|
|
229
|
+
config: { value: input.config ?? null, isWritable: false },
|
|
230
|
+
vault: { value: input.vault ?? null, isWritable: true },
|
|
231
|
+
admin: { value: input.admin ?? null, isWritable: false },
|
|
232
|
+
};
|
|
233
|
+
const accounts = originalAccounts as Record<
|
|
234
|
+
keyof typeof originalAccounts,
|
|
235
|
+
ResolvedAccount
|
|
236
|
+
>;
|
|
237
|
+
|
|
238
|
+
// Original args.
|
|
239
|
+
const args = { ...input };
|
|
240
|
+
|
|
241
|
+
const getAccountMeta = getAccountMetaFactory(programAddress, "programId");
|
|
242
|
+
return Object.freeze({
|
|
243
|
+
accounts: [
|
|
244
|
+
getAccountMeta(accounts.config),
|
|
245
|
+
getAccountMeta(accounts.vault),
|
|
246
|
+
getAccountMeta(accounts.admin),
|
|
247
|
+
],
|
|
248
|
+
data: getOmlpUpdateMaxBorrowCapInstructionDataEncoder().encode(
|
|
249
|
+
args as OmlpUpdateMaxBorrowCapInstructionDataArgs,
|
|
250
|
+
),
|
|
251
|
+
programAddress,
|
|
252
|
+
} as OmlpUpdateMaxBorrowCapInstruction<
|
|
253
|
+
TProgramAddress,
|
|
254
|
+
TAccountConfig,
|
|
255
|
+
TAccountVault,
|
|
256
|
+
TAccountAdmin
|
|
257
|
+
>);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export type ParsedOmlpUpdateMaxBorrowCapInstruction<
|
|
261
|
+
TProgram extends string = typeof OPTION_PROGRAM_PROGRAM_ADDRESS,
|
|
262
|
+
TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[],
|
|
263
|
+
> = {
|
|
264
|
+
programAddress: Address<TProgram>;
|
|
265
|
+
accounts: {
|
|
266
|
+
/** Protocol config - stores admin authority */
|
|
267
|
+
config: TAccountMetas[0];
|
|
268
|
+
vault: TAccountMetas[1];
|
|
269
|
+
/** Admin signer - must match config.admin */
|
|
270
|
+
admin: TAccountMetas[2];
|
|
271
|
+
};
|
|
272
|
+
data: OmlpUpdateMaxBorrowCapInstructionData;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
export function parseOmlpUpdateMaxBorrowCapInstruction<
|
|
276
|
+
TProgram extends string,
|
|
277
|
+
TAccountMetas extends readonly AccountMeta[],
|
|
278
|
+
>(
|
|
279
|
+
instruction: Instruction<TProgram> &
|
|
280
|
+
InstructionWithAccounts<TAccountMetas> &
|
|
281
|
+
InstructionWithData<ReadonlyUint8Array>,
|
|
282
|
+
): ParsedOmlpUpdateMaxBorrowCapInstruction<TProgram, TAccountMetas> {
|
|
283
|
+
if (instruction.accounts.length < 3) {
|
|
284
|
+
// TODO: Coded error.
|
|
285
|
+
throw new Error("Not enough accounts");
|
|
286
|
+
}
|
|
287
|
+
let accountIndex = 0;
|
|
288
|
+
const getNextAccount = () => {
|
|
289
|
+
const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!;
|
|
290
|
+
accountIndex += 1;
|
|
291
|
+
return accountMeta;
|
|
292
|
+
};
|
|
293
|
+
return {
|
|
294
|
+
programAddress: instruction.programAddress,
|
|
295
|
+
accounts: {
|
|
296
|
+
config: getNextAccount(),
|
|
297
|
+
vault: getNextAccount(),
|
|
298
|
+
admin: getNextAccount(),
|
|
299
|
+
},
|
|
300
|
+
data: getOmlpUpdateMaxBorrowCapInstructionDataDecoder().decode(
|
|
301
|
+
instruction.data,
|
|
302
|
+
),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
@@ -219,6 +219,7 @@ export type OptionMintInstructionData = {
|
|
|
219
219
|
collateralMint: Address;
|
|
220
220
|
makerCollateralAmount: bigint;
|
|
221
221
|
borrowedAmount: bigint;
|
|
222
|
+
maxRequiredCollateralAmount: bigint;
|
|
222
223
|
};
|
|
223
224
|
|
|
224
225
|
export type OptionMintInstructionDataArgs = {
|
|
@@ -231,6 +232,7 @@ export type OptionMintInstructionDataArgs = {
|
|
|
231
232
|
collateralMint: Address;
|
|
232
233
|
makerCollateralAmount: number | bigint;
|
|
233
234
|
borrowedAmount: number | bigint;
|
|
235
|
+
maxRequiredCollateralAmount: number | bigint;
|
|
234
236
|
};
|
|
235
237
|
|
|
236
238
|
export function getOptionMintInstructionDataEncoder(): Encoder<OptionMintInstructionDataArgs> {
|
|
@@ -249,6 +251,7 @@ export function getOptionMintInstructionDataEncoder(): Encoder<OptionMintInstruc
|
|
|
249
251
|
["collateralMint", getAddressEncoder()],
|
|
250
252
|
["makerCollateralAmount", getU64Encoder()],
|
|
251
253
|
["borrowedAmount", getU64Encoder()],
|
|
254
|
+
["maxRequiredCollateralAmount", getU64Encoder()],
|
|
252
255
|
]),
|
|
253
256
|
(value) => ({ ...value, discriminator: OPTION_MINT_DISCRIMINATOR }),
|
|
254
257
|
);
|
|
@@ -269,6 +272,7 @@ export function getOptionMintInstructionDataDecoder(): Decoder<OptionMintInstruc
|
|
|
269
272
|
["collateralMint", getAddressDecoder()],
|
|
270
273
|
["makerCollateralAmount", getU64Decoder()],
|
|
271
274
|
["borrowedAmount", getU64Decoder()],
|
|
275
|
+
["maxRequiredCollateralAmount", getU64Decoder()],
|
|
272
276
|
]);
|
|
273
277
|
}
|
|
274
278
|
|
|
@@ -389,6 +393,7 @@ export type OptionMintAsyncInput<
|
|
|
389
393
|
collateralMintArg: OptionMintInstructionDataArgs["collateralMint"];
|
|
390
394
|
makerCollateralAmount: OptionMintInstructionDataArgs["makerCollateralAmount"];
|
|
391
395
|
borrowedAmount: OptionMintInstructionDataArgs["borrowedAmount"];
|
|
396
|
+
maxRequiredCollateralAmount: OptionMintInstructionDataArgs["maxRequiredCollateralAmount"];
|
|
392
397
|
};
|
|
393
398
|
|
|
394
399
|
export async function getOptionMintInstructionAsync<
|
|
@@ -973,6 +978,7 @@ export type OptionMintInput<
|
|
|
973
978
|
collateralMintArg: OptionMintInstructionDataArgs["collateralMint"];
|
|
974
979
|
makerCollateralAmount: OptionMintInstructionDataArgs["makerCollateralAmount"];
|
|
975
980
|
borrowedAmount: OptionMintInstructionDataArgs["borrowedAmount"];
|
|
981
|
+
maxRequiredCollateralAmount: OptionMintInstructionDataArgs["maxRequiredCollateralAmount"];
|
|
976
982
|
};
|
|
977
983
|
|
|
978
984
|
export function getOptionMintInstruction<
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
type ParsedInitOptionPoolInstruction,
|
|
32
32
|
type ParsedLiquidateWriterPositionInstruction,
|
|
33
33
|
type ParsedOmlpCreateVaultInstruction,
|
|
34
|
+
type ParsedOmlpUpdateMaxBorrowCapInstruction,
|
|
34
35
|
type ParsedOmlpUpdateMaxLeverageInstruction,
|
|
35
36
|
type ParsedOmlpUpdateProtocolFeeInstruction,
|
|
36
37
|
type ParsedOmlpUpdateSupplyLimitInstruction,
|
|
@@ -51,7 +52,7 @@ import {
|
|
|
51
52
|
} from "../instructions";
|
|
52
53
|
|
|
53
54
|
export const OPTION_PROGRAM_PROGRAM_ADDRESS =
|
|
54
|
-
"
|
|
55
|
+
"CCigZRDDjYiJCRL3FfkG3LbNYBpYaG6mGRiYGQnct1co" as Address<"CCigZRDDjYiJCRL3FfkG3LbNYBpYaG6mGRiYGQnct1co">;
|
|
55
56
|
|
|
56
57
|
export enum OptionProgramAccount {
|
|
57
58
|
CollateralPool,
|
|
@@ -227,6 +228,7 @@ export enum OptionProgramInstruction {
|
|
|
227
228
|
InitializeMarketData,
|
|
228
229
|
LiquidateWriterPosition,
|
|
229
230
|
OmlpCreateVault,
|
|
231
|
+
OmlpUpdateMaxBorrowCap,
|
|
230
232
|
OmlpUpdateMaxLeverage,
|
|
231
233
|
OmlpUpdateProtocolFee,
|
|
232
234
|
OmlpUpdateSupplyLimit,
|
|
@@ -437,6 +439,17 @@ export function identifyOptionProgramInstruction(
|
|
|
437
439
|
) {
|
|
438
440
|
return OptionProgramInstruction.OmlpCreateVault;
|
|
439
441
|
}
|
|
442
|
+
if (
|
|
443
|
+
containsBytes(
|
|
444
|
+
data,
|
|
445
|
+
fixEncoderSize(getBytesEncoder(), 8).encode(
|
|
446
|
+
new Uint8Array([156, 196, 135, 28, 127, 162, 101, 52]),
|
|
447
|
+
),
|
|
448
|
+
0,
|
|
449
|
+
)
|
|
450
|
+
) {
|
|
451
|
+
return OptionProgramInstruction.OmlpUpdateMaxBorrowCap;
|
|
452
|
+
}
|
|
440
453
|
if (
|
|
441
454
|
containsBytes(
|
|
442
455
|
data,
|
|
@@ -630,7 +643,7 @@ export function identifyOptionProgramInstruction(
|
|
|
630
643
|
}
|
|
631
644
|
|
|
632
645
|
export type ParsedOptionProgramInstruction<
|
|
633
|
-
TProgram extends string = "
|
|
646
|
+
TProgram extends string = "CCigZRDDjYiJCRL3FfkG3LbNYBpYaG6mGRiYGQnct1co",
|
|
634
647
|
> =
|
|
635
648
|
| ({
|
|
636
649
|
instructionType: OptionProgramInstruction.AcceptAdmin;
|
|
@@ -683,6 +696,9 @@ export type ParsedOptionProgramInstruction<
|
|
|
683
696
|
| ({
|
|
684
697
|
instructionType: OptionProgramInstruction.OmlpCreateVault;
|
|
685
698
|
} & ParsedOmlpCreateVaultInstruction<TProgram>)
|
|
699
|
+
| ({
|
|
700
|
+
instructionType: OptionProgramInstruction.OmlpUpdateMaxBorrowCap;
|
|
701
|
+
} & ParsedOmlpUpdateMaxBorrowCapInstruction<TProgram>)
|
|
686
702
|
| ({
|
|
687
703
|
instructionType: OptionProgramInstruction.OmlpUpdateMaxLeverage;
|
|
688
704
|
} & ParsedOmlpUpdateMaxLeverageInstruction<TProgram>)
|
package/index.ts
CHANGED
|
@@ -30,11 +30,18 @@ export * from "./short/preflight";
|
|
|
30
30
|
|
|
31
31
|
export * from "./omlp/builders";
|
|
32
32
|
export * from "./omlp/service";
|
|
33
|
+
export * from "./shared/trade-config";
|
|
33
34
|
export {
|
|
34
35
|
resolveSwitchboardFeedFromMarketData,
|
|
35
36
|
buildSwitchboardPullFeedUpdate,
|
|
37
|
+
buildSwitchboardCrank,
|
|
38
|
+
inferSwitchboardNetwork,
|
|
39
|
+
prependSwitchboardCrank,
|
|
36
40
|
type SwitchboardPullFeedLike,
|
|
37
41
|
type BuildSwitchboardPullFeedUpdateParams,
|
|
42
|
+
type BuildSwitchboardCrankParams,
|
|
43
|
+
type SwitchboardCrankResult,
|
|
44
|
+
type SwitchboardNetwork,
|
|
38
45
|
} from "./oracle/switchboard";
|
|
39
46
|
|
|
40
47
|
export {
|
package/long/builders.ts
CHANGED
|
@@ -24,7 +24,13 @@ import {
|
|
|
24
24
|
} from "../wsol/instructions";
|
|
25
25
|
import { fetchMarketDataAccount, fetchOptionPool } from "../accounts/fetchers";
|
|
26
26
|
import { getBuyFromPoolRemainingAccounts } from "./remaining-accounts";
|
|
27
|
+
import { applySlippageBps } from "./quotes";
|
|
28
|
+
import {
|
|
29
|
+
buildSwitchboardCrank,
|
|
30
|
+
prependSwitchboardCrank,
|
|
31
|
+
} from "../oracle/switchboard";
|
|
27
32
|
import { fetchWriterPositionsForPool } from "../accounts/list";
|
|
33
|
+
import { getGlobalTradeConfig } from "../shared/trade-config";
|
|
28
34
|
import bs58 from "bs58";
|
|
29
35
|
|
|
30
36
|
export interface BuildBuyFromPoolParams {
|
|
@@ -143,6 +149,9 @@ export interface BuildBuyFromPoolTransactionWithDerivationParams {
|
|
|
143
149
|
buyerPosition?: AddressLike;
|
|
144
150
|
buyerOptionAccount?: AddressLike;
|
|
145
151
|
remainingAccounts?: RemainingAccountInput[];
|
|
152
|
+
disableSwitchboardCrank?: boolean;
|
|
153
|
+
switchboardCrossbarUrl?: string;
|
|
154
|
+
switchboardNumSignatures?: number;
|
|
146
155
|
}
|
|
147
156
|
|
|
148
157
|
const DEFAULT_MARKET_ORDER_SLIPPAGE_BUFFER_BASE_UNITS = 500_000n;
|
|
@@ -224,7 +233,7 @@ export async function buildBuyFromPoolTransactionWithDerivation(
|
|
|
224
233
|
Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
|
|
225
234
|
);
|
|
226
235
|
|
|
227
|
-
|
|
236
|
+
const actionTx = await buildBuyFromPoolTransaction({
|
|
228
237
|
optionPool: resolved.optionPool,
|
|
229
238
|
optionAccount: resolved.optionAccount,
|
|
230
239
|
longMint: resolved.longMint,
|
|
@@ -241,6 +250,20 @@ export async function buildBuyFromPoolTransactionWithDerivation(
|
|
|
241
250
|
buyerOptionAccount,
|
|
242
251
|
remainingAccounts: params.remainingAccounts,
|
|
243
252
|
});
|
|
253
|
+
|
|
254
|
+
if (params.disableSwitchboardCrank) {
|
|
255
|
+
return actionTx;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const crank = await buildSwitchboardCrank({
|
|
259
|
+
rpc: params.rpc,
|
|
260
|
+
payer: params.buyer,
|
|
261
|
+
switchboardFeed,
|
|
262
|
+
marketData: resolved.marketData,
|
|
263
|
+
crossbarUrl: params.switchboardCrossbarUrl,
|
|
264
|
+
numSignatures: params.switchboardNumSignatures,
|
|
265
|
+
});
|
|
266
|
+
return prependSwitchboardCrank(crank, actionTx);
|
|
244
267
|
}
|
|
245
268
|
|
|
246
269
|
export interface BuildBuyFromPoolMarketOrderParams
|
|
@@ -311,10 +334,16 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
311
334
|
`This may indicate data staleness - please refresh and retry.`
|
|
312
335
|
);
|
|
313
336
|
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
337
|
+
const globalTradeConfig = getGlobalTradeConfig();
|
|
338
|
+
const hasExplicitSlippageBuffer =
|
|
339
|
+
params.slippageBufferBaseUnits !== undefined ||
|
|
340
|
+
params.slippageBufferLamports !== undefined;
|
|
341
|
+
const slippageBuffer = hasExplicitSlippageBuffer
|
|
342
|
+
? normalizeMarketOrderSlippageBuffer(params, refetchedPool.underlyingMint)
|
|
343
|
+
: globalTradeConfig.slippageBps !== undefined
|
|
344
|
+
? applySlippageBps(params.quotedPremiumTotal, globalTradeConfig.slippageBps) -
|
|
345
|
+
BigInt(params.quotedPremiumTotal)
|
|
346
|
+
: normalizeMarketOrderSlippageBuffer(params, refetchedPool.underlyingMint);
|
|
318
347
|
const maxPremiumAmount = BigInt(params.quotedPremiumTotal) + slippageBuffer;
|
|
319
348
|
assertPositiveAmount(maxPremiumAmount, "maxPremiumAmount");
|
|
320
349
|
|
|
@@ -329,7 +358,7 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
329
358
|
Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
|
|
330
359
|
);
|
|
331
360
|
|
|
332
|
-
|
|
361
|
+
const actionTx = await buildBuyFromPoolTransaction({
|
|
333
362
|
optionPool: resolved.optionPool,
|
|
334
363
|
optionAccount: resolved.optionAccount,
|
|
335
364
|
longMint: resolved.longMint,
|
|
@@ -346,6 +375,20 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
346
375
|
buyerOptionAccount,
|
|
347
376
|
remainingAccounts,
|
|
348
377
|
});
|
|
378
|
+
|
|
379
|
+
if (params.disableSwitchboardCrank) {
|
|
380
|
+
return actionTx;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const crank = await buildSwitchboardCrank({
|
|
384
|
+
rpc: params.rpc,
|
|
385
|
+
payer: params.buyer,
|
|
386
|
+
switchboardFeed,
|
|
387
|
+
marketData: resolved.marketData,
|
|
388
|
+
crossbarUrl: params.switchboardCrossbarUrl,
|
|
389
|
+
numSignatures: params.switchboardNumSignatures,
|
|
390
|
+
});
|
|
391
|
+
return prependSwitchboardCrank(crank, actionTx);
|
|
349
392
|
}
|
|
350
393
|
|
|
351
394
|
export async function buildCloseLongToPoolInstruction(
|
|
@@ -438,6 +481,9 @@ export interface BuildCloseLongToPoolTransactionWithDerivationParams {
|
|
|
438
481
|
*/
|
|
439
482
|
unwrapPayoutSol?: boolean;
|
|
440
483
|
remainingAccounts?: RemainingAccountInput[];
|
|
484
|
+
disableSwitchboardCrank?: boolean;
|
|
485
|
+
switchboardCrossbarUrl?: string;
|
|
486
|
+
switchboardNumSignatures?: number;
|
|
441
487
|
}
|
|
442
488
|
|
|
443
489
|
export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
@@ -485,7 +531,7 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
|
485
531
|
Array.from(marketDataAccount.switchboardFeedId as unknown as Uint8Array)
|
|
486
532
|
);
|
|
487
533
|
|
|
488
|
-
|
|
534
|
+
const actionTx = await buildCloseLongToPoolTransaction({
|
|
489
535
|
optionPool: resolved.optionPool,
|
|
490
536
|
optionAccount: resolved.optionAccount,
|
|
491
537
|
collateralPool: resolved.collateralPool,
|
|
@@ -507,4 +553,18 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
|
507
553
|
unwrapPayoutSol,
|
|
508
554
|
remainingAccounts: params.remainingAccounts,
|
|
509
555
|
});
|
|
556
|
+
|
|
557
|
+
if (params.disableSwitchboardCrank) {
|
|
558
|
+
return actionTx;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const crank = await buildSwitchboardCrank({
|
|
562
|
+
rpc: params.rpc,
|
|
563
|
+
payer: params.buyer,
|
|
564
|
+
switchboardFeed,
|
|
565
|
+
marketData: resolved.marketData,
|
|
566
|
+
crossbarUrl: params.switchboardCrossbarUrl,
|
|
567
|
+
numSignatures: params.switchboardNumSignatures,
|
|
568
|
+
});
|
|
569
|
+
return prependSwitchboardCrank(crank, actionTx);
|
|
510
570
|
}
|
package/long/exercise.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { getOptionExerciseInstruction } from "../generated/instructions";
|
|
2
2
|
import type { Instruction } from "@solana/kit";
|
|
3
3
|
import { toAddress } from "../client/program";
|
|
4
|
-
import type { AddressLike, BuiltTransaction } from "../client/types";
|
|
4
|
+
import type { AddressLike, BuiltTransaction, KitRpc } from "../client/types";
|
|
5
|
+
import {
|
|
6
|
+
buildSwitchboardCrank,
|
|
7
|
+
prependSwitchboardCrank,
|
|
8
|
+
} from "../oracle/switchboard";
|
|
5
9
|
|
|
6
10
|
export interface BuildOptionExerciseParams {
|
|
7
11
|
optionAccount: AddressLike;
|
|
@@ -16,6 +20,10 @@ export interface BuildOptionExerciseParams {
|
|
|
16
20
|
escrowAuthority: AddressLike;
|
|
17
21
|
buyer: AddressLike;
|
|
18
22
|
tokenProgram?: AddressLike;
|
|
23
|
+
rpc?: KitRpc;
|
|
24
|
+
disableSwitchboardCrank?: boolean;
|
|
25
|
+
switchboardCrossbarUrl?: string;
|
|
26
|
+
switchboardNumSignatures?: number;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
/**
|
|
@@ -48,7 +56,18 @@ export function buildOptionExerciseInstruction(
|
|
|
48
56
|
*/
|
|
49
57
|
export function buildOptionExerciseTransaction(
|
|
50
58
|
params: BuildOptionExerciseParams
|
|
51
|
-
): BuiltTransaction {
|
|
59
|
+
): Promise<BuiltTransaction> {
|
|
52
60
|
const instruction = buildOptionExerciseInstruction(params);
|
|
53
|
-
|
|
61
|
+
const actionTx = { instructions: [instruction] };
|
|
62
|
+
if (params.disableSwitchboardCrank || !params.rpc) {
|
|
63
|
+
return Promise.resolve(actionTx);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return buildSwitchboardCrank({
|
|
67
|
+
rpc: params.rpc,
|
|
68
|
+
payer: params.buyer,
|
|
69
|
+
switchboardFeed: params.switchboardFeed,
|
|
70
|
+
crossbarUrl: params.switchboardCrossbarUrl,
|
|
71
|
+
numSignatures: params.switchboardNumSignatures,
|
|
72
|
+
}).then((crank) => prependSwitchboardCrank(crank, actionTx));
|
|
54
73
|
}
|
package/omlp/builders.ts
CHANGED
|
@@ -4,9 +4,13 @@ import {
|
|
|
4
4
|
} from "../generated/instructions";
|
|
5
5
|
import type { Instruction } from "@solana/kit";
|
|
6
6
|
import { toAddress } from "../client/program";
|
|
7
|
-
import type { AddressLike, BuiltTransaction } from "../client/types";
|
|
7
|
+
import type { AddressLike, BuiltTransaction, KitRpc } from "../client/types";
|
|
8
8
|
import { assertPositiveAmount } from "../shared/amounts";
|
|
9
9
|
import { getCloseAccountInstruction, NATIVE_MINT } from "../wsol/instructions";
|
|
10
|
+
import {
|
|
11
|
+
buildSwitchboardCrank,
|
|
12
|
+
prependSwitchboardCrank,
|
|
13
|
+
} from "../oracle/switchboard";
|
|
10
14
|
|
|
11
15
|
export interface BuildDepositToPositionParams {
|
|
12
16
|
vault: AddressLike;
|
|
@@ -15,6 +19,12 @@ export interface BuildDepositToPositionParams {
|
|
|
15
19
|
lender: AddressLike;
|
|
16
20
|
amount: bigint | number;
|
|
17
21
|
position?: AddressLike;
|
|
22
|
+
rpc?: KitRpc;
|
|
23
|
+
switchboardFeed?: AddressLike;
|
|
24
|
+
marketData?: AddressLike;
|
|
25
|
+
disableSwitchboardCrank?: boolean;
|
|
26
|
+
switchboardCrossbarUrl?: string;
|
|
27
|
+
switchboardNumSignatures?: number;
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
export interface BuildWithdrawFromPositionParams {
|
|
@@ -26,6 +36,12 @@ export interface BuildWithdrawFromPositionParams {
|
|
|
26
36
|
position?: AddressLike;
|
|
27
37
|
unwrapSol?: boolean;
|
|
28
38
|
vaultMint?: AddressLike;
|
|
39
|
+
rpc?: KitRpc;
|
|
40
|
+
switchboardFeed?: AddressLike;
|
|
41
|
+
marketData?: AddressLike;
|
|
42
|
+
disableSwitchboardCrank?: boolean;
|
|
43
|
+
switchboardCrossbarUrl?: string;
|
|
44
|
+
switchboardNumSignatures?: number;
|
|
29
45
|
}
|
|
30
46
|
|
|
31
47
|
export async function buildDepositToPositionInstruction(
|
|
@@ -49,7 +65,24 @@ export async function buildDepositToPositionTransaction(
|
|
|
49
65
|
params: BuildDepositToPositionParams
|
|
50
66
|
): Promise<BuiltTransaction> {
|
|
51
67
|
const instruction = await buildDepositToPositionInstruction(params);
|
|
52
|
-
|
|
68
|
+
const actionTx = { instructions: [instruction] };
|
|
69
|
+
if (
|
|
70
|
+
params.disableSwitchboardCrank ||
|
|
71
|
+
!params.rpc ||
|
|
72
|
+
(!params.switchboardFeed && !params.marketData)
|
|
73
|
+
) {
|
|
74
|
+
return actionTx;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const crank = await buildSwitchboardCrank({
|
|
78
|
+
rpc: params.rpc,
|
|
79
|
+
payer: params.lender,
|
|
80
|
+
switchboardFeed: params.switchboardFeed,
|
|
81
|
+
marketData: params.marketData,
|
|
82
|
+
crossbarUrl: params.switchboardCrossbarUrl,
|
|
83
|
+
numSignatures: params.switchboardNumSignatures,
|
|
84
|
+
});
|
|
85
|
+
return prependSwitchboardCrank(crank, actionTx);
|
|
53
86
|
}
|
|
54
87
|
|
|
55
88
|
export async function buildWithdrawFromPositionInstruction(
|
|
@@ -90,5 +123,22 @@ export async function buildWithdrawFromPositionTransaction(
|
|
|
90
123
|
);
|
|
91
124
|
}
|
|
92
125
|
|
|
93
|
-
|
|
126
|
+
const actionTx = { instructions };
|
|
127
|
+
if (
|
|
128
|
+
params.disableSwitchboardCrank ||
|
|
129
|
+
!params.rpc ||
|
|
130
|
+
(!params.switchboardFeed && !params.marketData)
|
|
131
|
+
) {
|
|
132
|
+
return actionTx;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const crank = await buildSwitchboardCrank({
|
|
136
|
+
rpc: params.rpc,
|
|
137
|
+
payer: params.lender,
|
|
138
|
+
switchboardFeed: params.switchboardFeed,
|
|
139
|
+
marketData: params.marketData,
|
|
140
|
+
crossbarUrl: params.switchboardCrossbarUrl,
|
|
141
|
+
numSignatures: params.switchboardNumSignatures,
|
|
142
|
+
});
|
|
143
|
+
return prependSwitchboardCrank(crank, actionTx);
|
|
94
144
|
}
|
package/oracle/switchboard.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import type { Address } from "@solana/kit";
|
|
1
|
+
import type { Address, Instruction } from "@solana/kit";
|
|
2
|
+
import { fromLegacyTransactionInstruction } from "@solana/compat";
|
|
3
|
+
import { CrossbarClient } from "@switchboard-xyz/common";
|
|
2
4
|
import bs58 from "bs58";
|
|
3
5
|
import { toAddress } from "../client/program";
|
|
4
|
-
import type { AddressLike, KitRpc } from "../client/types";
|
|
6
|
+
import type { AddressLike, BuiltTransaction, KitRpc } from "../client/types";
|
|
5
7
|
import { fetchMarketDataAccount } from "../accounts/fetchers";
|
|
6
8
|
import { invariant } from "../shared/errors";
|
|
7
9
|
|
|
10
|
+
const DEVNET_GENESIS_HASH = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
|
|
11
|
+
const MAINNET_BETA_GENESIS_HASH = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
|
|
12
|
+
|
|
13
|
+
export type SwitchboardNetwork = "devnet" | "mainnet";
|
|
14
|
+
|
|
8
15
|
export async function resolveSwitchboardFeedFromMarketData(
|
|
9
16
|
rpc: KitRpc,
|
|
10
17
|
marketData: AddressLike
|
|
@@ -54,3 +61,84 @@ export async function buildSwitchboardPullFeedUpdate<
|
|
|
54
61
|
lookupTables: luts ?? [],
|
|
55
62
|
};
|
|
56
63
|
}
|
|
64
|
+
|
|
65
|
+
export interface BuildSwitchboardCrankParams {
|
|
66
|
+
rpc: KitRpc;
|
|
67
|
+
payer: AddressLike;
|
|
68
|
+
switchboardFeed?: AddressLike;
|
|
69
|
+
marketData?: AddressLike;
|
|
70
|
+
network?: SwitchboardNetwork;
|
|
71
|
+
crossbarUrl?: string;
|
|
72
|
+
numSignatures?: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface SwitchboardCrankResult {
|
|
76
|
+
instructions: Instruction<string>[];
|
|
77
|
+
addressLookupTableAddresses: AddressLike[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function inferSwitchboardNetwork(
|
|
81
|
+
rpc: KitRpc
|
|
82
|
+
): Promise<SwitchboardNetwork> {
|
|
83
|
+
const genesisHash = await rpc.getGenesisHash().send();
|
|
84
|
+
if (genesisHash === DEVNET_GENESIS_HASH) {
|
|
85
|
+
return "devnet";
|
|
86
|
+
}
|
|
87
|
+
if (genesisHash === MAINNET_BETA_GENESIS_HASH) {
|
|
88
|
+
return "mainnet";
|
|
89
|
+
}
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Unable to infer Switchboard network from genesis hash: ${genesisHash}`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function buildSwitchboardCrank(
|
|
96
|
+
params: BuildSwitchboardCrankParams
|
|
97
|
+
): Promise<SwitchboardCrankResult> {
|
|
98
|
+
const resolvedFeed =
|
|
99
|
+
params.switchboardFeed ??
|
|
100
|
+
(params.marketData
|
|
101
|
+
? await resolveSwitchboardFeedFromMarketData(params.rpc, params.marketData)
|
|
102
|
+
: undefined);
|
|
103
|
+
|
|
104
|
+
invariant(
|
|
105
|
+
!!resolvedFeed,
|
|
106
|
+
"switchboardFeed or marketData is required to build Switchboard crank instructions."
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const network = params.network ?? (await inferSwitchboardNetwork(params.rpc));
|
|
110
|
+
const crossbar = params.crossbarUrl
|
|
111
|
+
? new CrossbarClient(params.crossbarUrl)
|
|
112
|
+
: CrossbarClient.default();
|
|
113
|
+
const updates = await crossbar.fetchSolanaUpdates(
|
|
114
|
+
network,
|
|
115
|
+
[toAddress(resolvedFeed)],
|
|
116
|
+
toAddress(params.payer),
|
|
117
|
+
params.numSignatures
|
|
118
|
+
);
|
|
119
|
+
const update = updates[0];
|
|
120
|
+
|
|
121
|
+
const instructions =
|
|
122
|
+
update?.pullIxns?.map((instruction) =>
|
|
123
|
+
fromLegacyTransactionInstruction(instruction)
|
|
124
|
+
) ?? [];
|
|
125
|
+
const addressLookupTableAddresses = update?.lookupTables ?? [];
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
instructions,
|
|
129
|
+
addressLookupTableAddresses,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function prependSwitchboardCrank(
|
|
134
|
+
crank: SwitchboardCrankResult,
|
|
135
|
+
action: BuiltTransaction
|
|
136
|
+
): BuiltTransaction {
|
|
137
|
+
return {
|
|
138
|
+
instructions: [...crank.instructions, ...action.instructions],
|
|
139
|
+
addressLookupTableAddresses: [
|
|
140
|
+
...(crank.addressLookupTableAddresses ?? []),
|
|
141
|
+
...(action.addressLookupTableAddresses ?? []),
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epicentral/sos-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-alpha",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Solana Option Standard SDK. The frontend-first SDK for Native Options Trading on Solana. Created by Epicentral Labs.",
|
|
6
6
|
"type": "module",
|
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
"@solana-program/address-lookup-table": "^0.11.0",
|
|
19
19
|
"@solana-program/compute-budget": "^0.13.0",
|
|
20
20
|
"@solana-program/system": "^0.11.0",
|
|
21
|
+
"@solana/compat": "^6.1.0",
|
|
21
22
|
"@solana/kit": "^6.1.0",
|
|
23
|
+
"@switchboard-xyz/common": "^5.7.0",
|
|
24
|
+
"@switchboard-xyz/on-demand": "^3.9.0",
|
|
22
25
|
"bs58": "^6.0.0",
|
|
23
26
|
"decimal.js": "^10.4.3"
|
|
24
27
|
},
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface TradeConfig {
|
|
2
|
+
slippageBps?: number;
|
|
3
|
+
computeUnitLimit?: number;
|
|
4
|
+
computeUnitPriceMicroLamports?: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
let globalTradeConfig: TradeConfig = {};
|
|
8
|
+
|
|
9
|
+
export function setGlobalTradeConfig(config: TradeConfig): void {
|
|
10
|
+
globalTradeConfig = { ...config };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function updateGlobalTradeConfig(config: Partial<TradeConfig>): void {
|
|
14
|
+
globalTradeConfig = { ...globalTradeConfig, ...config };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getGlobalTradeConfig(): TradeConfig {
|
|
18
|
+
return { ...globalTradeConfig };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resetGlobalTradeConfig(): void {
|
|
22
|
+
globalTradeConfig = {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveTradeConfig(overrides?: Partial<TradeConfig>): TradeConfig {
|
|
26
|
+
return { ...globalTradeConfig, ...overrides };
|
|
27
|
+
}
|
package/shared/transactions.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
getLookupTableAddressForNetwork,
|
|
26
26
|
type LookupTableNetwork,
|
|
27
27
|
} from "../client/lookup-table";
|
|
28
|
+
import { getGlobalTradeConfig } from "./trade-config";
|
|
28
29
|
|
|
29
30
|
export interface SendBuiltTransactionParams extends BuiltTransaction {
|
|
30
31
|
rpc: KitRpc;
|
|
@@ -56,18 +57,24 @@ export async function sendBuiltTransaction(
|
|
|
56
57
|
params: SendBuiltTransactionParams
|
|
57
58
|
): Promise<string> {
|
|
58
59
|
const commitment = params.commitment ?? "confirmed";
|
|
60
|
+
const globalTradeConfig = getGlobalTradeConfig();
|
|
59
61
|
const { value: latestBlockhash } = await params.rpc.getLatestBlockhash().send();
|
|
60
62
|
|
|
61
63
|
const computeBudgetInstructions: Instruction<string>[] = [];
|
|
62
|
-
|
|
64
|
+
const computeUnitLimit =
|
|
65
|
+
params.computeUnitLimit ?? globalTradeConfig.computeUnitLimit;
|
|
66
|
+
if (computeUnitLimit !== undefined) {
|
|
63
67
|
computeBudgetInstructions.push(
|
|
64
|
-
getSetComputeUnitLimitInstruction({ units:
|
|
68
|
+
getSetComputeUnitLimitInstruction({ units: computeUnitLimit })
|
|
65
69
|
);
|
|
66
70
|
}
|
|
67
|
-
|
|
71
|
+
const computeUnitPriceMicroLamports =
|
|
72
|
+
params.computeUnitPriceMicroLamports ??
|
|
73
|
+
globalTradeConfig.computeUnitPriceMicroLamports;
|
|
74
|
+
if (computeUnitPriceMicroLamports !== undefined) {
|
|
68
75
|
computeBudgetInstructions.push(
|
|
69
76
|
getSetComputeUnitPriceInstruction({
|
|
70
|
-
microLamports:
|
|
77
|
+
microLamports: computeUnitPriceMicroLamports,
|
|
71
78
|
})
|
|
72
79
|
);
|
|
73
80
|
}
|
package/short/builders.ts
CHANGED
|
@@ -31,6 +31,12 @@ import {
|
|
|
31
31
|
getWrapSOLInstructions,
|
|
32
32
|
} from "../wsol/instructions";
|
|
33
33
|
import { preflightUnwindWriterUnsold } from "./preflight";
|
|
34
|
+
import {
|
|
35
|
+
buildSwitchboardCrank,
|
|
36
|
+
prependSwitchboardCrank,
|
|
37
|
+
} from "../oracle/switchboard";
|
|
38
|
+
import { applySlippageBps } from "../long/quotes";
|
|
39
|
+
import { getGlobalTradeConfig } from "../shared/trade-config";
|
|
34
40
|
import bs58 from "bs58";
|
|
35
41
|
|
|
36
42
|
export interface BuildOptionMintParams {
|
|
@@ -48,6 +54,7 @@ export interface BuildOptionMintParams {
|
|
|
48
54
|
collateralMint?: AddressLike;
|
|
49
55
|
makerCollateralAmount: bigint | number;
|
|
50
56
|
borrowedAmount: bigint | number;
|
|
57
|
+
maxRequiredCollateralAmount?: bigint | number;
|
|
51
58
|
maker: AddressLike;
|
|
52
59
|
makerCollateralAccount: AddressLike;
|
|
53
60
|
underlyingMint: AddressLike;
|
|
@@ -133,6 +140,16 @@ export async function buildOptionMintInstruction(
|
|
|
133
140
|
invariant(params.underlyingSymbol.length > 0, "underlyingSymbol is required.");
|
|
134
141
|
|
|
135
142
|
const borrowedAmount = BigInt(params.borrowedAmount);
|
|
143
|
+
const globalTradeConfig = getGlobalTradeConfig();
|
|
144
|
+
const maxRequiredCollateralAmount =
|
|
145
|
+
params.maxRequiredCollateralAmount !== undefined
|
|
146
|
+
? BigInt(params.maxRequiredCollateralAmount)
|
|
147
|
+
: globalTradeConfig.slippageBps !== undefined
|
|
148
|
+
? applySlippageBps(
|
|
149
|
+
BigInt(params.makerCollateralAmount) + borrowedAmount,
|
|
150
|
+
globalTradeConfig.slippageBps
|
|
151
|
+
)
|
|
152
|
+
: BigInt(params.makerCollateralAmount) + borrowedAmount;
|
|
136
153
|
if (borrowedAmount > 0n) {
|
|
137
154
|
invariant(!!params.vault, "vault is required when borrowedAmount > 0");
|
|
138
155
|
invariant(
|
|
@@ -209,6 +226,7 @@ export async function buildOptionMintInstruction(
|
|
|
209
226
|
collateralMintArg: toAddress(params.collateralMint ?? params.underlyingMint),
|
|
210
227
|
makerCollateralAmount: params.makerCollateralAmount,
|
|
211
228
|
borrowedAmount: params.borrowedAmount,
|
|
229
|
+
maxRequiredCollateralAmount,
|
|
212
230
|
});
|
|
213
231
|
|
|
214
232
|
return appendRemainingAccounts(kitInstruction, params.remainingAccounts);
|
|
@@ -251,6 +269,7 @@ export interface BuildOptionMintTransactionWithDerivationParams {
|
|
|
251
269
|
collateralMint?: AddressLike;
|
|
252
270
|
makerCollateralAmount: bigint | number;
|
|
253
271
|
borrowedAmount: bigint | number;
|
|
272
|
+
maxRequiredCollateralAmount?: bigint | number;
|
|
254
273
|
maker: AddressLike;
|
|
255
274
|
/**
|
|
256
275
|
* Optional. When omitted, the SDK derives the maker's collateral ATA for collateralMint
|
|
@@ -268,6 +287,9 @@ export interface BuildOptionMintTransactionWithDerivationParams {
|
|
|
268
287
|
escrowTokenAccount?: AddressLike;
|
|
269
288
|
poolLoan?: AddressLike;
|
|
270
289
|
remainingAccounts?: RemainingAccountInput[];
|
|
290
|
+
disableSwitchboardCrank?: boolean;
|
|
291
|
+
switchboardCrossbarUrl?: string;
|
|
292
|
+
switchboardNumSignatures?: number;
|
|
271
293
|
}
|
|
272
294
|
|
|
273
295
|
export async function buildOptionMintTransactionWithDerivation(
|
|
@@ -357,9 +379,23 @@ export async function buildOptionMintTransactionWithDerivation(
|
|
|
357
379
|
makerCollateralAccount
|
|
358
380
|
);
|
|
359
381
|
|
|
360
|
-
|
|
382
|
+
const actionTx = {
|
|
361
383
|
instructions: [createAtaIx, ...tx.instructions],
|
|
362
384
|
};
|
|
385
|
+
|
|
386
|
+
if (params.disableSwitchboardCrank) {
|
|
387
|
+
return actionTx;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const crank = await buildSwitchboardCrank({
|
|
391
|
+
rpc: params.rpc,
|
|
392
|
+
payer: params.maker,
|
|
393
|
+
switchboardFeed,
|
|
394
|
+
marketData: resolved.marketData,
|
|
395
|
+
crossbarUrl: params.switchboardCrossbarUrl,
|
|
396
|
+
numSignatures: params.switchboardNumSignatures,
|
|
397
|
+
});
|
|
398
|
+
return prependSwitchboardCrank(crank, actionTx);
|
|
363
399
|
}
|
|
364
400
|
|
|
365
401
|
export async function buildUnwindWriterUnsoldInstruction(
|
|
@@ -480,8 +516,12 @@ export async function buildUnwindWriterUnsoldWithLoanRepayment(
|
|
|
480
516
|
]);
|
|
481
517
|
|
|
482
518
|
const vaultLoans = loans
|
|
483
|
-
.filter((item) => toAddress(item.data.vault) === vaultPdaStr)
|
|
484
|
-
|
|
519
|
+
.filter((item) => toAddress(item.data.vault) === vaultPdaStr);
|
|
520
|
+
|
|
521
|
+
invariant(
|
|
522
|
+
vaultLoans.length <= MAX_POOL_LOANS_PER_UNWIND,
|
|
523
|
+
`Too many active pool loans for unwind: ${vaultLoans.length}. Max supported in one unwind is ${MAX_POOL_LOANS_PER_UNWIND}.`
|
|
524
|
+
);
|
|
485
525
|
|
|
486
526
|
const remainingAccounts: RemainingAccountInput[] = vaultLoans.map((item) => ({
|
|
487
527
|
address: item.address,
|
|
@@ -515,8 +555,8 @@ export async function buildUnwindWriterUnsoldWithLoanRepayment(
|
|
|
515
555
|
: 0n;
|
|
516
556
|
|
|
517
557
|
invariant(
|
|
518
|
-
preflight.
|
|
519
|
-
`Unwind cannot
|
|
558
|
+
preflight.canRepayRequestedSlice,
|
|
559
|
+
`Unwind cannot repay the requested slice: required=${preflight.summary.proportionalTotalOwed} available_now=${preflight.summary.collateralVaultAvailable + preflight.summary.walletFallbackAvailable} native_sol_available=${preflight.summary.nativeSolAvailable} remaining_shortfall=${preflight.summary.proportionalTotalOwed - (preflight.summary.collateralVaultAvailable + preflight.summary.walletFallbackAvailable + preflight.summary.nativeSolAvailable)}`
|
|
520
560
|
);
|
|
521
561
|
if (isWsolPath && lamportsToWrap > 0n && !params.includeWrapForShortfall) {
|
|
522
562
|
invariant(
|
package/short/preflight.ts
CHANGED
|
@@ -87,6 +87,8 @@ export interface UnwindPreflightSummary {
|
|
|
87
87
|
|
|
88
88
|
export interface UnwindPreflightResult {
|
|
89
89
|
canUnwind: boolean;
|
|
90
|
+
canRepayRequestedSlice: boolean;
|
|
91
|
+
/** @deprecated Use canRepayRequestedSlice. */
|
|
90
92
|
canRepayFully: boolean;
|
|
91
93
|
reason?: string;
|
|
92
94
|
writerPositionAddress: string;
|
|
@@ -145,6 +147,7 @@ export async function preflightUnwindWriterUnsold(
|
|
|
145
147
|
if (unwindQty <= 0n) {
|
|
146
148
|
return {
|
|
147
149
|
canUnwind: false,
|
|
150
|
+
canRepayRequestedSlice: false,
|
|
148
151
|
canRepayFully: false,
|
|
149
152
|
reason: "unwindQty must be > 0",
|
|
150
153
|
writerPositionAddress: String(writerPositionAddress),
|
|
@@ -178,6 +181,7 @@ export async function preflightUnwindWriterUnsold(
|
|
|
178
181
|
if (unwindQty > unsoldQty) {
|
|
179
182
|
return {
|
|
180
183
|
canUnwind: false,
|
|
184
|
+
canRepayRequestedSlice: false,
|
|
181
185
|
canRepayFully: false,
|
|
182
186
|
reason: "unwindQty exceeds writer unsold quantity",
|
|
183
187
|
writerPositionAddress: String(writerPositionAddress),
|
|
@@ -211,7 +215,7 @@ export async function preflightUnwindWriterUnsold(
|
|
|
211
215
|
|
|
212
216
|
const slotNow = toBigInt(currentSlot);
|
|
213
217
|
const protocolFeeBps = BigInt(vault.protocolFeeBps);
|
|
214
|
-
const slotsPerYear =
|
|
218
|
+
const slotsPerYear = 78_840_000n;
|
|
215
219
|
const loanBreakdown: Array<UnwindLoanBreakdown> = [];
|
|
216
220
|
|
|
217
221
|
for (const loan of loans) {
|
|
@@ -267,20 +271,20 @@ export async function preflightUnwindWriterUnsold(
|
|
|
267
271
|
|
|
268
272
|
// Calculate proportional obligations for partial unwinds
|
|
269
273
|
const writtenQty = toBigInt(writerPosition.writtenQty);
|
|
270
|
-
const unwindRatio = writtenQty > 0n ? (unwindQty * 1_000_000n) / writtenQty : 0n; // Basis points precision
|
|
271
|
-
const unwindRatioDecimal = Number(unwindRatio) / 1_000_000; // Convert to decimal
|
|
272
|
-
|
|
273
|
-
// Proportional obligations (for partial unwind logic)
|
|
274
274
|
const proportionalPrincipal = writtenQty > 0n ? (totals.principal * unwindQty) / writtenQty : 0n;
|
|
275
275
|
const proportionalInterest = writtenQty > 0n ? (totals.interest * unwindQty) / writtenQty : 0n;
|
|
276
276
|
const proportionalProtocolFees = writtenQty > 0n ? (totals.fees * unwindQty) / writtenQty : 0n;
|
|
277
277
|
const proportionalTotalOwed = proportionalPrincipal + proportionalInterest + proportionalProtocolFees;
|
|
278
278
|
|
|
279
|
-
//
|
|
279
|
+
// On-chain unwind repays from collateral vault first, then optional wallet fallback.
|
|
280
|
+
// The collateral share returned to the writer is therefore reduced only by the
|
|
281
|
+
// amount actually sourced from the collateral vault, not by the entire debt slice.
|
|
280
282
|
const collateralDeposited = toBigInt(writerPosition.collateralDeposited);
|
|
281
283
|
const proportionalCollateralShare = writtenQty > 0n ? (collateralDeposited * unwindQty) / writtenQty : 0n;
|
|
282
|
-
const
|
|
283
|
-
?
|
|
284
|
+
const collateralUsedForRepay =
|
|
285
|
+
proportionalTotalOwed > collateralVaultAvailable ? collateralVaultAvailable : proportionalTotalOwed;
|
|
286
|
+
const returnableCollateral = proportionalCollateralShare > collateralUsedForRepay
|
|
287
|
+
? proportionalCollateralShare - collateralUsedForRepay
|
|
284
288
|
: 0n;
|
|
285
289
|
|
|
286
290
|
// Calculate shortfall against proportional obligations
|
|
@@ -295,14 +299,13 @@ export async function preflightUnwindWriterUnsold(
|
|
|
295
299
|
const effectiveShortfall =
|
|
296
300
|
proportionalTotalOwed > effectiveTotalAvailable ? proportionalTotalOwed - effectiveTotalAvailable : 0n;
|
|
297
301
|
|
|
298
|
-
// For top-up UX
|
|
299
|
-
const collateralVaultShortfall =
|
|
300
|
-
|
|
301
|
-
: 0n;
|
|
302
|
-
const needsWalletTopUp = collateralVaultShortfall > 0n && walletFallbackAvailable < collateralVaultShortfall;
|
|
302
|
+
// For top-up UX, surface the wallet contribution required after collateral-vault-first repayment.
|
|
303
|
+
const collateralVaultShortfall = walletFallbackRequired;
|
|
304
|
+
const needsWalletTopUp = walletFallbackRequired > walletFallbackAvailable;
|
|
303
305
|
|
|
304
306
|
return {
|
|
305
307
|
canUnwind: true,
|
|
308
|
+
canRepayRequestedSlice: effectiveShortfall === 0n,
|
|
306
309
|
canRepayFully: effectiveShortfall === 0n,
|
|
307
310
|
reason:
|
|
308
311
|
effectiveShortfall === 0n
|