@gabox-labs/sdk 0.6.0 → 0.7.1

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/dist/index.js CHANGED
@@ -1,457 +1,9 @@
1
- import { B as COMPUTE_BUDGET_PROGRAM_ADDRESS, E as curveBuyExactOut, G as computeBudgetInstructions, H as DEFAULT_COMPUTE_UNIT_LIMIT, K as getSetComputeUnitLimitInstruction, Lt as raydiumIds, P as launchlabBuyAccounts, R as BUY_PACK_COMPUTE_UNITS, U as MAX_COMPUTE_UNIT_LIMIT, V as CREATE_MACHINE_COMPUTE_UNITS, W as REDEEM_COMPUTE_UNITS, _ as newCurveReserves, b as wsolAccountFor, c as getLaunchInstruction, cn as WSOL_MINT, ct as ata, en as LAUNCH_TOTAL_BASE_SELL, ft as creatorFeeVaultAddress, kt as LAUNCH_DECIMALS, l as buildMessage, m as fetchCurveSettings, nn as PLATFORM_ADMIN, on as SYSTEM_PROGRAM_ADDRESS, ot as tokenAccountAmount, q as getSetComputeUnitPriceInstruction, sn as TOKEN_PROGRAM_ADDRESS, t as raydium_exports, tn as METAPLEX_PROGRAM_ADDRESS, u as withRemainingAccounts, v as resolveVenue, vt as launchlabPoolAddress, xt as platformFeeVaultAddress, yt as launchlabVaultAddress, z as CLAIM_COMPUTE_UNITS, zt as ASSOCIATED_TOKEN_PROGRAM_ADDRESS } from "./raydium-B-l9V3O-.js";
2
- import { DRAW_DISCRIMINATOR, DRAW_RESOLVED_EVENT_DISCRIMINATOR, GABOX_PROGRAM_ADDRESS, PACK_BOUGHT_EVENT_DISCRIMINATOR, POOL_CREATED_EVENT_DISCRIMINATOR, POOL_DISCRIMINATOR, PRIZES_FUNDED_EVENT_DISCRIMINATOR, RANDOMNESS_RETRIED_EVENT_DISCRIMINATOR, TOKENS_SOLD_EVENT_DISCRIMINATOR, WALLET_ACTIVITY_DISCRIMINATOR, decodeDraw, decodePool, decodeWalletActivity, findActivityPda, findDrawPda, findIdentityPda, findPoolPda, getBuyPackInstructionAsync, getDrawResolvedEventDecoder, getDrawSize, getExpireDrawInstruction, getFundPrizesInstruction, getInitializePoolInstructionAsync, getPackBoughtEventDecoder, getPoolCreatedEventDecoder, getPoolSize, getPrizesFundedEventDecoder, getRandomnessRetriedEventDecoder, getRetryDrawInstruction, getSellTokensInstructionAsync, getTokensSoldEventDecoder, getWalletActivitySize, t as generated_exports } from "./generated/index.js";
3
- import { address, createSolanaRpc, createSolanaRpcSubscriptions, fetchEncodedAccount, getAddressEncoder, getBase58Decoder, getBase64Encoder, getBytesEncoder, getProgramDerivedAddress } from "@solana/kit";
1
+ import { Dt as POOL_DISCRIMINATOR, I as getFundPrizesInstruction, Ot as decodePool, Rt as DRAW_DISCRIMINATOR, W as getExpireDrawInstruction, _t as decodeWalletActivity, b as getRetryDrawInstruction, ft as findPoolPda, gt as WALLET_ACTIVITY_DISCRIMINATOR, ht as findActivityPda, k as getInitializePoolInstructionAsync, mt as findDrawPda, p as getSellTokensInstructionAsync, pt as findIdentityPda, st as getBuyPackInstructionAsync, zt as decodeDraw } from "./gabox-DGTCh34U.js";
2
+ import { $ as computeBudgetInstructions, $t as launchlabVaultAddress, B as fetchQuoteAsset, Bt as vaultAddress, Cn as PACK_TOKENS, Dn as VRF_DEFAULT_QUEUE, E as cpmmSwapBaseOutput, En as TIMEOUT_SLOTS, Et as decodeCpmmPool, Ft as activityAddress, G as readAccounts, H as fetchQuoteDisplay, Ht as ata, I as launchlabBuyAccounts, In as LAUNCH_DECIMALS, It as associatedTokenAddress, J as COMPUTE_BUDGET_PROGRAM_ADDRESS, K as BUY_PACK_COMPUTE_UNITS, Kn as ASSOCIATED_TOKEN_PROGRAM_ADDRESS, Kt as creatorFeeVaultAddress, Lt as drawAddress, Nt as tokenAccountAmount, O as curveBuyExactOut, On as VRF_PROGRAM_ADDRESS, Q as REDEEM_COMPUTE_UNITS, Qt as launchlabPoolAddress, Rt as poolAddress, S as wsolAccountFor, Sn as MAX_ATTEMPTS, T as cpmmSwapBaseInput, Tn as SLOT_HASHES_SYSVAR, Tt as decodeCpmmAmmConfig, Vt as vrfIdentityAddress, Wn as raydiumIds, X as DEFAULT_COMPUTE_UNIT_LIMIT, Xn as CPMM_SWAP_BASE_INPUT, Y as CREATE_MACHINE_COMPUTE_UNITS, Z as MAX_COMPUTE_UNIT_LIMIT, Zn as CPMM_SWAP_BASE_OUTPUT, _n as validatePack, _t as tiersOf, an as MAX_MULTIPLIER_BPS, at as POOL_MINT_OFFSET, b as resolveVenue, bn as IDENTITY_SEED, c as getLaunchInstruction, cn as TIERS, cr as METAPLEX_PROGRAM_ADDRESS, ct as fetchPoolByMint, dn as maxMultiplierBps, dt as fetchWalletActivity, et as getSetComputeUnitLimitInstruction, f as creatorFeeOnInput, fn as quote, ft as listDraws, gn as uncappedMaximum, gr as WSOL_MINT, gt as quotePool, hn as tierAmount, hr as TOKEN_PROGRAM_ADDRESS, ht as listPools, in as GaboxMathError, it as POOL_CREATOR_OFFSET, l as buildMessage, ln as averageMultiplierBps, lr as PLATFORM_ADMIN, lt as fetchPoolInventory, m as fetchCurveSettings, mn as seedTokens, mt as listDrawsByPurchaser, nn as BPS, nt as DRAW_POOL_OFFSET, on as MIN_SEED_MULTIPLIER_BPS, ot as fetchDraw, pn as resolveReservation, pr as SYSTEM_PROGRAM_ADDRESS, pt as listDrawsByPool, q as CLAIM_COMPUTE_UNITS, rn as DEFAULT_TIERS, rt as DRAW_PURCHASER_OFFSET, sn as TICKETS, sr as LAUNCH_TOTAL_BASE_SELL, st as fetchPoolAt, t as raydium_exports, tn as platformFeeVaultAddress, tt as getSetComputeUnitPriceInstruction, u as withRemainingAccounts, un as choose, ut as fetchVaultBalance, v as newCurveReserves, vn as validateTiers, wn as RETRY_SLOTS, xn as INSTRUCTIONS_SYSVAR, y as quoteAccountFor, yn as GABOX_PROGRAM_ID, z as order, zt as scopedVrfIdentityAddress } from "./raydium-CU-tZzIk.js";
3
+ import { DRAW_RESOLVED_EVENT_DISCRIMINATOR, PACK_BOUGHT_EVENT_DISCRIMINATOR, POOL_CREATED_EVENT_DISCRIMINATOR, PRIZES_FUNDED_EVENT_DISCRIMINATOR, RANDOMNESS_RETRIED_EVENT_DISCRIMINATOR, TOKENS_SOLD_EVENT_DISCRIMINATOR, getDrawResolvedEventDecoder, getPackBoughtEventDecoder, getPoolCreatedEventDecoder, getPrizesFundedEventDecoder, getRandomnessRetriedEventDecoder, getTokensSoldEventDecoder, t as generated_exports } from "./generated/index.js";
4
+ import { AccountRole, address, createNoopSigner, createSolanaRpc, createSolanaRpcSubscriptions, getAddressDecoder, getBase64Encoder, getU64Encoder } from "@solana/kit";
4
5
  import { getCloseAccountInstruction, getCreateAssociatedTokenIdempotentInstruction, getSyncNativeInstruction } from "@solana-program/token";
5
6
  import { getTransferSolInstruction } from "@solana-program/system";
