@epicentral/sos-sdk 0.12.0-beta → 0.12.1-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/README.md +2 -1
- package/generated/instructions/buyFromPool.ts +34 -9
- package/generated/instructions/optionMint.ts +4 -4
- package/long/builders.ts +111 -65
- package/package.json +1 -1
- package/shared/amounts.ts +40 -0
- package/shared/balances.ts +42 -7
- package/short/preflight.ts +6 -5
package/README.md
CHANGED
|
@@ -442,7 +442,8 @@ const tx = await buildBuyFromPoolMarketOrderTransactionWithDerivation({
|
|
|
442
442
|
|
|
443
443
|
### Buy premium semantics (market orders)
|
|
444
444
|
|
|
445
|
-
- Premium is paid in
|
|
445
|
+
- Premium is paid in **`premium_vault.mint`** (on-chain source of truth). For **new** pools: underlying when physical (`collateral == underlying`), collateral mint when cash (e.g. USDC). **Legacy** cash pools may still use SOL premium vault — resolve mint from chain before building `buyer_payment_account`.
|
|
446
|
+
- `buyer_payment_account.mint` must match `premium_vault.mint`; pass `payment_mint` to `buy_from_pool`.
|
|
446
447
|
- `premiumAmount` / `max_premium_amount` is a **max premium cap**, not an exact premium target.
|
|
447
448
|
- Program computes premium on-chain at execution time and fails with `SlippageToleranceExceeded` if computed premium exceeds the cap.
|
|
448
449
|
- High-level market builder computes cap as `quotedPremiumTotal + buffer`:
|
|
@@ -65,6 +65,7 @@ export type BuyFromPoolInstruction<
|
|
|
65
65
|
"Sysvar1nstructions1111111111111111111111111",
|
|
66
66
|
TAccountBuyerPosition extends string | AccountMeta<string> = string,
|
|
67
67
|
TAccountBuyerOptionAccount extends string | AccountMeta<string> = string,
|
|
68
|
+
TAccountPaymentMint extends string | AccountMeta<string> = string,
|
|
68
69
|
TAccountBuyerPaymentAccount extends string | AccountMeta<string> = string,
|
|
69
70
|
TAccountEscrowLongAccount extends string | AccountMeta<string> = string,
|
|
70
71
|
TAccountPremiumVault extends string | AccountMeta<string> = string,
|
|
@@ -111,6 +112,9 @@ export type BuyFromPoolInstruction<
|
|
|
111
112
|
TAccountBuyerOptionAccount extends string
|
|
112
113
|
? WritableAccount<TAccountBuyerOptionAccount>
|
|
113
114
|
: TAccountBuyerOptionAccount,
|
|
115
|
+
TAccountPaymentMint extends string
|
|
116
|
+
? ReadonlyAccount<TAccountPaymentMint>
|
|
117
|
+
: TAccountPaymentMint,
|
|
114
118
|
TAccountBuyerPaymentAccount extends string
|
|
115
119
|
? WritableAccount<TAccountBuyerPaymentAccount>
|
|
116
120
|
: TAccountBuyerPaymentAccount,
|
|
@@ -191,6 +195,7 @@ export type BuyFromPoolAsyncInput<
|
|
|
191
195
|
TAccountInstructionsSysvar extends string = string,
|
|
192
196
|
TAccountBuyerPosition extends string = string,
|
|
193
197
|
TAccountBuyerOptionAccount extends string = string,
|
|
198
|
+
TAccountPaymentMint extends string = string,
|
|
194
199
|
TAccountBuyerPaymentAccount extends string = string,
|
|
195
200
|
TAccountEscrowLongAccount extends string = string,
|
|
196
201
|
TAccountPremiumVault extends string = string,
|
|
@@ -220,6 +225,8 @@ export type BuyFromPoolAsyncInput<
|
|
|
220
225
|
buyerPosition?: Address<TAccountBuyerPosition>;
|
|
221
226
|
/** Buyer's LONG token account (receives LONG tokens) */
|
|
222
227
|
buyerOptionAccount?: Address<TAccountBuyerOptionAccount>;
|
|
228
|
+
/** Premium payment mint (must match premium_vault.mint on-chain) */
|
|
229
|
+
paymentMint: Address<TAccountPaymentMint>;
|
|
223
230
|
/** Buyer's payment account (source of premium) */
|
|
224
231
|
buyerPaymentAccount: Address<TAccountBuyerPaymentAccount>;
|
|
225
232
|
/** Pool's LONG escrow (source of LONG tokens) */
|
|
@@ -247,6 +254,7 @@ export async function getBuyFromPoolInstructionAsync<
|
|
|
247
254
|
TAccountInstructionsSysvar extends string,
|
|
248
255
|
TAccountBuyerPosition extends string,
|
|
249
256
|
TAccountBuyerOptionAccount extends string,
|
|
257
|
+
TAccountPaymentMint extends string,
|
|
250
258
|
TAccountBuyerPaymentAccount extends string,
|
|
251
259
|
TAccountEscrowLongAccount extends string,
|
|
252
260
|
TAccountPremiumVault extends string,
|
|
@@ -268,6 +276,7 @@ export async function getBuyFromPoolInstructionAsync<
|
|
|
268
276
|
TAccountInstructionsSysvar,
|
|
269
277
|
TAccountBuyerPosition,
|
|
270
278
|
TAccountBuyerOptionAccount,
|
|
279
|
+
TAccountPaymentMint,
|
|
271
280
|
TAccountBuyerPaymentAccount,
|
|
272
281
|
TAccountEscrowLongAccount,
|
|
273
282
|
TAccountPremiumVault,
|
|
@@ -291,6 +300,7 @@ export async function getBuyFromPoolInstructionAsync<
|
|
|
291
300
|
TAccountInstructionsSysvar,
|
|
292
301
|
TAccountBuyerPosition,
|
|
293
302
|
TAccountBuyerOptionAccount,
|
|
303
|
+
TAccountPaymentMint,
|
|
294
304
|
TAccountBuyerPaymentAccount,
|
|
295
305
|
TAccountEscrowLongAccount,
|
|
296
306
|
TAccountPremiumVault,
|
|
@@ -329,6 +339,7 @@ export async function getBuyFromPoolInstructionAsync<
|
|
|
329
339
|
value: input.buyerOptionAccount ?? null,
|
|
330
340
|
isWritable: true,
|
|
331
341
|
},
|
|
342
|
+
paymentMint: { value: input.paymentMint ?? null, isWritable: false },
|
|
332
343
|
buyerPaymentAccount: {
|
|
333
344
|
value: input.buyerPaymentAccount ?? null,
|
|
334
345
|
isWritable: true,
|
|
@@ -433,6 +444,7 @@ export async function getBuyFromPoolInstructionAsync<
|
|
|
433
444
|
getAccountMeta(accounts.instructionsSysvar),
|
|
434
445
|
getAccountMeta(accounts.buyerPosition),
|
|
435
446
|
getAccountMeta(accounts.buyerOptionAccount),
|
|
447
|
+
getAccountMeta(accounts.paymentMint),
|
|
436
448
|
getAccountMeta(accounts.buyerPaymentAccount),
|
|
437
449
|
getAccountMeta(accounts.escrowLongAccount),
|
|
438
450
|
getAccountMeta(accounts.premiumVault),
|
|
@@ -458,6 +470,7 @@ export async function getBuyFromPoolInstructionAsync<
|
|
|
458
470
|
TAccountInstructionsSysvar,
|
|
459
471
|
TAccountBuyerPosition,
|
|
460
472
|
TAccountBuyerOptionAccount,
|
|
473
|
+
TAccountPaymentMint,
|
|
461
474
|
TAccountBuyerPaymentAccount,
|
|
462
475
|
TAccountEscrowLongAccount,
|
|
463
476
|
TAccountPremiumVault,
|
|
@@ -480,6 +493,7 @@ export type BuyFromPoolInput<
|
|
|
480
493
|
TAccountInstructionsSysvar extends string = string,
|
|
481
494
|
TAccountBuyerPosition extends string = string,
|
|
482
495
|
TAccountBuyerOptionAccount extends string = string,
|
|
496
|
+
TAccountPaymentMint extends string = string,
|
|
483
497
|
TAccountBuyerPaymentAccount extends string = string,
|
|
484
498
|
TAccountEscrowLongAccount extends string = string,
|
|
485
499
|
TAccountPremiumVault extends string = string,
|
|
@@ -509,6 +523,8 @@ export type BuyFromPoolInput<
|
|
|
509
523
|
buyerPosition: Address<TAccountBuyerPosition>;
|
|
510
524
|
/** Buyer's LONG token account (receives LONG tokens) */
|
|
511
525
|
buyerOptionAccount: Address<TAccountBuyerOptionAccount>;
|
|
526
|
+
/** Premium payment mint (must match premium_vault.mint on-chain) */
|
|
527
|
+
paymentMint: Address<TAccountPaymentMint>;
|
|
512
528
|
/** Buyer's payment account (source of premium) */
|
|
513
529
|
buyerPaymentAccount: Address<TAccountBuyerPaymentAccount>;
|
|
514
530
|
/** Pool's LONG escrow (source of LONG tokens) */
|
|
@@ -536,6 +552,7 @@ export function getBuyFromPoolInstruction<
|
|
|
536
552
|
TAccountInstructionsSysvar extends string,
|
|
537
553
|
TAccountBuyerPosition extends string,
|
|
538
554
|
TAccountBuyerOptionAccount extends string,
|
|
555
|
+
TAccountPaymentMint extends string,
|
|
539
556
|
TAccountBuyerPaymentAccount extends string,
|
|
540
557
|
TAccountEscrowLongAccount extends string,
|
|
541
558
|
TAccountPremiumVault extends string,
|
|
@@ -557,6 +574,7 @@ export function getBuyFromPoolInstruction<
|
|
|
557
574
|
TAccountInstructionsSysvar,
|
|
558
575
|
TAccountBuyerPosition,
|
|
559
576
|
TAccountBuyerOptionAccount,
|
|
577
|
+
TAccountPaymentMint,
|
|
560
578
|
TAccountBuyerPaymentAccount,
|
|
561
579
|
TAccountEscrowLongAccount,
|
|
562
580
|
TAccountPremiumVault,
|
|
@@ -579,6 +597,7 @@ export function getBuyFromPoolInstruction<
|
|
|
579
597
|
TAccountInstructionsSysvar,
|
|
580
598
|
TAccountBuyerPosition,
|
|
581
599
|
TAccountBuyerOptionAccount,
|
|
600
|
+
TAccountPaymentMint,
|
|
582
601
|
TAccountBuyerPaymentAccount,
|
|
583
602
|
TAccountEscrowLongAccount,
|
|
584
603
|
TAccountPremiumVault,
|
|
@@ -616,6 +635,7 @@ export function getBuyFromPoolInstruction<
|
|
|
616
635
|
value: input.buyerOptionAccount ?? null,
|
|
617
636
|
isWritable: true,
|
|
618
637
|
},
|
|
638
|
+
paymentMint: { value: input.paymentMint ?? null, isWritable: false },
|
|
619
639
|
buyerPaymentAccount: {
|
|
620
640
|
value: input.buyerPaymentAccount ?? null,
|
|
621
641
|
isWritable: true,
|
|
@@ -677,6 +697,7 @@ export function getBuyFromPoolInstruction<
|
|
|
677
697
|
getAccountMeta(accounts.instructionsSysvar),
|
|
678
698
|
getAccountMeta(accounts.buyerPosition),
|
|
679
699
|
getAccountMeta(accounts.buyerOptionAccount),
|
|
700
|
+
getAccountMeta(accounts.paymentMint),
|
|
680
701
|
getAccountMeta(accounts.buyerPaymentAccount),
|
|
681
702
|
getAccountMeta(accounts.escrowLongAccount),
|
|
682
703
|
getAccountMeta(accounts.premiumVault),
|
|
@@ -702,6 +723,7 @@ export function getBuyFromPoolInstruction<
|
|
|
702
723
|
TAccountInstructionsSysvar,
|
|
703
724
|
TAccountBuyerPosition,
|
|
704
725
|
TAccountBuyerOptionAccount,
|
|
726
|
+
TAccountPaymentMint,
|
|
705
727
|
TAccountBuyerPaymentAccount,
|
|
706
728
|
TAccountEscrowLongAccount,
|
|
707
729
|
TAccountPremiumVault,
|
|
@@ -739,18 +761,20 @@ export type ParsedBuyFromPoolInstruction<
|
|
|
739
761
|
buyerPosition: TAccountMetas[8];
|
|
740
762
|
/** Buyer's LONG token account (receives LONG tokens) */
|
|
741
763
|
buyerOptionAccount: TAccountMetas[9];
|
|
764
|
+
/** Premium payment mint (must match premium_vault.mint on-chain) */
|
|
765
|
+
paymentMint: TAccountMetas[10];
|
|
742
766
|
/** Buyer's payment account (source of premium) */
|
|
743
|
-
buyerPaymentAccount: TAccountMetas[
|
|
767
|
+
buyerPaymentAccount: TAccountMetas[11];
|
|
744
768
|
/** Pool's LONG escrow (source of LONG tokens) */
|
|
745
|
-
escrowLongAccount: TAccountMetas[
|
|
769
|
+
escrowLongAccount: TAccountMetas[12];
|
|
746
770
|
/** Pool's premium vault (receives premium) */
|
|
747
|
-
premiumVault: TAccountMetas[
|
|
771
|
+
premiumVault: TAccountMetas[13];
|
|
748
772
|
/** Collateral pool (settlement mint for buyer position tracking) */
|
|
749
|
-
collateralPool: TAccountMetas[
|
|
750
|
-
buyer: TAccountMetas[
|
|
751
|
-
tokenProgram: TAccountMetas[
|
|
752
|
-
associatedTokenProgram: TAccountMetas[
|
|
753
|
-
systemProgram: TAccountMetas[
|
|
773
|
+
collateralPool: TAccountMetas[14];
|
|
774
|
+
buyer: TAccountMetas[15];
|
|
775
|
+
tokenProgram: TAccountMetas[16];
|
|
776
|
+
associatedTokenProgram: TAccountMetas[17];
|
|
777
|
+
systemProgram: TAccountMetas[18];
|
|
754
778
|
};
|
|
755
779
|
data: BuyFromPoolInstructionData;
|
|
756
780
|
};
|
|
@@ -763,7 +787,7 @@ export function parseBuyFromPoolInstruction<
|
|
|
763
787
|
InstructionWithAccounts<TAccountMetas> &
|
|
764
788
|
InstructionWithData<ReadonlyUint8Array>,
|
|
765
789
|
): ParsedBuyFromPoolInstruction<TProgram, TAccountMetas> {
|
|
766
|
-
if (instruction.accounts.length <
|
|
790
|
+
if (instruction.accounts.length < 19) {
|
|
767
791
|
// TODO: Coded error.
|
|
768
792
|
throw new Error("Not enough accounts");
|
|
769
793
|
}
|
|
@@ -786,6 +810,7 @@ export function parseBuyFromPoolInstruction<
|
|
|
786
810
|
instructionsSysvar: getNextAccount(),
|
|
787
811
|
buyerPosition: getNextAccount(),
|
|
788
812
|
buyerOptionAccount: getNextAccount(),
|
|
813
|
+
paymentMint: getNextAccount(),
|
|
789
814
|
buyerPaymentAccount: getNextAccount(),
|
|
790
815
|
escrowLongAccount: getNextAccount(),
|
|
791
816
|
premiumVault: getNextAccount(),
|
|
@@ -350,7 +350,7 @@ export type OptionMintAsyncInput<
|
|
|
350
350
|
optionPool?: Address<TAccountOptionPool>;
|
|
351
351
|
/** Pool's escrow for holding LONG tokens (for buyers to purchase) */
|
|
352
352
|
escrowLongAccount?: Address<TAccountEscrowLongAccount>;
|
|
353
|
-
/** Pool
|
|
353
|
+
/** Pool vault for premiums (underlying when physical, collateral mint for cash) */
|
|
354
354
|
premiumVault?: Address<TAccountPremiumVault>;
|
|
355
355
|
/** Collateral pool for this option */
|
|
356
356
|
collateralPool?: Address<TAccountCollateralPool>;
|
|
@@ -707,7 +707,7 @@ export async function getOptionMintInstructionAsync<
|
|
|
707
707
|
]),
|
|
708
708
|
),
|
|
709
709
|
getAddressEncoder().encode(
|
|
710
|
-
expectAddress(accounts.
|
|
710
|
+
expectAddress(accounts.collateralMint.value),
|
|
711
711
|
),
|
|
712
712
|
],
|
|
713
713
|
});
|
|
@@ -924,7 +924,7 @@ export type OptionMintInput<
|
|
|
924
924
|
optionPool: Address<TAccountOptionPool>;
|
|
925
925
|
/** Pool's escrow for holding LONG tokens (for buyers to purchase) */
|
|
926
926
|
escrowLongAccount: Address<TAccountEscrowLongAccount>;
|
|
927
|
-
/** Pool
|
|
927
|
+
/** Pool vault for premiums (underlying when physical, collateral mint for cash) */
|
|
928
928
|
premiumVault: Address<TAccountPremiumVault>;
|
|
929
929
|
/** Collateral pool for this option */
|
|
930
930
|
collateralPool: Address<TAccountCollateralPool>;
|
|
@@ -1278,7 +1278,7 @@ export type ParsedOptionMintInstruction<
|
|
|
1278
1278
|
optionPool: TAccountMetas[11];
|
|
1279
1279
|
/** Pool's escrow for holding LONG tokens (for buyers to purchase) */
|
|
1280
1280
|
escrowLongAccount: TAccountMetas[12];
|
|
1281
|
-
/** Pool
|
|
1281
|
+
/** Pool vault for premiums (underlying when physical, collateral mint for cash) */
|
|
1282
1282
|
premiumVault: TAccountMetas[13];
|
|
1283
1283
|
/** Collateral pool for this option */
|
|
1284
1284
|
collateralPool: TAccountMetas[14];
|
package/long/builders.ts
CHANGED
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
deriveAssociatedTokenAddress,
|
|
11
11
|
deriveBuyerPositionPda,
|
|
12
12
|
} from "../accounts/pdas";
|
|
13
|
-
import { assertNonNegativeAmount, assertPositiveAmount } from "../shared/amounts";
|
|
13
|
+
import { assertNonNegativeAmount, assertPositiveAmount, getPremiumMint } from "../shared/amounts";
|
|
14
|
+
import { fetchTokenAccountMint } from "../shared/balances";
|
|
14
15
|
import { invariant } from "../shared/errors";
|
|
15
16
|
import {
|
|
16
17
|
appendRemainingAccounts,
|
|
@@ -47,6 +48,8 @@ export interface BuildBuyFromPoolParams {
|
|
|
47
48
|
/** Program `switchboard_queue` — use {@link getDefaultSwitchboardQueueAddress} for the cluster. */
|
|
48
49
|
switchboardQueue: AddressLike;
|
|
49
50
|
buyer: AddressLike;
|
|
51
|
+
/** Premium payment mint — must match on-chain premium_vault.mint. */
|
|
52
|
+
paymentMint: AddressLike;
|
|
50
53
|
buyerPaymentAccount: AddressLike;
|
|
51
54
|
escrowLongAccount: AddressLike;
|
|
52
55
|
premiumVault: AddressLike;
|
|
@@ -57,6 +60,29 @@ export interface BuildBuyFromPoolParams {
|
|
|
57
60
|
remainingAccounts?: RemainingAccountInput[];
|
|
58
61
|
}
|
|
59
62
|
|
|
63
|
+
async function resolveBuyPaymentAccounts(
|
|
64
|
+
params: {
|
|
65
|
+
rpc: KitRpc;
|
|
66
|
+
buyer: AddressLike;
|
|
67
|
+
premiumVault: AddressLike;
|
|
68
|
+
underlyingMint: AddressLike;
|
|
69
|
+
collateralMint?: AddressLike;
|
|
70
|
+
paymentMint?: AddressLike;
|
|
71
|
+
buyerPaymentAccount?: AddressLike;
|
|
72
|
+
}
|
|
73
|
+
): Promise<{ paymentMint: AddressLike; buyerPaymentAccount: AddressLike }> {
|
|
74
|
+
const onChainPremiumMint = await fetchTokenAccountMint(params.rpc, params.premiumVault);
|
|
75
|
+
const paymentMint = params.paymentMint ?? onChainPremiumMint;
|
|
76
|
+
invariant(
|
|
77
|
+
toAddress(paymentMint) === toAddress(onChainPremiumMint),
|
|
78
|
+
"paymentMint must match on-chain premium_vault.mint"
|
|
79
|
+
);
|
|
80
|
+
const buyerPaymentAccount =
|
|
81
|
+
params.buyerPaymentAccount ??
|
|
82
|
+
(await deriveAssociatedTokenAddress(params.buyer, paymentMint));
|
|
83
|
+
return { paymentMint, buyerPaymentAccount };
|
|
84
|
+
}
|
|
85
|
+
|
|
60
86
|
export interface BuildCloseLongToPoolParams {
|
|
61
87
|
optionPool: AddressLike;
|
|
62
88
|
optionAccount: AddressLike;
|
|
@@ -110,6 +136,7 @@ export async function buildBuyFromPoolInstruction(
|
|
|
110
136
|
? toAddress(params.buyerOptionAccount)
|
|
111
137
|
: undefined,
|
|
112
138
|
buyerPaymentAccount: toAddress(params.buyerPaymentAccount),
|
|
139
|
+
paymentMint: toAddress(params.paymentMint),
|
|
113
140
|
escrowLongAccount: toAddress(params.escrowLongAccount),
|
|
114
141
|
premiumVault: toAddress(params.premiumVault),
|
|
115
142
|
quantity: params.quantity,
|
|
@@ -125,13 +152,13 @@ export async function buildBuyFromPoolInstruction(
|
|
|
125
152
|
* first-time buyers succeed without a separate setup step.
|
|
126
153
|
*/
|
|
127
154
|
export async function buildBuyFromPoolTransaction(
|
|
128
|
-
params: BuildBuyFromPoolParams
|
|
155
|
+
params: BuildBuyFromPoolParams & { createPaymentAta?: boolean }
|
|
129
156
|
): Promise<BuiltTransaction> {
|
|
130
157
|
const buyerOptionAccountAddress = params.buyerOptionAccount
|
|
131
158
|
? toAddress(params.buyerOptionAccount)
|
|
132
159
|
: await deriveAssociatedTokenAddress(params.buyer, params.longMint);
|
|
133
160
|
|
|
134
|
-
const
|
|
161
|
+
const createLongAtaIx =
|
|
135
162
|
await getCreateAssociatedTokenIdempotentInstructionWithAddress(
|
|
136
163
|
params.buyer,
|
|
137
164
|
params.buyer,
|
|
@@ -139,8 +166,22 @@ export async function buildBuyFromPoolTransaction(
|
|
|
139
166
|
buyerOptionAccountAddress
|
|
140
167
|
);
|
|
141
168
|
|
|
169
|
+
const instructions: Instruction<string>[] = [createLongAtaIx];
|
|
170
|
+
|
|
171
|
+
if (params.createPaymentAta !== false) {
|
|
172
|
+
const createPaymentAtaIx =
|
|
173
|
+
await getCreateAssociatedTokenIdempotentInstructionWithAddress(
|
|
174
|
+
params.buyer,
|
|
175
|
+
params.buyer,
|
|
176
|
+
params.paymentMint,
|
|
177
|
+
toAddress(params.buyerPaymentAccount)
|
|
178
|
+
);
|
|
179
|
+
instructions.push(createPaymentAtaIx);
|
|
180
|
+
}
|
|
181
|
+
|
|
142
182
|
const buyFromPoolIx = await buildBuyFromPoolInstruction(params);
|
|
143
|
-
|
|
183
|
+
instructions.push(buyFromPoolIx);
|
|
184
|
+
return { instructions };
|
|
144
185
|
}
|
|
145
186
|
|
|
146
187
|
export interface BuildBuyFromPoolTransactionWithDerivationParams {
|
|
@@ -149,7 +190,10 @@ export interface BuildBuyFromPoolTransactionWithDerivationParams {
|
|
|
149
190
|
strikePrice: number;
|
|
150
191
|
expirationDate: bigint | number;
|
|
151
192
|
buyer: AddressLike;
|
|
152
|
-
|
|
193
|
+
/** Optional — derived from on-chain premium_vault.mint when omitted. */
|
|
194
|
+
buyerPaymentAccount?: AddressLike;
|
|
195
|
+
/** Optional — derived from on-chain premium_vault.mint when omitted. */
|
|
196
|
+
paymentMint?: AddressLike;
|
|
153
197
|
/** When `disableSwitchboardCrank` is true, optional explicit queue (else devnet/mainnet default). */
|
|
154
198
|
switchboardQueue?: AddressLike;
|
|
155
199
|
quantity: bigint | number;
|
|
@@ -235,7 +279,7 @@ export async function buildBuyFromPoolTransactionWithDerivation(
|
|
|
235
279
|
"Option pool must exist; ensure rpc is provided and pool is initialized."
|
|
236
280
|
);
|
|
237
281
|
|
|
238
|
-
const [buyerPosition, buyerOptionAccount] = await Promise.all([
|
|
282
|
+
const [buyerPosition, buyerOptionAccount, paymentAccounts] = await Promise.all([
|
|
239
283
|
params.buyerPosition
|
|
240
284
|
? Promise.resolve(params.buyerPosition)
|
|
241
285
|
: deriveBuyerPositionPda(
|
|
@@ -246,8 +290,35 @@ export async function buildBuyFromPoolTransactionWithDerivation(
|
|
|
246
290
|
params.buyerOptionAccount
|
|
247
291
|
? Promise.resolve(params.buyerOptionAccount)
|
|
248
292
|
: deriveAssociatedTokenAddress(params.buyer, resolved.longMint),
|
|
293
|
+
resolveBuyPaymentAccounts({
|
|
294
|
+
rpc: params.rpc,
|
|
295
|
+
buyer: params.buyer,
|
|
296
|
+
premiumVault: resolved.premiumVault!,
|
|
297
|
+
underlyingMint: resolved.underlyingMint!,
|
|
298
|
+
collateralMint: resolved.collateralPoolData?.collateralMint,
|
|
299
|
+
paymentMint: params.paymentMint,
|
|
300
|
+
buyerPaymentAccount: params.buyerPaymentAccount,
|
|
301
|
+
}),
|
|
249
302
|
]);
|
|
250
303
|
|
|
304
|
+
const buyParams = {
|
|
305
|
+
optionPool: resolved.optionPool,
|
|
306
|
+
optionAccount: resolved.optionAccount,
|
|
307
|
+
longMint: resolved.longMint,
|
|
308
|
+
underlyingMint: resolved.underlyingMint!,
|
|
309
|
+
marketData: resolved.marketData,
|
|
310
|
+
buyer: params.buyer,
|
|
311
|
+
paymentMint: paymentAccounts.paymentMint,
|
|
312
|
+
buyerPaymentAccount: paymentAccounts.buyerPaymentAccount,
|
|
313
|
+
escrowLongAccount: resolved.escrowLongAccount!,
|
|
314
|
+
premiumVault: resolved.premiumVault!,
|
|
315
|
+
quantity: params.quantity,
|
|
316
|
+
premiumAmount: params.premiumAmount,
|
|
317
|
+
buyerPosition,
|
|
318
|
+
buyerOptionAccount,
|
|
319
|
+
remainingAccounts: params.remainingAccounts,
|
|
320
|
+
};
|
|
321
|
+
|
|
251
322
|
const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
|
|
252
323
|
invariant(
|
|
253
324
|
!!marketDataAccount,
|
|
@@ -263,21 +334,8 @@ export async function buildBuyFromPoolTransactionWithDerivation(
|
|
|
263
334
|
? toAddress(params.switchboardQueue)
|
|
264
335
|
: getDefaultSwitchboardQueueAddress(network);
|
|
265
336
|
return buildBuyFromPoolTransaction({
|
|
266
|
-
|
|
267
|
-
optionAccount: resolved.optionAccount,
|
|
268
|
-
longMint: resolved.longMint,
|
|
269
|
-
underlyingMint: resolved.underlyingMint!,
|
|
270
|
-
marketData: resolved.marketData,
|
|
337
|
+
...buyParams,
|
|
271
338
|
switchboardQueue,
|
|
272
|
-
buyer: params.buyer,
|
|
273
|
-
buyerPaymentAccount: params.buyerPaymentAccount,
|
|
274
|
-
escrowLongAccount: resolved.escrowLongAccount!,
|
|
275
|
-
premiumVault: resolved.premiumVault!,
|
|
276
|
-
quantity: params.quantity,
|
|
277
|
-
premiumAmount: params.premiumAmount,
|
|
278
|
-
buyerPosition,
|
|
279
|
-
buyerOptionAccount,
|
|
280
|
-
remainingAccounts: params.remainingAccounts,
|
|
281
339
|
});
|
|
282
340
|
}
|
|
283
341
|
|
|
@@ -296,21 +354,8 @@ export async function buildBuyFromPoolTransactionWithDerivation(
|
|
|
296
354
|
});
|
|
297
355
|
|
|
298
356
|
const actionTx = await buildBuyFromPoolTransaction({
|
|
299
|
-
|
|
300
|
-
optionAccount: resolved.optionAccount,
|
|
301
|
-
longMint: resolved.longMint,
|
|
302
|
-
underlyingMint: resolved.underlyingMint!,
|
|
303
|
-
marketData: resolved.marketData,
|
|
357
|
+
...buyParams,
|
|
304
358
|
switchboardQueue: getDefaultSwitchboardQueueAddress(network),
|
|
305
|
-
buyer: params.buyer,
|
|
306
|
-
buyerPaymentAccount: params.buyerPaymentAccount,
|
|
307
|
-
escrowLongAccount: resolved.escrowLongAccount!,
|
|
308
|
-
premiumVault: resolved.premiumVault!,
|
|
309
|
-
quantity: params.quantity,
|
|
310
|
-
premiumAmount: params.premiumAmount,
|
|
311
|
-
buyerPosition,
|
|
312
|
-
buyerOptionAccount,
|
|
313
|
-
remainingAccounts: params.remainingAccounts,
|
|
314
359
|
});
|
|
315
360
|
|
|
316
361
|
return prependSwitchboardQuote(quote, actionTx);
|
|
@@ -345,7 +390,7 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
345
390
|
rpc: params.rpc,
|
|
346
391
|
});
|
|
347
392
|
|
|
348
|
-
const [refetchedPool, remainingAccounts, buyerPosition, buyerOptionAccount] =
|
|
393
|
+
const [refetchedPool, remainingAccounts, buyerPosition, buyerOptionAccount, paymentAccounts] =
|
|
349
394
|
await Promise.all([
|
|
350
395
|
fetchOptionPool(params.rpc, resolved.optionPool),
|
|
351
396
|
getBuyFromPoolRemainingAccounts(params.rpc, resolved.optionPool, params.programId),
|
|
@@ -359,6 +404,15 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
359
404
|
params.buyerOptionAccount
|
|
360
405
|
? Promise.resolve(params.buyerOptionAccount)
|
|
361
406
|
: deriveAssociatedTokenAddress(params.buyer, resolved.longMint),
|
|
407
|
+
resolveBuyPaymentAccounts({
|
|
408
|
+
rpc: params.rpc,
|
|
409
|
+
buyer: params.buyer,
|
|
410
|
+
premiumVault: resolved.premiumVault!,
|
|
411
|
+
underlyingMint: resolved.underlyingMint!,
|
|
412
|
+
collateralMint: resolved.collateralPoolData?.collateralMint,
|
|
413
|
+
paymentMint: params.paymentMint,
|
|
414
|
+
buyerPaymentAccount: params.buyerPaymentAccount,
|
|
415
|
+
}),
|
|
362
416
|
]);
|
|
363
417
|
|
|
364
418
|
invariant(
|
|
@@ -392,18 +446,36 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
392
446
|
const onchainPad = (q: bigint) =>
|
|
393
447
|
(q * BigInt(POOL_BUY_MAX_PREMIUM_ONCHAIN_PAD_BPS)) / 10_000n;
|
|
394
448
|
const slippageBuffer = hasExplicitSlippageBuffer
|
|
395
|
-
? normalizeMarketOrderSlippageBuffer(params,
|
|
449
|
+
? normalizeMarketOrderSlippageBuffer(params, paymentAccounts.paymentMint) +
|
|
396
450
|
onchainPad(quotePremium)
|
|
397
451
|
: globalTradeConfig.slippageBps !== undefined
|
|
398
452
|
? applySlippageBps(
|
|
399
453
|
quotePremium,
|
|
400
454
|
globalTradeConfig.slippageBps + POOL_BUY_MAX_PREMIUM_ONCHAIN_PAD_BPS
|
|
401
455
|
) - quotePremium
|
|
402
|
-
: normalizeMarketOrderSlippageBuffer(params,
|
|
456
|
+
: normalizeMarketOrderSlippageBuffer(params, paymentAccounts.paymentMint) +
|
|
403
457
|
onchainPad(quotePremium);
|
|
404
458
|
const maxPremiumAmount = quotePremium + slippageBuffer;
|
|
405
459
|
assertPositiveAmount(maxPremiumAmount, "maxPremiumAmount");
|
|
406
460
|
|
|
461
|
+
const marketBuyParams = {
|
|
462
|
+
optionPool: resolved.optionPool,
|
|
463
|
+
optionAccount: resolved.optionAccount,
|
|
464
|
+
longMint: resolved.longMint,
|
|
465
|
+
underlyingMint: refetchedPool.underlyingMint,
|
|
466
|
+
marketData: resolved.marketData,
|
|
467
|
+
buyer: params.buyer,
|
|
468
|
+
paymentMint: paymentAccounts.paymentMint,
|
|
469
|
+
buyerPaymentAccount: paymentAccounts.buyerPaymentAccount,
|
|
470
|
+
escrowLongAccount: refetchedPool.escrowLongAccount,
|
|
471
|
+
premiumVault: refetchedPool.premiumVault,
|
|
472
|
+
quantity: params.quantity,
|
|
473
|
+
premiumAmount: maxPremiumAmount,
|
|
474
|
+
buyerPosition,
|
|
475
|
+
buyerOptionAccount,
|
|
476
|
+
remainingAccounts,
|
|
477
|
+
};
|
|
478
|
+
|
|
407
479
|
const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
|
|
408
480
|
invariant(
|
|
409
481
|
!!marketDataAccount,
|
|
@@ -419,21 +491,8 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
419
491
|
? toAddress(params.switchboardQueue)
|
|
420
492
|
: getDefaultSwitchboardQueueAddress(network);
|
|
421
493
|
return buildBuyFromPoolTransaction({
|
|
422
|
-
|
|
423
|
-
optionAccount: resolved.optionAccount,
|
|
424
|
-
longMint: resolved.longMint,
|
|
425
|
-
underlyingMint: refetchedPool.underlyingMint,
|
|
426
|
-
marketData: resolved.marketData,
|
|
494
|
+
...marketBuyParams,
|
|
427
495
|
switchboardQueue,
|
|
428
|
-
buyer: params.buyer,
|
|
429
|
-
buyerPaymentAccount: params.buyerPaymentAccount,
|
|
430
|
-
escrowLongAccount: refetchedPool.escrowLongAccount,
|
|
431
|
-
premiumVault: refetchedPool.premiumVault,
|
|
432
|
-
quantity: params.quantity,
|
|
433
|
-
premiumAmount: maxPremiumAmount,
|
|
434
|
-
buyerPosition,
|
|
435
|
-
buyerOptionAccount,
|
|
436
|
-
remainingAccounts,
|
|
437
496
|
});
|
|
438
497
|
}
|
|
439
498
|
|
|
@@ -452,21 +511,8 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
452
511
|
});
|
|
453
512
|
|
|
454
513
|
const actionTx = await buildBuyFromPoolTransaction({
|
|
455
|
-
|
|
456
|
-
optionAccount: resolved.optionAccount,
|
|
457
|
-
longMint: resolved.longMint,
|
|
458
|
-
underlyingMint: refetchedPool.underlyingMint,
|
|
459
|
-
marketData: resolved.marketData,
|
|
514
|
+
...marketBuyParams,
|
|
460
515
|
switchboardQueue: getDefaultSwitchboardQueueAddress(network),
|
|
461
|
-
buyer: params.buyer,
|
|
462
|
-
buyerPaymentAccount: params.buyerPaymentAccount,
|
|
463
|
-
escrowLongAccount: refetchedPool.escrowLongAccount,
|
|
464
|
-
premiumVault: refetchedPool.premiumVault,
|
|
465
|
-
quantity: params.quantity,
|
|
466
|
-
premiumAmount: maxPremiumAmount,
|
|
467
|
-
buyerPosition,
|
|
468
|
-
buyerOptionAccount,
|
|
469
|
-
remainingAccounts,
|
|
470
516
|
});
|
|
471
517
|
|
|
472
518
|
return prependSwitchboardQuote(quote, actionTx);
|
package/package.json
CHANGED
package/shared/amounts.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Decimal from "decimal.js";
|
|
2
|
+
import type { AddressLike } from "../client/types";
|
|
2
3
|
import { SdkValidationError } from "./errors";
|
|
3
4
|
|
|
4
5
|
export function toBaseUnits(amount: Decimal.Value, decimals: number): bigint {
|
|
@@ -72,3 +73,42 @@ export function calculateRequiredCollateralLegacy(
|
|
|
72
73
|
"physical"
|
|
73
74
|
);
|
|
74
75
|
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Premium payment mint for a pool: underlying when physical, collateral when cash.
|
|
79
|
+
* For **existing** pools, prefer on-chain `premium_vault.mint` (legacy cash may differ).
|
|
80
|
+
*/
|
|
81
|
+
export function getPremiumMint(
|
|
82
|
+
underlyingMint: AddressLike,
|
|
83
|
+
collateralMint: AddressLike
|
|
84
|
+
): string {
|
|
85
|
+
const underlying = String(underlyingMint);
|
|
86
|
+
const collateral = String(collateralMint);
|
|
87
|
+
return underlying === collateral ? underlying : collateral;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Convert BS premium USD to payment-mint base units.
|
|
92
|
+
* - **Cash:** `premiumUsd × 10^decimals` (~$1 stable collateral)
|
|
93
|
+
* - **Physical:** `(premiumUsd / underlyingSpot) × 10^decimals`
|
|
94
|
+
*/
|
|
95
|
+
export function convertPremiumUsdToBaseUnits(
|
|
96
|
+
premiumUsd: number,
|
|
97
|
+
settlementMode: SettlementMode,
|
|
98
|
+
underlyingSpot: number,
|
|
99
|
+
tokenDecimals: number
|
|
100
|
+
): bigint {
|
|
101
|
+
if (!Number.isFinite(premiumUsd) || premiumUsd <= 0) {
|
|
102
|
+
return 0n;
|
|
103
|
+
}
|
|
104
|
+
const scale = 10 ** tokenDecimals;
|
|
105
|
+
if (settlementMode === "cash") {
|
|
106
|
+
return BigInt(Math.ceil(premiumUsd * scale - Number.EPSILON));
|
|
107
|
+
}
|
|
108
|
+
if (!Number.isFinite(underlyingSpot) || underlyingSpot <= 0) {
|
|
109
|
+
return 0n;
|
|
110
|
+
}
|
|
111
|
+
return BigInt(
|
|
112
|
+
Math.ceil((premiumUsd / underlyingSpot) * scale - Number.EPSILON)
|
|
113
|
+
);
|
|
114
|
+
}
|
package/shared/balances.ts
CHANGED
|
@@ -1,27 +1,62 @@
|
|
|
1
1
|
import type { Address } from "@solana/kit";
|
|
2
|
+
import { getAddressDecoder } from "@solana/kit";
|
|
2
3
|
import { deriveAssociatedTokenAddress } from "../accounts/pdas";
|
|
3
4
|
import { toAddress } from "../client/program";
|
|
4
5
|
import type { AddressLike, KitRpc } from "../client/types";
|
|
5
6
|
import { NATIVE_MINT } from "../wsol/instructions";
|
|
6
7
|
|
|
7
|
-
/** SPL Token account data: amount
|
|
8
|
+
/** SPL Token account data: mint at offset 0 (32 bytes); amount at offset 64 (u64 LE). */
|
|
9
|
+
const TOKEN_ACCOUNT_MINT_OFFSET = 0;
|
|
8
10
|
const TOKEN_ACCOUNT_AMOUNT_OFFSET = 64;
|
|
9
11
|
|
|
12
|
+
function decodeTokenAccountData(b64: string): Uint8Array {
|
|
13
|
+
const binary = atob(b64);
|
|
14
|
+
const data = new Uint8Array(binary.length);
|
|
15
|
+
for (let i = 0; i < binary.length; i++) data[i] = binary.charCodeAt(i);
|
|
16
|
+
return data;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function decodeTokenAccountMint(data: Uint8Array): Address | null {
|
|
20
|
+
if (data.length < TOKEN_ACCOUNT_MINT_OFFSET + 32) return null;
|
|
21
|
+
return getAddressDecoder().decode(
|
|
22
|
+
data.subarray(TOKEN_ACCOUNT_MINT_OFFSET, TOKEN_ACCOUNT_MINT_OFFSET + 32)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
10
26
|
function decodeTokenAccountAmount(data: Uint8Array): bigint {
|
|
11
27
|
if (data.length < TOKEN_ACCOUNT_AMOUNT_OFFSET + 8) return BigInt(0);
|
|
12
28
|
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
13
29
|
return view.getBigUint64(TOKEN_ACCOUNT_AMOUNT_OFFSET, true);
|
|
14
30
|
}
|
|
15
31
|
|
|
16
|
-
async function
|
|
32
|
+
async function fetchTokenAccountData(rpc: KitRpc, ata: Address): Promise<Uint8Array | null> {
|
|
17
33
|
const response = await rpc.getAccountInfo(ata, { encoding: "base64" }).send();
|
|
18
34
|
const accountInfo = response.value;
|
|
19
|
-
if (!accountInfo) return
|
|
35
|
+
if (!accountInfo) return null;
|
|
20
36
|
const [b64] = accountInfo.data;
|
|
21
|
-
if (!b64) return
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
if (!b64) return null;
|
|
38
|
+
return decodeTokenAccountData(b64);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Read the mint pubkey from an SPL token account address. */
|
|
42
|
+
export async function fetchTokenAccountMint(
|
|
43
|
+
rpc: KitRpc,
|
|
44
|
+
tokenAccount: AddressLike
|
|
45
|
+
): Promise<Address> {
|
|
46
|
+
const data = await fetchTokenAccountData(rpc, toAddress(tokenAccount));
|
|
47
|
+
if (!data) {
|
|
48
|
+
throw new Error(`Token account not found: ${String(tokenAccount)}`);
|
|
49
|
+
}
|
|
50
|
+
const mint = decodeTokenAccountMint(data);
|
|
51
|
+
if (!mint) {
|
|
52
|
+
throw new Error(`Invalid token account data: ${String(tokenAccount)}`);
|
|
53
|
+
}
|
|
54
|
+
return mint;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function fetchTokenAccountBalance(rpc: KitRpc, ata: Address): Promise<bigint> {
|
|
58
|
+
const data = await fetchTokenAccountData(rpc, ata);
|
|
59
|
+
if (!data) return BigInt(0);
|
|
25
60
|
return decodeTokenAccountAmount(data);
|
|
26
61
|
}
|
|
27
62
|
|
package/short/preflight.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { fetchPoolLoansByMaker } from "../accounts/list";
|
|
|
6
6
|
import { deriveAssociatedTokenAddress, deriveVaultPda, deriveWriterPositionPda } from "../accounts/pdas";
|
|
7
7
|
import { resolveOptionAccounts } from "../accounts/resolve-option";
|
|
8
8
|
import { invariant } from "../shared/errors";
|
|
9
|
+
import { fetchTokenAccountMint } from "../shared/balances";
|
|
9
10
|
|
|
10
11
|
const TOKEN_ACCOUNT_AMOUNT_OFFSET = 64;
|
|
11
12
|
const BPS_DENOMINATOR = 10_000n;
|
|
@@ -455,12 +456,12 @@ export async function preflightUnwindWriterUnsold(
|
|
|
455
456
|
const thetaAvailable = computeThetaBalance(writerPosition, optionPoolData.accThetaPerOiFp);
|
|
456
457
|
|
|
457
458
|
// Theta can only offset debt when premium_vault.mint == omlp_vault.mint.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
459
|
+
const premiumVaultMint = await fetchTokenAccountMint(
|
|
460
|
+
params.rpc,
|
|
461
|
+
resolved.premiumVault!
|
|
462
|
+
);
|
|
462
463
|
const premiumMintMatchesCollateralMint =
|
|
463
|
-
toAddress(
|
|
464
|
+
toAddress(premiumVaultMint) === toAddress(collateralPoolData.collateralMint);
|
|
464
465
|
|
|
465
466
|
const thetaToDebt = premiumMintMatchesCollateralMint
|
|
466
467
|
? minBigInt(thetaAvailable, proportionalTotalOwed)
|