@epicentral/sos-sdk 0.12.0-beta → 0.12.2-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 +153 -72
- package/package.json +1 -1
- package/shared/amounts.ts +40 -0
- package/shared/balances.ts +42 -7
- package/short/preflight.ts +9 -7
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;
|
|
@@ -84,10 +110,12 @@ export interface BuildCloseLongToPoolParams {
|
|
|
84
110
|
*/
|
|
85
111
|
closeLongTokenAccount?: boolean;
|
|
86
112
|
/**
|
|
87
|
-
* When true and
|
|
88
|
-
*
|
|
113
|
+
* When true and settlement is physical (collateral mint == underlying) with WSOL underlying,
|
|
114
|
+
* appends CloseAccount to unwrap the payout WSOL ATA to native SOL.
|
|
89
115
|
*/
|
|
90
116
|
unwrapPayoutSol?: boolean;
|
|
117
|
+
/** When false, skips idempotent create-ATA for payout accounts (default: true). */
|
|
118
|
+
createPayoutAtas?: boolean;
|
|
91
119
|
remainingAccounts?: RemainingAccountInput[];
|
|
92
120
|
}
|
|
93
121
|
|
|
@@ -110,6 +138,7 @@ export async function buildBuyFromPoolInstruction(
|
|
|
110
138
|
? toAddress(params.buyerOptionAccount)
|
|
111
139
|
: undefined,
|
|
112
140
|
buyerPaymentAccount: toAddress(params.buyerPaymentAccount),
|
|
141
|
+
paymentMint: toAddress(params.paymentMint),
|
|
113
142
|
escrowLongAccount: toAddress(params.escrowLongAccount),
|
|
114
143
|
premiumVault: toAddress(params.premiumVault),
|
|
115
144
|
quantity: params.quantity,
|
|
@@ -125,13 +154,13 @@ export async function buildBuyFromPoolInstruction(
|
|
|
125
154
|
* first-time buyers succeed without a separate setup step.
|
|
126
155
|
*/
|
|
127
156
|
export async function buildBuyFromPoolTransaction(
|
|
128
|
-
params: BuildBuyFromPoolParams
|
|
157
|
+
params: BuildBuyFromPoolParams & { createPaymentAta?: boolean }
|
|
129
158
|
): Promise<BuiltTransaction> {
|
|
130
159
|
const buyerOptionAccountAddress = params.buyerOptionAccount
|
|
131
160
|
? toAddress(params.buyerOptionAccount)
|
|
132
161
|
: await deriveAssociatedTokenAddress(params.buyer, params.longMint);
|
|
133
162
|
|
|
134
|
-
const
|
|
163
|
+
const createLongAtaIx =
|
|
135
164
|
await getCreateAssociatedTokenIdempotentInstructionWithAddress(
|
|
136
165
|
params.buyer,
|
|
137
166
|
params.buyer,
|
|
@@ -139,8 +168,22 @@ export async function buildBuyFromPoolTransaction(
|
|
|
139
168
|
buyerOptionAccountAddress
|
|
140
169
|
);
|
|
141
170
|
|
|
171
|
+
const instructions: Instruction<string>[] = [createLongAtaIx];
|
|
172
|
+
|
|
173
|
+
if (params.createPaymentAta !== false) {
|
|
174
|
+
const createPaymentAtaIx =
|
|
175
|
+
await getCreateAssociatedTokenIdempotentInstructionWithAddress(
|
|
176
|
+
params.buyer,
|
|
177
|
+
params.buyer,
|
|
178
|
+
params.paymentMint,
|
|
179
|
+
toAddress(params.buyerPaymentAccount)
|
|
180
|
+
);
|
|
181
|
+
instructions.push(createPaymentAtaIx);
|
|
182
|
+
}
|
|
183
|
+
|
|
142
184
|
const buyFromPoolIx = await buildBuyFromPoolInstruction(params);
|
|
143
|
-
|
|
185
|
+
instructions.push(buyFromPoolIx);
|
|
186
|
+
return { instructions };
|
|
144
187
|
}
|
|
145
188
|
|
|
146
189
|
export interface BuildBuyFromPoolTransactionWithDerivationParams {
|
|
@@ -149,7 +192,10 @@ export interface BuildBuyFromPoolTransactionWithDerivationParams {
|
|
|
149
192
|
strikePrice: number;
|
|
150
193
|
expirationDate: bigint | number;
|
|
151
194
|
buyer: AddressLike;
|
|
152
|
-
|
|
195
|
+
/** Optional — derived from on-chain premium_vault.mint when omitted. */
|
|
196
|
+
buyerPaymentAccount?: AddressLike;
|
|
197
|
+
/** Optional — derived from on-chain premium_vault.mint when omitted. */
|
|
198
|
+
paymentMint?: AddressLike;
|
|
153
199
|
/** When `disableSwitchboardCrank` is true, optional explicit queue (else devnet/mainnet default). */
|
|
154
200
|
switchboardQueue?: AddressLike;
|
|
155
201
|
quantity: bigint | number;
|
|
@@ -235,7 +281,7 @@ export async function buildBuyFromPoolTransactionWithDerivation(
|
|
|
235
281
|
"Option pool must exist; ensure rpc is provided and pool is initialized."
|
|
236
282
|
);
|
|
237
283
|
|
|
238
|
-
const [buyerPosition, buyerOptionAccount] = await Promise.all([
|
|
284
|
+
const [buyerPosition, buyerOptionAccount, paymentAccounts] = await Promise.all([
|
|
239
285
|
params.buyerPosition
|
|
240
286
|
? Promise.resolve(params.buyerPosition)
|
|
241
287
|
: deriveBuyerPositionPda(
|
|
@@ -246,8 +292,35 @@ export async function buildBuyFromPoolTransactionWithDerivation(
|
|
|
246
292
|
params.buyerOptionAccount
|
|
247
293
|
? Promise.resolve(params.buyerOptionAccount)
|
|
248
294
|
: deriveAssociatedTokenAddress(params.buyer, resolved.longMint),
|
|
295
|
+
resolveBuyPaymentAccounts({
|
|
296
|
+
rpc: params.rpc,
|
|
297
|
+
buyer: params.buyer,
|
|
298
|
+
premiumVault: resolved.premiumVault!,
|
|
299
|
+
underlyingMint: resolved.underlyingMint!,
|
|
300
|
+
collateralMint: resolved.collateralPoolData?.collateralMint,
|
|
301
|
+
paymentMint: params.paymentMint,
|
|
302
|
+
buyerPaymentAccount: params.buyerPaymentAccount,
|
|
303
|
+
}),
|
|
249
304
|
]);
|
|
250
305
|
|
|
306
|
+
const buyParams = {
|
|
307
|
+
optionPool: resolved.optionPool,
|
|
308
|
+
optionAccount: resolved.optionAccount,
|
|
309
|
+
longMint: resolved.longMint,
|
|
310
|
+
underlyingMint: resolved.underlyingMint!,
|
|
311
|
+
marketData: resolved.marketData,
|
|
312
|
+
buyer: params.buyer,
|
|
313
|
+
paymentMint: paymentAccounts.paymentMint,
|
|
314
|
+
buyerPaymentAccount: paymentAccounts.buyerPaymentAccount,
|
|
315
|
+
escrowLongAccount: resolved.escrowLongAccount!,
|
|
316
|
+
premiumVault: resolved.premiumVault!,
|
|
317
|
+
quantity: params.quantity,
|
|
318
|
+
premiumAmount: params.premiumAmount,
|
|
319
|
+
buyerPosition,
|
|
320
|
+
buyerOptionAccount,
|
|
321
|
+
remainingAccounts: params.remainingAccounts,
|
|
322
|
+
};
|
|
323
|
+
|
|
251
324
|
const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
|
|
252
325
|
invariant(
|
|
253
326
|
!!marketDataAccount,
|
|
@@ -263,21 +336,8 @@ export async function buildBuyFromPoolTransactionWithDerivation(
|
|
|
263
336
|
? toAddress(params.switchboardQueue)
|
|
264
337
|
: getDefaultSwitchboardQueueAddress(network);
|
|
265
338
|
return buildBuyFromPoolTransaction({
|
|
266
|
-
|
|
267
|
-
optionAccount: resolved.optionAccount,
|
|
268
|
-
longMint: resolved.longMint,
|
|
269
|
-
underlyingMint: resolved.underlyingMint!,
|
|
270
|
-
marketData: resolved.marketData,
|
|
339
|
+
...buyParams,
|
|
271
340
|
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
341
|
});
|
|
282
342
|
}
|
|
283
343
|
|
|
@@ -296,21 +356,8 @@ export async function buildBuyFromPoolTransactionWithDerivation(
|
|
|
296
356
|
});
|
|
297
357
|
|
|
298
358
|
const actionTx = await buildBuyFromPoolTransaction({
|
|
299
|
-
|
|
300
|
-
optionAccount: resolved.optionAccount,
|
|
301
|
-
longMint: resolved.longMint,
|
|
302
|
-
underlyingMint: resolved.underlyingMint!,
|
|
303
|
-
marketData: resolved.marketData,
|
|
359
|
+
...buyParams,
|
|
304
360
|
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
361
|
});
|
|
315
362
|
|
|
316
363
|
return prependSwitchboardQuote(quote, actionTx);
|
|
@@ -345,7 +392,7 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
345
392
|
rpc: params.rpc,
|
|
346
393
|
});
|
|
347
394
|
|
|
348
|
-
const [refetchedPool, remainingAccounts, buyerPosition, buyerOptionAccount] =
|
|
395
|
+
const [refetchedPool, remainingAccounts, buyerPosition, buyerOptionAccount, paymentAccounts] =
|
|
349
396
|
await Promise.all([
|
|
350
397
|
fetchOptionPool(params.rpc, resolved.optionPool),
|
|
351
398
|
getBuyFromPoolRemainingAccounts(params.rpc, resolved.optionPool, params.programId),
|
|
@@ -359,6 +406,15 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
359
406
|
params.buyerOptionAccount
|
|
360
407
|
? Promise.resolve(params.buyerOptionAccount)
|
|
361
408
|
: deriveAssociatedTokenAddress(params.buyer, resolved.longMint),
|
|
409
|
+
resolveBuyPaymentAccounts({
|
|
410
|
+
rpc: params.rpc,
|
|
411
|
+
buyer: params.buyer,
|
|
412
|
+
premiumVault: resolved.premiumVault!,
|
|
413
|
+
underlyingMint: resolved.underlyingMint!,
|
|
414
|
+
collateralMint: resolved.collateralPoolData?.collateralMint,
|
|
415
|
+
paymentMint: params.paymentMint,
|
|
416
|
+
buyerPaymentAccount: params.buyerPaymentAccount,
|
|
417
|
+
}),
|
|
362
418
|
]);
|
|
363
419
|
|
|
364
420
|
invariant(
|
|
@@ -392,18 +448,36 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
392
448
|
const onchainPad = (q: bigint) =>
|
|
393
449
|
(q * BigInt(POOL_BUY_MAX_PREMIUM_ONCHAIN_PAD_BPS)) / 10_000n;
|
|
394
450
|
const slippageBuffer = hasExplicitSlippageBuffer
|
|
395
|
-
? normalizeMarketOrderSlippageBuffer(params,
|
|
451
|
+
? normalizeMarketOrderSlippageBuffer(params, paymentAccounts.paymentMint) +
|
|
396
452
|
onchainPad(quotePremium)
|
|
397
453
|
: globalTradeConfig.slippageBps !== undefined
|
|
398
454
|
? applySlippageBps(
|
|
399
455
|
quotePremium,
|
|
400
456
|
globalTradeConfig.slippageBps + POOL_BUY_MAX_PREMIUM_ONCHAIN_PAD_BPS
|
|
401
457
|
) - quotePremium
|
|
402
|
-
: normalizeMarketOrderSlippageBuffer(params,
|
|
458
|
+
: normalizeMarketOrderSlippageBuffer(params, paymentAccounts.paymentMint) +
|
|
403
459
|
onchainPad(quotePremium);
|
|
404
460
|
const maxPremiumAmount = quotePremium + slippageBuffer;
|
|
405
461
|
assertPositiveAmount(maxPremiumAmount, "maxPremiumAmount");
|
|
406
462
|
|
|
463
|
+
const marketBuyParams = {
|
|
464
|
+
optionPool: resolved.optionPool,
|
|
465
|
+
optionAccount: resolved.optionAccount,
|
|
466
|
+
longMint: resolved.longMint,
|
|
467
|
+
underlyingMint: refetchedPool.underlyingMint,
|
|
468
|
+
marketData: resolved.marketData,
|
|
469
|
+
buyer: params.buyer,
|
|
470
|
+
paymentMint: paymentAccounts.paymentMint,
|
|
471
|
+
buyerPaymentAccount: paymentAccounts.buyerPaymentAccount,
|
|
472
|
+
escrowLongAccount: refetchedPool.escrowLongAccount,
|
|
473
|
+
premiumVault: refetchedPool.premiumVault,
|
|
474
|
+
quantity: params.quantity,
|
|
475
|
+
premiumAmount: maxPremiumAmount,
|
|
476
|
+
buyerPosition,
|
|
477
|
+
buyerOptionAccount,
|
|
478
|
+
remainingAccounts,
|
|
479
|
+
};
|
|
480
|
+
|
|
407
481
|
const marketDataAccount = await fetchMarketDataAccount(params.rpc, resolved.marketData);
|
|
408
482
|
invariant(
|
|
409
483
|
!!marketDataAccount,
|
|
@@ -419,21 +493,8 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
419
493
|
? toAddress(params.switchboardQueue)
|
|
420
494
|
: getDefaultSwitchboardQueueAddress(network);
|
|
421
495
|
return buildBuyFromPoolTransaction({
|
|
422
|
-
|
|
423
|
-
optionAccount: resolved.optionAccount,
|
|
424
|
-
longMint: resolved.longMint,
|
|
425
|
-
underlyingMint: refetchedPool.underlyingMint,
|
|
426
|
-
marketData: resolved.marketData,
|
|
496
|
+
...marketBuyParams,
|
|
427
497
|
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
498
|
});
|
|
438
499
|
}
|
|
439
500
|
|
|
@@ -452,21 +513,8 @@ export async function buildBuyFromPoolMarketOrderTransactionWithDerivation(
|
|
|
452
513
|
});
|
|
453
514
|
|
|
454
515
|
const actionTx = await buildBuyFromPoolTransaction({
|
|
455
|
-
|
|
456
|
-
optionAccount: resolved.optionAccount,
|
|
457
|
-
longMint: resolved.longMint,
|
|
458
|
-
underlyingMint: refetchedPool.underlyingMint,
|
|
459
|
-
marketData: resolved.marketData,
|
|
516
|
+
...marketBuyParams,
|
|
460
517
|
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
518
|
});
|
|
471
519
|
|
|
472
520
|
return prependSwitchboardQuote(quote, actionTx);
|
|
@@ -508,10 +556,36 @@ export async function buildCloseLongToPoolInstruction(
|
|
|
508
556
|
}
|
|
509
557
|
|
|
510
558
|
export async function buildCloseLongToPoolTransaction(
|
|
511
|
-
params: BuildCloseLongToPoolParams
|
|
559
|
+
params: BuildCloseLongToPoolParams & { createPayoutAtas?: boolean }
|
|
512
560
|
): Promise<BuiltTransaction> {
|
|
561
|
+
const collateralMint = params.collateralMint ?? params.underlyingMint;
|
|
562
|
+
const isPhysicalSettlement =
|
|
563
|
+
toAddress(collateralMint) === toAddress(params.underlyingMint);
|
|
564
|
+
const instructions: Instruction<string>[] = [];
|
|
565
|
+
|
|
566
|
+
if (params.createPayoutAtas !== false) {
|
|
567
|
+
instructions.push(
|
|
568
|
+
await getCreateAssociatedTokenIdempotentInstructionWithAddress(
|
|
569
|
+
params.buyer,
|
|
570
|
+
params.buyer,
|
|
571
|
+
toAddress(collateralMint),
|
|
572
|
+
toAddress(params.buyerPayoutAccount)
|
|
573
|
+
)
|
|
574
|
+
);
|
|
575
|
+
if (!isPhysicalSettlement) {
|
|
576
|
+
instructions.push(
|
|
577
|
+
await getCreateAssociatedTokenIdempotentInstructionWithAddress(
|
|
578
|
+
params.buyer,
|
|
579
|
+
params.buyer,
|
|
580
|
+
toAddress(params.underlyingMint),
|
|
581
|
+
toAddress(params.buyerUnderlyingPayoutAccount)
|
|
582
|
+
)
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
513
587
|
const instruction = await buildCloseLongToPoolInstruction(params);
|
|
514
|
-
|
|
588
|
+
instructions.push(instruction);
|
|
515
589
|
|
|
516
590
|
if (params.closeLongTokenAccount === true) {
|
|
517
591
|
instructions.push(
|
|
@@ -563,10 +637,12 @@ export interface BuildCloseLongToPoolTransactionWithDerivationParams {
|
|
|
563
637
|
*/
|
|
564
638
|
closeLongTokenAccount?: boolean;
|
|
565
639
|
/**
|
|
566
|
-
* When true
|
|
567
|
-
*
|
|
640
|
+
* When true, unwraps physical-settlement WSOL payout to native SOL after close.
|
|
641
|
+
* Defaults to true only for physical WSOL pools; cash-settled pools default to false.
|
|
568
642
|
*/
|
|
569
643
|
unwrapPayoutSol?: boolean;
|
|
644
|
+
/** When false, skips idempotent create-ATA for payout accounts (default: true). */
|
|
645
|
+
createPayoutAtas?: boolean;
|
|
570
646
|
remainingAccounts?: RemainingAccountInput[];
|
|
571
647
|
disableSwitchboardCrank?: boolean;
|
|
572
648
|
switchboardCrossbarUrl?: string;
|
|
@@ -619,10 +695,13 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
|
619
695
|
|
|
620
696
|
const isWsolUnderlying =
|
|
621
697
|
toAddress(resolved.underlyingMint!) === toAddress(NATIVE_MINT);
|
|
698
|
+
const isPhysicalSettlement =
|
|
699
|
+
toAddress(collateralMint) === toAddress(resolved.underlyingMint!);
|
|
622
700
|
const closeLongTokenAccount =
|
|
623
701
|
params.closeLongTokenAccount !== false;
|
|
624
702
|
const unwrapPayoutSol =
|
|
625
|
-
params.unwrapPayoutSol
|
|
703
|
+
params.unwrapPayoutSol ??
|
|
704
|
+
(isWsolUnderlying && isPhysicalSettlement);
|
|
626
705
|
|
|
627
706
|
// close_long_to_pool requires a complete set of active WriterPositions so the
|
|
628
707
|
// program can run the strict Hamilton completeness check. Auto-populate if
|
|
@@ -670,6 +749,7 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
|
670
749
|
buyerPosition,
|
|
671
750
|
closeLongTokenAccount,
|
|
672
751
|
unwrapPayoutSol,
|
|
752
|
+
createPayoutAtas: params.createPayoutAtas,
|
|
673
753
|
remainingAccounts,
|
|
674
754
|
});
|
|
675
755
|
}
|
|
@@ -709,6 +789,7 @@ export async function buildCloseLongToPoolTransactionWithDerivation(
|
|
|
709
789
|
buyerPosition,
|
|
710
790
|
closeLongTokenAccount,
|
|
711
791
|
unwrapPayoutSol,
|
|
792
|
+
createPayoutAtas: params.createPayoutAtas,
|
|
712
793
|
remainingAccounts,
|
|
713
794
|
});
|
|
714
795
|
|
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;
|
|
@@ -251,9 +252,10 @@ export async function preflightUnwindWriterUnsold(
|
|
|
251
252
|
const collateralPoolData = resolved.collateralPoolData;
|
|
252
253
|
|
|
253
254
|
const underlyingMint = params.underlyingMint ?? resolved.underlyingMint;
|
|
254
|
-
const
|
|
255
|
+
const collateralMint = collateralPoolData.collateralMint ?? underlyingMint;
|
|
256
|
+
const [vaultPda] = await deriveVaultPda(collateralMint, params.programId);
|
|
255
257
|
const vaultPdaAddress = toAddress(vaultPda);
|
|
256
|
-
const writerDefaultRepaymentAta = await deriveAssociatedTokenAddress(params.writer,
|
|
258
|
+
const writerDefaultRepaymentAta = await deriveAssociatedTokenAddress(params.writer, collateralMint);
|
|
257
259
|
// `writerRepaymentAccount` stays in the result for ABI compatibility with
|
|
258
260
|
// existing callers (e.g. wallet-fallback UX from pre-convergence), but it
|
|
259
261
|
// is not used on-chain anymore.
|
|
@@ -455,12 +457,12 @@ export async function preflightUnwindWriterUnsold(
|
|
|
455
457
|
const thetaAvailable = computeThetaBalance(writerPosition, optionPoolData.accThetaPerOiFp);
|
|
456
458
|
|
|
457
459
|
// Theta can only offset debt when premium_vault.mint == omlp_vault.mint.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
460
|
+
const premiumVaultMint = await fetchTokenAccountMint(
|
|
461
|
+
params.rpc,
|
|
462
|
+
resolved.premiumVault!
|
|
463
|
+
);
|
|
462
464
|
const premiumMintMatchesCollateralMint =
|
|
463
|
-
toAddress(
|
|
465
|
+
toAddress(premiumVaultMint) === toAddress(collateralPoolData.collateralMint);
|
|
464
466
|
|
|
465
467
|
const thetaToDebt = premiumMintMatchesCollateralMint
|
|
466
468
|
? minBigInt(thetaAvailable, proportionalTotalOwed)
|