6
- //#region src/ids.ts
7
- /** `declare_id!` in `programs/gabox/src/lib.rs`. */
8
- const GABOX_PROGRAM_ID = GABOX_PROGRAM_ADDRESS;
9
- /** MagicBlock's ephemeral VRF program. `vrf.rs` pins it and refuses any other. */
10
- const VRF_PROGRAM_ADDRESS = "Vrf1RNUjXmQGjmQrQLvJHs9SNkvDJEsRVFPkfSQUwGz";
11
- /**
12
- * MagicBlock's default oracle queue. The program pins this one address, so a pool creator cannot
13
- * point a draw at an oracle they control.
14
- */
15
- const VRF_DEFAULT_QUEUE = "Cuj97ggrhhidhbu39TijNVqE74xvKJ69gDervRUXAxGh";
16
- /** The seed of both identity PDAs. See `pdas.ts` — there are two, under different programs. */
17
- const IDENTITY_SEED = new Uint8Array([
18
- 105,
19
- 100,
20
- 101,
21
- 110,
22
- 116,
23
- 105,
24
- 116,
25
- 121
26
- ]);
27
- const SLOT_HASHES_SYSVAR = "SysvarS1otHashes111111111111111111111111111";
28
- const INSTRUCTIONS_SYSVAR = "Sysvar1nstructions1111111111111111111111111";
29
- /** `constants.rs`. A retry may not run sooner than this after the last attempt. */
30
- const RETRY_SLOTS = 300n;
31
- /** `constants.rs`. After this many slots from the purchase, anyone may expire the draw. */
32
- const TIMEOUT_SLOTS = 216000n;
33
- /** `constants.rs`. Three requests in total, counting the one `buy_pack` makes. */
34
- const MAX_ATTEMPTS = 3;
35
- /**
36
- * `constants.rs`. Tokens in one pack, in base units: 1,000,000 tokens at the fixed 6 decimals, or
37
- * 0.1% of the 1,000,000,000 supply. Every pool sells packs of this size. The venue decides what a
38
- * pack costs, so the pack price follows the coin. Read `pool.packTokens` rather than this when a
39
- * pool is at hand: a later program version may change the constant.
40
- */
41
- const PACK_TOKENS = 1000000n * 1000000n;
42
- //#endregion
43
- //#region src/math.ts
44
- /**
45
- * A bigint port of `programs/gabox/src/math.rs`.
46
- *
47
- * The point is that a client can show a buyer the exact prize table the program will freeze into
48
- * their `Draw`, before they pay. Every rounding step here matches the Rust, including the direction
49
- * of each division. `test/math.test.ts` runs the same vectors as `programs/gabox/tests/math.rs`.
50
- *
51
- * All amounts are `bigint`, in the mint's smallest unit. `multiplierBps` and `tickets` are numbers
52
- * because both are `u32` in the program and both stay small.
53
- */
54
- /** Basis points. A multiplier of 10_000 pays back exactly one pack. */
55
- const BPS = 10000n;
56
- /**
57
- * `constants::MIN_SEED_MULTIPLIER_BPS`. The mandatory seed makes a 3x top prize payable on the
58
- * first pack, or the full top prize when the table's top tier is below 3x. A creator can add more
59
- * seed on top through `extraSeedTokens`.
60
- */
61
- const MIN_SEED_MULTIPLIER_BPS = 3e4;
62
- /**
63
- * `constants::MAX_MULTIPLIER_BPS`. A table's top tier can be at most 20x. Seed beyond what a 20x
64
- * prize needs stays in the vault as backup for the draws after a top-tier hit.
65
- */
66
- const MAX_MULTIPLIER_BPS = 2e5;
67
- /** Ticket counts must sum to this. A uniform 16-bit word then maps with no modulo bias. */
68
- const TICKETS = 65536;
69
- /** The prize table has exactly this many slots. Unused slots are all-zero. */
70
- const TIERS = 8;
71
- /** Thrown by every function here. `code` matches a `GaboxError` variant name. */
72
- var GaboxMathError = class extends Error {
73
- code;
74
- constructor(code, message) {
75
- super(`${code}: ${message}`);
76
- this.name = "GaboxMathError";
77
- this.code = code;
78
- }
79
- };
80
- const U64_MAX = (1n << 64n) - 1n;
81
- const U32_MAX = 4294967295;
82
- /**
83
- * The default prize table. The program does not enforce this table: it accepts any table that
84
- * passes `validateTiers`. This is only the table the Gabox app creates its pools with, and the
85
- * starting point for a client that has no reason to pick another one.
86
- *
87
- * Common 75% at 0.52x, Rare 20% at 1.2x, Epic 4% at 3x, Mythic 1% at 20x. Expected payout is
88
- * 0.9499x of a pack, so the seed is 19 packs.
89
- */
90
- const DEFAULT_TIERS = Object.freeze([
91
- Object.freeze({
92
- multiplierBps: 5201,
93
- tickets: 49153
94
- }),
95
- Object.freeze({
96
- multiplierBps: 12e3,
97
- tickets: 13107
98
- }),
99
- Object.freeze({
100
- multiplierBps: 3e4,
101
- tickets: 2621
102
- }),
103
- Object.freeze({
104
- multiplierBps: 2e5,
105
- tickets: 655
106
- }),
107
- Object.freeze({
108
- multiplierBps: 0,
109
- tickets: 0
110
- }),
111
- Object.freeze({
112
- multiplierBps: 0,
113
- tickets: 0
114
- }),
115
- Object.freeze({
116
- multiplierBps: 0,
117
- tickets: 0
118
- }),
119
- Object.freeze({
120
- multiplierBps: 0,
121
- tickets: 0
122
- })
123
- ]);
124
- function checkedU64(value, what) {
125
- if (value < 0n || value > U64_MAX) throw new GaboxMathError("Arithmetic", `${what} does not fit in u64`);
126
- return value;
127
- }
128
- /** Tier fields encode as program `u32`s. Reject JavaScript-only values before `BigInt` or codecs. */
129
- function checkedU32(value, what) {
130
- if (!Number.isInteger(value) || value < 0 || value > U32_MAX) throw new GaboxMathError("InvalidDistribution", `${what} must be an integer from 0 to ${U32_MAX}`);
131
- return value;
132
- }
133
- /** `math::tokens`. Floor division, and an overflow past u64 is an error, not a wrap. */
134
- function tierAmount(base, multiplierBps) {
135
- return checkedU64(base * BigInt(checkedU32(multiplierBps, "multiplierBps")) / BPS, "tier amount");
136
- }
137
- /**
138
- * `math::validate`. Checks the table alone, with no base.
139
- *
140
- * Four rules, and each one closes a way to sell a bad ticket:
141
- * - ticket counts sum to exactly 65,536, so the 16-bit draw is uniform;
142
- * - an unused row is all-zero, so a hidden multiplier cannot ride along;
143
- * - a ticketed tier's multiplier is from 1 to `MAX_MULTIPLIER_BPS` (20x);
144
- * - the expected multiplier over all tickets is at most 1x, so the table cannot promise more
145
- * tokens than a pack buys. This bounds tokens, not cash value.
146
- */
147
- function validateTiers(tiers) {
148
- if (tiers.length !== 8) throw new GaboxMathError("InvalidDistribution", `expected 8 tiers, got ${tiers.length}`);
149
- let count = 0n;
150
- let expected = 0n;
151
- for (const [index, tier] of tiers.entries()) {
152
- checkedU32(tier.tickets, `tier ${index} tickets`);
153
- checkedU32(tier.multiplierBps, `tier ${index} multiplierBps`);
154
- if (tier.tickets === 0) {
155
- if (tier.multiplierBps !== 0) throw new GaboxMathError("InvalidDistribution", "a tier with no tickets must have no multiplier");
156
- continue;
157
- }
158
- if (tier.multiplierBps <= 0) throw new GaboxMathError("InvalidDistribution", "a ticketed tier must pay something; it would sell a ticket that can only win zero");
159
- if (tier.multiplierBps > 2e5) throw new GaboxMathError("InvalidDistribution", `tier ${index} multiplierBps exceeds the ${MAX_MULTIPLIER_BPS} maximum (20x)`);
160
- count += BigInt(tier.tickets);
161
- expected += BigInt(tier.multiplierBps) * BigInt(tier.tickets);
162
- }
163
- if (count !== BigInt(65536)) throw new GaboxMathError("InvalidDistribution", `tickets must sum to ${TICKETS}, they sum to ${count}`);
164
- if (expected > 10000n * BigInt(65536)) throw new GaboxMathError("UnfundedExpectation", "the expected token award exceeds the tokens a pack buys");
165
- }
166
- /**
167
- * `math::validate_pack`. Every ticketed tier must pay at least one token for a pack of this size.
168
- *
169
- * The program checks this once at creation. At `PACK_TOKENS` no sane table fails it; it exists so
170
- * a table cannot sell a ticket that can only win zero.
171
- */
172
- function validatePack(packTokens, tiers) {
173
- if (packTokens <= 0n) throw new GaboxMathError("ZeroAmount", "packTokens must be positive");
174
- checkedU64(packTokens, "pack tokens");
175
- validateTiers(tiers);
176
- for (const [i, tier] of tiers.entries()) {
177
- if (tier.tickets === 0) continue;
178
- if (tierAmount(packTokens, tier.multiplierBps) === 0n) throw new GaboxMathError("PackTooSmall", `tier ${i} rounds to zero tokens at this pack size`);
179
- }
180
- }
181
- /**
182
- * `math::seed_tokens`. The mandatory seed a table needs, in tokens.
183
- *
184
- * The mandatory seed makes a 3x top prize payable on the first pack, or the full top prize when
185
- * the table's top tier is below 3x. A jackpot above 3x still shows the full table once later
186
- * packs grow the free vault; the mandatory seed alone does not have to fund it. The pack itself
187
- * brings `packTokens` into the vault, so the vault has to hold the rest beforehand:
188
- *
189
- * seedTokens = min(uncappedMaximum(packTokens, tiers), tierAmount(packTokens, MIN_SEED_MULTIPLIER_BPS)) - packTokens
190
- *
191
- * A 20x or a 3x top tier on a 1M-token pack both need a 2M-token seed, since the mandatory seed
192
- * stops at the 3x minimum. A 2x top tier needs a 1M-token seed, already below the minimum. A
193
- * table whose top tier pays exactly one pack needs no seed. A top tier below one pack is refused:
194
- * every ticket would lose. `createMachine` lets a creator add more seed on top of this mandatory
195
- * amount through `extraSeedTokens`, for example to fund a top tier above 3x from the first pack.
196
- *
197
- * Pool initialization computes this number itself and buys exactly that many tokens on the curve.
198
- * The creator only signs a maximum SOL cost.
199
- */
200
- function seedTokens(packTokens, tiers) {
201
- validateTiers(tiers);
202
- const largest = uncappedMaximum(packTokens, tiers);
203
- if (largest < packTokens) throw new GaboxMathError("JackpotBelowOnePack", "the largest tier must pay at least one pack");
204
- const minimum = tierAmount(packTokens, MIN_SEED_MULTIPLIER_BPS);
205
- return (largest < minimum ? largest : minimum) - packTokens;
206
- }
207
- /**
208
- * `math::uncapped_maximum`. The largest tier's award for this base, before any inventory cap.
209
- *
210
- * At `packTokens` this is the jackpot in tokens. `seedTokens` is this minus one pack.
211
- */
212
- function uncappedMaximum(base, tiers) {
213
- validateTiers(tiers);
214
- let largest = 0n;
215
- for (const tier of tiers) {
216
- if (tier.tickets === 0) continue;
217
- const amount = tierAmount(base, tier.multiplierBps);
218
- if (amount > largest) largest = amount;
219
- }
220
- return largest;
221
- }
222
- /**
223
- * `math::quote`. The offer a pack of `base` tokens would freeze right now. `base` is always
224
- * `pool.packTokens`; the parameter stays general so the vectors can use small numbers.
225
- *
226
- * `inventory` is the vault's token balance and `reserved` is `pool.reserved`. The difference is
227
- * free inventory. This pack's own `base` is added to it, because the purchase and the offer are
228
- * one transaction — the tokens are in the vault before the draw is written.
229
- *
230
- * Every amount is capped at what is actually available. A tier that rounds to zero tokens is an
231
- * error: the pool must not sell a ticket that can only win nothing.
232
- */
233
- function quote(base, tiers, inventory, reserved) {
234
- validateTiers(tiers);
235
- if (inventory < reserved) throw new GaboxMathError("InsolventInventory", "the vault holds less than the pool has already reserved");
236
- const available = checkedU64(inventory - reserved + base, "available inventory");
237
- const prizes = Array.from({ length: 8 }, () => ({
238
- amount: 0n,
239
- tickets: 0
240
- }));
241
- let maximum = 0n;
242
- let minimum = U64_MAX;
243
- for (const [i, tier] of tiers.entries()) {
244
- if (tier.tickets === 0) continue;
245
- const uncapped = tierAmount(base, tier.multiplierBps);
246
- if (uncapped === 0n) throw new GaboxMathError("PackTooSmall", `tier ${i} rounds to zero tokens at this pack size`);
247
- const amount = uncapped < available ? uncapped : available;
248
- prizes[i] = {
249
- amount,
250
- tickets: tier.tickets
251
- };
252
- if (amount > maximum) maximum = amount;
253
- if (amount < minimum) minimum = amount;
254
- }
255
- return {
256
- prizes,
257
- maximum,
258
- minimum
259
- };
260
- }
261
- /**
262
- * `math::choose`. Which prize a 16-bit ticket wins.
263
- *
264
- * The rows are consecutive ranges in table order, so this is a running total and a comparison.
265
- * A ticket past the last row returns `0`, which cannot happen for a validated table.
266
- */
267
- function choose(prizes, ticket) {
268
- let end = 0;
269
- for (const prize of prizes) {
270
- end += prize.tickets;
271
- if (ticket < end) return prize.amount;
272
- }
273
- return 0n;
274
- }
275
- /** `math::resolve_reservation`. Release the unwon part of a maximum, keep the award reserved. */
276
- function resolveReservation(reserved, maximum, award) {
277
- if (award > maximum) throw new GaboxMathError("InsolventInventory", "the award exceeds the reserved maximum");
278
- if (reserved < maximum) throw new GaboxMathError("Arithmetic", "the pool has reserved less than this draw holds");
279
- return checkedU64(reserved - maximum + award, "reserved");
280
- }
281
- /** The largest `multiplierBps` on any ticketed row. `0` for a table with no rows. */
282
- function maxMultiplierBps(tiers) {
283
- validateTiers(tiers);
284
- let largest = 0;
285
- for (const tier of tiers) {
286
- if (tier.tickets === 0) continue;
287
- if (tier.multiplierBps > largest) largest = tier.multiplierBps;
288
- }
289
- return largest;
290
- }
291
- /**
292
- * The expected multiplier over all 65,536 tickets, in basis points. Rounded down.
293
- *
294
- * `validateTiers` caps this at 10,000. A table at 9,800 keeps 2% of every pack's tokens in the
295
- * vault on average, which is what lets a pool survive a run of top-tier wins.
296
- */
297
- function averageMultiplierBps(tiers) {
298
- validateTiers(tiers);
299
- let weighted = 0n;
300
- for (const tier of tiers) {
301
- if (tier.tickets === 0) continue;
302
- weighted += BigInt(tier.multiplierBps) * BigInt(tier.tickets);
303
- }
304
- return Number(weighted / BigInt(TICKETS));
305
- }
306
- //#endregion
307
- //#region src/pdas.ts
308
- /**
309
- * Gabox's own program-derived addresses.
310
- *
311
- * Raydium's live in `src/raydium/pdas.ts`, because they depend on the cluster. These do not: the
312
- * Gabox program id is the same everywhere.
313
- *
314
- * Classic SPL Token only. Both venues use it, and the accepted LaunchLab create instruction mints
315
- * the coin with it, so no derivation here takes a token program any more.
316
- */
317
- /** `["pool", mint]`. One pool per coin. */
318
- const poolAddress = async (mint) => (await findPoolPda({ mint }))[0];
319
- /** `["draw", pool, seq]`, with the sequence as eight little-endian bytes. */
320
- const drawAddress = async (pool, seq) => (await findDrawPda({
321
- pool,
322
- seq
323
- }))[0];
324
- /** The per-wallet `WalletActivity` PDA. `buy_pack` derives it from the purchaser automatically. */
325
- const activityAddress = async (wallet) => (await findActivityPda({ purchaser: wallet }))[0];
326
- /** `["identity"]` under Gabox. The PDA the program signs its randomness request with. */
327
- const vrfIdentityAddress = async () => (await findIdentityPda())[0];
328
- /** `["identity", gabox_program_id]` under the VRF program. MagicBlock signs the callback with it. */
329
- async function scopedVrfIdentityAddress() {
330
- return await getProgramDerivedAddress({
331
- programAddress: VRF_PROGRAM_ADDRESS,
332
- seeds: [getBytesEncoder().encode(IDENTITY_SEED), getAddressEncoder().encode(GABOX_PROGRAM_ID)]
333
- });
334
- }
335
- /** An associated token account under classic SPL Token. */
336
- const associatedTokenAddress = async (owner, mint, tokenProgram = TOKEN_PROGRAM_ADDRESS) => await ata(owner, mint, tokenProgram);
337
- /** The prize vault: the pool PDA's own associated token account for the coin. */
338
- const vaultAddress = async (mint) => await associatedTokenAddress(await poolAddress(mint), mint);
339
- //#endregion
340
- //#region src/accounts.ts
341
- /** Account readers and bounded discovery scans. Old pool layouts are intentionally unsupported. */
342
- const DISCRIMINATOR = 8;
343
- const DRAW_POOL_OFFSET = DISCRIMINATOR;
344
- const DRAW_PURCHASER_OFFSET = 40;
345
- const POOL_CREATOR_OFFSET = DISCRIMINATOR;
346
- const POOL_MINT_OFFSET = 40;
347
- const base58 = getBase58Decoder();
348
- const base64 = getBase64Encoder();
349
- const asBase58 = (bytes) => base58.decode(bytes);
350
- const memcmp = (offset, bytes) => ({ memcmp: {
351
- offset: BigInt(offset),
352
- bytes,
353
- encoding: "base58"
354
- } });
355
- const encoded = (address, data) => ({
356
- address,
357
- data,
358
- exists: true,
359
- executable: false,
360
- lamports: 0n,
361
- programAddress: GABOX_PROGRAM_ID,
362
- space: BigInt(data.length)
363
- });
364
- async function fetchPoolByMint(client, mint) {
365
- return await fetchPoolAt(client, await poolAddress(mint));
366
- }
367
- async function fetchPoolAt(client, address) {
368
- return decodeCurrentPool(await fetchEncodedAccount(client.rpc, address, { commitment: "confirmed" }));
369
- }
370
- function decodeCurrentPool(account) {
371
- if (!account.exists) return null;
372
- if (account.data.length !== getPoolSize()) throw new Error(`pool ${account.address} is not a Gabox pool and is intentionally unsupported`);
373
- return decodePool(account).data;
374
- }
375
- function decodeCurrentDraw(account) {
376
- if (!account.exists) return null;
377
- if (account.data.length !== getDrawSize()) throw new Error(`draw ${account.address} is not a Gabox draw and is intentionally unsupported`);
378
- return decodeDraw(account).data;
379
- }
380
- async function fetchDraw(client, address) {
381
- return decodeCurrentDraw(await fetchEncodedAccount(client.rpc, address, { commitment: "confirmed" }));
382
- }
383
- function decodeCurrentWalletActivity(account) {
384
- if (!account.exists) return null;
385
- if (account.data.length !== getWalletActivitySize()) throw new Error(`activity account ${account.address} is not a Gabox WalletActivity and is intentionally unsupported`);
386
- return decodeWalletActivity(account).data;
387
- }
388
- /**
389
- * A wallet's lifetime pack-buying activity, or `null` before it has bought its first pack.
390
- * `buy_pack` creates this account `init_if_needed`, so a fresh wallet has none yet.
391
- */
392
- async function fetchWalletActivity(client, wallet) {
393
- return decodeCurrentWalletActivity(await fetchEncodedAccount(client.rpc, await activityAddress(wallet), { commitment: "confirmed" }));
394
- }
395
- async function scan(rpc, filters, decode) {
396
- return (await rpc.getProgramAccounts(GABOX_PROGRAM_ID, {
397
- encoding: "base64",
398
- commitment: "confirmed",
399
- filters
400
- }).send()).map(({ pubkey, account }) => decode(pubkey, new Uint8Array(base64.encode(account.data[0]))));
401
- }
402
- async function listPools(client) {
403
- return await scan(client.rpc, [memcmp(0, asBase58(POOL_DISCRIMINATOR)), { dataSize: BigInt(getPoolSize()) }], (address, data) => ({
404
- address,
405
- data: decodePool(encoded(address, data)).data
406
- }));
407
- }
408
- async function listDraws(client, query = {}) {
409
- const filters = [memcmp(0, asBase58(DRAW_DISCRIMINATOR)), { dataSize: BigInt(getDrawSize()) }];
410
- if (query.pool) filters.push(memcmp(8, query.pool));
411
- if (query.purchaser) filters.push(memcmp(40, query.purchaser));
412
- return await scan(client.rpc, filters, (address, data) => ({
413
- address,
414
- data: decodeDraw(encoded(address, data)).data
415
- }));
416
- }
417
- const listDrawsByPool = async (client, pool) => await listDraws(client, { pool });
418
- const listDrawsByPurchaser = async (client, purchaser) => await listDraws(client, { purchaser });
419
- async function fetchVaultBalance(client, mint) {
420
- const vault = await vaultAddress(mint);
421
- const { value } = await client.rpc.getAccountInfo(vault, {
422
- encoding: "base64",
423
- commitment: "confirmed"
424
- }).send();
425
- return value ? tokenAccountAmount(new Uint8Array(base64.encode(value.data[0]))) : 0n;
426
- }
427
- async function fetchPoolInventory(client, mint) {
428
- const poolPda = await poolAddress(mint);
429
- const vaultPda = await vaultAddress(mint);
430
- const { value } = await client.rpc.getMultipleAccounts([poolPda, vaultPda], {
431
- encoding: "base64",
432
- commitment: "confirmed"
433
- }).send();
434
- const poolAccount = value[0];
435
- if (!poolAccount) return null;
436
- const pool = decodeCurrentPool(encoded(poolPda, new Uint8Array(base64.encode(poolAccount.data[0]))));
437
- if (!pool) return null;
438
- if (pool.vault !== vaultPda) throw new Error(`pool ${poolPda} has an unexpected vault; it is not a supported Gabox pool`);
439
- const vaultAccount = value[1];
440
- const inventory = vaultAccount ? tokenAccountAmount(new Uint8Array(base64.encode(vaultAccount.data[0]))) : 0n;
441
- return {
442
- poolAddress: poolPda,
443
- pool,
444
- inventory,
445
- reserved: pool.reserved,
446
- free: inventory > pool.reserved ? inventory - pool.reserved : 0n
447
- };
448
- }
449
- const tiersOf = (pool) => pool.tiers.map(({ multiplierBps, tickets }) => ({
450
- multiplierBps,
451
- tickets
452
- }));
453
- const quotePool = (inventory) => quote(inventory.pool.packTokens, tiersOf(inventory.pool), inventory.inventory, inventory.reserved);
454
- //#endregion
455
7
  //#region src/events.ts
456
8
  /** Decode Gabox's Anchor events. Draw accounts are closed on delivery; use `DrawResolved` as final state. */
457
9
  const b64 = getBase64Encoder();
@@ -583,7 +135,7 @@ async function findResolvedDraw(client, address) {
583
135
  //#endregion
584
136
  //#region src/lookupTables.ts
585
137
  /**
586
- * Shared devnet address lookup table, verified on 2026-09-18T07:26:38.218Z.
138
+ * Shared devnet address lookup table, verified on 2026-09-18T11:04:06.369Z.
587
139
  * Generated by scripts/deploy-lookup-table.ts. Existing indices are immutable;
588
140
  * keep this table active while clients use it. Authority is the devnet deploy wallet.
589
141
  */
@@ -702,7 +254,14 @@ const DEVNET_LOOKUP_TABLE_ADDRESSES = [
702
254
  address("DRaycpLY18LhpbydsBWbVJtxpNv9oXPgjRSfpF2bWpYb"),
703
255
  address("CXniRufdq5xL8t8jZAPxsPZDpuudwuJSPWnbcD5Y5Nxq"),
704
256
  address("G7YfJJp1TX1VtzN4V2yhPNSU23AKPSy1U2miRdwAByK5"),
705
- address("5WcPTEQ59UqpQzjZUPbU8QRGCbj7NeQNLDa7DbsLkLKT")
257
+ address("5WcPTEQ59UqpQzjZUPbU8QRGCbj7NeQNLDa7DbsLkLKT"),
258
+ address("USDCoctVLVnvTXBEuP9s8hntucdJokbo17RwHuNXemT"),
259
+ address("4wHbNkobu7iARU9MbCEqDSAq6JuQreGupG2Jsf2R3DFP"),
260
+ address("5Eu2G2USTy1pqphmQzQ2SBXWrBq5sdhgEh7hso9R2xix"),
261
+ address("A9qBhPy4k5UYW72hSgAkh1Epr2do69P54yzzcMV3yv6b"),
262
+ address("Aw93pmXP52u6WSW2HcafRxua1LDht5MZhhXaaR7qCjsN"),
263
+ address("CPLUA2NTYSGjsB1E9iXT3MrPn69WRFJvKTdJZw5NdEjh"),
264
+ address("7LnqjXdqJEdccWZQs5YJobQ8MDmcK4sG2oo4Ty4LBC8c")
706
265
  ];
707
266
  const DEVNET_ADDRESS_LOOKUP_TABLES = { [DEVNET_LOOKUP_TABLE_ADDRESS]: [...DEVNET_LOOKUP_TABLE_ADDRESSES] };
708
267
  /**
@@ -716,29 +275,207 @@ const DEVNET_ADDRESS_LOOKUP_TABLES = { [DEVNET_LOOKUP_TABLE_ADDRESS]: [...DEVNET
716
275
  function defaultAddressLookupTables(cluster) {
717
276
  return cluster === "devnet" ? { ...DEVNET_ADDRESS_LOOKUP_TABLES } : {};
718
277
  }
278
+ /** The address lookup table program. It owns every table account. */
279
+ const LOOKUP_TABLE_PROGRAM = address("AddressLookupTab1e1111111111111111111111111");
280
+ /**
281
+ * The fixed part of a table account, before its addresses: a 4-byte discriminator, the two slot
282
+ * fields, the start index, the optional authority, and two padding bytes.
283
+ */
284
+ const LOOKUP_TABLE_HEADER = 56;
285
+ /**
286
+ * Read lookup tables off chain by address, for compressing against tables this SDK does not pin.
287
+ *
288
+ * A router picks its own tables per quote, so their contents are only known at run time, and a
289
+ * message can only be compressed against a table whose addresses are loaded. An address with no
290
+ * account, a wrong owner, or a malformed body is skipped rather than failing the whole route: the
291
+ * message then carries those accounts in full, which is correct, only larger.
292
+ */
293
+ async function fetchAddressLookupTables(client, addresses) {
294
+ const wanted = [...new Set(addresses)];
295
+ if (wanted.length === 0) return {};
296
+ const { value } = await client.rpc.getMultipleAccounts(wanted, {
297
+ encoding: "base64",
298
+ commitment: "confirmed"
299
+ }).send();
300
+ const decoder = getAddressDecoder();
301
+ const tables = {};
302
+ for (const [index, account] of value.entries()) {
303
+ if (!account || account.owner !== LOOKUP_TABLE_PROGRAM) continue;
304
+ const data = Buffer.from(account.data[0], "base64");
305
+ const body = data.length - LOOKUP_TABLE_HEADER;
306
+ if (body <= 0 || body % 32 !== 0) continue;
307
+ const stored = [];
308
+ for (let at = LOOKUP_TABLE_HEADER; at < data.length; at += 32) stored.push(decoder.decode(new Uint8Array(data.subarray(at, at + 32))));
309
+ tables[wanted[index]] = stored;
310
+ }
311
+ return tables;
312
+ }
313
+ //#endregion
314
+ //#region src/route/leg.ts
315
+ /**
316
+ * The margin added to an exact-in fallback, in basis points.
317
+ *
318
+ * An exact-in quote prices one spend. The spend that buys the amount wanted is worked out from that
319
+ * price, and the price moves against a larger spend, so the result is always a little short without
320
+ * a margin. 1% is the same order as the slippage a caller already signs for on the pack itself.
321
+ */
322
+ const EXACT_IN_MARGIN_BPS = 100n;
323
+ /**
324
+ * The first exact-in quote's size, in lamports, when a pair has no exact-out route.
325
+ *
326
+ * It exists only to learn a price, so it is small enough that its own impact on the route is
327
+ * small, and large enough that a route quotes it at all. The real spend is worked out from the
328
+ * price it returns and re-quoted.
329
+ */
330
+ const PROBE_LAMPORTS = 100000000n;
331
+ /** How many times the exact-in fallback re-quotes before it gives up. */
332
+ const EXACT_IN_ATTEMPTS = 3;
333
+ /**
334
+ * A swap that leaves at least `amount` of `quoteMint` in the user's quote account, paid for in SOL.
335
+ *
336
+ * Exact-out when the pair has such a route, so the buyer spends only what the pack costs. Exact-in
337
+ * otherwise, which overshoots on purpose: the leftover quote stays in the buyer's own account.
338
+ */
339
+ async function routeQuoteIn(client, provider, input) {
340
+ const { quoteMint, amount, user } = input;
341
+ if (amount <= 0n) throw new Error("the quote amount to buy must be positive");
342
+ if (quoteMint === "So11111111111111111111111111111111111111112") throw new Error("a WSOL-quoted pool needs no route: the builders wrap SOL themselves");
343
+ try {
344
+ const route = await provider.exactOut(client, WSOL_MINT, quoteMint, amount, user);
345
+ if (route.outAmount < amount) throw new Error(`the exact-out route buys ${route.outAmount} of ${quoteMint}, which is below the ${amount} the pack costs`);
346
+ return route;
347
+ } catch (exactOutFailure) {
348
+ return await exactInFallback(client, provider, quoteMint, amount, user, exactOutFailure);
349
+ }
350
+ }
351
+ /**
352
+ * Work out the SOL that buys `amount` of the quote at the exact-in price, then swap it.
353
+ *
354
+ * The first quote is a small probe, only to learn a price. Every later quote scales the last one by
355
+ * what it actually returned, so an impact the probe did not show is corrected rather than guessed
356
+ * at. Three quotes at most, and a route that still falls short is an error rather than a buy that
357
+ * fails on chain.
358
+ */
359
+ async function exactInFallback(client, provider, quoteMint, amount, user, exactOutFailure) {
360
+ let spend = PROBE_LAMPORTS;
361
+ let last;
362
+ for (let attempt = 0; attempt < EXACT_IN_ATTEMPTS; attempt++) {
363
+ let route;
364
+ try {
365
+ route = await provider.exactIn(client, WSOL_MINT, quoteMint, spend, user);
366
+ } catch (exactInFailure) {
367
+ throw new Error(`no route from SOL to ${quoteMint}. Exact-out failed with "${messageOf(exactOutFailure)}" and exact-in with "${messageOf(exactInFailure)}".`);
368
+ }
369
+ last = route;
370
+ if (route.outAmount >= amount) return route;
371
+ if (route.outAmount <= 0n) break;
372
+ const scaled = ceilDiv(route.inAmount * amount, route.outAmount);
373
+ const next = scaled + scaled * EXACT_IN_MARGIN_BPS / 10000n;
374
+ if (next <= spend) break;
375
+ spend = next;
376
+ }
377
+ throw new Error(`no route from SOL to ${quoteMint} buys ${amount}. The best exact-in quote returned ${last?.outAmount ?? 0n} for ${last?.inAmount ?? spend} lamports, and exact-out failed with "${messageOf(exactOutFailure)}".`);
378
+ }
379
+ /**
380
+ * A swap that turns exactly `amount` of `quoteMint` into SOL.
381
+ *
382
+ * `sellTokens` uses it on the proceeds floor it already signs for, so the amount swapped is one the
383
+ * sale is guaranteed to have produced. Anything the sale paid above that floor stays in the
384
+ * seller's quote account.
385
+ */
386
+ async function routeQuoteOut(client, provider, input) {
387
+ const { quoteMint, amount, user } = input;
388
+ if (amount <= 0n) throw new Error("the quote amount to sell must be positive");
389
+ if (quoteMint === "So11111111111111111111111111111111111111112") throw new Error("a WSOL-quoted pool needs no route: the builders unwrap SOL themselves");
390
+ return await provider.exactIn(client, quoteMint, WSOL_MINT, amount, user);
391
+ }
392
+ /**
393
+ * Refuse a route that would touch Gabox state, or that does not settle in the account the program
394
+ * binds.
395
+ *
396
+ * `forbidden` is every Gabox account the transaction itself uses, plus the Gabox program id.
397
+ * `settlesIn` is the user's quote associated token account: the swap has to name it, because that
398
+ * is where `buy_pack` measures the quote it spends and where `sell_tokens` measures the proceeds.
399
+ */
400
+ function assertRouteIsSafe(route, expect) {
401
+ const forbidden = new Set(expect.forbidden);
402
+ let settles = false;
403
+ for (const instruction of route.instructions) {
404
+ if (forbidden.has(instruction.programAddress)) throw new Error(`the route calls ${instruction.programAddress}, which is a Gabox program or account. A route must never touch Gabox state.`);
405
+ for (const account of instruction.accounts ?? []) {
406
+ if (forbidden.has(account.address)) throw new Error(`the route names the Gabox account ${account.address}. A route must never touch Gabox state.`);
407
+ if (account.address === expect.settlesIn) settles = true;
408
+ }
409
+ }
410
+ if (!settles) throw new Error(`the route never names ${expect.settlesIn}, the quote account the program settles in. The swap would pay somewhere the buy cannot spend from.`);
411
+ }
412
+ /**
413
+ * What `amount` of a quote token costs in SOL, through the client's route provider.
414
+ *
415
+ * `null` when the client has no provider, or when the provider has no exact-out route. A price is a
416
+ * display, so a missing one is not an error. A WSOL amount is already SOL and comes back unchanged.
417
+ *
418
+ * No fallback to exact-in here on purpose: an exact-in price answers a different question, and a
419
+ * display that silently swapped the two would be wrong rather than missing.
420
+ */
421
+ async function solPriceOf(client, quoteMint, amount) {
422
+ if (quoteMint === "So11111111111111111111111111111111111111112") return amount;
423
+ if (!client.route || amount <= 0n) return null;
424
+ try {
425
+ return (await client.route.exactOut(client, WSOL_MINT, quoteMint, amount, quoteMint)).inAmount;
426
+ } catch {
427
+ return null;
428
+ }
429
+ }
430
+ /** `ceil(numerator / denominator)` for non-negative values. */
431
+ function ceilDiv(numerator, denominator) {
432
+ return (numerator + denominator - 1n) / denominator;
433
+ }
434
+ const messageOf = (cause) => cause instanceof Error ? cause.message : String(cause);
719
435
  //#endregion
720
436
  //#region src/offer.ts
721
437
  /**
722
- * The full offer for one machine. Two round trips: the pool and its vault, then the venue.
438
+ * The full offer for one machine. Three round trips: the pool and its vault, the venue, then the
439
+ * quote mint and its metadata. A non-SOL pool adds one HTTP call to the route provider for
440
+ * `solAmount`.
723
441
  *
724
442
  * Throws when the coin has no pool.
725
443
  */
726
444
  async function getOffer(client, mint, options = {}) {
727
445
  const inventory = await fetchPoolInventory(client, mint);
728
446
  if (!inventory) throw new Error(`no gabox pool for mint ${mint}`);
447
+ const { pool } = inventory;
729
448
  const venue = await resolveVenue(client, {
730
449
  mint,
731
- user: options.user ?? inventory.pool.creator,
450
+ user: options.user ?? pool.creator,
451
+ quote: {
452
+ mint: pool.quoteMint,
453
+ config: pool.quoteConfig,
454
+ tokenProgram: pool.quoteTokenProgram
455
+ },
732
456
  ...options.venue ? { venue: options.venue } : {}
733
457
  });
734
- return offerFromState(inventory, venue.kind, venue.quoteBuy(inventory.pool.packTokens));
458
+ const quoteAmount = venue.quoteBuy(pool.packTokens);
459
+ const display = await fetchQuoteDisplay(client, pool.quoteMint);
460
+ return offerFromState(inventory, venue.kind, quoteAmount, {
461
+ quoteDecimals: display.decimals,
462
+ quoteSymbol: display.symbol,
463
+ solAmount: await solPriceOf(client, pool.quoteMint, quoteAmount)
464
+ });
735
465
  }
736
466
  /**
737
467
  * The same computation with the reads already done. Useful when a caller holds a `ResolvedVenue`
738
468
  * and wants to re-price without touching the network. `quoteAmount` is
739
469
  * `venue.quoteBuy(pool.packTokens)`.
470
+ *
471
+ * `display` is optional: a caller that only wants the prize numbers can leave it out, and the three
472
+ * display fields then report the quote's own base units with no symbol and no SOL price.
740
473
  */
741
- function offerFromState(inventory, venue, quoteAmount) {
474
+ function offerFromState(inventory, venue, quoteAmount, display = {
475
+ quoteDecimals: 0,
476
+ quoteSymbol: null,
477
+ solAmount: null
478
+ }) {
742
479
  const { pool } = inventory;
743
480
  const tiers = tiersOf(pool);
744
481
  const offer = quote(pool.packTokens, tiers, inventory.inventory, inventory.reserved);
@@ -749,6 +486,9 @@ function offerFromState(inventory, venue, quoteAmount) {
749
486
  packTokens: pool.packTokens,
750
487
  quoteAmount,
751
488
  quoteMint: pool.quoteMint,
489
+ quoteDecimals: display.quoteDecimals,
490
+ quoteSymbol: display.quoteSymbol,
491
+ solAmount: display.solAmount,
752
492
  seedQuoteAmount: pool.seedQuoteAmount,
753
493
  seedTokens: seedTokens(pool.packTokens, tiers),
754
494
  venue,
@@ -777,6 +517,374 @@ function seedShortfall(offer) {
777
517
  return offer.free >= needed ? 0n : needed - offer.free;
778
518
  }
779
519
  //#endregion
520
+ //#region src/route/cpmm.ts
521
+ /**
522
+ * A route provider backed by one Raydium CPMM pool.
523
+ *
524
+ * Jupiter does not serve devnet, so a devnet machine quoted in a test token needs a route this SDK
525
+ * can build itself. Given a CPMM pool that holds the SOL/quote pair, this provider swaps through
526
+ * it with the same two instructions Gabox already forwards for a graduated coin, priced with the
527
+ * same bigint port of Raydium's math.
528
+ *
529
+ * It works anywhere such a pool exists, mainnet included. It is not a router: it uses the one pool
530
+ * the caller names and nothing else.
531
+ *
532
+ * # Wrapping
533
+ *
534
+ * CPMM settles in WSOL, never in native SOL. So the route wraps the SOL it spends and closes the
535
+ * WSOL account afterwards, exactly as Jupiter's `wrapAndUnwrapSol` does. A sale into SOL creates
536
+ * the WSOL account, swaps into it, and closes it, which is what turns the proceeds into SOL.
537
+ *
538
+ * # No signer objects
539
+ *
540
+ * The user is an address, not a signer. Every signing slot is marked as a signer and left for the
541
+ * fee payer to sign, the same way a Jupiter instruction arrives. The wallet paying for the Gabox
542
+ * transaction is the same wallet, so its one signature covers all of them.
543
+ *
544
+ * That is not a shortcut, it is required. Kit refuses to sign a message that carries two distinct
545
+ * signer objects for one address: `signTransactionMessageWithSigners` fails with "Multiple distinct
546
+ * signers were identified for address". The token and system builders below only accept a signer,
547
+ * so `withoutSigners` strips the object again and keeps the role.
548
+ */
549
+ const u64 = getU64Encoder();
550
+ /**
551
+ * The slippage this provider signs for on a pool swap, in basis points. 1%, the same as the
552
+ * Jupiter provider's default. It only widens the on-chain bound; the price itself is exact.
553
+ */
554
+ const CPMM_ROUTE_SLIPPAGE_BPS = 100n;
555
+ /**
556
+ * The compute units one swap through this provider adds to a transaction.
557
+ *
558
+ * Measured on devnet on 2026-09-18 against one Raydium CPMM pool, as the difference from the same
559
+ * builder with no route: `55,110` and `29,272` on `createMachine`, `21,488` and `52,980` on
560
+ * `buyPack`, and `30,138` and `51,138` on `sellTokens`. The spread is wide because which token
561
+ * accounts already exist changes from run to run, so this rounds up to the top of it.
562
+ *
563
+ * It is a safe figure here and nowhere else: this provider always uses exactly one pool. A router
564
+ * that may pick several hops states its own number; see `JUPITER_DEFAULT_COMPUTE_UNITS`.
565
+ */
566
+ const CPMM_ROUTE_COMPUTE_UNITS = 75e3;
567
+ /**
568
+ * Swap through one named Raydium CPMM pool.
569
+ *
570
+ * The pool must hold the pair the route asks for. On devnet the SOL/USDC-test pool with the most
571
+ * liquidity is `5Eu2G2USTy1pqphmQzQ2SBXWrBq5sdhgEh7hso9R2xix`, under the fee tier
572
+ * `A9qBhPy4k5UYW72hSgAkh1Epr2do69P54yzzcMV3yv6b`.
573
+ */
574
+ function raydiumCpmmRoute(poolAddress) {
575
+ return {
576
+ exactOut: async (client, input, output, amount, user) => await swap(client, poolAddress, {
577
+ input,
578
+ output,
579
+ amount,
580
+ user,
581
+ mode: "exactOut"
582
+ }),
583
+ exactIn: async (client, input, output, amount, user) => await swap(client, poolAddress, {
584
+ input,
585
+ output,
586
+ amount,
587
+ user,
588
+ mode: "exactIn"
589
+ })
590
+ };
591
+ }
592
+ async function swap(client, poolAddress, request) {
593
+ const { input, output, amount, user, mode } = request;
594
+ if (amount <= 0n) throw new Error("the route amount must be positive");
595
+ const ids = raydiumIds(client.cluster);
596
+ const [poolAccount] = await readAccounts(client.rpc, [poolAddress]);
597
+ if (!poolAccount || poolAccount.owner !== ids.cpmm) throw new Error(`${poolAddress} is not a Raydium CPMM pool on ${client.cluster}`);
598
+ const pool = decodeCpmmPool(poolAccount.data);
599
+ const inputIsToken0 = pool.token0Mint === input;
600
+ if (!(inputIsToken0 ? pool.token1Mint === output : pool.token1Mint === input && pool.token0Mint === output)) throw new Error(`the CPMM pool at ${poolAddress} holds ${pool.token0Mint} and ${pool.token1Mint}, not ${input} and ${output}`);
601
+ const inputVault = inputIsToken0 ? pool.token0Vault : pool.token1Vault;
602
+ const outputVault = inputIsToken0 ? pool.token1Vault : pool.token0Vault;
603
+ const inputTokenProgram = inputIsToken0 ? pool.token0Program : pool.token1Program;
604
+ const outputTokenProgram = inputIsToken0 ? pool.token1Program : pool.token0Program;
605
+ const [configAccount, inputVaultAccount, outputVaultAccount] = await readAccounts(client.rpc, [
606
+ pool.ammConfig,
607
+ inputVault,
608
+ outputVault
609
+ ]);
610
+ if (!configAccount) throw new Error(`the CPMM pool at ${poolAddress} names a fee tier that is not on chain`);
611
+ if (!inputVaultAccount || !outputVaultAccount) throw new Error(`the CPMM pool at ${poolAddress} has no reserve accounts`);
612
+ const config = decodeCpmmAmmConfig(configAccount.data);
613
+ const owed = (token0) => token0 ? pool.protocolFeesToken0 + pool.fundFeesToken0 + pool.creatorFeesToken0 : pool.protocolFeesToken1 + pool.fundFeesToken1 + pool.creatorFeesToken1;
614
+ const sides = {
615
+ inputReserve: tokenAccountAmount(inputVaultAccount.data) - owed(inputIsToken0),
616
+ outputReserve: tokenAccountAmount(outputVaultAccount.data) - owed(!inputIsToken0)
617
+ };
618
+ if (sides.inputReserve <= 0n || sides.outputReserve <= 0n) throw new Error(`the CPMM pool at ${poolAddress} has no tradable reserves`);
619
+ const rates = {
620
+ tradeFeeRate: config.tradeFeeRate,
621
+ creatorFeeRate: pool.enableCreatorFee ? config.creatorFeeRate : 0n,
622
+ creatorFeeOnInput: creatorFeeOnInput(pool, input)
623
+ };
624
+ const userInput = await ata(user, input, inputTokenProgram);
625
+ const userOutput = await ata(user, output, outputTokenProgram);
626
+ const abi = mode === "exactOut" ? CPMM_SWAP_BASE_OUTPUT : CPMM_SWAP_BASE_INPUT;
627
+ const accounts = order(abi, {
628
+ payer: user,
629
+ authority: ids.cpmmAuthority,
630
+ amm_config: pool.ammConfig,
631
+ pool_state: poolAddress,
632
+ input_token_account: userInput,
633
+ output_token_account: userOutput,
634
+ input_vault: inputVault,
635
+ output_vault: outputVault,
636
+ input_token_program: inputTokenProgram,
637
+ output_token_program: outputTokenProgram,
638
+ input_token_mint: input,
639
+ output_token_mint: output,
640
+ observation_state: pool.observationKey
641
+ });
642
+ const exactOut = mode === "exactOut";
643
+ const priced = exactOut ? cpmmSwapBaseOutput(sides, rates, amount) : cpmmSwapBaseInput(sides, rates, amount);
644
+ const inAmount = exactOut ? widen(priced) : amount;
645
+ const outAmount = exactOut ? amount : narrow(priced);
646
+ return {
647
+ instructions: wrap({
648
+ user,
649
+ input,
650
+ output,
651
+ userInput,
652
+ userOutput,
653
+ inputTokenProgram,
654
+ outputTokenProgram,
655
+ lamportsIn: inAmount,
656
+ middle: [{
657
+ programAddress: ids.cpmm,
658
+ accounts,
659
+ data: new Uint8Array([
660
+ ...abi.discriminator,
661
+ ...u64.encode(exactOut ? inAmount : amount),
662
+ ...u64.encode(exactOut ? amount : outAmount)
663
+ ])
664
+ }]
665
+ }),
666
+ lookupTables: {},
667
+ inAmount,
668
+ outAmount,
669
+ mode,
670
+ computeUnits: CPMM_ROUTE_COMPUTE_UNITS
671
+ };
672
+ }
673
+ /** Add the slippage margin to a cost the caller signs as a maximum. */
674
+ const widen = (amount) => amount + amount * CPMM_ROUTE_SLIPPAGE_BPS / 10000n;
675
+ /** Take the slippage margin off a payout the caller signs as a minimum. */
676
+ const narrow = (amount) => amount - amount * CPMM_ROUTE_SLIPPAGE_BPS / 10000n;
677
+ /**
678
+ * Create the two token accounts the swap needs, wrap the SOL it spends, and close the WSOL account
679
+ * afterwards.
680
+ *
681
+ * Only one side is ever WSOL here: a Gabox pool quoted in WSOL never uses a route at all.
682
+ */
683
+ function wrap(input) {
684
+ const payer = createNoopSigner(input.user);
685
+ const createAta = (account, mint, tokenProgram) => withoutSigners(getCreateAssociatedTokenIdempotentInstruction({
686
+ payer,
687
+ ata: account,
688
+ owner: input.user,
689
+ mint,
690
+ tokenProgram
691
+ }));
692
+ const before = [createAta(input.userInput, input.input, input.inputTokenProgram), createAta(input.userOutput, input.output, input.outputTokenProgram)];
693
+ const after = [];
694
+ if (input.input === "So11111111111111111111111111111111111111112") {
695
+ before.push(withoutSigners(getTransferSolInstruction({
696
+ source: payer,
697
+ destination: input.userInput,
698
+ amount: input.lamportsIn
699
+ })), getSyncNativeInstruction({ account: input.userInput }));
700
+ after.push(closeWsol(input.userInput, payer));
701
+ }
702
+ if (input.output === "So11111111111111111111111111111111111111112") after.push(closeWsol(input.userOutput, payer));
703
+ return [
704
+ ...before,
705
+ ...input.middle,
706
+ ...after
707
+ ];
708
+ }
709
+ /**
710
+ * Close a WSOL account, sending every lamport in it back to the owner as SOL.
711
+ *
712
+ * The owner has to sign, so it goes in as a signer and comes out as a plain signing slot.
713
+ */
714
+ const closeWsol = (account, owner) => withoutSigners(getCloseAccountInstruction({
715
+ account,
716
+ destination: owner.address,
717
+ owner
718
+ }));
719
+ /**
720
+ * Drop every attached signer object, keeping each account's address and role.
721
+ *
722
+ * A signing slot stays a signing slot: the compiled message still requires that signature, and the
723
+ * wallet paying for the transaction provides it. What goes away is the second signer object for an
724
+ * address the fee payer already covers, which kit refuses to sign.
725
+ */
726
+ function withoutSigners(instruction) {
727
+ const accounts = (instruction.accounts ?? []).map((account) => ({
728
+ address: account.address,
729
+ role: account.role
730
+ }));
731
+ return {
732
+ ...instruction,
733
+ accounts
734
+ };
735
+ }
736
+ //#endregion
737
+ //#region src/route/jupiter.ts
738
+ /**
739
+ * The Jupiter route provider.
740
+ *
741
+ * Jupiter is an HTTP service, not a program this SDK builds instructions for. Two calls per route:
742
+ *
743
+ * 1. `GET /swap/v1/quote` prices the swap and returns a quote object.
744
+ * 2. `POST /swap/v1/swap-instructions` turns that quote into instructions.
745
+ *
746
+ * The response gives `setupInstructions`, `swapInstruction` and `cleanupInstruction`, each as a
747
+ * program id, a list of accounts and base64 data. This module decodes those into kit instructions
748
+ * and leaves everything else alone. Jupiter's own compute budget instructions are dropped: every
749
+ * builder in this SDK sets its own budget, and two `SetComputeUnitLimit` instructions in one message
750
+ * is one too many. Their **number** is kept, though: it is Jupiter's own answer to "how much does
751
+ * this route cost", and a builder adds it to its own limit.
752
+ *
753
+ * `wrapAndUnwrapSol: true` is always sent, so Jupiter creates the wallet's WSOL account, funds it
754
+ * from the wallet's lamports and closes it again inside its own instructions. That is what makes
755
+ * "pay in SOL" true from the wallet's side.
756
+ *
757
+ * # No Jupiter package
758
+ *
759
+ * The public surface of this SDK is `@solana/kit` only. Nothing here imports a Jupiter package; the
760
+ * response is plain JSON and the decoding below is a dozen lines.
761
+ *
762
+ * # Exact-out is not always available
763
+ *
764
+ * Jupiter answers `NO_ROUTES_FOUND` for an exact-out quote whenever the best route has more than
765
+ * one hop. Verified on 2026-09-18: SOL to USDC quotes exact-out, while SOL to the stock token
766
+ * `XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB` only quotes exact-in. `routeQuoteIn` in `leg.ts`
767
+ * handles that fallback; this file only reports the failure.
768
+ */
769
+ /** Jupiter's free endpoint. The keyed host `https://api.jup.ag/swap/v1` has the same shape. */
770
+ const JUPITER_LITE_URL = "https://lite-api.jup.ag/swap/v1";
771
+ /** The slippage Jupiter prices a route with when the caller names none. 1%. */
772
+ const JUPITER_DEFAULT_SLIPPAGE_BPS = 100;
773
+ /**
774
+ * The compute units a Jupiter route is assumed to need when the response carries no limit.
775
+ *
776
+ * Jupiter normally sends a `SetComputeUnitLimit` of its own, and that number is what this SDK uses.
777
+ * When it does not, this is the fallback: enough for a route through several pools, and still far
778
+ * below the 1,400,000-unit ceiling once the Gabox instruction's own budget is added. A caller who
779
+ * knows better passes `computeUnitLimit` to the builder.
780
+ */
781
+ const JUPITER_DEFAULT_COMPUTE_UNITS = 4e5;
782
+ /** `ComputeBudgetInstruction::SetComputeUnitLimit`, whose data is the tag then a u32 of units. */
783
+ const SET_COMPUTE_UNIT_LIMIT = 2;
784
+ const base64 = getBase64Encoder();
785
+ /**
786
+ * A route provider backed by Jupiter. Use it on mainnet, where Jupiter has the liquidity.
787
+ *
788
+ * It makes read-only HTTP calls and never sends a transaction: the instructions come back to the
789
+ * caller, who signs them together with the Gabox instruction.
790
+ */
791
+ function jupiterRoute(options = {}) {
792
+ const url = (options.url ?? "https://lite-api.jup.ag/swap/v1").replace(/\/+$/, "");
793
+ const slippageBps = options.slippageBps ?? 100;
794
+ const build = async (client, input, output, amount, user, swapMode) => {
795
+ if (amount <= 0n) throw new Error("the route amount must be positive");
796
+ const quote = await fetchQuote(url, {
797
+ input,
798
+ output,
799
+ amount,
800
+ swapMode,
801
+ slippageBps
802
+ });
803
+ const response = await fetchSwapInstructions(url, quote, user);
804
+ const threshold = BigInt(String(quote.otherAmountThreshold));
805
+ return await routeFrom(client, response, {
806
+ inAmount: swapMode === "ExactOut" ? threshold : BigInt(String(quote.inAmount)),
807
+ outAmount: swapMode === "ExactOut" ? BigInt(String(quote.outAmount)) : threshold,
808
+ mode: swapMode === "ExactOut" ? "exactOut" : "exactIn"
809
+ });
810
+ };
811
+ return {
812
+ exactOut: async (client, input, output, amount, user) => await build(client, input, output, amount, user, "ExactOut"),
813
+ exactIn: async (client, input, output, amount, user) => await build(client, input, output, amount, user, "ExactIn")
814
+ };
815
+ }
816
+ async function fetchQuote(url, input) {
817
+ const query = new URLSearchParams({
818
+ inputMint: input.input,
819
+ outputMint: input.output,
820
+ amount: input.amount.toString(),
821
+ swapMode: input.swapMode,
822
+ slippageBps: String(input.slippageBps)
823
+ });
824
+ const response = await fetch(`${url}/quote?${query.toString()}`);
825
+ const body = await response.json();
826
+ if (!response.ok || body.error) throw new Error(`Jupiter has no ${input.swapMode} route from ${input.input} to ${input.output}: ` + `${body.errorCode ?? response.status} ${body.error ?? ""}`.trim());
827
+ return body;
828
+ }
829
+ async function fetchSwapInstructions(url, quoteResponse, userPublicKey) {
830
+ const response = await fetch(`${url}/swap-instructions`, {
831
+ method: "POST",
832
+ headers: { "Content-Type": "application/json" },
833
+ body: JSON.stringify({
834
+ quoteResponse,
835
+ userPublicKey,
836
+ wrapAndUnwrapSol: true
837
+ })
838
+ });
839
+ const body = await response.json();
840
+ if (!response.ok || body.error || !body.swapInstruction) throw new Error(`Jupiter could not build the swap instructions: ${response.status} ${body.error ?? ""}`.trim());
841
+ return body;
842
+ }
843
+ /**
844
+ * Turn a decoded `swap-instructions` response into a `Route`.
845
+ *
846
+ * Exported so a test can read a recorded response without making an HTTP call. The lookup tables
847
+ * are read through the client, because the response names them by address only.
848
+ */
849
+ async function routeFrom(client, response, amounts) {
850
+ return {
851
+ instructions: [
852
+ ...(response.setupInstructions ?? []).map(toKitInstruction),
853
+ toKitInstruction(response.swapInstruction),
854
+ ...response.cleanupInstruction ? [toKitInstruction(response.cleanupInstruction)] : []
855
+ ],
856
+ lookupTables: await fetchAddressLookupTables(client, response.addressLookupTableAddresses ?? []),
857
+ computeUnits: computeUnitsOf(response),
858
+ ...amounts
859
+ };
860
+ }
861
+ /**
862
+ * The unit limit Jupiter asked for, or `JUPITER_DEFAULT_COMPUTE_UNITS` when it asked for none.
863
+ *
864
+ * `SetComputeUnitLimit` is five bytes: the tag `2`, then the units as a little-endian u32. Any other
865
+ * compute budget instruction, such as a unit price, is skipped.
866
+ */
867
+ function computeUnitsOf(response) {
868
+ for (const instruction of response.computeBudgetInstructions ?? []) {
869
+ const data = new Uint8Array(base64.encode(instruction.data));
870
+ if (data.length < 5 || data[0] !== SET_COMPUTE_UNIT_LIMIT) continue;
871
+ return new DataView(data.buffer, data.byteOffset).getUint32(1, true);
872
+ }
873
+ return JUPITER_DEFAULT_COMPUTE_UNITS;
874
+ }
875
+ /** One Jupiter instruction as a kit instruction. The roles come from the two booleans. */
876
+ function toKitInstruction(instruction) {
877
+ const accounts = instruction.accounts.map((account) => ({
878
+ address: account.pubkey,
879
+ role: account.isSigner ? account.isWritable ? AccountRole.WRITABLE_SIGNER : AccountRole.READONLY_SIGNER : account.isWritable ? AccountRole.WRITABLE : AccountRole.READONLY
880
+ }));
881
+ return {
882
+ programAddress: instruction.programId,
883
+ accounts,
884
+ data: new Uint8Array(base64.encode(instruction.data))
885
+ };
886
+ }
887
+ //#endregion
780
888
  //#region src/rpc.ts
781
889
  /**
782
890
  * The client, and the cluster guard.
@@ -866,9 +974,19 @@ function createClient(config) {
866
974
  wsUrl,
867
975
  rpc: createSolanaRpc(url),
868
976
  rpcSubscriptions: createSolanaRpcSubscriptions(wsUrl),
869
- addressLookupTables: config.addressLookupTables ?? defaultAddressLookupTables(cluster)
977
+ addressLookupTables: config.addressLookupTables ?? defaultAddressLookupTables(cluster),
978
+ route: config.route === void 0 ? defaultRoute(cluster) : config.route
870
979
  };
871
980
  }
981
+ /**
982
+ * The swap provider a cluster gets when the caller names none.
983
+ *
984
+ * Jupiter on mainnet, nothing anywhere else. Jupiter's API only prices mainnet liquidity, and a
985
+ * devnet caller has to say which pool to route through, so there is nothing to guess.
986
+ */
987
+ function defaultRoute(cluster) {
988
+ return cluster === "mainnet-beta" ? jupiterRoute() : null;
989
+ }
872
990
  //#endregion
873
991
  //#region src/tx/wsol.ts
874
992
  /**
@@ -922,6 +1040,176 @@ function unwrapWsol(owner, account) {
922
1040
  });
923
1041
  }
924
1042
  //#endregion
1043
+ //#region src/tx/quoteLeg.ts
1044
+ /**
1045
+ * Getting the quote token into, and out of, the wallet's own quote account.
1046
+ *
1047
+ * Gabox settles in the pool's quote asset and nothing else. Both venues move that token in and out
1048
+ * of one account: the wallet's associated token account for the quote mint, under the quote's own
1049
+ * token program. The program pins that address and measures the exact delta there. So every builder
1050
+ * in this directory has to make sure the account exists, and holds what the trade will spend.
1051
+ *
1052
+ * Three shapes, and which one applies follows from the pool's quote and the caller's choice:
1053
+ *
1054
+ * - **A WSOL pool.** The wallet pays in SOL already. Create the WSOL account, move the lamports
1055
+ * into it, `syncNative`, and close it afterwards so the change and any proceeds come back as
1056
+ * SOL. This is what every 0.6.0 flow did, unchanged.
1057
+ * - **Another quote, paying in that quote.** The wallet already holds the token. Create the
1058
+ * account if it is missing and leave it alone: it is not WSOL, so closing it would be wrong.
1059
+ * - **Another quote, paying in SOL.** A route turns SOL into the quote token in the same
1060
+ * transaction, before `buy_pack`. A sale does the reverse afterwards. `routeQuoteIn` and
1061
+ * `routeQuoteOut` build those, and `assertRouteIsSafe` checks the result before it is used.
1062
+ *
1063
+ * The third shape belongs to packs only. A machine's seed is always paid by its creator, in the
1064
+ * machine's own quote token, so `createMachine` uses `quoteLegFromWallet` and never swaps.
1065
+ *
1066
+ * Nothing here ever splits the work across two transactions. A swap that settles separately would
1067
+ * leave the wallet holding a token it never asked for whenever the second half failed.
1068
+ */
1069
+ /** Create the wallet's quote account if it is missing. Idempotent, so a second create is free. */
1070
+ function createQuoteAccount(owner, venue) {
1071
+ return getCreateAssociatedTokenIdempotentInstruction({
1072
+ payer: owner,
1073
+ ata: venue.userQuoteToken,
1074
+ owner: owner.address,
1075
+ mint: venue.quoteMint,
1076
+ tokenProgram: venue.quoteTokenProgram
1077
+ });
1078
+ }
1079
+ /**
1080
+ * The leg for a wallet that already holds what it is about to spend. No swap.
1081
+ *
1082
+ * A WSOL pool is the one case where "already holds it" means lamports, so those are wrapped and the
1083
+ * account is closed again afterwards. Any other quote only needs its account to exist; closing it
1084
+ * would throw away a real balance.
1085
+ *
1086
+ * `createMachine` uses this and nothing else: the seed is always the creator's own money, in the
1087
+ * machine's quote token.
1088
+ */
1089
+ async function quoteLegFromWallet(venue, payer, maxQuoteIn) {
1090
+ if (venue.quoteMint === "So11111111111111111111111111111111111111112") {
1091
+ const wsol = await fundWsol(payer, maxQuoteIn);
1092
+ return {
1093
+ before: wsol.instructions,
1094
+ after: [unwrapWsol(payer, wsol.account)],
1095
+ lookupTables: {},
1096
+ mode: null,
1097
+ solAmount: maxQuoteIn,
1098
+ computeUnits: 0
1099
+ };
1100
+ }
1101
+ return {
1102
+ before: [createQuoteAccount(payer, venue)],
1103
+ after: [],
1104
+ lookupTables: {},
1105
+ mode: null,
1106
+ solAmount: null,
1107
+ computeUnits: 0
1108
+ };
1109
+ }
1110
+ /**
1111
+ * The leg that puts `maxQuoteIn` of the quote token in the buyer's quote account.
1112
+ *
1113
+ * `payWith` decides where it comes from. On a WSOL pool the choice makes no difference: the quote
1114
+ * token is SOL either way, so the builder wraps it.
1115
+ */
1116
+ async function quoteLegIn(client, input) {
1117
+ const { venue, payer, maxQuoteIn, payWith } = input;
1118
+ if (venue.quoteMint === "So11111111111111111111111111111111111111112" || payWith === "quote") return await quoteLegFromWallet(venue, payer, maxQuoteIn);
1119
+ const route = await routeQuoteIn(client, providerOf(client, venue.quoteMint), {
1120
+ quoteMint: venue.quoteMint,
1121
+ amount: maxQuoteIn,
1122
+ user: payer.address
1123
+ });
1124
+ assertRouteIsSafe(route, {
1125
+ forbidden: [GABOX_PROGRAM_ID, ...input.gaboxAccounts],
1126
+ settlesIn: venue.userQuoteToken
1127
+ });
1128
+ return {
1129
+ before: route.instructions,
1130
+ after: [],
1131
+ lookupTables: route.lookupTables,
1132
+ mode: route.mode,
1133
+ solAmount: route.inAmount,
1134
+ computeUnits: route.computeUnits
1135
+ };
1136
+ }
1137
+ /**
1138
+ * The leg around a sale: make sure the quote account exists, and turn the proceeds into SOL when
1139
+ * the seller asked for SOL.
1140
+ *
1141
+ * The swap is an exact-in of `minQuoteOutput`, the floor the seller already signs for on the sale
1142
+ * itself. Anything the venue pays above that floor stays in the seller's quote account: a swap can
1143
+ * only spend what the sale is guaranteed to have produced.
1144
+ */
1145
+ async function quoteLegOut(client, input) {
1146
+ const { venue, seller, minQuoteOutput, receive } = input;
1147
+ if (venue.quoteMint === "So11111111111111111111111111111111111111112") {
1148
+ const wsol = await fundWsol(seller, 0n);
1149
+ return {
1150
+ before: wsol.instructions,
1151
+ after: [unwrapWsol(seller, wsol.account)],
1152
+ lookupTables: {},
1153
+ mode: null,
1154
+ solAmount: null,
1155
+ computeUnits: 0
1156
+ };
1157
+ }
1158
+ const create = createQuoteAccount(seller, venue);
1159
+ if (receive === "quote") return {
1160
+ before: [create],
1161
+ after: [],
1162
+ lookupTables: {},
1163
+ mode: null,
1164
+ solAmount: null,
1165
+ computeUnits: 0
1166
+ };
1167
+ const route = await routeQuoteOut(client, providerOf(client, venue.quoteMint), {
1168
+ quoteMint: venue.quoteMint,
1169
+ amount: minQuoteOutput,
1170
+ user: seller.address
1171
+ });
1172
+ assertRouteIsSafe(route, {
1173
+ forbidden: [GABOX_PROGRAM_ID, ...input.gaboxAccounts],
1174
+ settlesIn: venue.userQuoteToken
1175
+ });
1176
+ return {
1177
+ before: [create],
1178
+ after: route.instructions,
1179
+ lookupTables: route.lookupTables,
1180
+ mode: route.mode,
1181
+ solAmount: route.outAmount,
1182
+ computeUnits: route.computeUnits
1183
+ };
1184
+ }
1185
+ /** The client's route provider, with a message that says what to do when it has none. */
1186
+ function providerOf(client, quoteMint) {
1187
+ if (!client.route) throw new Error(`this ${client.cluster} client has no route provider, so it cannot pay in SOL for a pool quoted in ${quoteMint}. Pass \`route\` to createClient — raydiumCpmmRoute(pool) for a CPMM pool that holds the SOL pair — or pay in the quote token itself.`);
1188
+ return client.route;
1189
+ }
1190
+ /**
1191
+ * The compute limit a builder asks for: its own budget plus whatever the route needs.
1192
+ *
1193
+ * Capped at the runtime's ceiling. Jupiter often asks for the whole 1,400,000 units rather than
1194
+ * estimating, and a request above the ceiling is rejected outright, so the sum has to be clamped
1195
+ * rather than passed through.
1196
+ */
1197
+ function computeUnitsWithRoute(own, leg) {
1198
+ return Math.min(own + leg.computeUnits, MAX_COMPUTE_UNIT_LIMIT);
1199
+ }
1200
+ /**
1201
+ * Add the route to a "transaction is too large" error.
1202
+ *
1203
+ * `buildMessage` already refuses a message above the 1,232-byte limit. When a swap is in the same
1204
+ * message, the reason is usually the swap, and the fix is not to split the transaction: the two
1205
+ * halves have to settle together. So the message says what a caller can actually do instead.
1206
+ */
1207
+ function routeSizeHint(cause, leg) {
1208
+ if (leg.mode === null) return cause;
1209
+ if (!(cause instanceof Error) || !cause.message.includes("Solana allows 1232")) return cause;
1210
+ return new Error(`${cause.message} The swap and the Gabox instruction share one transaction on purpose, so this SDK never splits them. Supply more address lookup tables, or pay in the quote token.`, { cause });
1211
+ }
1212
+ //#endregion
925
1213
  //#region src/tx/buyPack.ts
926
1214
  async function buyPack(client, input) {
927
1215
  if (input.maxQuoteIn <= 0n) throw new Error("maxQuoteIn must be positive");
@@ -933,6 +1221,11 @@ async function buyPack(client, input) {
933
1221
  const venue = await resolveVenue(client, {
934
1222
  mint: input.mint,
935
1223
  user: purchaser,
1224
+ quote: {
1225
+ mint: pool.quoteMint,
1226
+ config: pool.quoteConfig,
1227
+ tokenProgram: pool.quoteTokenProgram
1228
+ },
936
1229
  ...input.venue ? { venue: input.venue } : {}
937
1230
  });
938
1231
  const draw = await drawAddress(poolAddress, input.seq ?? pool.nextSeq);
@@ -944,30 +1237,50 @@ async function buyPack(client, input) {
944
1237
  quoteMint: pool.quoteMint,
945
1238
  vault: pool.vault,
946
1239
  venue: venue.program,
1240
+ quoteTokenProgram: pool.quoteTokenProgram,
947
1241
  maxQuoteIn: input.maxQuoteIn,
948
1242
  minMaximum: input.minMaximum,
949
1243
  maxNativeDebit: input.maxNativeDebit
950
1244
  });
951
- const wsol = await fundWsol(input.purchaser, input.maxQuoteIn);
1245
+ const leg = await quoteLegIn(client, {
1246
+ venue,
1247
+ payer: input.purchaser,
1248
+ maxQuoteIn: input.maxQuoteIn,
1249
+ payWith: input.payWith ?? "sol",
1250
+ gaboxAccounts: [
1251
+ poolAddress,
1252
+ pool.vault,
1253
+ draw,
1254
+ await activityAddress(purchaser),
1255
+ await associatedTokenAddress(purchaser, input.mint)
1256
+ ]
1257
+ });
952
1258
  const instructions = [
953
- ...wsol.instructions,
1259
+ ...leg.before,
954
1260
  withRemainingAccounts(buy, venue.buyAccounts),
955
- unwrapWsol(input.purchaser, wsol.account)
1261
+ ...leg.after
956
1262
  ];
957
- return await buildMessage(client, input.purchaser, instructions, {
958
- addressLookupTables: input.addressLookupTables,
959
- computeUnitLimit: input.computeUnitLimit ?? 24e4,
960
- ...input.computeUnitPrice === void 0 ? {} : { computeUnitPrice: input.computeUnitPrice }
961
- });
1263
+ try {
1264
+ return await buildMessage(client, input.purchaser, instructions, {
1265
+ addressLookupTables: {
1266
+ ...input.addressLookupTables ?? client.addressLookupTables,
1267
+ ...leg.lookupTables
1268
+ },
1269
+ computeUnitLimit: input.computeUnitLimit ?? computeUnitsWithRoute(235e3, leg),
1270
+ ...input.computeUnitPrice === void 0 ? {} : { computeUnitPrice: input.computeUnitPrice }
1271
+ });
1272
+ } catch (cause) {
1273
+ throw routeSizeHint(cause, leg);
1274
+ }
962
1275
  }
963
1276
  //#endregion
964
1277
  //#region src/tx/createMachine.ts
965
1278
  /**
966
1279
  * Build the transaction message. Sign it with both `creator` and `mintKeypair`.
967
1280
  *
968
- * Reads LaunchLab's global config and the Gabox platform config, because the seed price depends on
969
- * their fee rates. Nothing else needs the chain: the coin does not exist yet, so every other
970
- * account is a derivation.
1281
+ * Reads the quote's LaunchLab config, the quote mint, and the Gabox platform config, because the
1282
+ * seed price and every quote-side account depend on them. Nothing else needs the chain: the coin
1283
+ * does not exist yet, so every other account is a derivation.
971
1284
  */
972
1285
  async function createMachine(client, input) {
973
1286
  const { creator, mintKeypair, name, symbol, uri, maxSeedQuoteIn, maxSeedNativeDebit } = input;
@@ -983,52 +1296,65 @@ async function createMachine(client, input) {
983
1296
  if (maxSeedNativeDebit < 0n) throw new Error("maxSeedNativeDebit must not be negative");
984
1297
  const ids = raydiumIds(client.cluster);
985
1298
  const mint = mintKeypair.address;
986
- const quoteMint = WSOL_MINT;
1299
+ const quote = await fetchQuoteAsset(client, input.quote?.mint ?? "So11111111111111111111111111111111111111112", ids);
1300
+ const raise = resolveRaise(client, quote, input.raise, ids);
987
1301
  const create = await getLaunchInstruction({
988
1302
  mint: mintKeypair,
989
1303
  creator,
990
1304
  name,
991
1305
  symbol,
992
- uri
1306
+ uri,
1307
+ quoteMint: quote.mint,
1308
+ quoteConfig: quote.config,
1309
+ quoteTokenProgram: quote.tokenProgram,
1310
+ raise
993
1311
  }, ids);
994
- const poolState = await launchlabPoolAddress(ids.launchlab, mint, quoteMint);
1312
+ const poolState = await launchlabPoolAddress(ids.launchlab, mint, quote.mint);
1313
+ const userQuoteToken = await quoteAccountFor(creator.address, quote.mint, quote.tokenProgram);
995
1314
  const venueAccounts = launchlabBuyAccounts({
996
1315
  launchlab: ids.launchlab,
997
1316
  launchlabAuthority: ids.launchlabAuthority,
998
1317
  launchlabEventAuthority: ids.launchlabEventAuthority,
999
- globalConfig: ids.solGlobalConfig,
1318
+ globalConfig: quote.config,
1000
1319
  platformConfig: ids.gaboxPlatform,
1001
1320
  poolState,
1002
1321
  mint,
1003
- quoteMint,
1322
+ quoteMint: quote.mint,
1004
1323
  baseVault: await launchlabVaultAddress(ids.launchlab, poolState, mint),
1005
- quoteVault: await launchlabVaultAddress(ids.launchlab, poolState, quoteMint),
1324
+ quoteVault: await launchlabVaultAddress(ids.launchlab, poolState, quote.mint),
1006
1325
  user: creator.address,
1007
1326
  userBaseToken: await ata(creator.address, mint),
1008
- userQuoteToken: await ata(creator.address, quoteMint),
1009
- platformFeeVault: await platformFeeVaultAddress(ids.launchlab, ids.gaboxPlatform, quoteMint),
1010
- creatorFeeVault: await creatorFeeVaultAddress(ids.launchlab, creator.address, quoteMint)
1327
+ userQuoteToken,
1328
+ quoteTokenProgram: quote.tokenProgram,
1329
+ platformFeeVault: await platformFeeVaultAddress(ids.launchlab, ids.gaboxPlatform, quote.mint),
1330
+ creatorFeeVault: await creatorFeeVaultAddress(ids.launchlab, creator.address, quote.mint)
1011
1331
  });
1012
1332
  const initialize = await getInitializePoolInstructionAsync({
1013
1333
  creator,
1014
1334
  mint,
1015
- quoteMint,
1335
+ quoteMint: quote.mint,
1336
+ quoteConfig: quote.config,
1337
+ quoteTokenProgram: quote.tokenProgram,
1016
1338
  venue: ids.launchlab,
1017
1339
  tiers,
1018
1340
  maxSeedQuoteIn,
1019
1341
  maxSeedNativeDebit,
1020
1342
  extraSeedTokens
1021
1343
  });
1022
- const wsol = await fundWsol(creator, maxSeedQuoteIn);
1344
+ const leg = await quoteLegFromWallet({
1345
+ quoteMint: quote.mint,
1346
+ quoteTokenProgram: quote.tokenProgram,
1347
+ userQuoteToken
1348
+ }, creator, maxSeedQuoteIn);
1023
1349
  const instructions = [
1024
1350
  create,
1025
- ...wsol.instructions,
1351
+ ...leg.before,
1026
1352
  withRemainingAccounts(initialize, venueAccounts),
1027
- unwrapWsol(creator, wsol.account)
1353
+ ...leg.after
1028
1354
  ];
1029
1355
  return await buildMessage(client, creator, instructions, {
1030
- addressLookupTables: input.addressLookupTables,
1031
- computeUnitLimit: input.computeUnitLimit ?? 385e3,
1356
+ addressLookupTables: input.addressLookupTables ?? client.addressLookupTables,
1357
+ computeUnitLimit: input.computeUnitLimit ?? 35e4,
1032
1358
  ...input.computeUnitPrice === void 0 ? {} : { computeUnitPrice: input.computeUnitPrice }
1033
1359
  });
1034
1360
  }
@@ -1036,13 +1362,17 @@ async function createMachine(client, input) {
1036
1362
  * What the seed for this table costs, fees included, and how many tokens it is.
1037
1363
  *
1038
1364
  * The coin does not exist yet, so the price comes from the starting reserves LaunchLab derives from
1039
- * the pinned launch shape. Nothing trades on the curve before `initialize_pool` runs in the same
1040
- * transaction, so this is exact up to a change in Raydium's fee rates between the read and the
1365
+ * the launch shape and the raise. Nothing trades on the curve before `initialize_pool` runs in the
1366
+ * same transaction, so this is exact up to a change in Raydium's fee rates between the read and the
1041
1367
  * send.
1042
1368
  *
1043
- * Defaults to `DEFAULT_TIERS` and no extra seed. Throws if `tiers` fails
1044
- * `validateTiers`/`validatePack`, if `extraSeedTokens` is negative, or if the total seed is bigger
1045
- * than the curve sells.
1369
+ * `solAmount` is what that cost is worth in SOL, priced through the client's route provider. It is
1370
+ * a display: the creator pays in the quote token. It is `null` when the client has no provider, or
1371
+ * when no route exists: a devnet client has none unless the caller passes `raydiumCpmmRoute(pool)`.
1372
+ *
1373
+ * Defaults to `DEFAULT_TIERS`, wrapped SOL and no extra seed. Throws if `tiers` fails
1374
+ * `validateTiers`/`validatePack`, if `extraSeedTokens` is negative, if the total seed is bigger
1375
+ * than the curve sells, or if the raise is missing or below what LaunchLab accepts.
1046
1376
  */
1047
1377
  async function seedCostEstimate(client, tiers = DEFAULT_TIERS, options = {}) {
1048
1378
  const copiedTiers = cloneTiers(tiers);
@@ -1054,16 +1384,39 @@ async function seedCostEstimate(client, tiers = DEFAULT_TIERS, options = {}) {
1054
1384
  const seed = mandatorySeed + extraSeedTokens;
1055
1385
  assertSeedFitsCurve(seed);
1056
1386
  const ids = raydiumIds(client.cluster);
1057
- const settings = await fetchCurveSettings(client, ids);
1387
+ const quote = await fetchQuoteAsset(client, options.quote?.mint ?? "So11111111111111111111111111111111111111112", ids);
1388
+ const raise = resolveRaise(client, quote, options.raise, ids);
1389
+ const settings = await fetchCurveSettings(client, quote.config, ids);
1390
+ const quoteAmount = seed === 0n ? 0n : curveBuyExactOut(newCurveReserves(raise, settings.migrateFee), settings.rates, seed);
1058
1391
  return {
1059
1392
  tiers: copiedTiers,
1060
1393
  seedTokens: mandatorySeed,
1061
1394
  extraSeedTokens,
1062
1395
  totalSeedTokens: seed,
1063
- quoteAmount: seed === 0n ? 0n : curveBuyExactOut(newCurveReserves(ids, settings.migrateFee), settings.rates, seed),
1064
- quoteMint: WSOL_MINT
1396
+ quoteAmount,
1397
+ quoteMint: quote.mint,
1398
+ quoteDecimals: quote.decimals,
1399
+ quoteSymbol: quote.symbol,
1400
+ raise,
1401
+ solAmount: await solPriceOf(client, quote.mint, quoteAmount)
1065
1402
  };
1066
1403
  }
1404
+ /**
1405
+ * The raise a launch uses, checked before anything is built.
1406
+ *
1407
+ * The program pins it for a WSOL pool, so a different value there is a launch that would be
1408
+ * refused on chain. Any other quote has no default and no pin: the caller names it, and LaunchLab's
1409
+ * own minimum for that quote is the floor.
1410
+ */
1411
+ function resolveRaise(client, quote, raise, ids) {
1412
+ if (quote.mint === "So11111111111111111111111111111111111111112") {
1413
+ if (raise !== void 0 && raise !== ids.launchQuoteRaise) throw new Error(`a WSOL pool raises exactly ${ids.launchQuoteRaise} lamports on ${client.cluster}; the program refuses ${raise}. Leave \`raise\` out, or pick another quote asset.`);
1414
+ return ids.launchQuoteRaise;
1415
+ }
1416
+ if (raise === void 0) throw new Error(`a pool quoted in ${quote.mint} needs a \`raise\`, in that token's base units. There is no default, because the same number means a different amount in every token.`);
1417
+ if (raise < quote.minQuoteFundRaising) throw new Error(`LaunchLab takes at least ${quote.minQuoteFundRaising} base units of ${quote.mint} as a raise, and this launch asks for ${raise}`);
1418
+ return raise;
1419
+ }
1067
1420
  /** A mutable table shape, owned by this call and safe to pass to Codama's builder. */
1068
1421
  function cloneTiers(tiers) {
1069
1422
  return tiers.map(({ multiplierBps, tickets }) => ({
@@ -1162,29 +1515,53 @@ async function sellTokens(client, input) {
1162
1515
  const venue = await resolveVenue(client, {
1163
1516
  mint: input.mint,
1164
1517
  user: input.seller.address,
1518
+ quote: {
1519
+ mint: pool.quoteMint,
1520
+ config: pool.quoteConfig,
1521
+ tokenProgram: pool.quoteTokenProgram
1522
+ },
1165
1523
  ...input.venue ? { venue: input.venue } : {}
1166
1524
  });
1525
+ const gaboxPool = await poolAddress(input.mint);
1167
1526
  const sell = await getSellTokensInstructionAsync({
1168
1527
  seller: input.seller,
1169
- pool: await poolAddress(input.mint),
1528
+ pool: gaboxPool,
1170
1529
  mint: input.mint,
1171
1530
  quoteMint: pool.quoteMint,
1172
1531
  venue: venue.program,
1532
+ quoteTokenProgram: pool.quoteTokenProgram,
1173
1533
  amount: input.amount,
1174
1534
  minQuoteOutput: input.minQuoteOutput,
1175
1535
  maxNativeDebit: input.maxNativeDebit
1176
1536
  });
1177
- const wsol = await fundWsol(input.seller, 0n);
1537
+ const leg = await quoteLegOut(client, {
1538
+ venue,
1539
+ seller: input.seller,
1540
+ minQuoteOutput: input.minQuoteOutput,
1541
+ receive: input.receive ?? "sol",
1542
+ gaboxAccounts: [
1543
+ gaboxPool,
1544
+ await vaultAddress(input.mint),
1545
+ await associatedTokenAddress(input.seller.address, input.mint)
1546
+ ]
1547
+ });
1178
1548
  const instructions = [
1179
- ...wsol.instructions,
1549
+ ...leg.before,
1180
1550
  withRemainingAccounts(sell, venue.sellAccounts),
1181
- unwrapWsol(input.seller, wsol.account)
1551
+ ...leg.after
1182
1552
  ];
1183
- return await buildMessage(client, input.seller, instructions, {
1184
- addressLookupTables: input.addressLookupTables,
1185
- computeUnitLimit: input.computeUnitLimit ?? 135e3,
1186
- ...input.computeUnitPrice === void 0 ? {} : { computeUnitPrice: input.computeUnitPrice }
1187
- });
1553
+ try {
1554
+ return await buildMessage(client, input.seller, instructions, {
1555
+ addressLookupTables: {
1556
+ ...input.addressLookupTables ?? client.addressLookupTables,
1557
+ ...leg.lookupTables
1558
+ },
1559
+ computeUnitLimit: input.computeUnitLimit ?? computeUnitsWithRoute(125e3, leg),
1560
+ ...input.computeUnitPrice === void 0 ? {} : { computeUnitPrice: input.computeUnitPrice }
1561
+ });
1562
+ } catch (cause) {
1563
+ throw routeSizeHint(cause, leg);
1564
+ }
1188
1565
  }
1189
1566
  //#endregion
1190
1567
  //#region src/vrf.ts
@@ -1204,6 +1581,6 @@ async function oracleAccounts() {
1204
1581
  };
1205
1582
  }
1206
1583
  //#endregion
1207
- export { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, BPS, BUY_PACK_COMPUTE_UNITS, CLAIM_COMPUTE_UNITS, CLUSTER_ENDPOINTS, COMPUTE_BUDGET_PROGRAM_ADDRESS, CREATE_MACHINE_COMPUTE_UNITS, DEFAULT_COMPUTE_UNIT_LIMIT, DEFAULT_TIERS, DEVNET_ADDRESS_LOOKUP_TABLES, DEVNET_HTTP, DEVNET_LOOKUP_TABLE_ADDRESS, DEVNET_LOOKUP_TABLE_ADDRESSES, DEVNET_WS, DRAW_DISCRIMINATOR, DRAW_POOL_OFFSET, DRAW_PURCHASER_OFFSET, GABOX_PROGRAM_ID, GaboxMathError, IDENTITY_SEED, INSTRUCTIONS_SYSVAR, LAUNCH_DECIMALS, MAX_ATTEMPTS, MAX_COMPUTE_UNIT_LIMIT, MAX_MULTIPLIER_BPS, METAPLEX_PROGRAM_ADDRESS, MIN_SEED_MULTIPLIER_BPS, PACK_TOKENS, PLATFORM_ADMIN, POOL_CREATOR_OFFSET, POOL_DISCRIMINATOR, POOL_MINT_OFFSET, REDEEM_COMPUTE_UNITS, RETRY_SLOTS, SLOT_HASHES_SYSVAR, SYSTEM_PROGRAM_ADDRESS, TICKETS, TIERS, TIMEOUT_SLOTS, TOKEN_PROGRAM_ADDRESS, VRF_DEFAULT_QUEUE, VRF_PROGRAM_ADDRESS, WALLET_ACTIVITY_DISCRIMINATOR, WSOL_MINT, activityAddress, assertClusterUrl, associatedTokenAddress, averageMultiplierBps, buildMessage, buyPack, choose, clusterNamedBy, computeBudgetInstructions, createClient, createMachine, decodeDraw, decodeEvent, decodeEvents, decodePool, decodeWalletActivity, defaultAddressLookupTables, drawAddress, drawAvailability, expireDraw, fetchDraw, fetchEvents, fetchPoolAt, fetchPoolByMint, fetchPoolInventory, fetchVaultBalance, fetchWalletActivity, findActivityPda, findDrawPda, findIdentityPda, findPoolPda, findResolvedDraw, fundPrizes, fundWsol, generated_exports as generated, getOffer, getSetComputeUnitLimitInstruction, getSetComputeUnitPriceInstruction, listDraws, listDrawsByPool, listDrawsByPurchaser, listPools, maxMultiplierBps, offerFromState, oracleAccounts, poolAddress, quote, quotePool, raydium_exports as raydium, resolveReservation, retryDraw, scopedVrfIdentityAddress, seedCostEstimate, seedShortfall, seedTokens, sellTokens, tierAmount, tiersOf, uncappedMaximum, unwrapWsol, validatePack, validateTiers, vaultAddress, vrfIdentityAddress, websocketUrlFor, withRemainingAccounts };
1584
+ export { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, BPS, BUY_PACK_COMPUTE_UNITS, CLAIM_COMPUTE_UNITS, CLUSTER_ENDPOINTS, COMPUTE_BUDGET_PROGRAM_ADDRESS, CPMM_ROUTE_COMPUTE_UNITS, CPMM_ROUTE_SLIPPAGE_BPS, CREATE_MACHINE_COMPUTE_UNITS, DEFAULT_COMPUTE_UNIT_LIMIT, DEFAULT_TIERS, DEVNET_ADDRESS_LOOKUP_TABLES, DEVNET_HTTP, DEVNET_LOOKUP_TABLE_ADDRESS, DEVNET_LOOKUP_TABLE_ADDRESSES, DEVNET_WS, DRAW_DISCRIMINATOR, DRAW_POOL_OFFSET, DRAW_PURCHASER_OFFSET, EXACT_IN_MARGIN_BPS, GABOX_PROGRAM_ID, GaboxMathError, IDENTITY_SEED, INSTRUCTIONS_SYSVAR, JUPITER_DEFAULT_COMPUTE_UNITS, JUPITER_DEFAULT_SLIPPAGE_BPS, JUPITER_LITE_URL, LAUNCH_DECIMALS, MAX_ATTEMPTS, MAX_COMPUTE_UNIT_LIMIT, MAX_MULTIPLIER_BPS, METAPLEX_PROGRAM_ADDRESS, MIN_SEED_MULTIPLIER_BPS, PACK_TOKENS, PLATFORM_ADMIN, POOL_CREATOR_OFFSET, POOL_DISCRIMINATOR, POOL_MINT_OFFSET, REDEEM_COMPUTE_UNITS, RETRY_SLOTS, SLOT_HASHES_SYSVAR, SYSTEM_PROGRAM_ADDRESS, TICKETS, TIERS, TIMEOUT_SLOTS, TOKEN_PROGRAM_ADDRESS, VRF_DEFAULT_QUEUE, VRF_PROGRAM_ADDRESS, WALLET_ACTIVITY_DISCRIMINATOR, WSOL_MINT, activityAddress, assertClusterUrl, assertRouteIsSafe, associatedTokenAddress, averageMultiplierBps, buildMessage, buyPack, choose, clusterNamedBy, computeBudgetInstructions, computeUnitsOf, computeUnitsWithRoute, createClient, createMachine, createQuoteAccount, decodeDraw, decodeEvent, decodeEvents, decodePool, decodeWalletActivity, defaultAddressLookupTables, defaultRoute, drawAddress, drawAvailability, expireDraw, fetchAddressLookupTables, fetchDraw, fetchEvents, fetchPoolAt, fetchPoolByMint, fetchPoolInventory, fetchVaultBalance, fetchWalletActivity, findActivityPda, findDrawPda, findIdentityPda, findPoolPda, findResolvedDraw, fundPrizes, fundWsol, generated_exports as generated, getOffer, getSetComputeUnitLimitInstruction, getSetComputeUnitPriceInstruction, jupiterRoute, listDraws, listDrawsByPool, listDrawsByPurchaser, listPools, maxMultiplierBps, offerFromState, oracleAccounts, poolAddress, providerOf, quote, quoteLegFromWallet, quoteLegIn, quoteLegOut, quotePool, raydium_exports as raydium, raydiumCpmmRoute, resolveReservation, retryDraw, routeFrom, routeQuoteIn, routeQuoteOut, routeSizeHint, scopedVrfIdentityAddress, seedCostEstimate, seedShortfall, seedTokens, sellTokens, solPriceOf, tierAmount, tiersOf, uncappedMaximum, unwrapWsol, validatePack, validateTiers, vaultAddress, vrfIdentityAddress, websocketUrlFor, withRemainingAccounts };
1208
1585
 
1209
1586
  //# sourceMappingURL=index.js.map