@gabox-labs/sdk 0.2.0 → 0.7.0
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/CHANGELOG.md +310 -0
- package/README.md +173 -438
- package/dist/{gaboxV2-CV2XltqC.js → gabox-DGTCh34U.js} +575 -1584
- package/dist/gabox-DGTCh34U.js.map +1 -0
- package/dist/generated/index.d.ts +384 -752
- package/dist/generated/index.js +116 -338
- package/dist/generated/index.js.map +1 -1
- package/dist/index-C4at2cZ_.d.ts +1184 -0
- package/dist/index.d.ts +474 -432
- package/dist/index.js +1186 -1374
- package/dist/index.js.map +1 -1
- package/dist/raydium/index.d.ts +2 -0
- package/dist/raydium/index.js +2 -0
- package/dist/raydium-CU-tZzIk.js +3282 -0
- package/dist/raydium-CU-tZzIk.js.map +1 -0
- package/llms.txt +7 -3
- package/package.json +11 -16
- package/skills/gabox-sdk/SKILL.md +57 -146
- package/skills/gabox-sdk/references/api.md +155 -145
- package/dist/gaboxV2-CV2XltqC.js.map +0 -1
- package/dist/index-BxvSkzCO.d.ts +0 -471
- package/dist/pump/index.d.ts +0 -2
- package/dist/pump/index.js +0 -2
- package/dist/pump-D0K_0uiC.js +0 -1531
- package/dist/pump-D0K_0uiC.js.map +0 -1
|
@@ -0,0 +1,3282 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import { Dt as POOL_DISCRIMINATOR, Et as getWalletActivitySize, Jt as getDrawSize, Lt as getPoolSize, Ot as decodePool, Rt as DRAW_DISCRIMINATOR, _t as decodeWalletActivity, ft as findPoolPda, ht as findActivityPda, mt as findDrawPda, pt as findIdentityPda, t as GABOX_PROGRAM_ADDRESS, zt as decodeDraw } from "./gabox-DGTCh34U.js";
|
|
3
|
+
import { AccountRole, Endian, addEncoderSizePrefix, appendTransactionMessageInstructions, compileTransaction, compressTransactionMessageUsingAddressLookupTables, createTransactionMessage, fetchEncodedAccount, fixDecoderSize, getAddressDecoder, getAddressEncoder, getBase58Decoder, getBase64Encoder, getBooleanDecoder, getBytesDecoder, getBytesEncoder, getProgramDerivedAddress, getStructDecoder, getStructEncoder, getTransactionEncoder, getU16Decoder, getU16Encoder, getU32Encoder, getU64Decoder, getU64Encoder, getU8Decoder, getU8Encoder, getUtf8Encoder, pipe, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash } from "@solana/kit";
|
|
4
|
+
import { getCloseAccountInstruction, getCreateAssociatedTokenIdempotentInstruction } from "@solana-program/token";
|
|
5
|
+
//#region src/raydium/abi.ts
|
|
6
|
+
const RAYDIUM_MAINNET_IDS = {
|
|
7
|
+
launchlab: "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj",
|
|
8
|
+
launchlabAuthority: "WLHv2UAZm6z4KyaaELi5pjdbJh6RESMva1Rnn8pJVVh",
|
|
9
|
+
launchlabEventAuthority: "2DPAtwB8L12vrMRExbLuyGnC7n2J5LNoZQSejeQGpwkr",
|
|
10
|
+
gaboxPlatform: "8tXUG97CEpSKTNXdGtanYyC33RWmTTuSin4VdaWwNj8q",
|
|
11
|
+
cpmm: "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C",
|
|
12
|
+
cpmmAuthority: "GpMZbSM2GgvTKHJirzeGfMFoaZ8UR2X7F4v8vHTvxFbL",
|
|
13
|
+
launchQuoteRaise: 85000000000n
|
|
14
|
+
};
|
|
15
|
+
const RAYDIUM_DEVNET_IDS = {
|
|
16
|
+
launchlab: "DRay6fNdQ5J82H7xV6uq2aV3mNrUZ1J4PgSKsWgptcm6",
|
|
17
|
+
launchlabAuthority: "5xqNaZXX5eUi4p5HU4oz9i5QnwRNT2y6oN7yyn4qENeq",
|
|
18
|
+
launchlabEventAuthority: "4uAB7seenFJKPUXqYewAdfra2u6baBgjiXU8x1SC7Ycz",
|
|
19
|
+
gaboxPlatform: "DdEeCPXbCAzHE2PZSoR3RZng4WA4bSztrezQznrJ4ooB",
|
|
20
|
+
cpmm: "DRaycpLY18LhpbydsBWbVJtxpNv9oXPgjRSfpF2bWpYb",
|
|
21
|
+
cpmmAuthority: "CXniRufdq5xL8t8jZAPxsPZDpuudwuJSPWnbcD5Y5Nxq",
|
|
22
|
+
launchQuoteRaise: 3000000000n
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Wrapped SOL: the default quote, and the one every price in SOL is measured in. Gabox accepts any
|
|
26
|
+
* quote Raydium enabled on LaunchLab, so this is not the only one.
|
|
27
|
+
*/
|
|
28
|
+
const WSOL_MINT = "So11111111111111111111111111111111111111112";
|
|
29
|
+
/** Metaplex Token Metadata, which LaunchLab's create instruction writes to. */
|
|
30
|
+
const METAPLEX_PROGRAM_ADDRESS = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
|
|
31
|
+
/**
|
|
32
|
+
* `PLATFORM_ADMIN` in the program's constants.rs. The Gabox platform config PDA derives from this
|
|
33
|
+
* wallet, and LaunchLab pays the platform share of every curve trade to it.
|
|
34
|
+
*/
|
|
35
|
+
const PLATFORM_ADMIN = "5WcPTEQ59UqpQzjZUPbU8QRGCbj7NeQNLDa7DbsLkLKT";
|
|
36
|
+
/** The coin supply every Gabox launch mints: 1,000,000,000 coins at 6 decimals. */
|
|
37
|
+
const LAUNCH_SUPPLY = 1000000000000000n;
|
|
38
|
+
/** The part of the supply the LaunchLab curve sells: 793,100,000 coins. */
|
|
39
|
+
const LAUNCH_TOTAL_BASE_SELL = 793100000000000n;
|
|
40
|
+
/** Classic SPL Token. The base coin is always under it, on both venues. */
|
|
41
|
+
const TOKEN_PROGRAM_ADDRESS = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
42
|
+
/**
|
|
43
|
+
* Token-2022. A quote mint may be under it, inside the bounded profile the program's
|
|
44
|
+
* `tokens::validate_quote_mint` accepts. The base coin never is.
|
|
45
|
+
*/
|
|
46
|
+
const TOKEN_2022_PROGRAM_ADDRESS = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
|
|
47
|
+
const ASSOCIATED_TOKEN_PROGRAM_ADDRESS = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
48
|
+
const SYSTEM_PROGRAM_ADDRESS = "11111111111111111111111111111111";
|
|
49
|
+
/** The rent sysvar. LaunchLab's create instruction still takes it. */
|
|
50
|
+
const RENT_SYSVAR_ADDRESS = "SysvarRent111111111111111111111111111111111";
|
|
51
|
+
/**
|
|
52
|
+
* The first eight bytes of each Raydium account this SDK decodes. `adapter.ts` checks them before
|
|
53
|
+
* it reads a field, so an account of the wrong type fails loudly instead of decoding as rubbish.
|
|
54
|
+
*
|
|
55
|
+
* LaunchLab and CPMM give their pool account the same eight bytes, because Anchor derives a
|
|
56
|
+
* discriminator from the struct name alone and both are called `PoolState`. The two programs own
|
|
57
|
+
* different accounts, so the pair never collides in practice.
|
|
58
|
+
*/
|
|
59
|
+
const LAUNCHLAB_POOL_STATE_DISCRIMINATOR = new Uint8Array([
|
|
60
|
+
247,
|
|
61
|
+
237,
|
|
62
|
+
227,
|
|
63
|
+
245,
|
|
64
|
+
215,
|
|
65
|
+
195,
|
|
66
|
+
222,
|
|
67
|
+
70
|
|
68
|
+
]);
|
|
69
|
+
const LAUNCHLAB_GLOBAL_CONFIG_DISCRIMINATOR = new Uint8Array([
|
|
70
|
+
149,
|
|
71
|
+
8,
|
|
72
|
+
156,
|
|
73
|
+
202,
|
|
74
|
+
160,
|
|
75
|
+
252,
|
|
76
|
+
176,
|
|
77
|
+
217
|
|
78
|
+
]);
|
|
79
|
+
const LAUNCHLAB_PLATFORM_CONFIG_DISCRIMINATOR = new Uint8Array([
|
|
80
|
+
160,
|
|
81
|
+
78,
|
|
82
|
+
128,
|
|
83
|
+
0,
|
|
84
|
+
248,
|
|
85
|
+
83,
|
|
86
|
+
230,
|
|
87
|
+
160
|
|
88
|
+
]);
|
|
89
|
+
const CPMM_POOL_STATE_DISCRIMINATOR = new Uint8Array([
|
|
90
|
+
247,
|
|
91
|
+
237,
|
|
92
|
+
227,
|
|
93
|
+
245,
|
|
94
|
+
215,
|
|
95
|
+
195,
|
|
96
|
+
222,
|
|
97
|
+
70
|
|
98
|
+
]);
|
|
99
|
+
const CPMM_AMM_CONFIG_DISCRIMINATOR = new Uint8Array([
|
|
100
|
+
218,
|
|
101
|
+
244,
|
|
102
|
+
33,
|
|
103
|
+
104,
|
|
104
|
+
203,
|
|
105
|
+
203,
|
|
106
|
+
43,
|
|
107
|
+
111
|
|
108
|
+
]);
|
|
109
|
+
/**
|
|
110
|
+
* `initialize_v2` on `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj` — cross-checked against interfaces.json.
|
|
111
|
+
*/
|
|
112
|
+
const LAUNCHLAB_INITIALIZE = {
|
|
113
|
+
name: "initialize_v2",
|
|
114
|
+
discriminator: new Uint8Array([
|
|
115
|
+
67,
|
|
116
|
+
153,
|
|
117
|
+
175,
|
|
118
|
+
39,
|
|
119
|
+
218,
|
|
120
|
+
16,
|
|
121
|
+
38,
|
|
122
|
+
32
|
|
123
|
+
]),
|
|
124
|
+
accounts: [
|
|
125
|
+
{
|
|
126
|
+
name: "payer",
|
|
127
|
+
writable: true,
|
|
128
|
+
signer: true
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "creator",
|
|
132
|
+
writable: false,
|
|
133
|
+
signer: false
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "global_config",
|
|
137
|
+
writable: false,
|
|
138
|
+
signer: false
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "platform_config",
|
|
142
|
+
writable: false,
|
|
143
|
+
signer: false
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "authority",
|
|
147
|
+
writable: false,
|
|
148
|
+
signer: false
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "pool_state",
|
|
152
|
+
writable: true,
|
|
153
|
+
signer: false
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "base_mint",
|
|
157
|
+
writable: true,
|
|
158
|
+
signer: true
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: "quote_mint",
|
|
162
|
+
writable: false,
|
|
163
|
+
signer: false
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "base_vault",
|
|
167
|
+
writable: true,
|
|
168
|
+
signer: false
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "quote_vault",
|
|
172
|
+
writable: true,
|
|
173
|
+
signer: false
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "metadata_account",
|
|
177
|
+
writable: true,
|
|
178
|
+
signer: false
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "base_token_program",
|
|
182
|
+
writable: false,
|
|
183
|
+
signer: false
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "quote_token_program",
|
|
187
|
+
writable: false,
|
|
188
|
+
signer: false
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "metadata_program",
|
|
192
|
+
writable: false,
|
|
193
|
+
signer: false
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "system_program",
|
|
197
|
+
writable: false,
|
|
198
|
+
signer: false
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: "rent_program",
|
|
202
|
+
writable: false,
|
|
203
|
+
signer: false
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "event_authority",
|
|
207
|
+
writable: false,
|
|
208
|
+
signer: false
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "program",
|
|
212
|
+
writable: false,
|
|
213
|
+
signer: false
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* `buy_exact_in` on `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj` — not in interfaces.json (the program never builds it).
|
|
219
|
+
* The last three accounts are not in the IDL. LaunchLab reads them from the remaining
|
|
220
|
+
* accounts, in this order.
|
|
221
|
+
*/
|
|
222
|
+
const LAUNCHLAB_BUY_EXACT_IN = {
|
|
223
|
+
name: "buy_exact_in",
|
|
224
|
+
discriminator: new Uint8Array([
|
|
225
|
+
250,
|
|
226
|
+
234,
|
|
227
|
+
13,
|
|
228
|
+
123,
|
|
229
|
+
213,
|
|
230
|
+
156,
|
|
231
|
+
19,
|
|
232
|
+
236
|
|
233
|
+
]),
|
|
234
|
+
accounts: [
|
|
235
|
+
{
|
|
236
|
+
name: "payer",
|
|
237
|
+
writable: true,
|
|
238
|
+
signer: true
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: "authority",
|
|
242
|
+
writable: false,
|
|
243
|
+
signer: false
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: "global_config",
|
|
247
|
+
writable: false,
|
|
248
|
+
signer: false
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: "platform_config",
|
|
252
|
+
writable: false,
|
|
253
|
+
signer: false
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "pool_state",
|
|
257
|
+
writable: true,
|
|
258
|
+
signer: false
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: "user_base_token",
|
|
262
|
+
writable: true,
|
|
263
|
+
signer: false
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "user_quote_token",
|
|
267
|
+
writable: true,
|
|
268
|
+
signer: false
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: "base_vault",
|
|
272
|
+
writable: true,
|
|
273
|
+
signer: false
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: "quote_vault",
|
|
277
|
+
writable: true,
|
|
278
|
+
signer: false
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: "base_token_mint",
|
|
282
|
+
writable: false,
|
|
283
|
+
signer: false
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "quote_token_mint",
|
|
287
|
+
writable: false,
|
|
288
|
+
signer: false
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: "base_token_program",
|
|
292
|
+
writable: false,
|
|
293
|
+
signer: false
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: "quote_token_program",
|
|
297
|
+
writable: false,
|
|
298
|
+
signer: false
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: "event_authority",
|
|
302
|
+
writable: false,
|
|
303
|
+
signer: false
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: "program",
|
|
307
|
+
writable: false,
|
|
308
|
+
signer: false
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: "system_program",
|
|
312
|
+
writable: false,
|
|
313
|
+
signer: false
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
name: "platform_fee_vault",
|
|
317
|
+
writable: true,
|
|
318
|
+
signer: false
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: "creator_fee_vault",
|
|
322
|
+
writable: true,
|
|
323
|
+
signer: false
|
|
324
|
+
}
|
|
325
|
+
]
|
|
326
|
+
};
|
|
327
|
+
/**
|
|
328
|
+
* `buy_exact_out` on `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj` — cross-checked against interfaces.json.
|
|
329
|
+
* The last three accounts are not in the IDL. LaunchLab reads them from the remaining
|
|
330
|
+
* accounts, in this order.
|
|
331
|
+
*/
|
|
332
|
+
const LAUNCHLAB_BUY_EXACT_OUT = {
|
|
333
|
+
name: "buy_exact_out",
|
|
334
|
+
discriminator: new Uint8Array([
|
|
335
|
+
24,
|
|
336
|
+
211,
|
|
337
|
+
116,
|
|
338
|
+
40,
|
|
339
|
+
105,
|
|
340
|
+
3,
|
|
341
|
+
153,
|
|
342
|
+
56
|
|
343
|
+
]),
|
|
344
|
+
accounts: [
|
|
345
|
+
{
|
|
346
|
+
name: "payer",
|
|
347
|
+
writable: true,
|
|
348
|
+
signer: true
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: "authority",
|
|
352
|
+
writable: false,
|
|
353
|
+
signer: false
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: "global_config",
|
|
357
|
+
writable: false,
|
|
358
|
+
signer: false
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: "platform_config",
|
|
362
|
+
writable: false,
|
|
363
|
+
signer: false
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: "pool_state",
|
|
367
|
+
writable: true,
|
|
368
|
+
signer: false
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: "user_base_token",
|
|
372
|
+
writable: true,
|
|
373
|
+
signer: false
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: "user_quote_token",
|
|
377
|
+
writable: true,
|
|
378
|
+
signer: false
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: "base_vault",
|
|
382
|
+
writable: true,
|
|
383
|
+
signer: false
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
name: "quote_vault",
|
|
387
|
+
writable: true,
|
|
388
|
+
signer: false
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: "base_token_mint",
|
|
392
|
+
writable: false,
|
|
393
|
+
signer: false
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: "quote_token_mint",
|
|
397
|
+
writable: false,
|
|
398
|
+
signer: false
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: "base_token_program",
|
|
402
|
+
writable: false,
|
|
403
|
+
signer: false
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: "quote_token_program",
|
|
407
|
+
writable: false,
|
|
408
|
+
signer: false
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: "event_authority",
|
|
412
|
+
writable: false,
|
|
413
|
+
signer: false
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: "program",
|
|
417
|
+
writable: false,
|
|
418
|
+
signer: false
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: "system_program",
|
|
422
|
+
writable: false,
|
|
423
|
+
signer: false
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: "platform_fee_vault",
|
|
427
|
+
writable: true,
|
|
428
|
+
signer: false
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
name: "creator_fee_vault",
|
|
432
|
+
writable: true,
|
|
433
|
+
signer: false
|
|
434
|
+
}
|
|
435
|
+
]
|
|
436
|
+
};
|
|
437
|
+
/**
|
|
438
|
+
* `sell_exact_in` on `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj` — cross-checked against interfaces.json.
|
|
439
|
+
* The last three accounts are not in the IDL. LaunchLab reads them from the remaining
|
|
440
|
+
* accounts, in this order.
|
|
441
|
+
*/
|
|
442
|
+
const LAUNCHLAB_SELL_EXACT_IN = {
|
|
443
|
+
name: "sell_exact_in",
|
|
444
|
+
discriminator: new Uint8Array([
|
|
445
|
+
149,
|
|
446
|
+
39,
|
|
447
|
+
222,
|
|
448
|
+
155,
|
|
449
|
+
211,
|
|
450
|
+
124,
|
|
451
|
+
152,
|
|
452
|
+
26
|
|
453
|
+
]),
|
|
454
|
+
accounts: [
|
|
455
|
+
{
|
|
456
|
+
name: "payer",
|
|
457
|
+
writable: true,
|
|
458
|
+
signer: true
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: "authority",
|
|
462
|
+
writable: false,
|
|
463
|
+
signer: false
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: "global_config",
|
|
467
|
+
writable: false,
|
|
468
|
+
signer: false
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
name: "platform_config",
|
|
472
|
+
writable: false,
|
|
473
|
+
signer: false
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: "pool_state",
|
|
477
|
+
writable: true,
|
|
478
|
+
signer: false
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
name: "user_base_token",
|
|
482
|
+
writable: true,
|
|
483
|
+
signer: false
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: "user_quote_token",
|
|
487
|
+
writable: true,
|
|
488
|
+
signer: false
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: "base_vault",
|
|
492
|
+
writable: true,
|
|
493
|
+
signer: false
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
name: "quote_vault",
|
|
497
|
+
writable: true,
|
|
498
|
+
signer: false
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: "base_token_mint",
|
|
502
|
+
writable: false,
|
|
503
|
+
signer: false
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
name: "quote_token_mint",
|
|
507
|
+
writable: false,
|
|
508
|
+
signer: false
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
name: "base_token_program",
|
|
512
|
+
writable: false,
|
|
513
|
+
signer: false
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
name: "quote_token_program",
|
|
517
|
+
writable: false,
|
|
518
|
+
signer: false
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
name: "event_authority",
|
|
522
|
+
writable: false,
|
|
523
|
+
signer: false
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
name: "program",
|
|
527
|
+
writable: false,
|
|
528
|
+
signer: false
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
name: "system_program",
|
|
532
|
+
writable: false,
|
|
533
|
+
signer: false
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
name: "platform_fee_vault",
|
|
537
|
+
writable: true,
|
|
538
|
+
signer: false
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
name: "creator_fee_vault",
|
|
542
|
+
writable: true,
|
|
543
|
+
signer: false
|
|
544
|
+
}
|
|
545
|
+
]
|
|
546
|
+
};
|
|
547
|
+
/**
|
|
548
|
+
* `claim_creator_fee` on `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj` — not in interfaces.json (the program never builds it).
|
|
549
|
+
*/
|
|
550
|
+
const LAUNCHLAB_CLAIM_CREATOR_FEE = {
|
|
551
|
+
name: "claim_creator_fee",
|
|
552
|
+
discriminator: new Uint8Array([
|
|
553
|
+
26,
|
|
554
|
+
97,
|
|
555
|
+
138,
|
|
556
|
+
203,
|
|
557
|
+
132,
|
|
558
|
+
171,
|
|
559
|
+
141,
|
|
560
|
+
252
|
|
561
|
+
]),
|
|
562
|
+
accounts: [
|
|
563
|
+
{
|
|
564
|
+
name: "creator",
|
|
565
|
+
writable: true,
|
|
566
|
+
signer: true
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
name: "fee_vault_authority",
|
|
570
|
+
writable: false,
|
|
571
|
+
signer: false
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: "creator_fee_vault",
|
|
575
|
+
writable: true,
|
|
576
|
+
signer: false
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
name: "recipient_token_account",
|
|
580
|
+
writable: true,
|
|
581
|
+
signer: false
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: "quote_mint",
|
|
585
|
+
writable: false,
|
|
586
|
+
signer: false
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
name: "token_program",
|
|
590
|
+
writable: false,
|
|
591
|
+
signer: false
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
name: "system_program",
|
|
595
|
+
writable: false,
|
|
596
|
+
signer: false
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
name: "associated_token_program",
|
|
600
|
+
writable: false,
|
|
601
|
+
signer: false
|
|
602
|
+
}
|
|
603
|
+
]
|
|
604
|
+
};
|
|
605
|
+
/**
|
|
606
|
+
* `swap_base_output` on `CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C` — cross-checked against interfaces.json.
|
|
607
|
+
*/
|
|
608
|
+
const CPMM_SWAP_BASE_OUTPUT = {
|
|
609
|
+
name: "swap_base_output",
|
|
610
|
+
discriminator: new Uint8Array([
|
|
611
|
+
55,
|
|
612
|
+
217,
|
|
613
|
+
98,
|
|
614
|
+
86,
|
|
615
|
+
163,
|
|
616
|
+
74,
|
|
617
|
+
180,
|
|
618
|
+
173
|
|
619
|
+
]),
|
|
620
|
+
accounts: [
|
|
621
|
+
{
|
|
622
|
+
name: "payer",
|
|
623
|
+
writable: false,
|
|
624
|
+
signer: true
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
name: "authority",
|
|
628
|
+
writable: false,
|
|
629
|
+
signer: false
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
name: "amm_config",
|
|
633
|
+
writable: false,
|
|
634
|
+
signer: false
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
name: "pool_state",
|
|
638
|
+
writable: true,
|
|
639
|
+
signer: false
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
name: "input_token_account",
|
|
643
|
+
writable: true,
|
|
644
|
+
signer: false
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
name: "output_token_account",
|
|
648
|
+
writable: true,
|
|
649
|
+
signer: false
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
name: "input_vault",
|
|
653
|
+
writable: true,
|
|
654
|
+
signer: false
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
name: "output_vault",
|
|
658
|
+
writable: true,
|
|
659
|
+
signer: false
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
name: "input_token_program",
|
|
663
|
+
writable: false,
|
|
664
|
+
signer: false
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
name: "output_token_program",
|
|
668
|
+
writable: false,
|
|
669
|
+
signer: false
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
name: "input_token_mint",
|
|
673
|
+
writable: false,
|
|
674
|
+
signer: false
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
name: "output_token_mint",
|
|
678
|
+
writable: false,
|
|
679
|
+
signer: false
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
name: "observation_state",
|
|
683
|
+
writable: true,
|
|
684
|
+
signer: false
|
|
685
|
+
}
|
|
686
|
+
]
|
|
687
|
+
};
|
|
688
|
+
/**
|
|
689
|
+
* `swap_base_input` on `CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C` — cross-checked against interfaces.json.
|
|
690
|
+
*/
|
|
691
|
+
const CPMM_SWAP_BASE_INPUT = {
|
|
692
|
+
name: "swap_base_input",
|
|
693
|
+
discriminator: new Uint8Array([
|
|
694
|
+
143,
|
|
695
|
+
190,
|
|
696
|
+
90,
|
|
697
|
+
218,
|
|
698
|
+
196,
|
|
699
|
+
30,
|
|
700
|
+
51,
|
|
701
|
+
222
|
|
702
|
+
]),
|
|
703
|
+
accounts: [
|
|
704
|
+
{
|
|
705
|
+
name: "payer",
|
|
706
|
+
writable: false,
|
|
707
|
+
signer: true
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
name: "authority",
|
|
711
|
+
writable: false,
|
|
712
|
+
signer: false
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
name: "amm_config",
|
|
716
|
+
writable: false,
|
|
717
|
+
signer: false
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
name: "pool_state",
|
|
721
|
+
writable: true,
|
|
722
|
+
signer: false
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
name: "input_token_account",
|
|
726
|
+
writable: true,
|
|
727
|
+
signer: false
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
name: "output_token_account",
|
|
731
|
+
writable: true,
|
|
732
|
+
signer: false
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
name: "input_vault",
|
|
736
|
+
writable: true,
|
|
737
|
+
signer: false
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
name: "output_vault",
|
|
741
|
+
writable: true,
|
|
742
|
+
signer: false
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
name: "input_token_program",
|
|
746
|
+
writable: false,
|
|
747
|
+
signer: false
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
name: "output_token_program",
|
|
751
|
+
writable: false,
|
|
752
|
+
signer: false
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
name: "input_token_mint",
|
|
756
|
+
writable: false,
|
|
757
|
+
signer: false
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
name: "output_token_mint",
|
|
761
|
+
writable: false,
|
|
762
|
+
signer: false
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
name: "observation_state",
|
|
766
|
+
writable: true,
|
|
767
|
+
signer: false
|
|
768
|
+
}
|
|
769
|
+
]
|
|
770
|
+
};
|
|
771
|
+
/**
|
|
772
|
+
* `collect_creator_fee` on `CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C` — not in interfaces.json (the program never builds it).
|
|
773
|
+
* The last account is not in the IDL snapshot; see `CPMM_CREATOR_FEE_SHARE_ACCOUNT` in
|
|
774
|
+
* scripts/codegen.mjs. Derive it as PDA(["creator_fee_share", creator, amm_config]).
|
|
775
|
+
*/
|
|
776
|
+
const CPMM_COLLECT_CREATOR_FEE = {
|
|
777
|
+
name: "collect_creator_fee",
|
|
778
|
+
discriminator: new Uint8Array([
|
|
779
|
+
20,
|
|
780
|
+
22,
|
|
781
|
+
86,
|
|
782
|
+
123,
|
|
783
|
+
198,
|
|
784
|
+
28,
|
|
785
|
+
219,
|
|
786
|
+
132
|
|
787
|
+
]),
|
|
788
|
+
accounts: [
|
|
789
|
+
{
|
|
790
|
+
name: "creator",
|
|
791
|
+
writable: true,
|
|
792
|
+
signer: true
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
name: "authority",
|
|
796
|
+
writable: false,
|
|
797
|
+
signer: false
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
name: "pool_state",
|
|
801
|
+
writable: true,
|
|
802
|
+
signer: false
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
name: "amm_config",
|
|
806
|
+
writable: false,
|
|
807
|
+
signer: false
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
name: "token_0_vault",
|
|
811
|
+
writable: true,
|
|
812
|
+
signer: false
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
name: "token_1_vault",
|
|
816
|
+
writable: true,
|
|
817
|
+
signer: false
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
name: "vault_0_mint",
|
|
821
|
+
writable: false,
|
|
822
|
+
signer: false
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
name: "vault_1_mint",
|
|
826
|
+
writable: false,
|
|
827
|
+
signer: false
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
name: "creator_token_0",
|
|
831
|
+
writable: true,
|
|
832
|
+
signer: false
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
name: "creator_token_1",
|
|
836
|
+
writable: true,
|
|
837
|
+
signer: false
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
name: "token_0_program",
|
|
841
|
+
writable: false,
|
|
842
|
+
signer: false
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
name: "token_1_program",
|
|
846
|
+
writable: false,
|
|
847
|
+
signer: false
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
name: "associated_token_program",
|
|
851
|
+
writable: false,
|
|
852
|
+
signer: false
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
name: "system_program",
|
|
856
|
+
writable: false,
|
|
857
|
+
signer: false
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
name: "creator_fee_share",
|
|
861
|
+
writable: false,
|
|
862
|
+
signer: false
|
|
863
|
+
}
|
|
864
|
+
]
|
|
865
|
+
};
|
|
866
|
+
//#endregion
|
|
867
|
+
//#region src/raydium/ids.ts
|
|
868
|
+
/**
|
|
869
|
+
* The Raydium addresses and launch numbers Gabox pins, per cluster.
|
|
870
|
+
*
|
|
871
|
+
* Mainnet and devnet run different Raydium deployments. Every program id and every PDA differs, and
|
|
872
|
+
* so does the raise a WSOL-quoted curve must reach. So nothing here is a global: a caller passes the
|
|
873
|
+
* cluster, which comes from `createClient({ cluster })`.
|
|
874
|
+
*
|
|
875
|
+
* None of these strings is typed in this file. `scripts/codegen.mjs` reads them out of the Rust
|
|
876
|
+
* program's `programs/gabox/src/venue/ids.rs` and writes `abi.ts`. The Rust file is the source of
|
|
877
|
+
* truth, because the program checks every one of these addresses on chain.
|
|
878
|
+
* `test/raydium-pdas.test.ts` derives each PDA again and compares.
|
|
879
|
+
*/
|
|
880
|
+
const ADDRESS_ENCODER = getAddressEncoder();
|
|
881
|
+
/**
|
|
882
|
+
* The decimals every Gabox coin is launched with. `venue/launch.rs` refuses any other value, and
|
|
883
|
+
* `tokens.rs` refuses a pool mint that does not have exactly this many.
|
|
884
|
+
*/
|
|
885
|
+
const LAUNCH_DECIMALS = 6;
|
|
886
|
+
/**
|
|
887
|
+
* The Raydium deployment of each cluster.
|
|
888
|
+
*
|
|
889
|
+
* `localnet` uses the mainnet set. A local validator normally clones the mainnet Raydium programs,
|
|
890
|
+
* and the program's own default `anchor build` pins the mainnet addresses too; the `devnet` cargo
|
|
891
|
+
* feature is what swaps them. Pass your own ids to a builder if your validator clones devnet.
|
|
892
|
+
*/
|
|
893
|
+
const RAYDIUM_IDS = {
|
|
894
|
+
"mainnet-beta": RAYDIUM_MAINNET_IDS,
|
|
895
|
+
devnet: RAYDIUM_DEVNET_IDS,
|
|
896
|
+
localnet: RAYDIUM_MAINNET_IDS
|
|
897
|
+
};
|
|
898
|
+
/** The Raydium deployment one cluster trades on. Throws for a cluster this SDK does not know. */
|
|
899
|
+
function raydiumIds(cluster) {
|
|
900
|
+
const ids = RAYDIUM_IDS[cluster];
|
|
901
|
+
if (!ids) throw new Error(`unknown cluster ${JSON.stringify(cluster)}; expected 'devnet', 'mainnet-beta' or 'localnet'`);
|
|
902
|
+
return ids;
|
|
903
|
+
}
|
|
904
|
+
/** LaunchLab's PDA seeds, and the two Gabox coins are always sorted against. */
|
|
905
|
+
const LAUNCHLAB_SEEDS = {
|
|
906
|
+
/** `["vault_auth_seed"]`. */
|
|
907
|
+
authority: "vault_auth_seed",
|
|
908
|
+
/** `["__event_authority"]`. */
|
|
909
|
+
eventAuthority: "__event_authority",
|
|
910
|
+
/** `["global_config", quote_mint, u8 curve_type, u16 big-endian index]`. */
|
|
911
|
+
globalConfig: "global_config",
|
|
912
|
+
/** `["platform_config", platform_admin]`. */
|
|
913
|
+
platformConfig: "platform_config",
|
|
914
|
+
/** `["pool", base_mint, quote_mint]`. */
|
|
915
|
+
pool: "pool",
|
|
916
|
+
/** `["pool_vault", pool_state, mint]`. */
|
|
917
|
+
vault: "pool_vault",
|
|
918
|
+
/** `["creator_fee_vault_auth_seed"]`, the authority over every creator fee vault. */
|
|
919
|
+
creatorFeeVaultAuthority: "creator_fee_vault_auth_seed"
|
|
920
|
+
};
|
|
921
|
+
/** CPMM's PDA seeds. */
|
|
922
|
+
const CPMM_SEEDS = {
|
|
923
|
+
/** `["vault_and_lp_mint_auth_seed"]`. */
|
|
924
|
+
authority: "vault_and_lp_mint_auth_seed",
|
|
925
|
+
/** `["pool", amm_config, token_0_mint, token_1_mint]`, the mints sorted by byte order. */
|
|
926
|
+
pool: "pool",
|
|
927
|
+
/** `["pool_vault", pool_state, mint]`. */
|
|
928
|
+
vault: "pool_vault",
|
|
929
|
+
/** `["observation", pool_state]`. */
|
|
930
|
+
observation: "observation",
|
|
931
|
+
/** `["creator_fee_share", creator, amm_config]`. */
|
|
932
|
+
creatorFeeShare: "creator_fee_share"
|
|
933
|
+
};
|
|
934
|
+
/** Metaplex's metadata PDA seed: `["metadata", metaplex, mint]`. */
|
|
935
|
+
const METADATA_SEED = "metadata";
|
|
936
|
+
/**
|
|
937
|
+
* Every rate LaunchLab and CPMM state is out of 1,000,000, not out of 10,000. A `trade_fee_rate`
|
|
938
|
+
* of 5,000 is 0.5%.
|
|
939
|
+
*/
|
|
940
|
+
const RATE_DENOMINATOR = 1000000n;
|
|
941
|
+
/** The share-fee receiver Gabox never passes, so its rate is always zero on every trade. */
|
|
942
|
+
const SHARE_FEE_RATE = 0n;
|
|
943
|
+
/** `CurveParams::Constant`, the only curve shape Gabox launches. */
|
|
944
|
+
const CONSTANT_CURVE_TAG = 0;
|
|
945
|
+
/** `migrate_type` 1: the coin graduates into a CPMM pool, not into an AMM pool. */
|
|
946
|
+
const MIGRATE_TO_CPMM = 1;
|
|
947
|
+
/** `AmmCreatorFeeOn::QuoteToken`, so the CPMM creator fee is paid in the pool's quote asset. */
|
|
948
|
+
const CREATOR_FEE_ON_QUOTE = 0;
|
|
949
|
+
/** LaunchLab pool `status`: still selling on the curve. */
|
|
950
|
+
const LAUNCHLAB_STATUS_FUND = 0;
|
|
951
|
+
/** LaunchLab pool `status`: the raise is complete and Raydium's bot is migrating the coin. */
|
|
952
|
+
const LAUNCHLAB_STATUS_MIGRATE = 1;
|
|
953
|
+
/** LaunchLab pool `status`: the coin has graduated and trades on its CPMM pool. */
|
|
954
|
+
const LAUNCHLAB_STATUS_TRADE = 2;
|
|
955
|
+
/** Metaplex's own limits on the three strings a creator picks, in UTF-8 bytes. */
|
|
956
|
+
const METADATA_LIMITS = {
|
|
957
|
+
name: 32,
|
|
958
|
+
symbol: 10,
|
|
959
|
+
uri: 200
|
|
960
|
+
};
|
|
961
|
+
/** Raydium sorts a CPMM pair by raw address byte order. */
|
|
962
|
+
function sortedMints(a, b) {
|
|
963
|
+
return compareAddresses(a, b) <= 0 ? [a, b] : [b, a];
|
|
964
|
+
}
|
|
965
|
+
/** Compare two addresses the way the runtime does: by their 32 raw bytes, not by base58 text. */
|
|
966
|
+
function compareAddresses(a, b) {
|
|
967
|
+
const left = ADDRESS_ENCODER.encode(a);
|
|
968
|
+
const right = ADDRESS_ENCODER.encode(b);
|
|
969
|
+
for (let i = 0; i < 32; i++) {
|
|
970
|
+
const difference = (left[i] ?? 0) - (right[i] ?? 0);
|
|
971
|
+
if (difference !== 0) return difference;
|
|
972
|
+
}
|
|
973
|
+
return 0;
|
|
974
|
+
}
|
|
975
|
+
//#endregion
|
|
976
|
+
//#region src/ids.ts
|
|
977
|
+
/** `declare_id!` in `programs/gabox/src/lib.rs`. */
|
|
978
|
+
const GABOX_PROGRAM_ID = GABOX_PROGRAM_ADDRESS;
|
|
979
|
+
/** MagicBlock's ephemeral VRF program. `vrf.rs` pins it and refuses any other. */
|
|
980
|
+
const VRF_PROGRAM_ADDRESS = "Vrf1RNUjXmQGjmQrQLvJHs9SNkvDJEsRVFPkfSQUwGz";
|
|
981
|
+
/**
|
|
982
|
+
* MagicBlock's default oracle queue. The program pins this one address, so a pool creator cannot
|
|
983
|
+
* point a draw at an oracle they control.
|
|
984
|
+
*/
|
|
985
|
+
const VRF_DEFAULT_QUEUE = "Cuj97ggrhhidhbu39TijNVqE74xvKJ69gDervRUXAxGh";
|
|
986
|
+
/** The seed of both identity PDAs. See `pdas.ts` — there are two, under different programs. */
|
|
987
|
+
const IDENTITY_SEED = new Uint8Array([
|
|
988
|
+
105,
|
|
989
|
+
100,
|
|
990
|
+
101,
|
|
991
|
+
110,
|
|
992
|
+
116,
|
|
993
|
+
105,
|
|
994
|
+
116,
|
|
995
|
+
121
|
|
996
|
+
]);
|
|
997
|
+
const SLOT_HASHES_SYSVAR = "SysvarS1otHashes111111111111111111111111111";
|
|
998
|
+
const INSTRUCTIONS_SYSVAR = "Sysvar1nstructions1111111111111111111111111";
|
|
999
|
+
/** `constants.rs`. A retry may not run sooner than this after the last attempt. */
|
|
1000
|
+
const RETRY_SLOTS = 300n;
|
|
1001
|
+
/** `constants.rs`. After this many slots from the purchase, anyone may expire the draw. */
|
|
1002
|
+
const TIMEOUT_SLOTS = 216000n;
|
|
1003
|
+
/** `constants.rs`. Three requests in total, counting the one `buy_pack` makes. */
|
|
1004
|
+
const MAX_ATTEMPTS = 3;
|
|
1005
|
+
/**
|
|
1006
|
+
* `constants.rs`. Tokens in one pack, in base units: 1,000,000 tokens at the fixed 6 decimals, or
|
|
1007
|
+
* 0.1% of the 1,000,000,000 supply. Every pool sells packs of this size. The venue decides what a
|
|
1008
|
+
* pack costs, so the pack price follows the coin. Read `pool.packTokens` rather than this when a
|
|
1009
|
+
* pool is at hand: a later program version may change the constant.
|
|
1010
|
+
*/
|
|
1011
|
+
const PACK_TOKENS = 1000000n * 1000000n;
|
|
1012
|
+
//#endregion
|
|
1013
|
+
//#region src/math.ts
|
|
1014
|
+
/**
|
|
1015
|
+
* A bigint port of `programs/gabox/src/math.rs`.
|
|
1016
|
+
*
|
|
1017
|
+
* The point is that a client can show a buyer the exact prize table the program will freeze into
|
|
1018
|
+
* their `Draw`, before they pay. Every rounding step here matches the Rust, including the direction
|
|
1019
|
+
* of each division. `test/math.test.ts` runs the same vectors as `programs/gabox/tests/math.rs`.
|
|
1020
|
+
*
|
|
1021
|
+
* All amounts are `bigint`, in the mint's smallest unit. `multiplierBps` and `tickets` are numbers
|
|
1022
|
+
* because both are `u32` in the program and both stay small.
|
|
1023
|
+
*/
|
|
1024
|
+
/** Basis points. A multiplier of 10_000 pays back exactly one pack. */
|
|
1025
|
+
const BPS = 10000n;
|
|
1026
|
+
/**
|
|
1027
|
+
* `constants::MIN_SEED_MULTIPLIER_BPS`. The mandatory seed makes a 3x top prize payable on the
|
|
1028
|
+
* first pack, or the full top prize when the table's top tier is below 3x. A creator can add more
|
|
1029
|
+
* seed on top through `extraSeedTokens`.
|
|
1030
|
+
*/
|
|
1031
|
+
const MIN_SEED_MULTIPLIER_BPS = 3e4;
|
|
1032
|
+
/**
|
|
1033
|
+
* `constants::MAX_MULTIPLIER_BPS`. A table's top tier can be at most 20x. Seed beyond what a 20x
|
|
1034
|
+
* prize needs stays in the vault as backup for the draws after a top-tier hit.
|
|
1035
|
+
*/
|
|
1036
|
+
const MAX_MULTIPLIER_BPS = 2e5;
|
|
1037
|
+
/** Ticket counts must sum to this. A uniform 16-bit word then maps with no modulo bias. */
|
|
1038
|
+
const TICKETS = 65536;
|
|
1039
|
+
/** The prize table has exactly this many slots. Unused slots are all-zero. */
|
|
1040
|
+
const TIERS = 8;
|
|
1041
|
+
/** Thrown by every function here. `code` matches a `GaboxError` variant name. */
|
|
1042
|
+
var GaboxMathError = class extends Error {
|
|
1043
|
+
code;
|
|
1044
|
+
constructor(code, message) {
|
|
1045
|
+
super(`${code}: ${message}`);
|
|
1046
|
+
this.name = "GaboxMathError";
|
|
1047
|
+
this.code = code;
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
const U64_MAX = (1n << 64n) - 1n;
|
|
1051
|
+
const U32_MAX = 4294967295;
|
|
1052
|
+
/**
|
|
1053
|
+
* The default prize table. The program does not enforce this table: it accepts any table that
|
|
1054
|
+
* passes `validateTiers`. This is only the table the Gabox app creates its pools with, and the
|
|
1055
|
+
* starting point for a client that has no reason to pick another one.
|
|
1056
|
+
*
|
|
1057
|
+
* Common 75% at 0.52x, Rare 20% at 1.2x, Epic 4% at 3x, Mythic 1% at 20x. Expected payout is
|
|
1058
|
+
* 0.9499x of a pack, so the seed is 19 packs.
|
|
1059
|
+
*/
|
|
1060
|
+
const DEFAULT_TIERS = Object.freeze([
|
|
1061
|
+
Object.freeze({
|
|
1062
|
+
multiplierBps: 5201,
|
|
1063
|
+
tickets: 49153
|
|
1064
|
+
}),
|
|
1065
|
+
Object.freeze({
|
|
1066
|
+
multiplierBps: 12e3,
|
|
1067
|
+
tickets: 13107
|
|
1068
|
+
}),
|
|
1069
|
+
Object.freeze({
|
|
1070
|
+
multiplierBps: 3e4,
|
|
1071
|
+
tickets: 2621
|
|
1072
|
+
}),
|
|
1073
|
+
Object.freeze({
|
|
1074
|
+
multiplierBps: 2e5,
|
|
1075
|
+
tickets: 655
|
|
1076
|
+
}),
|
|
1077
|
+
Object.freeze({
|
|
1078
|
+
multiplierBps: 0,
|
|
1079
|
+
tickets: 0
|
|
1080
|
+
}),
|
|
1081
|
+
Object.freeze({
|
|
1082
|
+
multiplierBps: 0,
|
|
1083
|
+
tickets: 0
|
|
1084
|
+
}),
|
|
1085
|
+
Object.freeze({
|
|
1086
|
+
multiplierBps: 0,
|
|
1087
|
+
tickets: 0
|
|
1088
|
+
}),
|
|
1089
|
+
Object.freeze({
|
|
1090
|
+
multiplierBps: 0,
|
|
1091
|
+
tickets: 0
|
|
1092
|
+
})
|
|
1093
|
+
]);
|
|
1094
|
+
function checkedU64(value, what) {
|
|
1095
|
+
if (value < 0n || value > U64_MAX) throw new GaboxMathError("Arithmetic", `${what} does not fit in u64`);
|
|
1096
|
+
return value;
|
|
1097
|
+
}
|
|
1098
|
+
/** Tier fields encode as program `u32`s. Reject JavaScript-only values before `BigInt` or codecs. */
|
|
1099
|
+
function checkedU32(value, what) {
|
|
1100
|
+
if (!Number.isInteger(value) || value < 0 || value > U32_MAX) throw new GaboxMathError("InvalidDistribution", `${what} must be an integer from 0 to ${U32_MAX}`);
|
|
1101
|
+
return value;
|
|
1102
|
+
}
|
|
1103
|
+
/** `math::tokens`. Floor division, and an overflow past u64 is an error, not a wrap. */
|
|
1104
|
+
function tierAmount(base, multiplierBps) {
|
|
1105
|
+
return checkedU64(base * BigInt(checkedU32(multiplierBps, "multiplierBps")) / BPS, "tier amount");
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* `math::validate`. Checks the table alone, with no base.
|
|
1109
|
+
*
|
|
1110
|
+
* Four rules, and each one closes a way to sell a bad ticket:
|
|
1111
|
+
* - ticket counts sum to exactly 65,536, so the 16-bit draw is uniform;
|
|
1112
|
+
* - an unused row is all-zero, so a hidden multiplier cannot ride along;
|
|
1113
|
+
* - a ticketed tier's multiplier is from 1 to `MAX_MULTIPLIER_BPS` (20x);
|
|
1114
|
+
* - the expected multiplier over all tickets is at most 1x, so the table cannot promise more
|
|
1115
|
+
* tokens than a pack buys. This bounds tokens, not cash value.
|
|
1116
|
+
*/
|
|
1117
|
+
function validateTiers(tiers) {
|
|
1118
|
+
if (tiers.length !== 8) throw new GaboxMathError("InvalidDistribution", `expected 8 tiers, got ${tiers.length}`);
|
|
1119
|
+
let count = 0n;
|
|
1120
|
+
let expected = 0n;
|
|
1121
|
+
for (const [index, tier] of tiers.entries()) {
|
|
1122
|
+
checkedU32(tier.tickets, `tier ${index} tickets`);
|
|
1123
|
+
checkedU32(tier.multiplierBps, `tier ${index} multiplierBps`);
|
|
1124
|
+
if (tier.tickets === 0) {
|
|
1125
|
+
if (tier.multiplierBps !== 0) throw new GaboxMathError("InvalidDistribution", "a tier with no tickets must have no multiplier");
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
if (tier.multiplierBps <= 0) throw new GaboxMathError("InvalidDistribution", "a ticketed tier must pay something; it would sell a ticket that can only win zero");
|
|
1129
|
+
if (tier.multiplierBps > 2e5) throw new GaboxMathError("InvalidDistribution", `tier ${index} multiplierBps exceeds the ${MAX_MULTIPLIER_BPS} maximum (20x)`);
|
|
1130
|
+
count += BigInt(tier.tickets);
|
|
1131
|
+
expected += BigInt(tier.multiplierBps) * BigInt(tier.tickets);
|
|
1132
|
+
}
|
|
1133
|
+
if (count !== BigInt(65536)) throw new GaboxMathError("InvalidDistribution", `tickets must sum to ${TICKETS}, they sum to ${count}`);
|
|
1134
|
+
if (expected > 10000n * BigInt(65536)) throw new GaboxMathError("UnfundedExpectation", "the expected token award exceeds the tokens a pack buys");
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* `math::validate_pack`. Every ticketed tier must pay at least one token for a pack of this size.
|
|
1138
|
+
*
|
|
1139
|
+
* The program checks this once at creation. At `PACK_TOKENS` no sane table fails it; it exists so
|
|
1140
|
+
* a table cannot sell a ticket that can only win zero.
|
|
1141
|
+
*/
|
|
1142
|
+
function validatePack(packTokens, tiers) {
|
|
1143
|
+
if (packTokens <= 0n) throw new GaboxMathError("ZeroAmount", "packTokens must be positive");
|
|
1144
|
+
checkedU64(packTokens, "pack tokens");
|
|
1145
|
+
validateTiers(tiers);
|
|
1146
|
+
for (const [i, tier] of tiers.entries()) {
|
|
1147
|
+
if (tier.tickets === 0) continue;
|
|
1148
|
+
if (tierAmount(packTokens, tier.multiplierBps) === 0n) throw new GaboxMathError("PackTooSmall", `tier ${i} rounds to zero tokens at this pack size`);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* `math::seed_tokens`. The mandatory seed a table needs, in tokens.
|
|
1153
|
+
*
|
|
1154
|
+
* The mandatory seed makes a 3x top prize payable on the first pack, or the full top prize when
|
|
1155
|
+
* the table's top tier is below 3x. A jackpot above 3x still shows the full table once later
|
|
1156
|
+
* packs grow the free vault; the mandatory seed alone does not have to fund it. The pack itself
|
|
1157
|
+
* brings `packTokens` into the vault, so the vault has to hold the rest beforehand:
|
|
1158
|
+
*
|
|
1159
|
+
* seedTokens = min(uncappedMaximum(packTokens, tiers), tierAmount(packTokens, MIN_SEED_MULTIPLIER_BPS)) - packTokens
|
|
1160
|
+
*
|
|
1161
|
+
* A 20x or a 3x top tier on a 1M-token pack both need a 2M-token seed, since the mandatory seed
|
|
1162
|
+
* stops at the 3x minimum. A 2x top tier needs a 1M-token seed, already below the minimum. A
|
|
1163
|
+
* table whose top tier pays exactly one pack needs no seed. A top tier below one pack is refused:
|
|
1164
|
+
* every ticket would lose. `createMachine` lets a creator add more seed on top of this mandatory
|
|
1165
|
+
* amount through `extraSeedTokens`, for example to fund a top tier above 3x from the first pack.
|
|
1166
|
+
*
|
|
1167
|
+
* Pool initialization computes this number itself and buys exactly that many tokens on the curve.
|
|
1168
|
+
* The creator only signs a maximum SOL cost.
|
|
1169
|
+
*/
|
|
1170
|
+
function seedTokens(packTokens, tiers) {
|
|
1171
|
+
validateTiers(tiers);
|
|
1172
|
+
const largest = uncappedMaximum(packTokens, tiers);
|
|
1173
|
+
if (largest < packTokens) throw new GaboxMathError("JackpotBelowOnePack", "the largest tier must pay at least one pack");
|
|
1174
|
+
const minimum = tierAmount(packTokens, MIN_SEED_MULTIPLIER_BPS);
|
|
1175
|
+
return (largest < minimum ? largest : minimum) - packTokens;
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* `math::uncapped_maximum`. The largest tier's award for this base, before any inventory cap.
|
|
1179
|
+
*
|
|
1180
|
+
* At `packTokens` this is the jackpot in tokens. `seedTokens` is this minus one pack.
|
|
1181
|
+
*/
|
|
1182
|
+
function uncappedMaximum(base, tiers) {
|
|
1183
|
+
validateTiers(tiers);
|
|
1184
|
+
let largest = 0n;
|
|
1185
|
+
for (const tier of tiers) {
|
|
1186
|
+
if (tier.tickets === 0) continue;
|
|
1187
|
+
const amount = tierAmount(base, tier.multiplierBps);
|
|
1188
|
+
if (amount > largest) largest = amount;
|
|
1189
|
+
}
|
|
1190
|
+
return largest;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* `math::quote`. The offer a pack of `base` tokens would freeze right now. `base` is always
|
|
1194
|
+
* `pool.packTokens`; the parameter stays general so the vectors can use small numbers.
|
|
1195
|
+
*
|
|
1196
|
+
* `inventory` is the vault's token balance and `reserved` is `pool.reserved`. The difference is
|
|
1197
|
+
* free inventory. This pack's own `base` is added to it, because the purchase and the offer are
|
|
1198
|
+
* one transaction — the tokens are in the vault before the draw is written.
|
|
1199
|
+
*
|
|
1200
|
+
* Every amount is capped at what is actually available. A tier that rounds to zero tokens is an
|
|
1201
|
+
* error: the pool must not sell a ticket that can only win nothing.
|
|
1202
|
+
*/
|
|
1203
|
+
function quote(base, tiers, inventory, reserved) {
|
|
1204
|
+
validateTiers(tiers);
|
|
1205
|
+
if (inventory < reserved) throw new GaboxMathError("InsolventInventory", "the vault holds less than the pool has already reserved");
|
|
1206
|
+
const available = checkedU64(inventory - reserved + base, "available inventory");
|
|
1207
|
+
const prizes = Array.from({ length: 8 }, () => ({
|
|
1208
|
+
amount: 0n,
|
|
1209
|
+
tickets: 0
|
|
1210
|
+
}));
|
|
1211
|
+
let maximum = 0n;
|
|
1212
|
+
let minimum = U64_MAX;
|
|
1213
|
+
for (const [i, tier] of tiers.entries()) {
|
|
1214
|
+
if (tier.tickets === 0) continue;
|
|
1215
|
+
const uncapped = tierAmount(base, tier.multiplierBps);
|
|
1216
|
+
if (uncapped === 0n) throw new GaboxMathError("PackTooSmall", `tier ${i} rounds to zero tokens at this pack size`);
|
|
1217
|
+
const amount = uncapped < available ? uncapped : available;
|
|
1218
|
+
prizes[i] = {
|
|
1219
|
+
amount,
|
|
1220
|
+
tickets: tier.tickets
|
|
1221
|
+
};
|
|
1222
|
+
if (amount > maximum) maximum = amount;
|
|
1223
|
+
if (amount < minimum) minimum = amount;
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
prizes,
|
|
1227
|
+
maximum,
|
|
1228
|
+
minimum
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* `math::choose`. Which prize a 16-bit ticket wins.
|
|
1233
|
+
*
|
|
1234
|
+
* The rows are consecutive ranges in table order, so this is a running total and a comparison.
|
|
1235
|
+
* A ticket past the last row returns `0`, which cannot happen for a validated table.
|
|
1236
|
+
*/
|
|
1237
|
+
function choose(prizes, ticket) {
|
|
1238
|
+
let end = 0;
|
|
1239
|
+
for (const prize of prizes) {
|
|
1240
|
+
end += prize.tickets;
|
|
1241
|
+
if (ticket < end) return prize.amount;
|
|
1242
|
+
}
|
|
1243
|
+
return 0n;
|
|
1244
|
+
}
|
|
1245
|
+
/** `math::resolve_reservation`. Release the unwon part of a maximum, keep the award reserved. */
|
|
1246
|
+
function resolveReservation(reserved, maximum, award) {
|
|
1247
|
+
if (award > maximum) throw new GaboxMathError("InsolventInventory", "the award exceeds the reserved maximum");
|
|
1248
|
+
if (reserved < maximum) throw new GaboxMathError("Arithmetic", "the pool has reserved less than this draw holds");
|
|
1249
|
+
return checkedU64(reserved - maximum + award, "reserved");
|
|
1250
|
+
}
|
|
1251
|
+
/** The largest `multiplierBps` on any ticketed row. `0` for a table with no rows. */
|
|
1252
|
+
function maxMultiplierBps(tiers) {
|
|
1253
|
+
validateTiers(tiers);
|
|
1254
|
+
let largest = 0;
|
|
1255
|
+
for (const tier of tiers) {
|
|
1256
|
+
if (tier.tickets === 0) continue;
|
|
1257
|
+
if (tier.multiplierBps > largest) largest = tier.multiplierBps;
|
|
1258
|
+
}
|
|
1259
|
+
return largest;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* The expected multiplier over all 65,536 tickets, in basis points. Rounded down.
|
|
1263
|
+
*
|
|
1264
|
+
* `validateTiers` caps this at 10,000. A table at 9,800 keeps 2% of every pack's tokens in the
|
|
1265
|
+
* vault on average, which is what lets a pool survive a run of top-tier wins.
|
|
1266
|
+
*/
|
|
1267
|
+
function averageMultiplierBps(tiers) {
|
|
1268
|
+
validateTiers(tiers);
|
|
1269
|
+
let weighted = 0n;
|
|
1270
|
+
for (const tier of tiers) {
|
|
1271
|
+
if (tier.tickets === 0) continue;
|
|
1272
|
+
weighted += BigInt(tier.multiplierBps) * BigInt(tier.tickets);
|
|
1273
|
+
}
|
|
1274
|
+
return Number(weighted / BigInt(TICKETS));
|
|
1275
|
+
}
|
|
1276
|
+
//#endregion
|
|
1277
|
+
//#region src/raydium/pdas.ts
|
|
1278
|
+
/**
|
|
1279
|
+
* Every Raydium address a Gabox transaction derives.
|
|
1280
|
+
*
|
|
1281
|
+
* All of them are program-derived, so each one takes the program it lives under. Mainnet and devnet
|
|
1282
|
+
* run different Raydium programs, so the same seeds give different addresses on the two clusters.
|
|
1283
|
+
* Pass `raydiumIds(client.cluster)` and read the program out of it.
|
|
1284
|
+
*
|
|
1285
|
+
* `venue/trade.rs` and `venue/launch.rs` derive the same addresses on chain and refuse any account
|
|
1286
|
+
* that does not match. `test/raydium-pdas.test.ts` checks every one of these against the pinned
|
|
1287
|
+
* table in `abi.ts`, for both clusters.
|
|
1288
|
+
*/
|
|
1289
|
+
const address$1 = getAddressEncoder();
|
|
1290
|
+
const utf8 = getUtf8Encoder();
|
|
1291
|
+
const u8$1 = getU8Encoder();
|
|
1292
|
+
const u16be = getU16Encoder({ endian: Endian.Big });
|
|
1293
|
+
const derive = async (programAddress, seeds) => (await getProgramDerivedAddress({
|
|
1294
|
+
programAddress,
|
|
1295
|
+
seeds
|
|
1296
|
+
}))[0];
|
|
1297
|
+
/** `["vault_auth_seed"]`. LaunchLab signs its own vault transfers with this PDA. */
|
|
1298
|
+
const launchlabAuthority = async (launchlab) => await derive(launchlab, [utf8.encode(LAUNCHLAB_SEEDS.authority)]);
|
|
1299
|
+
/** `["__event_authority"]`. */
|
|
1300
|
+
const launchlabEventAuthority = async (launchlab) => await derive(launchlab, [utf8.encode(LAUNCHLAB_SEEDS.eventAuthority)]);
|
|
1301
|
+
/**
|
|
1302
|
+
* `["global_config", quote_mint, u8 curve_type, u16 big-endian index]`. The curve settings for one
|
|
1303
|
+
* quote asset. Gabox uses curve type 0 (constant product) and index 0.
|
|
1304
|
+
*/
|
|
1305
|
+
const launchlabGlobalConfig = async (launchlab, quoteMint, curveType = 0, index = 0) => await derive(launchlab, [
|
|
1306
|
+
utf8.encode(LAUNCHLAB_SEEDS.globalConfig),
|
|
1307
|
+
address$1.encode(quoteMint),
|
|
1308
|
+
u8$1.encode(curveType),
|
|
1309
|
+
u16be.encode(index)
|
|
1310
|
+
]);
|
|
1311
|
+
/** `["platform_config", platform_admin]`. The Gabox platform on LaunchLab. */
|
|
1312
|
+
const launchlabPlatformConfig = async (launchlab, platformAdmin) => await derive(launchlab, [utf8.encode(LAUNCHLAB_SEEDS.platformConfig), address$1.encode(platformAdmin)]);
|
|
1313
|
+
/** `["pool", base_mint, quote_mint]`. The curve pool of one coin. */
|
|
1314
|
+
const launchlabPoolAddress = async (launchlab, baseMint, quoteMint) => await derive(launchlab, [
|
|
1315
|
+
utf8.encode(LAUNCHLAB_SEEDS.pool),
|
|
1316
|
+
address$1.encode(baseMint),
|
|
1317
|
+
address$1.encode(quoteMint)
|
|
1318
|
+
]);
|
|
1319
|
+
/** `["pool_vault", pool_state, mint]`. One side of a curve pool's reserves. */
|
|
1320
|
+
const launchlabVaultAddress = async (launchlab, poolState, mint) => await derive(launchlab, [
|
|
1321
|
+
utf8.encode(LAUNCHLAB_SEEDS.vault),
|
|
1322
|
+
address$1.encode(poolState),
|
|
1323
|
+
address$1.encode(mint)
|
|
1324
|
+
]);
|
|
1325
|
+
/**
|
|
1326
|
+
* `[platform_config, quote_mint]`. LaunchLab pays the platform share of every curve trade here.
|
|
1327
|
+
*
|
|
1328
|
+
* The seeds carry no text prefix. That is Raydium's own derivation, not a mistake.
|
|
1329
|
+
*/
|
|
1330
|
+
const platformFeeVaultAddress = async (launchlab, platformConfig, quoteMint) => await derive(launchlab, [address$1.encode(platformConfig), address$1.encode(quoteMint)]);
|
|
1331
|
+
/**
|
|
1332
|
+
* `[creator, quote_mint]`. LaunchLab pays the creator share of every curve trade here.
|
|
1333
|
+
*
|
|
1334
|
+
* One vault per wallet per quote asset, not one per coin. A creator with several coins collects
|
|
1335
|
+
* all of them with a single `claim_creator_fee`.
|
|
1336
|
+
*/
|
|
1337
|
+
const creatorFeeVaultAddress = async (launchlab, creator, quoteMint) => await derive(launchlab, [address$1.encode(creator), address$1.encode(quoteMint)]);
|
|
1338
|
+
/** `["creator_fee_vault_auth_seed"]`. The authority `claim_creator_fee` signs the payout with. */
|
|
1339
|
+
const creatorFeeVaultAuthority = async (launchlab) => await derive(launchlab, [utf8.encode(LAUNCHLAB_SEEDS.creatorFeeVaultAuthority)]);
|
|
1340
|
+
/** `["metadata", metaplex, mint]` under Metaplex. LaunchLab's create instruction writes it. */
|
|
1341
|
+
const metadataAddress = async (mint) => await derive(METAPLEX_PROGRAM_ADDRESS, [
|
|
1342
|
+
utf8.encode(METADATA_SEED),
|
|
1343
|
+
address$1.encode(METAPLEX_PROGRAM_ADDRESS),
|
|
1344
|
+
address$1.encode(mint)
|
|
1345
|
+
]);
|
|
1346
|
+
/** `["vault_and_lp_mint_auth_seed"]`. */
|
|
1347
|
+
const cpmmAuthority = async (cpmm) => await derive(cpmm, [utf8.encode(CPMM_SEEDS.authority)]);
|
|
1348
|
+
/**
|
|
1349
|
+
* `["pool", amm_config, token_0, token_1]`, with the two mints sorted by raw byte order.
|
|
1350
|
+
*
|
|
1351
|
+
* This is where Raydium migrates a coin when the address is free. It is only a first guess: when the
|
|
1352
|
+
* account is taken, the migration lands somewhere else entirely. And the address alone proves
|
|
1353
|
+
* nothing, because anyone can open a CPMM pool for any pair under any config. `findCpmmPool` tries
|
|
1354
|
+
* this address, checks the account data, and scans for the real pool when it does not match.
|
|
1355
|
+
*
|
|
1356
|
+
* There are no vault or observation derivations here on purpose. `venue/trade.rs` reads those three
|
|
1357
|
+
* addresses out of the pool account, and so does this SDK: a migrated pool names its own vaults and
|
|
1358
|
+
* its own oracle, wherever it sits.
|
|
1359
|
+
*/
|
|
1360
|
+
const cpmmPoolAddress = async (cpmm, ammConfig, mintA, mintB) => {
|
|
1361
|
+
const [token0, token1] = sortedMints(mintA, mintB);
|
|
1362
|
+
return await derive(cpmm, [
|
|
1363
|
+
utf8.encode(CPMM_SEEDS.pool),
|
|
1364
|
+
address$1.encode(ammConfig),
|
|
1365
|
+
address$1.encode(token0),
|
|
1366
|
+
address$1.encode(token1)
|
|
1367
|
+
]);
|
|
1368
|
+
};
|
|
1369
|
+
/**
|
|
1370
|
+
* `["creator_fee_share", creator, amm_config]`. Where CPMM tracks what one creator is owed under
|
|
1371
|
+
* one fee tier.
|
|
1372
|
+
*
|
|
1373
|
+
* `collect_creator_fee` takes this account after the fourteen the mainnet IDL lists. That snapshot
|
|
1374
|
+
* predates the account; Raydium's devnet build declares it and stops with `AccountNotEnoughKeys`
|
|
1375
|
+
* (3005) when it is missing. One account per creator per fee tier, not one per pool.
|
|
1376
|
+
*/
|
|
1377
|
+
const cpmmCreatorFeeShare = async (cpmm, creator, ammConfig) => await derive(cpmm, [
|
|
1378
|
+
utf8.encode(CPMM_SEEDS.creatorFeeShare),
|
|
1379
|
+
address$1.encode(creator),
|
|
1380
|
+
address$1.encode(ammConfig)
|
|
1381
|
+
]);
|
|
1382
|
+
/** An associated token account. Off-curve owners are allowed: pool PDAs are all off-curve. */
|
|
1383
|
+
const ata = async (owner, mint, tokenProgram = TOKEN_PROGRAM_ADDRESS) => await derive(ASSOCIATED_TOKEN_PROGRAM_ADDRESS, [
|
|
1384
|
+
address$1.encode(owner),
|
|
1385
|
+
address$1.encode(tokenProgram),
|
|
1386
|
+
address$1.encode(mint)
|
|
1387
|
+
]);
|
|
1388
|
+
//#endregion
|
|
1389
|
+
//#region src/pdas.ts
|
|
1390
|
+
/**
|
|
1391
|
+
* Gabox's own program-derived addresses.
|
|
1392
|
+
*
|
|
1393
|
+
* Raydium's live in `src/raydium/pdas.ts`, because they depend on the cluster. These do not: the
|
|
1394
|
+
* Gabox program id is the same everywhere.
|
|
1395
|
+
*
|
|
1396
|
+
* Classic SPL Token only. Both venues use it, and the accepted LaunchLab create instruction mints
|
|
1397
|
+
* the coin with it, so no derivation here takes a token program any more.
|
|
1398
|
+
*/
|
|
1399
|
+
/** `["pool", mint]`. One pool per coin. */
|
|
1400
|
+
const poolAddress = async (mint) => (await findPoolPda({ mint }))[0];
|
|
1401
|
+
/** `["draw", pool, seq]`, with the sequence as eight little-endian bytes. */
|
|
1402
|
+
const drawAddress = async (pool, seq) => (await findDrawPda({
|
|
1403
|
+
pool,
|
|
1404
|
+
seq
|
|
1405
|
+
}))[0];
|
|
1406
|
+
/** The per-wallet `WalletActivity` PDA. `buy_pack` derives it from the purchaser automatically. */
|
|
1407
|
+
const activityAddress = async (wallet) => (await findActivityPda({ purchaser: wallet }))[0];
|
|
1408
|
+
/** `["identity"]` under Gabox. The PDA the program signs its randomness request with. */
|
|
1409
|
+
const vrfIdentityAddress = async () => (await findIdentityPda())[0];
|
|
1410
|
+
/** `["identity", gabox_program_id]` under the VRF program. MagicBlock signs the callback with it. */
|
|
1411
|
+
async function scopedVrfIdentityAddress() {
|
|
1412
|
+
return await getProgramDerivedAddress({
|
|
1413
|
+
programAddress: VRF_PROGRAM_ADDRESS,
|
|
1414
|
+
seeds: [getBytesEncoder().encode(IDENTITY_SEED), getAddressEncoder().encode(GABOX_PROGRAM_ID)]
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
/** An associated token account under classic SPL Token. */
|
|
1418
|
+
const associatedTokenAddress = async (owner, mint, tokenProgram = TOKEN_PROGRAM_ADDRESS) => await ata(owner, mint, tokenProgram);
|
|
1419
|
+
/** The prize vault: the pool PDA's own associated token account for the coin. */
|
|
1420
|
+
const vaultAddress = async (mint) => await associatedTokenAddress(await poolAddress(mint), mint);
|
|
1421
|
+
//#endregion
|
|
1422
|
+
//#region src/raydium/adapter.ts
|
|
1423
|
+
/**
|
|
1424
|
+
* Reading Raydium's accounts.
|
|
1425
|
+
*
|
|
1426
|
+
* Every decoder here is written from the field list in the two IDL snapshots under `idl/`. Nothing
|
|
1427
|
+
* is guessed: each struct is laid out in Borsh order with no padding between fields, so the offsets
|
|
1428
|
+
* follow from the field sizes alone. Each decoder checks the account's eight-byte discriminator
|
|
1429
|
+
* first, so a wrong account fails loudly instead of decoding as plausible rubbish.
|
|
1430
|
+
*
|
|
1431
|
+
* `test/raydium-decode.test.ts` builds one byte array per decoder by hand and reads it back.
|
|
1432
|
+
*
|
|
1433
|
+
* Only the fields Gabox needs are decoded. The trailing padding of each account is skipped, which
|
|
1434
|
+
* is also why a longer account than the one recorded here still decodes: Raydium only ever appends
|
|
1435
|
+
* into its own padding.
|
|
1436
|
+
*
|
|
1437
|
+
* Kit's codecs, never `Buffer`. This module is vendored into a Next.js app, and `Buffer` does not
|
|
1438
|
+
* exist in a browser.
|
|
1439
|
+
*/
|
|
1440
|
+
const DISCRIMINATOR$1 = 8;
|
|
1441
|
+
const u8 = getU8Decoder();
|
|
1442
|
+
const u16 = getU16Decoder();
|
|
1443
|
+
const u64$1 = getU64Decoder();
|
|
1444
|
+
const addressAt = getAddressDecoder();
|
|
1445
|
+
const bool = getBooleanDecoder();
|
|
1446
|
+
/** Refuse an account that is too short or carries another type's discriminator. */
|
|
1447
|
+
function checked(data, discriminator, what) {
|
|
1448
|
+
if (data.length < discriminator.length) throw new Error(`not a ${what}: ${data.length} bytes is shorter than a discriminator`);
|
|
1449
|
+
for (const [i, byte] of discriminator.entries()) if (data[i] !== byte) throw new Error(`not a ${what}: the discriminator does not match`);
|
|
1450
|
+
return data;
|
|
1451
|
+
}
|
|
1452
|
+
/** Refuse a truncated account before a decoder reads past its end. */
|
|
1453
|
+
function atLeast(data, size, what) {
|
|
1454
|
+
if (data.length < size) throw new Error(`not a ${what}: ${data.length} bytes, expected at least ${size}`);
|
|
1455
|
+
return data;
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* The `amount` field of an SPL token account: bytes 64 to 72, little-endian.
|
|
1459
|
+
*
|
|
1460
|
+
* Both venues and every Gabox account use classic SPL Token, whose account is 165 bytes. The
|
|
1461
|
+
* Token-2022 layout shares those first 165 bytes, so this reader covers a Token-2022 account too.
|
|
1462
|
+
*/
|
|
1463
|
+
function tokenAccountAmount(data) {
|
|
1464
|
+
if (data.length < 72) throw new Error("not a token account: fewer than 72 bytes");
|
|
1465
|
+
return u64$1.decode(data, 64);
|
|
1466
|
+
}
|
|
1467
|
+
/** The `mint` and `owner` of an SPL token account: bytes 0 to 32 and 32 to 64. */
|
|
1468
|
+
function tokenAccountOwnerAndMint(data) {
|
|
1469
|
+
if (data.length < 64) throw new Error("not a token account: fewer than 64 bytes");
|
|
1470
|
+
return {
|
|
1471
|
+
mint: addressAt.decode(data, 0),
|
|
1472
|
+
owner: addressAt.decode(data, 32)
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
/** The base SPL mint is 82 bytes. Token-2022 repeats those 82 and appends its extensions. */
|
|
1476
|
+
const MINT_BASE_SIZE = 82;
|
|
1477
|
+
/** `decimals` sits after `mint_authority` (4 + 32) and `supply` (8). */
|
|
1478
|
+
const MINT_DECIMALS_AT = 44;
|
|
1479
|
+
/**
|
|
1480
|
+
* The `decimals` of a mint. Both token programs share the first 82 bytes, so one reader covers a
|
|
1481
|
+
* classic SPL mint and a Token-2022 one.
|
|
1482
|
+
*/
|
|
1483
|
+
function mintDecimals(data) {
|
|
1484
|
+
if (data.length < MINT_BASE_SIZE) throw new Error(`not a mint: ${data.length} bytes, expected at least ${MINT_BASE_SIZE}`);
|
|
1485
|
+
return u8.decode(data, MINT_DECIMALS_AT);
|
|
1486
|
+
}
|
|
1487
|
+
/** A Borsh string: a u32 little-endian length, then that many UTF-8 bytes. */
|
|
1488
|
+
function borshString$1(data, at) {
|
|
1489
|
+
if (at + 4 > data.length) return null;
|
|
1490
|
+
const length = new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(at, true);
|
|
1491
|
+
const end = at + 4 + length;
|
|
1492
|
+
if (end > data.length) return null;
|
|
1493
|
+
return {
|
|
1494
|
+
text: new TextDecoder().decode(data.subarray(at + 4, end)),
|
|
1495
|
+
next: end
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
/** Metaplex pads its three strings with NUL bytes up to their maximum length. */
|
|
1499
|
+
const trimmed = (text) => {
|
|
1500
|
+
const clean = text.replace(/\0+$/, "").trim();
|
|
1501
|
+
return clean.length > 0 ? clean : null;
|
|
1502
|
+
};
|
|
1503
|
+
/**
|
|
1504
|
+
* The `symbol` of a Metaplex token metadata account, or `null` when it carries none.
|
|
1505
|
+
*
|
|
1506
|
+
* Layout: a one-byte key, `update_authority` and `mint`, then the three Borsh strings `name`,
|
|
1507
|
+
* `symbol` and `uri`. Metaplex stores them at their maximum length, NUL padded.
|
|
1508
|
+
*/
|
|
1509
|
+
function metaplexSymbol(data) {
|
|
1510
|
+
const name = borshString$1(data, 65);
|
|
1511
|
+
if (!name) return null;
|
|
1512
|
+
const symbol = borshString$1(data, name.next);
|
|
1513
|
+
return symbol ? trimmed(symbol.text) : null;
|
|
1514
|
+
}
|
|
1515
|
+
/** The Token-2022 extension type of `TokenMetadata`, from the SPL token metadata interface. */
|
|
1516
|
+
const TOKEN_METADATA_EXTENSION = 19;
|
|
1517
|
+
/** Token-2022 writes one account-type byte after the 82-byte mint, then its TLV extensions. */
|
|
1518
|
+
const EXTENSION_START = 166;
|
|
1519
|
+
/**
|
|
1520
|
+
* The `symbol` a Token-2022 mint stores in its own `TokenMetadata` extension, or `null`.
|
|
1521
|
+
*
|
|
1522
|
+
* The extensions are a TLV list: a u16 type, a u16 length, then the value. The metadata value is
|
|
1523
|
+
* `update_authority`, `mint`, then the Borsh strings `name`, `symbol` and `uri`.
|
|
1524
|
+
*/
|
|
1525
|
+
function token2022Symbol(data) {
|
|
1526
|
+
let at = EXTENSION_START;
|
|
1527
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
1528
|
+
while (at + 4 <= data.length) {
|
|
1529
|
+
const type = view.getUint16(at, true);
|
|
1530
|
+
const length = view.getUint16(at + 2, true);
|
|
1531
|
+
const value = at + 4;
|
|
1532
|
+
if (value + length > data.length) return null;
|
|
1533
|
+
if (type === TOKEN_METADATA_EXTENSION) {
|
|
1534
|
+
const name = borshString$1(data.subarray(value, value + length), 64);
|
|
1535
|
+
if (!name) return null;
|
|
1536
|
+
const symbol = borshString$1(data.subarray(value, value + length), name.next);
|
|
1537
|
+
return symbol ? trimmed(symbol.text) : null;
|
|
1538
|
+
}
|
|
1539
|
+
at = value + length;
|
|
1540
|
+
}
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
/** Offsets follow the `PoolState` field list in `idl/raydium_launchpad.json`. */
|
|
1544
|
+
const LAUNCHLAB_POOL_HEAD = getStructDecoder([
|
|
1545
|
+
["epoch", u64$1],
|
|
1546
|
+
["authBump", u8],
|
|
1547
|
+
["status", u8],
|
|
1548
|
+
["baseDecimals", u8],
|
|
1549
|
+
["quoteDecimals", u8],
|
|
1550
|
+
["migrateType", u8],
|
|
1551
|
+
["supply", u64$1],
|
|
1552
|
+
["totalBaseSell", u64$1],
|
|
1553
|
+
["virtualBase", u64$1],
|
|
1554
|
+
["virtualQuote", u64$1],
|
|
1555
|
+
["realBase", u64$1],
|
|
1556
|
+
["realQuote", u64$1],
|
|
1557
|
+
["totalQuoteFundRaising", u64$1],
|
|
1558
|
+
["quoteProtocolFee", u64$1],
|
|
1559
|
+
["platformFee", u64$1],
|
|
1560
|
+
["migrateFee", u64$1],
|
|
1561
|
+
["vestingTotalLockedAmount", u64$1],
|
|
1562
|
+
["vestingCliffPeriod", u64$1],
|
|
1563
|
+
["vestingUnlockPeriod", u64$1],
|
|
1564
|
+
["vestingStartTime", u64$1],
|
|
1565
|
+
["vestingAllocatedShareAmount", u64$1],
|
|
1566
|
+
["globalConfig", addressAt],
|
|
1567
|
+
["platformConfig", addressAt],
|
|
1568
|
+
["baseMint", addressAt],
|
|
1569
|
+
["quoteMint", addressAt],
|
|
1570
|
+
["baseVault", addressAt],
|
|
1571
|
+
["quoteVault", addressAt],
|
|
1572
|
+
["creator", addressAt],
|
|
1573
|
+
["tokenProgramFlag", u8],
|
|
1574
|
+
["ammCreatorFeeOn", u8]
|
|
1575
|
+
]);
|
|
1576
|
+
/** The bytes the decoder above reads, discriminator included. The account itself is longer. */
|
|
1577
|
+
const LAUNCHLAB_POOL_STATE_HEAD_SIZE = 367;
|
|
1578
|
+
function decodeLaunchlabPool(data) {
|
|
1579
|
+
checked(data, LAUNCHLAB_POOL_STATE_DISCRIMINATOR, "LaunchLab pool");
|
|
1580
|
+
atLeast(data, 367, "LaunchLab pool");
|
|
1581
|
+
const head = LAUNCHLAB_POOL_HEAD.decode(data, DISCRIMINATOR$1);
|
|
1582
|
+
return {
|
|
1583
|
+
status: head.status,
|
|
1584
|
+
baseDecimals: head.baseDecimals,
|
|
1585
|
+
quoteDecimals: head.quoteDecimals,
|
|
1586
|
+
migrateType: head.migrateType,
|
|
1587
|
+
supply: head.supply,
|
|
1588
|
+
totalBaseSell: head.totalBaseSell,
|
|
1589
|
+
virtualBase: head.virtualBase,
|
|
1590
|
+
virtualQuote: head.virtualQuote,
|
|
1591
|
+
realBase: head.realBase,
|
|
1592
|
+
realQuote: head.realQuote,
|
|
1593
|
+
totalQuoteFundRaising: head.totalQuoteFundRaising,
|
|
1594
|
+
migrateFee: head.migrateFee,
|
|
1595
|
+
globalConfig: head.globalConfig,
|
|
1596
|
+
platformConfig: head.platformConfig,
|
|
1597
|
+
baseMint: head.baseMint,
|
|
1598
|
+
quoteMint: head.quoteMint,
|
|
1599
|
+
baseVault: head.baseVault,
|
|
1600
|
+
quoteVault: head.quoteVault,
|
|
1601
|
+
creator: head.creator,
|
|
1602
|
+
ammCreatorFeeOn: head.ammCreatorFeeOn
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
const LAUNCHLAB_GLOBAL_CONFIG_HEAD = getStructDecoder([
|
|
1606
|
+
["epoch", u64$1],
|
|
1607
|
+
["curveType", u8],
|
|
1608
|
+
["index", u16],
|
|
1609
|
+
["migrateFee", u64$1],
|
|
1610
|
+
["tradeFeeRate", u64$1],
|
|
1611
|
+
["maxShareFeeRate", u64$1],
|
|
1612
|
+
["minBaseSupply", u64$1],
|
|
1613
|
+
["maxLockRate", u64$1],
|
|
1614
|
+
["minBaseSellRate", u64$1],
|
|
1615
|
+
["minBaseMigrateRate", u64$1],
|
|
1616
|
+
["minQuoteFundRaising", u64$1],
|
|
1617
|
+
["quoteMint", addressAt]
|
|
1618
|
+
]);
|
|
1619
|
+
const LAUNCHLAB_GLOBAL_CONFIG_HEAD_SIZE = 115;
|
|
1620
|
+
function decodeLaunchlabGlobalConfig(data) {
|
|
1621
|
+
checked(data, LAUNCHLAB_GLOBAL_CONFIG_DISCRIMINATOR, "LaunchLab global config");
|
|
1622
|
+
atLeast(data, 115, "LaunchLab global config");
|
|
1623
|
+
const head = LAUNCHLAB_GLOBAL_CONFIG_HEAD.decode(data, DISCRIMINATOR$1);
|
|
1624
|
+
return {
|
|
1625
|
+
curveType: head.curveType,
|
|
1626
|
+
index: head.index,
|
|
1627
|
+
migrateFee: head.migrateFee,
|
|
1628
|
+
tradeFeeRate: head.tradeFeeRate,
|
|
1629
|
+
minQuoteFundRaising: head.minQuoteFundRaising,
|
|
1630
|
+
quoteMint: head.quoteMint
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* The fixed-size head of `PlatformConfig`, before its `curve_params` vector. The three text fields
|
|
1635
|
+
* are fixed byte arrays, so this is the same length for every platform.
|
|
1636
|
+
*/
|
|
1637
|
+
const LAUNCHLAB_PLATFORM_CONFIG_HEAD = getStructDecoder([
|
|
1638
|
+
["epoch", u64$1],
|
|
1639
|
+
["platformFeeWallet", addressAt],
|
|
1640
|
+
["platformNftWallet", addressAt],
|
|
1641
|
+
["platformScale", u64$1],
|
|
1642
|
+
["creatorScale", u64$1],
|
|
1643
|
+
["burnScale", u64$1],
|
|
1644
|
+
["feeRate", u64$1]
|
|
1645
|
+
]);
|
|
1646
|
+
const PLATFORM_CPSWAP_CONFIG_OFFSET = 688;
|
|
1647
|
+
const PLATFORM_CREATOR_FEE_RATE_OFFSET = 720;
|
|
1648
|
+
const PLATFORM_CP_CREATOR_OFFSET = 800;
|
|
1649
|
+
/** Everything before `curve_params`, discriminator included. */
|
|
1650
|
+
const LAUNCHLAB_PLATFORM_CONFIG_HEAD_SIZE = 940;
|
|
1651
|
+
function decodeLaunchlabPlatformConfig(data) {
|
|
1652
|
+
checked(data, LAUNCHLAB_PLATFORM_CONFIG_DISCRIMINATOR, "LaunchLab platform config");
|
|
1653
|
+
atLeast(data, 940, "LaunchLab platform config");
|
|
1654
|
+
const head = LAUNCHLAB_PLATFORM_CONFIG_HEAD.decode(data, DISCRIMINATOR$1);
|
|
1655
|
+
return {
|
|
1656
|
+
platformFeeWallet: head.platformFeeWallet,
|
|
1657
|
+
feeRate: head.feeRate,
|
|
1658
|
+
creatorFeeRate: u64$1.decode(data, PLATFORM_CREATOR_FEE_RATE_OFFSET),
|
|
1659
|
+
cpswapConfig: addressAt.decode(data, PLATFORM_CPSWAP_CONFIG_OFFSET),
|
|
1660
|
+
platformCpCreator: addressAt.decode(data, PLATFORM_CP_CREATOR_OFFSET)
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
const CPMM_POOL_HEAD = getStructDecoder([
|
|
1664
|
+
["ammConfig", addressAt],
|
|
1665
|
+
["poolCreator", addressAt],
|
|
1666
|
+
["token0Vault", addressAt],
|
|
1667
|
+
["token1Vault", addressAt],
|
|
1668
|
+
["lpMint", addressAt],
|
|
1669
|
+
["token0Mint", addressAt],
|
|
1670
|
+
["token1Mint", addressAt],
|
|
1671
|
+
["token0Program", addressAt],
|
|
1672
|
+
["token1Program", addressAt],
|
|
1673
|
+
["observationKey", addressAt],
|
|
1674
|
+
["authBump", u8],
|
|
1675
|
+
["status", u8],
|
|
1676
|
+
["lpMintDecimals", u8],
|
|
1677
|
+
["mint0Decimals", u8],
|
|
1678
|
+
["mint1Decimals", u8],
|
|
1679
|
+
["lpSupply", u64$1],
|
|
1680
|
+
["protocolFeesToken0", u64$1],
|
|
1681
|
+
["protocolFeesToken1", u64$1],
|
|
1682
|
+
["fundFeesToken0", u64$1],
|
|
1683
|
+
["fundFeesToken1", u64$1],
|
|
1684
|
+
["openTime", u64$1],
|
|
1685
|
+
["recentEpoch", u64$1],
|
|
1686
|
+
["creatorFeeOn", u8],
|
|
1687
|
+
["enableCreatorFee", bool],
|
|
1688
|
+
["padding1", fixDecoderSize(getBytesDecoder(), 6)],
|
|
1689
|
+
["creatorFeesToken0", u64$1],
|
|
1690
|
+
["creatorFeesToken1", u64$1]
|
|
1691
|
+
]);
|
|
1692
|
+
const CPMM_POOL_STATE_HEAD_SIZE = 413;
|
|
1693
|
+
/**
|
|
1694
|
+
* The full byte length of a CPMM `PoolState`, trailing padding included. A `getProgramAccounts`
|
|
1695
|
+
* scan filters on it, so it has to be the whole account and not only the part decoded above.
|
|
1696
|
+
*/
|
|
1697
|
+
const CPMM_POOL_STATE_SIZE = 637;
|
|
1698
|
+
/**
|
|
1699
|
+
* Byte offsets a `getProgramAccounts` scan matches on. They are the same offsets `venue/trade.rs`
|
|
1700
|
+
* reads the fields at, so a pool that passes the scan passes the program's own check.
|
|
1701
|
+
*/
|
|
1702
|
+
const CPMM_POOL_OFFSETS = {
|
|
1703
|
+
ammConfig: 8,
|
|
1704
|
+
poolCreator: 40,
|
|
1705
|
+
token0Mint: 168,
|
|
1706
|
+
token1Mint: 200,
|
|
1707
|
+
enableCreatorFee: 390
|
|
1708
|
+
};
|
|
1709
|
+
function decodeCpmmPool(data) {
|
|
1710
|
+
checked(data, CPMM_POOL_STATE_DISCRIMINATOR, "CPMM pool");
|
|
1711
|
+
atLeast(data, 413, "CPMM pool");
|
|
1712
|
+
const head = CPMM_POOL_HEAD.decode(data, DISCRIMINATOR$1);
|
|
1713
|
+
return {
|
|
1714
|
+
ammConfig: head.ammConfig,
|
|
1715
|
+
poolCreator: head.poolCreator,
|
|
1716
|
+
token0Vault: head.token0Vault,
|
|
1717
|
+
token1Vault: head.token1Vault,
|
|
1718
|
+
token0Mint: head.token0Mint,
|
|
1719
|
+
token1Mint: head.token1Mint,
|
|
1720
|
+
token0Program: head.token0Program,
|
|
1721
|
+
token1Program: head.token1Program,
|
|
1722
|
+
observationKey: head.observationKey,
|
|
1723
|
+
status: head.status,
|
|
1724
|
+
mint0Decimals: head.mint0Decimals,
|
|
1725
|
+
mint1Decimals: head.mint1Decimals,
|
|
1726
|
+
protocolFeesToken0: head.protocolFeesToken0,
|
|
1727
|
+
protocolFeesToken1: head.protocolFeesToken1,
|
|
1728
|
+
fundFeesToken0: head.fundFeesToken0,
|
|
1729
|
+
fundFeesToken1: head.fundFeesToken1,
|
|
1730
|
+
creatorFeesToken0: head.creatorFeesToken0,
|
|
1731
|
+
creatorFeesToken1: head.creatorFeesToken1,
|
|
1732
|
+
openTime: head.openTime,
|
|
1733
|
+
creatorFeeOn: head.creatorFeeOn,
|
|
1734
|
+
enableCreatorFee: head.enableCreatorFee
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
const CPMM_AMM_CONFIG_HEAD = getStructDecoder([
|
|
1738
|
+
["bump", u8],
|
|
1739
|
+
["disableCreatePool", bool],
|
|
1740
|
+
["index", u16],
|
|
1741
|
+
["tradeFeeRate", u64$1],
|
|
1742
|
+
["protocolFeeRate", u64$1],
|
|
1743
|
+
["fundFeeRate", u64$1],
|
|
1744
|
+
["createPoolFee", u64$1],
|
|
1745
|
+
["protocolOwner", addressAt],
|
|
1746
|
+
["fundOwner", addressAt],
|
|
1747
|
+
["creatorFeeRate", u64$1]
|
|
1748
|
+
]);
|
|
1749
|
+
const CPMM_AMM_CONFIG_HEAD_SIZE = 116;
|
|
1750
|
+
function decodeCpmmAmmConfig(data) {
|
|
1751
|
+
checked(data, CPMM_AMM_CONFIG_DISCRIMINATOR, "CPMM amm config");
|
|
1752
|
+
atLeast(data, 116, "CPMM amm config");
|
|
1753
|
+
const head = CPMM_AMM_CONFIG_HEAD.decode(data, DISCRIMINATOR$1);
|
|
1754
|
+
return {
|
|
1755
|
+
index: head.index,
|
|
1756
|
+
disableCreatePool: head.disableCreatePool,
|
|
1757
|
+
tradeFeeRate: head.tradeFeeRate,
|
|
1758
|
+
protocolFeeRate: head.protocolFeeRate,
|
|
1759
|
+
fundFeeRate: head.fundFeeRate,
|
|
1760
|
+
creatorFeeRate: head.creatorFeeRate
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
//#endregion
|
|
1764
|
+
//#region src/accounts.ts
|
|
1765
|
+
/** Account readers and bounded discovery scans. Old pool layouts are intentionally unsupported. */
|
|
1766
|
+
const DISCRIMINATOR = 8;
|
|
1767
|
+
const DRAW_POOL_OFFSET = DISCRIMINATOR;
|
|
1768
|
+
const DRAW_PURCHASER_OFFSET = 40;
|
|
1769
|
+
const POOL_CREATOR_OFFSET = DISCRIMINATOR;
|
|
1770
|
+
const POOL_MINT_OFFSET = 40;
|
|
1771
|
+
const base58 = getBase58Decoder();
|
|
1772
|
+
const base64$1 = getBase64Encoder();
|
|
1773
|
+
const asBase58 = (bytes) => base58.decode(bytes);
|
|
1774
|
+
const memcmp$1 = (offset, bytes) => ({ memcmp: {
|
|
1775
|
+
offset: BigInt(offset),
|
|
1776
|
+
bytes,
|
|
1777
|
+
encoding: "base58"
|
|
1778
|
+
} });
|
|
1779
|
+
const encoded = (address, data) => ({
|
|
1780
|
+
address,
|
|
1781
|
+
data,
|
|
1782
|
+
exists: true,
|
|
1783
|
+
executable: false,
|
|
1784
|
+
lamports: 0n,
|
|
1785
|
+
programAddress: GABOX_PROGRAM_ID,
|
|
1786
|
+
space: BigInt(data.length)
|
|
1787
|
+
});
|
|
1788
|
+
async function fetchPoolByMint(client, mint) {
|
|
1789
|
+
return await fetchPoolAt(client, await poolAddress(mint));
|
|
1790
|
+
}
|
|
1791
|
+
async function fetchPoolAt(client, address) {
|
|
1792
|
+
return decodeCurrentPool(await fetchEncodedAccount(client.rpc, address, { commitment: "confirmed" }));
|
|
1793
|
+
}
|
|
1794
|
+
function decodeCurrentPool(account) {
|
|
1795
|
+
if (!account.exists) return null;
|
|
1796
|
+
if (account.data.length !== getPoolSize()) throw new Error(`pool ${account.address} is not a Gabox pool and is intentionally unsupported`);
|
|
1797
|
+
return decodePool(account).data;
|
|
1798
|
+
}
|
|
1799
|
+
function decodeCurrentDraw(account) {
|
|
1800
|
+
if (!account.exists) return null;
|
|
1801
|
+
if (account.data.length !== getDrawSize()) throw new Error(`draw ${account.address} is not a Gabox draw and is intentionally unsupported`);
|
|
1802
|
+
return decodeDraw(account).data;
|
|
1803
|
+
}
|
|
1804
|
+
async function fetchDraw(client, address) {
|
|
1805
|
+
return decodeCurrentDraw(await fetchEncodedAccount(client.rpc, address, { commitment: "confirmed" }));
|
|
1806
|
+
}
|
|
1807
|
+
function decodeCurrentWalletActivity(account) {
|
|
1808
|
+
if (!account.exists) return null;
|
|
1809
|
+
if (account.data.length !== getWalletActivitySize()) throw new Error(`activity account ${account.address} is not a Gabox WalletActivity and is intentionally unsupported`);
|
|
1810
|
+
return decodeWalletActivity(account).data;
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* A wallet's lifetime pack-buying activity, or `null` before it has bought its first pack.
|
|
1814
|
+
* `buy_pack` creates this account `init_if_needed`, so a fresh wallet has none yet.
|
|
1815
|
+
*/
|
|
1816
|
+
async function fetchWalletActivity(client, wallet) {
|
|
1817
|
+
return decodeCurrentWalletActivity(await fetchEncodedAccount(client.rpc, await activityAddress(wallet), { commitment: "confirmed" }));
|
|
1818
|
+
}
|
|
1819
|
+
async function scan(rpc, filters, decode) {
|
|
1820
|
+
return (await rpc.getProgramAccounts(GABOX_PROGRAM_ID, {
|
|
1821
|
+
encoding: "base64",
|
|
1822
|
+
commitment: "confirmed",
|
|
1823
|
+
filters
|
|
1824
|
+
}).send()).map(({ pubkey, account }) => decode(pubkey, new Uint8Array(base64$1.encode(account.data[0]))));
|
|
1825
|
+
}
|
|
1826
|
+
async function listPools(client) {
|
|
1827
|
+
return await scan(client.rpc, [memcmp$1(0, asBase58(POOL_DISCRIMINATOR)), { dataSize: BigInt(getPoolSize()) }], (address, data) => ({
|
|
1828
|
+
address,
|
|
1829
|
+
data: decodePool(encoded(address, data)).data
|
|
1830
|
+
}));
|
|
1831
|
+
}
|
|
1832
|
+
async function listDraws(client, query = {}) {
|
|
1833
|
+
const filters = [memcmp$1(0, asBase58(DRAW_DISCRIMINATOR)), { dataSize: BigInt(getDrawSize()) }];
|
|
1834
|
+
if (query.pool) filters.push(memcmp$1(8, query.pool));
|
|
1835
|
+
if (query.purchaser) filters.push(memcmp$1(40, query.purchaser));
|
|
1836
|
+
return await scan(client.rpc, filters, (address, data) => ({
|
|
1837
|
+
address,
|
|
1838
|
+
data: decodeDraw(encoded(address, data)).data
|
|
1839
|
+
}));
|
|
1840
|
+
}
|
|
1841
|
+
const listDrawsByPool = async (client, pool) => await listDraws(client, { pool });
|
|
1842
|
+
const listDrawsByPurchaser = async (client, purchaser) => await listDraws(client, { purchaser });
|
|
1843
|
+
async function fetchVaultBalance(client, mint) {
|
|
1844
|
+
const vault = await vaultAddress(mint);
|
|
1845
|
+
const { value } = await client.rpc.getAccountInfo(vault, {
|
|
1846
|
+
encoding: "base64",
|
|
1847
|
+
commitment: "confirmed"
|
|
1848
|
+
}).send();
|
|
1849
|
+
return value ? tokenAccountAmount(new Uint8Array(base64$1.encode(value.data[0]))) : 0n;
|
|
1850
|
+
}
|
|
1851
|
+
async function fetchPoolInventory(client, mint) {
|
|
1852
|
+
const poolPda = await poolAddress(mint);
|
|
1853
|
+
const vaultPda = await vaultAddress(mint);
|
|
1854
|
+
const { value } = await client.rpc.getMultipleAccounts([poolPda, vaultPda], {
|
|
1855
|
+
encoding: "base64",
|
|
1856
|
+
commitment: "confirmed"
|
|
1857
|
+
}).send();
|
|
1858
|
+
const poolAccount = value[0];
|
|
1859
|
+
if (!poolAccount) return null;
|
|
1860
|
+
const pool = decodeCurrentPool(encoded(poolPda, new Uint8Array(base64$1.encode(poolAccount.data[0]))));
|
|
1861
|
+
if (!pool) return null;
|
|
1862
|
+
if (pool.vault !== vaultPda) throw new Error(`pool ${poolPda} has an unexpected vault; it is not a supported Gabox pool`);
|
|
1863
|
+
const vaultAccount = value[1];
|
|
1864
|
+
const inventory = vaultAccount ? tokenAccountAmount(new Uint8Array(base64$1.encode(vaultAccount.data[0]))) : 0n;
|
|
1865
|
+
return {
|
|
1866
|
+
poolAddress: poolPda,
|
|
1867
|
+
pool,
|
|
1868
|
+
inventory,
|
|
1869
|
+
reserved: pool.reserved,
|
|
1870
|
+
free: inventory > pool.reserved ? inventory - pool.reserved : 0n
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
const tiersOf = (pool) => pool.tiers.map(({ multiplierBps, tickets }) => ({
|
|
1874
|
+
multiplierBps,
|
|
1875
|
+
tickets
|
|
1876
|
+
}));
|
|
1877
|
+
const quotePool = (inventory) => quote(inventory.pool.packTokens, tiersOf(inventory.pool), inventory.inventory, inventory.reserved);
|
|
1878
|
+
//#endregion
|
|
1879
|
+
//#region src/compute.ts
|
|
1880
|
+
/**
|
|
1881
|
+
* The two `ComputeBudget` instructions the transaction builders prepend.
|
|
1882
|
+
*
|
|
1883
|
+
* # Why every gabox transaction needs one
|
|
1884
|
+
*
|
|
1885
|
+
* The default budget is 200,000 compute units for the whole transaction. `buy_pack` alone does a
|
|
1886
|
+
* CPI into Raydium (which does its own CPIs into the token program and may create two fee vaults),
|
|
1887
|
+
* a `transfer_checked`, an account init, and a CPI into MagicBlock's VRF program that allocates a
|
|
1888
|
+
* request account. `createMachine` adds LaunchLab's `initialize_v2` to that, which mints the coin
|
|
1889
|
+
* and writes its Metaplex metadata. Neither fits in the default, and nothing on chain can raise its
|
|
1890
|
+
* own budget.
|
|
1891
|
+
*
|
|
1892
|
+
* # The numbers are measured
|
|
1893
|
+
*
|
|
1894
|
+
* Each figure below is about 1.3x the largest `computeUnitsConsumed` the devnet end-to-end test saw
|
|
1895
|
+
* over three runs on 2026-09-18, across both venues. That matters because the prioritisation fee is
|
|
1896
|
+
* `price x requested limit`: a request three times too large costs three times too much on every
|
|
1897
|
+
* pack. A caller may still override any of them.
|
|
1898
|
+
*
|
|
1899
|
+
* The three runs did not agree. A curve pack cost 164,717, then 167,723, then 181,225 units for the
|
|
1900
|
+
* same work, so the venue's own cost moves with state this SDK does not control. The 1.3x margin is
|
|
1901
|
+
* there to absorb that, and the largest of the three is always the basis.
|
|
1902
|
+
*
|
|
1903
|
+
* None of them covers a swap. When a builder puts a route in the same transaction, the route states
|
|
1904
|
+
* its own cost as `Route.computeUnits` and the builder adds it, capped at `MAX_COMPUTE_UNIT_LIMIT`.
|
|
1905
|
+
*
|
|
1906
|
+
* Re-measure after a change to a builder or to a Raydium program. `test/devnet.e2e.test.ts` prints
|
|
1907
|
+
* `CU <label>: <units> | <signature>` for every transaction it sends, which is where these came
|
|
1908
|
+
* from.
|
|
1909
|
+
*/
|
|
1910
|
+
const COMPUTE_BUDGET_PROGRAM_ADDRESS = "ComputeBudget111111111111111111111111111111";
|
|
1911
|
+
/** The runtime's per-transaction ceiling. A larger request is rejected outright. */
|
|
1912
|
+
const MAX_COMPUTE_UNIT_LIMIT = 14e5;
|
|
1913
|
+
/** What an instruction gets when no `SetComputeUnitLimit` is present. */
|
|
1914
|
+
const DEFAULT_COMPUTE_UNIT_LIMIT = 2e5;
|
|
1915
|
+
/**
|
|
1916
|
+
* LaunchLab `initialize_v2`, the WSOL wrap, pool initialization with a seed buy, and the WSOL
|
|
1917
|
+
* close, in one transaction. Measured at `266,488`, `258,988` and `267,988` over three devnet runs
|
|
1918
|
+
* against the slot-500297753 build.
|
|
1919
|
+
*/
|
|
1920
|
+
const CREATE_MACHINE_COMPUTE_UNITS = 35e4;
|
|
1921
|
+
/**
|
|
1922
|
+
* A WSOL wrap, a venue buy, an escrow transfer, a draw init, a VRF request and the WSOL close.
|
|
1923
|
+
* Measured at `178,690`, `163,686` and `157,692` on the curve, and `113,891`, `110,922` and
|
|
1924
|
+
* `106,387` on CPMM. The curve buy is dearer because a coin's first trade also creates the platform
|
|
1925
|
+
* and creator fee vaults.
|
|
1926
|
+
*/
|
|
1927
|
+
const BUY_PACK_COMPUTE_UNITS = 235e3;
|
|
1928
|
+
/**
|
|
1929
|
+
* A WSOL wrap, a venue sale, and the WSOL close. Measured at `94,246`, `86,745` and `88,246` on the
|
|
1930
|
+
* curve, and `61,545`, `55,576` and `57,046` on CPMM.
|
|
1931
|
+
*/
|
|
1932
|
+
const REDEEM_COMPUTE_UNITS = 125e3;
|
|
1933
|
+
/**
|
|
1934
|
+
* A creator fee claim: create the quote account, claim or collect, and close it when it is WSOL.
|
|
1935
|
+
* Measured at `31,319` every run for the LaunchLab claim and `40,959` for the CPMM collect. A claim
|
|
1936
|
+
* on a pool quoted in another token is cheaper, `17,635`, because nothing has to be unwrapped.
|
|
1937
|
+
*/
|
|
1938
|
+
const CLAIM_COMPUTE_UNITS = 55e3;
|
|
1939
|
+
/** `ComputeBudgetInstruction`'s discriminants. Positional and append-only upstream. */
|
|
1940
|
+
const SET_COMPUTE_UNIT_LIMIT = 2;
|
|
1941
|
+
const SET_COMPUTE_UNIT_PRICE = 3;
|
|
1942
|
+
/** `[u8 discriminant, u32 units]` — five bytes. */
|
|
1943
|
+
const LIMIT_ENCODER = getStructEncoder([["discriminant", getU8Encoder()], ["units", getU32Encoder()]]);
|
|
1944
|
+
/** `[u8 discriminant, u64 microLamports]` — nine bytes. */
|
|
1945
|
+
const PRICE_ENCODER = getStructEncoder([["discriminant", getU8Encoder()], ["microLamports", getU64Encoder()]]);
|
|
1946
|
+
function getSetComputeUnitLimitInstruction(units) {
|
|
1947
|
+
if (!Number.isInteger(units) || units < 0 || units > 14e5) throw new Error(`compute unit limit must be an integer in 0..=${MAX_COMPUTE_UNIT_LIMIT}, got ${units}`);
|
|
1948
|
+
return {
|
|
1949
|
+
programAddress: COMPUTE_BUDGET_PROGRAM_ADDRESS,
|
|
1950
|
+
accounts: [],
|
|
1951
|
+
data: LIMIT_ENCODER.encode({
|
|
1952
|
+
discriminant: SET_COMPUTE_UNIT_LIMIT,
|
|
1953
|
+
units
|
|
1954
|
+
})
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
/**
|
|
1958
|
+
* `SetComputeUnitPrice(microLamports)` — the priority fee, per compute unit.
|
|
1959
|
+
*
|
|
1960
|
+
* No default, deliberately. The fee that lands a transaction is a property of the network at the
|
|
1961
|
+
* moment you send it. A hardcoded price is either money burnt on an idle chain or a transaction
|
|
1962
|
+
* that quietly stops landing under load. Sample `getRecentPrioritizationFees`, or take it from
|
|
1963
|
+
* config.
|
|
1964
|
+
*/
|
|
1965
|
+
function getSetComputeUnitPriceInstruction(microLamports) {
|
|
1966
|
+
const price = BigInt(microLamports);
|
|
1967
|
+
if (price < 0n) throw new Error(`compute unit price must not be negative, got ${price}`);
|
|
1968
|
+
return {
|
|
1969
|
+
programAddress: COMPUTE_BUDGET_PROGRAM_ADDRESS,
|
|
1970
|
+
accounts: [],
|
|
1971
|
+
data: PRICE_ENCODER.encode({
|
|
1972
|
+
discriminant: SET_COMPUTE_UNIT_PRICE,
|
|
1973
|
+
microLamports: price
|
|
1974
|
+
})
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
/** The compute budget prefix a builder prepends: a limit, and a price only when one is asked for. */
|
|
1978
|
+
function computeBudgetInstructions(units, microLamports) {
|
|
1979
|
+
const instructions = [getSetComputeUnitLimitInstruction(units)];
|
|
1980
|
+
if (microLamports !== void 0) instructions.push(getSetComputeUnitPriceInstruction(microLamports));
|
|
1981
|
+
return instructions;
|
|
1982
|
+
}
|
|
1983
|
+
//#endregion
|
|
1984
|
+
//#region src/raydium/read.ts
|
|
1985
|
+
/**
|
|
1986
|
+
* One account reader, shared by every module that decodes a Raydium account.
|
|
1987
|
+
*
|
|
1988
|
+
* Kit's codecs, not `Buffer`. This SDK is vendored into a Next.js app and `Buffer` is not defined
|
|
1989
|
+
* in the browser.
|
|
1990
|
+
*/
|
|
1991
|
+
const base64 = getBase64Encoder();
|
|
1992
|
+
/** Read several accounts in one call. A missing account comes back as `null`, not as an error. */
|
|
1993
|
+
async function readAccounts(rpc, addresses) {
|
|
1994
|
+
const { value } = await rpc.getMultipleAccounts(addresses, {
|
|
1995
|
+
encoding: "base64",
|
|
1996
|
+
commitment: "confirmed"
|
|
1997
|
+
}).send();
|
|
1998
|
+
return value.map((account) => {
|
|
1999
|
+
if (!account) return null;
|
|
2000
|
+
const [encoded] = account.data;
|
|
2001
|
+
return {
|
|
2002
|
+
data: new Uint8Array(base64.encode(encoded)),
|
|
2003
|
+
owner: account.owner
|
|
2004
|
+
};
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
/** Decode one base64 account payload the RPC returned inside another shape. */
|
|
2008
|
+
const decodeBase64 = (encoded) => new Uint8Array(base64.encode(encoded));
|
|
2009
|
+
//#endregion
|
|
2010
|
+
//#region src/raydium/quote.ts
|
|
2011
|
+
/**
|
|
2012
|
+
* LaunchLab's global config for one quote mint, or `null` when Raydium enabled no such quote.
|
|
2013
|
+
*
|
|
2014
|
+
* Index 0 is the usual config, and the constant-product curve is the only shape Gabox launches, so
|
|
2015
|
+
* those two are fixed here. The decoded `quoteMint` has to be the mint asked for: a config for a
|
|
2016
|
+
* different quote would price the wrong asset.
|
|
2017
|
+
*/
|
|
2018
|
+
async function fetchQuoteConfig(client, mint, ids = raydiumIds(client.cluster)) {
|
|
2019
|
+
const address = await launchlabGlobalConfig(ids.launchlab, mint, 0, 0);
|
|
2020
|
+
const [account] = await readAccounts(client.rpc, [address]);
|
|
2021
|
+
if (!account || account.owner !== ids.launchlab) return null;
|
|
2022
|
+
let config;
|
|
2023
|
+
try {
|
|
2024
|
+
config = decodeLaunchlabGlobalConfig(account.data);
|
|
2025
|
+
} catch {
|
|
2026
|
+
return null;
|
|
2027
|
+
}
|
|
2028
|
+
if (config.curveType !== 0 || config.quoteMint !== mint) return null;
|
|
2029
|
+
return {
|
|
2030
|
+
...config,
|
|
2031
|
+
address
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
/** Can a Gabox machine be quoted in this mint? One account read. */
|
|
2035
|
+
async function isQuoteSupported(client, mint) {
|
|
2036
|
+
return await fetchQuoteConfig(client, mint) !== null;
|
|
2037
|
+
}
|
|
2038
|
+
/**
|
|
2039
|
+
* The full quote asset: its LaunchLab config, its token program, its decimals and its symbol.
|
|
2040
|
+
*
|
|
2041
|
+
* Two round trips. The first reads the global config, because the config address is derived from
|
|
2042
|
+
* the mint. The second reads the mint and its Metaplex metadata account together.
|
|
2043
|
+
*
|
|
2044
|
+
* Throws when Raydium has no config for the mint, because no pool can be created or traded then.
|
|
2045
|
+
*/
|
|
2046
|
+
async function fetchQuoteAsset(client, mint, ids = raydiumIds(client.cluster)) {
|
|
2047
|
+
const config = await fetchQuoteConfig(client, mint, ids);
|
|
2048
|
+
if (!config) throw new Error(`Raydium has no LaunchLab global config for ${mint}, so it is not a quote asset Gabox can use. Pick a quote Raydium enabled, such as wrapped SOL or USDC.`);
|
|
2049
|
+
const display = await fetchQuoteDisplay(client, mint);
|
|
2050
|
+
return {
|
|
2051
|
+
mint,
|
|
2052
|
+
config: config.address,
|
|
2053
|
+
tokenProgram: display.tokenProgram,
|
|
2054
|
+
decimals: display.decimals,
|
|
2055
|
+
symbol: display.symbol,
|
|
2056
|
+
tradeFeeRate: config.tradeFeeRate,
|
|
2057
|
+
migrateFee: config.migrateFee,
|
|
2058
|
+
minQuoteFundRaising: config.minQuoteFundRaising
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
/**
|
|
2062
|
+
* Read a quote mint and its Metaplex metadata in one call.
|
|
2063
|
+
*
|
|
2064
|
+
* A Token-2022 mint may carry its own `TokenMetadata` extension, and that one wins: it is the
|
|
2065
|
+
* issuer's own record. A classic SPL mint keeps its symbol in a Metaplex account, and a mint with
|
|
2066
|
+
* neither reports `null`.
|
|
2067
|
+
*/
|
|
2068
|
+
async function fetchQuoteDisplay(client, mint) {
|
|
2069
|
+
const [mintAccount, metadata] = await readAccounts(client.rpc, [mint, await metadataAddress(mint)]);
|
|
2070
|
+
if (!mintAccount) throw new Error(`the quote mint ${mint} is not on chain`);
|
|
2071
|
+
const tokenProgram = mintAccount.owner;
|
|
2072
|
+
if (tokenProgram !== "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" && tokenProgram !== "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb") throw new Error(`the quote mint ${mint} is owned by ${tokenProgram}, which is neither classic SPL Token nor Token-2022`);
|
|
2073
|
+
const own = tokenProgram === "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" ? token2022Symbol(mintAccount.data) : null;
|
|
2074
|
+
return {
|
|
2075
|
+
tokenProgram,
|
|
2076
|
+
decimals: mintDecimals(mintAccount.data),
|
|
2077
|
+
symbol: own ?? (metadata ? metaplexSymbol(metadata.data) : null)
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
//#endregion
|
|
2081
|
+
//#region src/raydium/accounts.ts
|
|
2082
|
+
/**
|
|
2083
|
+
* The ordered account list of every Raydium trade Gabox forwards.
|
|
2084
|
+
*
|
|
2085
|
+
* # Why these are built here
|
|
2086
|
+
*
|
|
2087
|
+
* `venue/trade.rs` accepts four trade instructions and no others: LaunchLab's `buy_exact_out` and
|
|
2088
|
+
* `sell_exact_in`, CPMM's `swap_base_output` and `swap_base_input`. It takes the venue's own
|
|
2089
|
+
* account list as `remaining_accounts` and walks it position by position. So the lists are
|
|
2090
|
+
* assembled here, from `abi.ts`, which codegen generated from the same Raydium IDLs the program's
|
|
2091
|
+
* `venue/abi.rs` came from.
|
|
2092
|
+
*
|
|
2093
|
+
* # How to read the output
|
|
2094
|
+
*
|
|
2095
|
+
* Each function returns `AccountMeta[]` in ABI order, with the role each slot's own ABI gives it.
|
|
2096
|
+
* That list goes into `initialize_pool`, `buy_pack` or `sell_tokens` as `remainingAccounts`,
|
|
2097
|
+
* unchanged. The program then requires every writable slot to be writable in the outer transaction,
|
|
2098
|
+
* requires the one signer slot to be the purchaser, and checks every address by name.
|
|
2099
|
+
*
|
|
2100
|
+
* # CPMM names its accounts by direction
|
|
2101
|
+
*
|
|
2102
|
+
* CPMM calls its four pairs `input_*` and `output_*`. A buy sends quote and receives the coin; a
|
|
2103
|
+
* sell is the other way round. So the two builders below fill the same four names with opposite
|
|
2104
|
+
* addresses, the token programs included. LaunchLab names its accounts by role instead, and uses
|
|
2105
|
+
* one list for both sides.
|
|
2106
|
+
*
|
|
2107
|
+
* # The token programs are inputs, not constants
|
|
2108
|
+
*
|
|
2109
|
+
* The base coin is always classic SPL Token. The quote may be Token-2022, so its program travels
|
|
2110
|
+
* with the pool: `pool.quoteTokenProgram` on the curve, and the two programs a migrated CPMM pool
|
|
2111
|
+
* records in its own data. `venue/trade.rs` checks both slots against the same values.
|
|
2112
|
+
*/
|
|
2113
|
+
/**
|
|
2114
|
+
* Turn `{name -> address}` into the ABI's ordered `AccountMeta[]`.
|
|
2115
|
+
*
|
|
2116
|
+
* The map is keyed by the IDL's own account names, so a missing entry names the slot it belongs to
|
|
2117
|
+
* rather than failing as an off-by-one further down. The role comes from the ABI table alone, never
|
|
2118
|
+
* from the caller, which is what keeps this list in step with `venue/abi.rs`.
|
|
2119
|
+
*/
|
|
2120
|
+
function order(abi, byName) {
|
|
2121
|
+
return abi.accounts.map((spec) => {
|
|
2122
|
+
const address = byName[spec.name];
|
|
2123
|
+
if (address === void 0) throw new Error(`${abi.name}: no address given for the "${spec.name}" account`);
|
|
2124
|
+
return {
|
|
2125
|
+
address,
|
|
2126
|
+
role: spec.signer ? spec.writable ? AccountRole.WRITABLE_SIGNER : AccountRole.READONLY_SIGNER : spec.writable ? AccountRole.WRITABLE : AccountRole.READONLY
|
|
2127
|
+
};
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
function launchlabTradeAccounts(abi, input) {
|
|
2131
|
+
return order(abi, {
|
|
2132
|
+
payer: input.user,
|
|
2133
|
+
authority: input.launchlabAuthority,
|
|
2134
|
+
global_config: input.globalConfig,
|
|
2135
|
+
platform_config: input.platformConfig,
|
|
2136
|
+
pool_state: input.poolState,
|
|
2137
|
+
user_base_token: input.userBaseToken,
|
|
2138
|
+
user_quote_token: input.userQuoteToken,
|
|
2139
|
+
base_vault: input.baseVault,
|
|
2140
|
+
quote_vault: input.quoteVault,
|
|
2141
|
+
base_token_mint: input.mint,
|
|
2142
|
+
quote_token_mint: input.quoteMint,
|
|
2143
|
+
base_token_program: TOKEN_PROGRAM_ADDRESS,
|
|
2144
|
+
quote_token_program: input.quoteTokenProgram,
|
|
2145
|
+
event_authority: input.launchlabEventAuthority,
|
|
2146
|
+
program: input.launchlab,
|
|
2147
|
+
system_program: SYSTEM_PROGRAM_ADDRESS,
|
|
2148
|
+
platform_fee_vault: input.platformFeeVault,
|
|
2149
|
+
creator_fee_vault: input.creatorFeeVault
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
/** `buy_exact_out`: 18 accounts. The first argument is the coins wanted, the second the cost cap. */
|
|
2153
|
+
const launchlabBuyAccounts = (input) => launchlabTradeAccounts(LAUNCHLAB_BUY_EXACT_OUT, input);
|
|
2154
|
+
/** `sell_exact_in`: the same 18 accounts. `venue/trade.rs` uses one table for both sides. */
|
|
2155
|
+
const launchlabSellAccounts = (input) => launchlabTradeAccounts(LAUNCHLAB_SELL_EXACT_IN, input);
|
|
2156
|
+
/**
|
|
2157
|
+
* `buy_exact_in`: the same 18 accounts again. Gabox never forwards this instruction; a wallet sends
|
|
2158
|
+
* it on its own, to buy the curve out with a fixed spend.
|
|
2159
|
+
*/
|
|
2160
|
+
const launchlabBuyExactInAccounts = (input) => launchlabTradeAccounts(LAUNCHLAB_BUY_EXACT_IN, input);
|
|
2161
|
+
/** A buy sends the quote and receives the coin. A sell is the other way round. */
|
|
2162
|
+
function cpmmTradeAccounts(abi, input, side) {
|
|
2163
|
+
const buying = side === "buy";
|
|
2164
|
+
return order(abi, {
|
|
2165
|
+
payer: input.user,
|
|
2166
|
+
authority: input.cpmmAuthority,
|
|
2167
|
+
amm_config: input.ammConfig,
|
|
2168
|
+
pool_state: input.poolState,
|
|
2169
|
+
input_token_account: buying ? input.userQuoteToken : input.userBaseToken,
|
|
2170
|
+
output_token_account: buying ? input.userBaseToken : input.userQuoteToken,
|
|
2171
|
+
input_vault: buying ? input.quoteVault : input.baseVault,
|
|
2172
|
+
output_vault: buying ? input.baseVault : input.quoteVault,
|
|
2173
|
+
input_token_program: buying ? input.quoteTokenProgram : input.baseTokenProgram,
|
|
2174
|
+
output_token_program: buying ? input.baseTokenProgram : input.quoteTokenProgram,
|
|
2175
|
+
input_token_mint: buying ? input.quoteMint : input.mint,
|
|
2176
|
+
output_token_mint: buying ? input.mint : input.quoteMint,
|
|
2177
|
+
observation_state: input.observationState
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
/** `swap_base_output`: 13 accounts. Note the argument order is `(max_amount_in, amount_out)`. */
|
|
2181
|
+
const cpmmBuyAccounts = (input) => cpmmTradeAccounts(CPMM_SWAP_BASE_OUTPUT, input, "buy");
|
|
2182
|
+
/** `swap_base_input`: 13 accounts, with the four pairs swapped. */
|
|
2183
|
+
const cpmmSellAccounts = (input) => cpmmTradeAccounts(CPMM_SWAP_BASE_INPUT, input, "sell");
|
|
2184
|
+
//#endregion
|
|
2185
|
+
//#region src/raydium/curve.ts
|
|
2186
|
+
/**
|
|
2187
|
+
* Raydium's own price math, ported to bigint.
|
|
2188
|
+
*
|
|
2189
|
+
* Every function here is a line-by-line port of Raydium's TypeScript, which is itself the same math
|
|
2190
|
+
* their on-chain programs run. The point is that a buyer sees the exact number the venue will
|
|
2191
|
+
* charge, not an estimate: `buy_pack` passes the pack size as an exact-tokens-out amount, and the
|
|
2192
|
+
* program then requires the token delta to equal one pack.
|
|
2193
|
+
*
|
|
2194
|
+
* Sources, read on 2026-09-18:
|
|
2195
|
+
*
|
|
2196
|
+
* - `raydium/launchpad/curve/constantProductCurve.ts` and `curve.ts` (LaunchLab)
|
|
2197
|
+
* - `raydium/cpmm/curve/calculator.ts`, `constantProduct.ts` and `fee.ts` (CPMM)
|
|
2198
|
+
*
|
|
2199
|
+
* Raydium uses `BN`, which truncates toward zero on division. Every value here is a non-negative
|
|
2200
|
+
* bigint, where `/` truncates the same way, so a plain `/` is the faithful port of their `.div()`.
|
|
2201
|
+
* Where they round up they call a ceiling helper, and so does this file. Nothing is approximated
|
|
2202
|
+
* and no step is reordered: a division that happens before a subtraction there happens before it
|
|
2203
|
+
* here, because the two orders give different answers.
|
|
2204
|
+
*
|
|
2205
|
+
* Every rate is out of 1,000,000, not out of 10,000.
|
|
2206
|
+
*/
|
|
2207
|
+
/** `ceilDiv(a, b, denominator)` in Raydium's code: `ceil(a * b / denominator)`. */
|
|
2208
|
+
function ceilDivRate(amount, rate, denominator = RATE_DENOMINATOR) {
|
|
2209
|
+
if (rate === 0n) return 0n;
|
|
2210
|
+
return ceilDiv(amount * rate, denominator);
|
|
2211
|
+
}
|
|
2212
|
+
/** `ceil(numerator / denominator)` for non-negative values. */
|
|
2213
|
+
function ceilDiv(numerator, denominator) {
|
|
2214
|
+
if (denominator === 0n) throw new Error("divide by zero");
|
|
2215
|
+
return (numerator + denominator - 1n) / denominator;
|
|
2216
|
+
}
|
|
2217
|
+
/**
|
|
2218
|
+
* The amount before a fee was taken, given the amount after it: `ceil(post * 1e6 / (1e6 - rate))`.
|
|
2219
|
+
*
|
|
2220
|
+
* Raydium calls this `calculatePreFee` on the curve and `calculatePreFeeAmount` on CPMM. Both are
|
|
2221
|
+
* the same expression.
|
|
2222
|
+
*/
|
|
2223
|
+
function preFeeAmount(postFeeAmount, rate) {
|
|
2224
|
+
if (rate === 0n) return postFeeAmount;
|
|
2225
|
+
if (rate >= 1000000n) throw new Error("a fee rate of 100% or more has no pre-fee amount");
|
|
2226
|
+
return ceilDiv(postFeeAmount * RATE_DENOMINATOR, RATE_DENOMINATOR - rate);
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* The whole fee a curve trade pays. Gabox never passes a share-fee receiver, so the fourth rate
|
|
2230
|
+
* Raydium supports is always zero and is left out here.
|
|
2231
|
+
*/
|
|
2232
|
+
function totalCurveFeeRate(rates) {
|
|
2233
|
+
const total = rates.tradeFeeRate + rates.platformFeeRate + rates.creatorFeeRate;
|
|
2234
|
+
if (total > 1000000n) throw new Error("the total curve fee rate exceeds 1,000,000");
|
|
2235
|
+
return total;
|
|
2236
|
+
}
|
|
2237
|
+
/** Coins the curve still has to sell. A buy cannot take more than this. */
|
|
2238
|
+
function remainingBase(pool) {
|
|
2239
|
+
return pool.totalBaseSell > pool.realBase ? pool.totalBaseSell - pool.realBase : 0n;
|
|
2240
|
+
}
|
|
2241
|
+
/** The two sides of the constant product, as the curve sees them right now. */
|
|
2242
|
+
const quoteSide = (pool) => pool.virtualQuote + pool.realQuote;
|
|
2243
|
+
const baseSide = (pool) => pool.virtualBase - pool.realBase;
|
|
2244
|
+
/**
|
|
2245
|
+
* Quote needed to buy exactly `tokens` coins on the curve, fees included.
|
|
2246
|
+
*
|
|
2247
|
+
* This is `Curve.buyExactOut` with no token transfer fee, which is always the case for Gabox: both
|
|
2248
|
+
* mints are classic SPL Token and carry no transfer-fee extension.
|
|
2249
|
+
*
|
|
2250
|
+
* Raydium caps the amount at what the curve has left to sell and still returns a price, so this
|
|
2251
|
+
* does the same. A caller that needs the whole `tokens` must check `remainingBase` first;
|
|
2252
|
+
* `resolveVenue` does.
|
|
2253
|
+
*/
|
|
2254
|
+
function curveBuyExactOut(pool, rates, tokens) {
|
|
2255
|
+
if (tokens <= 0n) throw new Error("tokens must be positive");
|
|
2256
|
+
const remaining = remainingBase(pool);
|
|
2257
|
+
const amount = tokens > remaining ? remaining : tokens;
|
|
2258
|
+
if (amount === 0n) throw new Error("the curve has no coins left to sell");
|
|
2259
|
+
return preFeeAmount(curveQuoteInForBaseOut(pool, amount), totalCurveFeeRate(rates));
|
|
2260
|
+
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Coins `quote` buys on the curve right now, fees already taken off the spend.
|
|
2263
|
+
*
|
|
2264
|
+
* This is `Curve.buyExactIn`. It is what a raw `buy_exact_in` pays out, which the devnet end-to-end
|
|
2265
|
+
* test uses to push a curve to graduation.
|
|
2266
|
+
*/
|
|
2267
|
+
function curveBuyExactIn(pool, rates, quote) {
|
|
2268
|
+
if (quote <= 0n) throw new Error("quote must be positive");
|
|
2269
|
+
const spend = quote - ceilDivRate(quote, totalCurveFeeRate(rates));
|
|
2270
|
+
if (spend <= 0n) throw new Error("the whole spend would go to fees");
|
|
2271
|
+
const tokens = curveBaseOutForQuoteIn(pool, spend);
|
|
2272
|
+
const remaining = remainingBase(pool);
|
|
2273
|
+
return tokens > remaining ? remaining : tokens;
|
|
2274
|
+
}
|
|
2275
|
+
/**
|
|
2276
|
+
* Quote a sale of exactly `tokens` coins returns, net of fees. This is `Curve.sellExactIn`.
|
|
2277
|
+
*/
|
|
2278
|
+
function curveSellExactIn(pool, rates, tokens) {
|
|
2279
|
+
if (tokens <= 0n) throw new Error("tokens must be positive");
|
|
2280
|
+
const gross = curveQuoteOutForBaseIn(pool, tokens);
|
|
2281
|
+
const fee = ceilDivRate(gross, totalCurveFeeRate(rates));
|
|
2282
|
+
return gross > fee ? gross - fee : 0n;
|
|
2283
|
+
}
|
|
2284
|
+
/** `getAmountIn` on the buy side: `ceil(quoteSide * out / (baseSide - out))`. */
|
|
2285
|
+
function curveQuoteInForBaseOut(pool, tokens) {
|
|
2286
|
+
const output = baseSide(pool);
|
|
2287
|
+
if (tokens >= output) throw new Error("the curve does not hold that many coins");
|
|
2288
|
+
return ceilDiv(quoteSide(pool) * tokens, output - tokens);
|
|
2289
|
+
}
|
|
2290
|
+
/** `getAmountOut` on the buy side: `floor(in * baseSide / (quoteSide + in))`. */
|
|
2291
|
+
function curveBaseOutForQuoteIn(pool, quote) {
|
|
2292
|
+
return quote * baseSide(pool) / (quoteSide(pool) + quote);
|
|
2293
|
+
}
|
|
2294
|
+
/** `getAmountOut` on the sell side: `floor(in * quoteSide / (baseSide + in))`. */
|
|
2295
|
+
function curveQuoteOutForBaseIn(pool, tokens) {
|
|
2296
|
+
return tokens * quoteSide(pool) / (baseSide(pool) + tokens);
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* The virtual reserves LaunchLab gives a coin that does not exist yet.
|
|
2300
|
+
*
|
|
2301
|
+
* This is `LaunchConstantProductCurve.getInitParam`. `createMachine` needs it because the seed buy
|
|
2302
|
+
* runs in the same transaction that creates the coin: there is no pool account to read yet, and the
|
|
2303
|
+
* reserves follow from the pinned launch shape alone.
|
|
2304
|
+
*
|
|
2305
|
+
* Checked against Raydium's own published numbers for the mainnet Gabox launch shape (supply
|
|
2306
|
+
* 1,000,000,000 coins, 793,100,000 sold on the curve, 85 SOL raised, no migrate fee):
|
|
2307
|
+
* `virtualBase` 1,073,025,605,596,382 and `virtualQuote` 30,000,852,951.
|
|
2308
|
+
*/
|
|
2309
|
+
function initialCurve(params) {
|
|
2310
|
+
const { supply, totalBaseSell, quoteRaise } = params;
|
|
2311
|
+
const locked = params.totalLockedAmount ?? 0n;
|
|
2312
|
+
const migrateFee = params.migrateFee ?? 0n;
|
|
2313
|
+
if (supply <= totalBaseSell) throw new Error("the supply must be larger than the curve sells");
|
|
2314
|
+
const migrateBase = supply - totalBaseSell - locked;
|
|
2315
|
+
if (migrateBase <= 0n) throw new Error("nothing would be left to seed the pool at graduation");
|
|
2316
|
+
const raiseLessMigrateFee = quoteRaise - migrateFee;
|
|
2317
|
+
if (raiseLessMigrateFee <= 0n) throw new Error("the migrate fee eats the whole raise");
|
|
2318
|
+
const numerator = raiseLessMigrateFee * totalBaseSell * totalBaseSell / migrateBase;
|
|
2319
|
+
const denominator = raiseLessMigrateFee * totalBaseSell / migrateBase - quoteRaise;
|
|
2320
|
+
if (denominator <= 0n) throw new Error("these launch parameters have no valid starting price");
|
|
2321
|
+
return {
|
|
2322
|
+
virtualBase: numerator / denominator,
|
|
2323
|
+
virtualQuote: quoteRaise * quoteRaise / denominator,
|
|
2324
|
+
realBase: 0n,
|
|
2325
|
+
realQuote: 0n,
|
|
2326
|
+
totalBaseSell
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Quote a CPMM buy of exactly `tokens` coins costs, both fees included.
|
|
2331
|
+
*
|
|
2332
|
+
* This is `CurveCalculator.swapBaseOutput`, which is what `swap_base_output` charges.
|
|
2333
|
+
*/
|
|
2334
|
+
function cpmmSwapBaseOutput(sides, rates, amountOut) {
|
|
2335
|
+
if (amountOut <= 0n) throw new Error("amountOut must be positive");
|
|
2336
|
+
const actualOut = rates.creatorFeeOnInput ? amountOut : preFeeAmount(amountOut, rates.creatorFeeRate);
|
|
2337
|
+
if (actualOut >= sides.outputReserve) throw new Error("the pool does not hold that much of the output token");
|
|
2338
|
+
return preFeeAmount(ceilDiv(sides.inputReserve * actualOut, sides.outputReserve - actualOut), rates.creatorFeeOnInput ? rates.tradeFeeRate + rates.creatorFeeRate : rates.tradeFeeRate);
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* What a CPMM swap of exactly `amountIn` returns, net of both fees.
|
|
2342
|
+
*
|
|
2343
|
+
* This is `CurveCalculator.swapBaseInput`, which is what `swap_base_input` pays out.
|
|
2344
|
+
*/
|
|
2345
|
+
function cpmmSwapBaseInput(sides, rates, amountIn) {
|
|
2346
|
+
if (amountIn <= 0n) throw new Error("amountIn must be positive");
|
|
2347
|
+
const tradeFee = ceilDivRate(amountIn, rates.tradeFeeRate);
|
|
2348
|
+
const creatorFeeIn = rates.creatorFeeOnInput ? ceilDivRate(amountIn, rates.creatorFeeRate) : 0n;
|
|
2349
|
+
const lessFees = amountIn - tradeFee - creatorFeeIn;
|
|
2350
|
+
if (lessFees <= 0n) throw new Error("the whole input would go to fees");
|
|
2351
|
+
const swapped = lessFees * sides.outputReserve / (sides.inputReserve + lessFees);
|
|
2352
|
+
if (rates.creatorFeeOnInput) return swapped;
|
|
2353
|
+
const creatorFeeOut = ceilDivRate(swapped, rates.creatorFeeRate);
|
|
2354
|
+
return swapped > creatorFeeOut ? swapped - creatorFeeOut : 0n;
|
|
2355
|
+
}
|
|
2356
|
+
//#endregion
|
|
2357
|
+
//#region src/raydium/venue.ts
|
|
2358
|
+
/** The quote binding a Gabox pool records. One account read. */
|
|
2359
|
+
async function fetchQuoteBinding(client, mint) {
|
|
2360
|
+
const pool = await fetchPoolByMint(client, mint);
|
|
2361
|
+
if (!pool) throw new Error(`no Gabox pool for mint ${mint}, so its quote asset is unknown. Pass \`quote\` to price a coin that has no pool.`);
|
|
2362
|
+
return {
|
|
2363
|
+
mint: pool.quoteMint,
|
|
2364
|
+
config: pool.quoteConfig,
|
|
2365
|
+
tokenProgram: pool.quoteTokenProgram
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
/**
|
|
2369
|
+
* The user's quote associated token account, under the quote's own token program.
|
|
2370
|
+
*
|
|
2371
|
+
* Both venues settle in this account, never in native SOL. It must exist and hold enough before a
|
|
2372
|
+
* pack is bought, and sale proceeds land in it. For a WSOL-quoted pool every builder in `tx/` wraps
|
|
2373
|
+
* the SOL it needs into this account and closes it again in the same transaction.
|
|
2374
|
+
*/
|
|
2375
|
+
async function quoteAccountFor(user, quoteMint, quoteTokenProgram = TOKEN_PROGRAM_ADDRESS) {
|
|
2376
|
+
return await ata(user, quoteMint, quoteTokenProgram);
|
|
2377
|
+
}
|
|
2378
|
+
/** The user's WSOL associated token account. WSOL is always classic SPL Token. */
|
|
2379
|
+
async function wsolAccountFor(user) {
|
|
2380
|
+
return await ata(user, WSOL_MINT, TOKEN_PROGRAM_ADDRESS);
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Read the quote's global config and the Gabox platform config, and take the four numbers a quote
|
|
2384
|
+
* needs out of them.
|
|
2385
|
+
*
|
|
2386
|
+
* `quoteConfig` is the LaunchLab global config of the pool's quote asset: `pool.quoteConfig`, or
|
|
2387
|
+
* the address `raydium.fetchQuoteConfig` derives for a coin that has no pool yet.
|
|
2388
|
+
*/
|
|
2389
|
+
async function fetchCurveSettings(client, quoteConfig, ids = raydiumIds(client.cluster)) {
|
|
2390
|
+
const [globalAccount, platformAccount] = await readAccounts(client.rpc, [quoteConfig, ids.gaboxPlatform]);
|
|
2391
|
+
if (!globalAccount) throw new Error(`LaunchLab has no global config at ${quoteConfig}`);
|
|
2392
|
+
if (!platformAccount) throw new Error(`the Gabox platform config is missing at ${ids.gaboxPlatform}`);
|
|
2393
|
+
return settingsFrom(globalAccount.data, platformAccount.data);
|
|
2394
|
+
}
|
|
2395
|
+
function settingsFrom(globalData, platformData) {
|
|
2396
|
+
const global = decodeLaunchlabGlobalConfig(globalData);
|
|
2397
|
+
const platform = decodeLaunchlabPlatformConfig(platformData);
|
|
2398
|
+
return {
|
|
2399
|
+
rates: {
|
|
2400
|
+
tradeFeeRate: global.tradeFeeRate,
|
|
2401
|
+
platformFeeRate: platform.feeRate,
|
|
2402
|
+
creatorFeeRate: platform.creatorFeeRate
|
|
2403
|
+
},
|
|
2404
|
+
migrateFee: global.migrateFee,
|
|
2405
|
+
cpswapConfig: platform.cpswapConfig
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
async function resolveVenue(client, options) {
|
|
2409
|
+
const ids = raydiumIds(client.cluster);
|
|
2410
|
+
const { mint, user } = options;
|
|
2411
|
+
const quote = options.quote ?? await fetchQuoteBinding(client, mint);
|
|
2412
|
+
const curvePool = await launchlabPoolAddress(ids.launchlab, mint, quote.mint);
|
|
2413
|
+
const [poolAccount, globalAccount, platformAccount] = await readAccounts(client.rpc, [
|
|
2414
|
+
curvePool,
|
|
2415
|
+
quote.config,
|
|
2416
|
+
ids.gaboxPlatform
|
|
2417
|
+
]);
|
|
2418
|
+
if (!poolAccount) throw new Error(`no LaunchLab pool at ${curvePool}. The coin was not launched on LaunchLab with ${quote.mint} as its quote, so it has no Gabox venue.`);
|
|
2419
|
+
if (!globalAccount) throw new Error(`LaunchLab has no global config at ${quote.config}`);
|
|
2420
|
+
if (!platformAccount) throw new Error(`the Gabox platform config is missing at ${ids.gaboxPlatform}`);
|
|
2421
|
+
const pool = decodeLaunchlabPool(poolAccount.data);
|
|
2422
|
+
if (pool.baseMint !== mint || pool.quoteMint !== quote.mint) throw new Error(`the LaunchLab pool at ${curvePool} is not the pool of ${mint}`);
|
|
2423
|
+
const settings = settingsFrom(globalAccount.data, platformAccount.data);
|
|
2424
|
+
const kind = options.venue ?? (pool.status === 2 ? "cpmm" : "launchlab");
|
|
2425
|
+
if (options.venue === void 0 && pool.status === 1) throw new Error(`${mint} is graduating: its LaunchLab curve is sold out and Raydium is moving it into a CPMM pool. Retry in a moment; the move usually finishes within a minute.`);
|
|
2426
|
+
if (kind === "launchlab") {
|
|
2427
|
+
if (pool.status !== 0) throw new Error(`${mint} no longer trades on its LaunchLab curve (status ${pool.status}). Use the CPMM venue.`);
|
|
2428
|
+
return await launchlabVenue(ids, pool, settings.rates, {
|
|
2429
|
+
mint,
|
|
2430
|
+
quote,
|
|
2431
|
+
user,
|
|
2432
|
+
poolState: curvePool
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
return await cpmmVenue(client, ids, pool, settings.cpswapConfig, {
|
|
2436
|
+
mint,
|
|
2437
|
+
quote,
|
|
2438
|
+
user
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
async function launchlabVenue(ids, pool, rates, where) {
|
|
2442
|
+
const { mint, quote, user, poolState } = where;
|
|
2443
|
+
const userQuoteToken = await quoteAccountFor(user, quote.mint, quote.tokenProgram);
|
|
2444
|
+
const shared = {
|
|
2445
|
+
launchlab: ids.launchlab,
|
|
2446
|
+
launchlabAuthority: ids.launchlabAuthority,
|
|
2447
|
+
launchlabEventAuthority: ids.launchlabEventAuthority,
|
|
2448
|
+
globalConfig: quote.config,
|
|
2449
|
+
platformConfig: ids.gaboxPlatform,
|
|
2450
|
+
poolState,
|
|
2451
|
+
mint,
|
|
2452
|
+
quoteMint: quote.mint,
|
|
2453
|
+
baseVault: await launchlabVaultAddress(ids.launchlab, poolState, mint),
|
|
2454
|
+
quoteVault: await launchlabVaultAddress(ids.launchlab, poolState, quote.mint),
|
|
2455
|
+
user,
|
|
2456
|
+
userBaseToken: await ata(user, mint),
|
|
2457
|
+
userQuoteToken,
|
|
2458
|
+
quoteTokenProgram: quote.tokenProgram,
|
|
2459
|
+
platformFeeVault: await platformFeeVaultAddress(ids.launchlab, ids.gaboxPlatform, quote.mint),
|
|
2460
|
+
creatorFeeVault: await creatorFeeVaultAddress(ids.launchlab, pool.creator, quote.mint)
|
|
2461
|
+
};
|
|
2462
|
+
const reserves = {
|
|
2463
|
+
virtualBase: pool.virtualBase,
|
|
2464
|
+
virtualQuote: pool.virtualQuote,
|
|
2465
|
+
realBase: pool.realBase,
|
|
2466
|
+
realQuote: pool.realQuote,
|
|
2467
|
+
totalBaseSell: pool.totalBaseSell
|
|
2468
|
+
};
|
|
2469
|
+
const remaining = remainingBase(reserves);
|
|
2470
|
+
return {
|
|
2471
|
+
kind: "launchlab",
|
|
2472
|
+
program: ids.launchlab,
|
|
2473
|
+
mint,
|
|
2474
|
+
quoteMint: quote.mint,
|
|
2475
|
+
quoteTokenProgram: quote.tokenProgram,
|
|
2476
|
+
userQuoteToken,
|
|
2477
|
+
poolState,
|
|
2478
|
+
status: pool.status,
|
|
2479
|
+
creator: pool.creator,
|
|
2480
|
+
ammConfig: null,
|
|
2481
|
+
remainingCurveBase: remaining,
|
|
2482
|
+
buyAccounts: launchlabBuyAccounts(shared),
|
|
2483
|
+
sellAccounts: launchlabSellAccounts(shared),
|
|
2484
|
+
quoteBuy: (tokens) => {
|
|
2485
|
+
if (tokens > remaining) throw new Error(`the curve has ${remaining} base units left and cannot sell ${tokens}. Wait for the coin to graduate, then buy on its CPMM pool.`);
|
|
2486
|
+
return curveBuyExactOut(reserves, rates, tokens);
|
|
2487
|
+
},
|
|
2488
|
+
quoteSell: (tokens) => curveSellExactIn(reserves, rates, tokens)
|
|
2489
|
+
};
|
|
2490
|
+
}
|
|
2491
|
+
/**
|
|
2492
|
+
* Every check the program makes on a CPMM pool account, in one place.
|
|
2493
|
+
*
|
|
2494
|
+
* `enable_creator_fee` is the one that identifies the migrated pool. Anyone can call CPMM's own
|
|
2495
|
+
* `initialize` and open a pool for the same pair with the same creator, but that pool always has
|
|
2496
|
+
* the flag false. Only a creator Raydium has permissioned can set it, and LaunchLab's migration is
|
|
2497
|
+
* one of those.
|
|
2498
|
+
*/
|
|
2499
|
+
function cpmmPoolMatches(pool, expect) {
|
|
2500
|
+
return pool.enableCreatorFee && pool.poolCreator === expect.creator && pool.token0Mint === expect.token0 && pool.token1Mint === expect.token1;
|
|
2501
|
+
}
|
|
2502
|
+
/**
|
|
2503
|
+
* The CPMM pool LaunchLab migrated a coin into.
|
|
2504
|
+
*
|
|
2505
|
+
* Two steps, because the pool is not always at a derived address. Raydium migrates into
|
|
2506
|
+
* `["pool", cpswap_config, token_0, token_1]` when that account is free, and into a random account
|
|
2507
|
+
* when it is not. So: try the derived address, and scan for the pool when it does not hold one.
|
|
2508
|
+
*
|
|
2509
|
+
* The scan filters on the account length and on the three fields at their fixed offsets, so the
|
|
2510
|
+
* RPC does the work and returns at most a handful of accounts. Every candidate still has to pass
|
|
2511
|
+
* `cpmmPoolMatches`, and more than one match is an error rather than a guess.
|
|
2512
|
+
*/
|
|
2513
|
+
async function findCpmmPool(client, options) {
|
|
2514
|
+
const ids = raydiumIds(client.cluster);
|
|
2515
|
+
const { mint, creator, cpswapConfig } = options;
|
|
2516
|
+
const quoteMint = options.quoteMint ?? "So11111111111111111111111111111111111111112";
|
|
2517
|
+
const [token0, token1] = sortedMints(mint, quoteMint);
|
|
2518
|
+
const expect = {
|
|
2519
|
+
creator,
|
|
2520
|
+
token0,
|
|
2521
|
+
token1
|
|
2522
|
+
};
|
|
2523
|
+
const derived = await cpmmPoolAddress(ids.cpmm, cpswapConfig, mint, quoteMint);
|
|
2524
|
+
const [account] = await readAccounts(client.rpc, [derived]);
|
|
2525
|
+
if (account && account.owner === ids.cpmm) {
|
|
2526
|
+
const pool = decodeCpmmPoolOrNull(account.data);
|
|
2527
|
+
if (pool && cpmmPoolMatches(pool, expect)) return {
|
|
2528
|
+
address: derived,
|
|
2529
|
+
pool
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
const rows = await client.rpc.getProgramAccounts(ids.cpmm, {
|
|
2533
|
+
encoding: "base64",
|
|
2534
|
+
commitment: "confirmed",
|
|
2535
|
+
filters: [
|
|
2536
|
+
{ dataSize: BigInt(637) },
|
|
2537
|
+
memcmp(CPMM_POOL_OFFSETS.poolCreator, creator),
|
|
2538
|
+
memcmp(CPMM_POOL_OFFSETS.token0Mint, token0),
|
|
2539
|
+
memcmp(CPMM_POOL_OFFSETS.token1Mint, token1)
|
|
2540
|
+
]
|
|
2541
|
+
}).send();
|
|
2542
|
+
const matches = [];
|
|
2543
|
+
for (const { pubkey, account: row } of rows) {
|
|
2544
|
+
const pool = decodeCpmmPoolOrNull(decodeBase64(row.data[0]));
|
|
2545
|
+
if (pool && cpmmPoolMatches(pool, expect)) matches.push({
|
|
2546
|
+
address: pubkey,
|
|
2547
|
+
pool
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2550
|
+
const [only] = matches;
|
|
2551
|
+
if (!only) throw new Error(`no migrated CPMM pool for ${mint}. The derived address ${derived} holds no matching pool, and no other CPMM pool has this coin, this creator and a creator fee enabled.`);
|
|
2552
|
+
if (matches.length > 1) throw new Error(`${matches.length} CPMM pools match ${mint}: ${matches.map((m) => m.address).join(", ")}. Pass the one you mean rather than letting this choose.`);
|
|
2553
|
+
return only;
|
|
2554
|
+
}
|
|
2555
|
+
/** A `getProgramAccounts` byte filter. The bytes of an address are its own base58 text. */
|
|
2556
|
+
function memcmp(offset, value) {
|
|
2557
|
+
return { memcmp: {
|
|
2558
|
+
offset: BigInt(offset),
|
|
2559
|
+
bytes: value,
|
|
2560
|
+
encoding: "base58"
|
|
2561
|
+
} };
|
|
2562
|
+
}
|
|
2563
|
+
/** Decode a candidate account, or give up quietly. A scan row that is not a pool is not an error. */
|
|
2564
|
+
function decodeCpmmPoolOrNull(data) {
|
|
2565
|
+
try {
|
|
2566
|
+
return decodeCpmmPool(data);
|
|
2567
|
+
} catch {
|
|
2568
|
+
return null;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
async function cpmmVenue(client, ids, curve, cpswapConfig, where) {
|
|
2572
|
+
const { mint, quote, user } = where;
|
|
2573
|
+
const { address: poolState, pool } = await findCpmmPool(client, {
|
|
2574
|
+
mint,
|
|
2575
|
+
quoteMint: quote.mint,
|
|
2576
|
+
creator: curve.creator,
|
|
2577
|
+
cpswapConfig
|
|
2578
|
+
});
|
|
2579
|
+
const baseIsToken0 = pool.token0Mint === mint;
|
|
2580
|
+
const baseVault = baseIsToken0 ? pool.token0Vault : pool.token1Vault;
|
|
2581
|
+
const quoteVault = baseIsToken0 ? pool.token1Vault : pool.token0Vault;
|
|
2582
|
+
const baseTokenProgram = baseIsToken0 ? pool.token0Program : pool.token1Program;
|
|
2583
|
+
const quoteTokenProgram = baseIsToken0 ? pool.token1Program : pool.token0Program;
|
|
2584
|
+
if (baseTokenProgram !== "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" || quoteTokenProgram !== quote.tokenProgram) throw new Error(`the CPMM pool at ${poolState} records token programs ${baseTokenProgram} and ${quoteTokenProgram}, which are not the ones this coin trades under`);
|
|
2585
|
+
const [configAccount, baseVaultAccount, quoteVaultAccount] = await readAccounts(client.rpc, [
|
|
2586
|
+
pool.ammConfig,
|
|
2587
|
+
baseVault,
|
|
2588
|
+
quoteVault
|
|
2589
|
+
]);
|
|
2590
|
+
if (!configAccount) throw new Error(`the CPMM pool at ${poolState} names a fee tier that is not on chain`);
|
|
2591
|
+
if (!baseVaultAccount || !quoteVaultAccount) throw new Error(`the CPMM pool at ${poolState} has no reserve accounts`);
|
|
2592
|
+
const config = decodeCpmmAmmConfig(configAccount.data);
|
|
2593
|
+
const owedBase = baseIsToken0 ? pool.protocolFeesToken0 + pool.fundFeesToken0 + pool.creatorFeesToken0 : pool.protocolFeesToken1 + pool.fundFeesToken1 + pool.creatorFeesToken1;
|
|
2594
|
+
const owedQuote = baseIsToken0 ? pool.protocolFeesToken1 + pool.fundFeesToken1 + pool.creatorFeesToken1 : pool.protocolFeesToken0 + pool.fundFeesToken0 + pool.creatorFeesToken0;
|
|
2595
|
+
const baseReserve = tokenAccountAmount(baseVaultAccount.data) - owedBase;
|
|
2596
|
+
const quoteReserve = tokenAccountAmount(quoteVaultAccount.data) - owedQuote;
|
|
2597
|
+
if (baseReserve <= 0n || quoteReserve <= 0n) throw new Error(`the CPMM pool at ${poolState} has no tradable reserves`);
|
|
2598
|
+
const userQuoteToken = await quoteAccountFor(user, quote.mint, quoteTokenProgram);
|
|
2599
|
+
const shared = {
|
|
2600
|
+
cpmmAuthority: ids.cpmmAuthority,
|
|
2601
|
+
ammConfig: pool.ammConfig,
|
|
2602
|
+
poolState,
|
|
2603
|
+
mint,
|
|
2604
|
+
quoteMint: quote.mint,
|
|
2605
|
+
baseVault,
|
|
2606
|
+
quoteVault,
|
|
2607
|
+
observationState: pool.observationKey,
|
|
2608
|
+
user,
|
|
2609
|
+
userBaseToken: await ata(user, mint),
|
|
2610
|
+
userQuoteToken,
|
|
2611
|
+
baseTokenProgram,
|
|
2612
|
+
quoteTokenProgram
|
|
2613
|
+
};
|
|
2614
|
+
const creatorFeeRate = pool.enableCreatorFee ? config.creatorFeeRate : 0n;
|
|
2615
|
+
const buySides = {
|
|
2616
|
+
inputReserve: quoteReserve,
|
|
2617
|
+
outputReserve: baseReserve
|
|
2618
|
+
};
|
|
2619
|
+
const sellSides = {
|
|
2620
|
+
inputReserve: baseReserve,
|
|
2621
|
+
outputReserve: quoteReserve
|
|
2622
|
+
};
|
|
2623
|
+
const buyRates = {
|
|
2624
|
+
tradeFeeRate: config.tradeFeeRate,
|
|
2625
|
+
creatorFeeRate,
|
|
2626
|
+
creatorFeeOnInput: creatorFeeOnInput(pool, quote.mint)
|
|
2627
|
+
};
|
|
2628
|
+
const sellRates = {
|
|
2629
|
+
tradeFeeRate: config.tradeFeeRate,
|
|
2630
|
+
creatorFeeRate,
|
|
2631
|
+
creatorFeeOnInput: creatorFeeOnInput(pool, mint)
|
|
2632
|
+
};
|
|
2633
|
+
return {
|
|
2634
|
+
kind: "cpmm",
|
|
2635
|
+
program: ids.cpmm,
|
|
2636
|
+
mint,
|
|
2637
|
+
quoteMint: quote.mint,
|
|
2638
|
+
quoteTokenProgram,
|
|
2639
|
+
userQuoteToken,
|
|
2640
|
+
poolState,
|
|
2641
|
+
status: curve.status,
|
|
2642
|
+
creator: pool.poolCreator,
|
|
2643
|
+
ammConfig: pool.ammConfig,
|
|
2644
|
+
remainingCurveBase: 0n,
|
|
2645
|
+
buyAccounts: cpmmBuyAccounts(shared),
|
|
2646
|
+
sellAccounts: cpmmSellAccounts(shared),
|
|
2647
|
+
quoteBuy: (tokens) => cpmmSwapBaseOutput(buySides, buyRates, tokens),
|
|
2648
|
+
quoteSell: (tokens) => cpmmSwapBaseInput(sellSides, sellRates, tokens)
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
/**
|
|
2652
|
+
* Does this side's creator fee come off the token going in?
|
|
2653
|
+
*
|
|
2654
|
+
* `creator_fee_on` is 0 when the fee follows the input token, 1 when it is always token 0, and 2
|
|
2655
|
+
* when it is always token 1. LaunchLab migrates a Gabox coin with `AmmCreatorFeeOn::QuoteToken`, so
|
|
2656
|
+
* the pool charges the creator fee in the quote token only: on the input of a buy, on the output of
|
|
2657
|
+
* a sale.
|
|
2658
|
+
*/
|
|
2659
|
+
function creatorFeeOnInput(pool, inputMint) {
|
|
2660
|
+
if (pool.creatorFeeOn === 0) return true;
|
|
2661
|
+
const inputIsToken0 = pool.token0Mint === inputMint;
|
|
2662
|
+
return pool.creatorFeeOn === 1 ? inputIsToken0 : !inputIsToken0;
|
|
2663
|
+
}
|
|
2664
|
+
/**
|
|
2665
|
+
* Quote token an exact-coins-out buy of `tokens` costs right now, on whichever venue the coin
|
|
2666
|
+
* trades. Pass `pool.packTokens` for the pack price.
|
|
2667
|
+
*
|
|
2668
|
+
* Use `resolveVenue` when you also need the account list, which every transaction builder does.
|
|
2669
|
+
* This is for a price display.
|
|
2670
|
+
*/
|
|
2671
|
+
async function curveQuote(client, mint, tokens, options = {}) {
|
|
2672
|
+
return (await resolveVenue(client, {
|
|
2673
|
+
mint,
|
|
2674
|
+
user: options.user ?? mint,
|
|
2675
|
+
...options.venue ? { venue: options.venue } : {},
|
|
2676
|
+
...options.quote ? { quote: options.quote } : {}
|
|
2677
|
+
})).quoteBuy(tokens);
|
|
2678
|
+
}
|
|
2679
|
+
/** Quote token a sale of `tokens` returns right now, net of the venue's fees. */
|
|
2680
|
+
async function sellQuote(client, mint, tokens, options = {}) {
|
|
2681
|
+
return (await resolveVenue(client, {
|
|
2682
|
+
mint,
|
|
2683
|
+
user: options.user ?? mint,
|
|
2684
|
+
...options.venue ? { venue: options.venue } : {},
|
|
2685
|
+
...options.quote ? { quote: options.quote } : {}
|
|
2686
|
+
})).quoteSell(tokens);
|
|
2687
|
+
}
|
|
2688
|
+
/**
|
|
2689
|
+
* Quote token an exact-coins-out buy of `tokens` would cost on a curve that does not exist yet,
|
|
2690
|
+
* fees included.
|
|
2691
|
+
*
|
|
2692
|
+
* This is what the seed costs. `initialize_pool` buys it in the same transaction that creates the
|
|
2693
|
+
* coin, so the curve is a brand-new LaunchLab curve with the pinned Gabox launch shape at that
|
|
2694
|
+
* moment. Exact, unless Raydium changes a fee rate between this read and the send.
|
|
2695
|
+
*/
|
|
2696
|
+
async function newCurveBuyCost(client, tokens, launch) {
|
|
2697
|
+
if (tokens <= 0n) return 0n;
|
|
2698
|
+
const ids = raydiumIds(client.cluster);
|
|
2699
|
+
const settings = await fetchCurveSettings(client, launch.quoteConfig, ids);
|
|
2700
|
+
return curveBuyExactOut(newCurveReserves(launch.raise, settings.migrateFee), settings.rates, tokens);
|
|
2701
|
+
}
|
|
2702
|
+
/**
|
|
2703
|
+
* The starting reserves of a brand-new Gabox curve.
|
|
2704
|
+
*
|
|
2705
|
+
* `raise` is `total_quote_fund_raising`, in the quote's own base units. It is the one launch number
|
|
2706
|
+
* the client picks, so the reserves follow it.
|
|
2707
|
+
*/
|
|
2708
|
+
function newCurveReserves(raise, migrateFee) {
|
|
2709
|
+
return initialCurve({
|
|
2710
|
+
supply: LAUNCH_SUPPLY,
|
|
2711
|
+
totalBaseSell: LAUNCH_TOTAL_BASE_SELL,
|
|
2712
|
+
quoteRaise: raise,
|
|
2713
|
+
migrateFee
|
|
2714
|
+
});
|
|
2715
|
+
}
|
|
2716
|
+
//#endregion
|
|
2717
|
+
//#region src/tx/message.ts
|
|
2718
|
+
/**
|
|
2719
|
+
* Assembling a transaction message.
|
|
2720
|
+
*
|
|
2721
|
+
* Every builder in this directory ends here: compute budget first, then the program instructions,
|
|
2722
|
+
* with a fee payer and a blockhash lifetime. The result is a message a wallet can sign and send —
|
|
2723
|
+
* nothing in this SDK signs or sends anything itself.
|
|
2724
|
+
*/
|
|
2725
|
+
/**
|
|
2726
|
+
* Build the message. One RPC read, for the blockhash.
|
|
2727
|
+
*
|
|
2728
|
+
* The blockhash expires in about a minute, so build the message when the user is ready to sign
|
|
2729
|
+
* rather than when the page loads.
|
|
2730
|
+
*/
|
|
2731
|
+
async function buildMessage(client, feePayer, instructions, options) {
|
|
2732
|
+
const { value: latestBlockhash } = await client.rpc.getLatestBlockhash({ commitment: "confirmed" }).send();
|
|
2733
|
+
const budget = computeBudgetInstructions(options.computeUnitLimit, options.computeUnitPrice);
|
|
2734
|
+
const message = pipe(createTransactionMessage({ version: 0 }), (m) => setTransactionMessageFeePayerSigner(feePayer, m), (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), (m) => appendTransactionMessageInstructions([...budget, ...instructions], m), (m) => compressTransactionMessageUsingAddressLookupTables(m, options.addressLookupTables ?? client.addressLookupTables));
|
|
2735
|
+
const size = getTransactionEncoder().encode(compileTransaction(message)).length;
|
|
2736
|
+
if (size > 1232) throw new Error(`Transaction is ${size} bytes; Solana allows 1232. Shorten metadata or supply additional address lookup tables.`);
|
|
2737
|
+
return message;
|
|
2738
|
+
}
|
|
2739
|
+
/** Append `remainingAccounts` to a generated instruction, which is how a venue's list is passed. */
|
|
2740
|
+
function withRemainingAccounts(instruction, remaining) {
|
|
2741
|
+
return {
|
|
2742
|
+
...instruction,
|
|
2743
|
+
accounts: [...instruction.accounts ?? [], ...remaining]
|
|
2744
|
+
};
|
|
2745
|
+
}
|
|
2746
|
+
//#endregion
|
|
2747
|
+
//#region src/raydium/launch.ts
|
|
2748
|
+
/**
|
|
2749
|
+
* LaunchLab's `initialize_v2`, built by hand.
|
|
2750
|
+
*
|
|
2751
|
+
* Put it **before** `initialize_pool` in the transaction. `venue/launch.rs` reads the Instructions
|
|
2752
|
+
* sysvar and looks for this instruction by discriminator, then checks all 18 of its accounts and
|
|
2753
|
+
* every one of its arguments. The order matters for a second reason too: the mint account has to
|
|
2754
|
+
* exist and deserialize before Anchor validates the Gabox accounts.
|
|
2755
|
+
*
|
|
2756
|
+
* # The launch shape is pinned
|
|
2757
|
+
*
|
|
2758
|
+
* A creator picks the name, the symbol and the metadata URI. Nothing else. Every other argument is
|
|
2759
|
+
* fixed, and the program refuses any other value:
|
|
2760
|
+
*
|
|
2761
|
+
* | Argument | Value |
|
|
2762
|
+
* | --- | --- |
|
|
2763
|
+
* | `decimals` | 6 (the coin; the quote keeps its own) |
|
|
2764
|
+
* | curve | `Constant` |
|
|
2765
|
+
* | `supply` | 1,000,000,000 coins |
|
|
2766
|
+
* | `total_base_sell` | 793,100,000 coins |
|
|
2767
|
+
* | `total_quote_fund_raising` | the caller's `raise`, pinned to 85 SOL on mainnet and 3 SOL on devnet for a WSOL pool |
|
|
2768
|
+
* | `migrate_type` | 1, which means graduate into a CPMM pool |
|
|
2769
|
+
* | `VestingParams` | all three fields 0 |
|
|
2770
|
+
* | `AmmCreatorFeeOn` | `QuoteToken` |
|
|
2771
|
+
*
|
|
2772
|
+
* The instruction data must end exactly after the last argument. Trailing bytes are refused.
|
|
2773
|
+
*
|
|
2774
|
+
* # The argument layout
|
|
2775
|
+
*
|
|
2776
|
+
* Borsh, in IDL order: `MintParams { decimals, name, symbol, uri }`, then the `CurveParams` enum
|
|
2777
|
+
* tag and its `ConstantCurve` fields, then `VestingParams`, then the `AmmCreatorFeeOn` enum tag.
|
|
2778
|
+
* An Anchor enum tag is one byte.
|
|
2779
|
+
*/
|
|
2780
|
+
const borshString = () => addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder());
|
|
2781
|
+
const ARGS_ENCODER = getStructEncoder([
|
|
2782
|
+
["decimals", getU8Encoder()],
|
|
2783
|
+
["name", borshString()],
|
|
2784
|
+
["symbol", borshString()],
|
|
2785
|
+
["uri", borshString()],
|
|
2786
|
+
["curveTag", getU8Encoder()],
|
|
2787
|
+
["supply", getU64Encoder()],
|
|
2788
|
+
["totalBaseSell", getU64Encoder()],
|
|
2789
|
+
["totalQuoteFundRaising", getU64Encoder()],
|
|
2790
|
+
["migrateType", getU8Encoder()],
|
|
2791
|
+
["totalLockedAmount", getU64Encoder()],
|
|
2792
|
+
["cliffPeriod", getU64Encoder()],
|
|
2793
|
+
["unlockPeriod", getU64Encoder()],
|
|
2794
|
+
["ammFeeOn", getU8Encoder()]
|
|
2795
|
+
]);
|
|
2796
|
+
/** Metaplex refuses a longer string, so refuse it here rather than on chain. */
|
|
2797
|
+
function bounded(value, limit, what) {
|
|
2798
|
+
const bytes = new TextEncoder().encode(value).length;
|
|
2799
|
+
if (bytes === 0) throw new Error(`${what} must not be empty`);
|
|
2800
|
+
if (bytes > limit) throw new Error(`${what} is ${bytes} UTF-8 bytes; Metaplex allows at most ${limit}`);
|
|
2801
|
+
return value;
|
|
2802
|
+
}
|
|
2803
|
+
/**
|
|
2804
|
+
* Build `initialize_v2` for one cluster's LaunchLab. `ids` is `raydiumIds(client.cluster)`.
|
|
2805
|
+
*
|
|
2806
|
+
* Two slots sign: the mint keypair, which LaunchLab takes as a signer rather than deriving, and the
|
|
2807
|
+
* creator, who pays for everything.
|
|
2808
|
+
*/
|
|
2809
|
+
async function getLaunchInstruction(input, ids) {
|
|
2810
|
+
const { mint, creator } = input;
|
|
2811
|
+
const name = bounded(input.name, METADATA_LIMITS.name, "name");
|
|
2812
|
+
const symbol = bounded(input.symbol, METADATA_LIMITS.symbol, "symbol");
|
|
2813
|
+
const uri = bounded(input.uri, METADATA_LIMITS.uri, "uri");
|
|
2814
|
+
const { quoteMint, quoteTokenProgram } = input;
|
|
2815
|
+
const poolState = await launchlabPoolAddress(ids.launchlab, mint.address, quoteMint);
|
|
2816
|
+
const metas = order(LAUNCHLAB_INITIALIZE, {
|
|
2817
|
+
payer: creator.address,
|
|
2818
|
+
creator: creator.address,
|
|
2819
|
+
global_config: input.quoteConfig,
|
|
2820
|
+
platform_config: ids.gaboxPlatform,
|
|
2821
|
+
authority: ids.launchlabAuthority,
|
|
2822
|
+
pool_state: poolState,
|
|
2823
|
+
base_mint: mint.address,
|
|
2824
|
+
quote_mint: quoteMint,
|
|
2825
|
+
base_vault: await launchlabVaultAddress(ids.launchlab, poolState, mint.address),
|
|
2826
|
+
quote_vault: await launchlabVaultAddress(ids.launchlab, poolState, quoteMint),
|
|
2827
|
+
metadata_account: await metadataAddress(mint.address),
|
|
2828
|
+
base_token_program: TOKEN_PROGRAM_ADDRESS,
|
|
2829
|
+
quote_token_program: quoteTokenProgram,
|
|
2830
|
+
metadata_program: METAPLEX_PROGRAM_ADDRESS,
|
|
2831
|
+
system_program: SYSTEM_PROGRAM_ADDRESS,
|
|
2832
|
+
rent_program: RENT_SYSVAR_ADDRESS,
|
|
2833
|
+
event_authority: ids.launchlabEventAuthority,
|
|
2834
|
+
program: ids.launchlab
|
|
2835
|
+
});
|
|
2836
|
+
const signerFor = /* @__PURE__ */ new Map([[mint.address, mint], [creator.address, creator]]);
|
|
2837
|
+
const accounts = metas.map((meta) => {
|
|
2838
|
+
const signs = meta.role === 2 || meta.role === 3;
|
|
2839
|
+
const signer = signerFor.get(meta.address);
|
|
2840
|
+
return signs && signer ? {
|
|
2841
|
+
...meta,
|
|
2842
|
+
signer
|
|
2843
|
+
} : meta;
|
|
2844
|
+
});
|
|
2845
|
+
const data = new Uint8Array([...LAUNCHLAB_INITIALIZE.discriminator, ...ARGS_ENCODER.encode({
|
|
2846
|
+
decimals: 6,
|
|
2847
|
+
name,
|
|
2848
|
+
symbol,
|
|
2849
|
+
uri,
|
|
2850
|
+
curveTag: 0,
|
|
2851
|
+
supply: LAUNCH_SUPPLY,
|
|
2852
|
+
totalBaseSell: LAUNCH_TOTAL_BASE_SELL,
|
|
2853
|
+
totalQuoteFundRaising: input.raise,
|
|
2854
|
+
migrateType: 1,
|
|
2855
|
+
totalLockedAmount: 0n,
|
|
2856
|
+
cliffPeriod: 0n,
|
|
2857
|
+
unlockPeriod: 0n,
|
|
2858
|
+
ammFeeOn: 0
|
|
2859
|
+
})]);
|
|
2860
|
+
return {
|
|
2861
|
+
programAddress: ids.launchlab,
|
|
2862
|
+
accounts,
|
|
2863
|
+
data
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2866
|
+
//#endregion
|
|
2867
|
+
//#region src/raydium/claim.ts
|
|
2868
|
+
/**
|
|
2869
|
+
* Collecting the creator's share of trading.
|
|
2870
|
+
*
|
|
2871
|
+
* Gabox charges no fee at all. The creator's income is the share Raydium pays out of its own
|
|
2872
|
+
* revenue, and it sits in two different places depending on where the coin trades:
|
|
2873
|
+
*
|
|
2874
|
+
* - **On the curve.** LaunchLab pays 0.5% of every trade into `[creator, quote_mint]` under
|
|
2875
|
+
* LaunchLab. One vault per wallet **per quote asset**, not one per coin, so a single
|
|
2876
|
+
* `claim_creator_fee` sweeps every coin that creator launched against that quote. A creator
|
|
2877
|
+
* with a SOL machine and a USDC machine claims twice, once per quote.
|
|
2878
|
+
* - **After graduation.** The CPMM pool keeps the creator's fee inside the pool account, and
|
|
2879
|
+
* `collect_creator_fee` pays it out. That is per pool, so it takes one call per coin.
|
|
2880
|
+
*
|
|
2881
|
+
* Both instructions pay into the creator's associated token account for the quote mint, under that
|
|
2882
|
+
* mint's own token program. When the quote is WSOL, every builder here closes that account
|
|
2883
|
+
* afterwards, so the creator ends up with plain SOL. For any other quote the account is created if
|
|
2884
|
+
* missing and left alone: closing it would throw away a real balance.
|
|
2885
|
+
*
|
|
2886
|
+
* **Closing unwraps everything.** If the creator already held WSOL in that account before the
|
|
2887
|
+
* claim, closing turns that into SOL too. Nothing is lost, but the balance moves.
|
|
2888
|
+
*/
|
|
2889
|
+
/**
|
|
2890
|
+
* Create the creator's quote account, if it is missing, and close it again when it is WSOL.
|
|
2891
|
+
*
|
|
2892
|
+
* Raydium's own instructions create the account when they need to, but an idempotent create here
|
|
2893
|
+
* costs nothing and makes the flow the same whichever instruction runs. The close is what turns
|
|
2894
|
+
* WSOL into SOL, so it only belongs on a WSOL claim: closing a USDC account would throw the payout
|
|
2895
|
+
* away.
|
|
2896
|
+
*/
|
|
2897
|
+
async function wrapAround(creator, quote, middle) {
|
|
2898
|
+
const account = await ata(creator.address, quote.mint, quote.tokenProgram);
|
|
2899
|
+
const create = getCreateAssociatedTokenIdempotentInstruction({
|
|
2900
|
+
payer: creator,
|
|
2901
|
+
ata: account,
|
|
2902
|
+
owner: creator.address,
|
|
2903
|
+
mint: quote.mint,
|
|
2904
|
+
tokenProgram: quote.tokenProgram
|
|
2905
|
+
});
|
|
2906
|
+
if (quote.mint !== "So11111111111111111111111111111111111111112") return [create, ...middle];
|
|
2907
|
+
return [
|
|
2908
|
+
create,
|
|
2909
|
+
...middle,
|
|
2910
|
+
getCloseAccountInstruction({
|
|
2911
|
+
account,
|
|
2912
|
+
destination: creator.address,
|
|
2913
|
+
owner: creator
|
|
2914
|
+
})
|
|
2915
|
+
];
|
|
2916
|
+
}
|
|
2917
|
+
/** Attach a signer to the one slot the ABI marks as signing. Roles never change here. */
|
|
2918
|
+
function withSigner(accounts, signer) {
|
|
2919
|
+
return accounts.map((meta) => (meta.role === AccountRole.READONLY_SIGNER || meta.role === AccountRole.WRITABLE_SIGNER) && meta.address === signer.address ? {
|
|
2920
|
+
...meta,
|
|
2921
|
+
signer
|
|
2922
|
+
} : meta);
|
|
2923
|
+
}
|
|
2924
|
+
/**
|
|
2925
|
+
* The instructions of a curve fee claim, for one quote asset: create the quote account, claim, and
|
|
2926
|
+
* close it again when the quote is WSOL.
|
|
2927
|
+
*
|
|
2928
|
+
* This sweeps **every** coin this wallet launched against that quote, because LaunchLab keeps one
|
|
2929
|
+
* vault per wallet per quote asset. There is no per-coin version, and a creator with machines in
|
|
2930
|
+
* two quote assets claims once per asset.
|
|
2931
|
+
*/
|
|
2932
|
+
async function getClaimCreatorFeeInstructions(creator, ids, quote) {
|
|
2933
|
+
return await wrapAround(creator, quote, [{
|
|
2934
|
+
programAddress: ids.launchlab,
|
|
2935
|
+
accounts: withSigner(order(LAUNCHLAB_CLAIM_CREATOR_FEE, {
|
|
2936
|
+
creator: creator.address,
|
|
2937
|
+
fee_vault_authority: await creatorFeeVaultAuthority(ids.launchlab),
|
|
2938
|
+
creator_fee_vault: await creatorFeeVaultAddress(ids.launchlab, creator.address, quote.mint),
|
|
2939
|
+
recipient_token_account: await ata(creator.address, quote.mint, quote.tokenProgram),
|
|
2940
|
+
quote_mint: quote.mint,
|
|
2941
|
+
token_program: quote.tokenProgram,
|
|
2942
|
+
system_program: SYSTEM_PROGRAM_ADDRESS,
|
|
2943
|
+
associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ADDRESS
|
|
2944
|
+
}), creator),
|
|
2945
|
+
data: new Uint8Array(LAUNCHLAB_CLAIM_CREATOR_FEE.discriminator)
|
|
2946
|
+
}]);
|
|
2947
|
+
}
|
|
2948
|
+
/**
|
|
2949
|
+
* Claim the curve creator fee for one quote asset.
|
|
2950
|
+
*
|
|
2951
|
+
* A WSOL claim arrives as SOL, because the builder closes the WSOL account. Any other quote arrives
|
|
2952
|
+
* as that token, in the creator's own account.
|
|
2953
|
+
*/
|
|
2954
|
+
async function claimCreatorFee(client, input) {
|
|
2955
|
+
const ids = raydiumIds(client.cluster);
|
|
2956
|
+
const quote = await quoteOf(client, input.quoteMint ?? "So11111111111111111111111111111111111111112");
|
|
2957
|
+
const instructions = await getClaimCreatorFeeInstructions(input.creator, ids, quote);
|
|
2958
|
+
return await buildMessage(client, input.creator, instructions, {
|
|
2959
|
+
addressLookupTables: input.addressLookupTables,
|
|
2960
|
+
computeUnitLimit: input.computeUnitLimit ?? 55e3,
|
|
2961
|
+
...input.computeUnitPrice === void 0 ? {} : { computeUnitPrice: input.computeUnitPrice }
|
|
2962
|
+
});
|
|
2963
|
+
}
|
|
2964
|
+
/**
|
|
2965
|
+
* The token program a quote mint lives under. WSOL is always classic SPL Token, so that one needs
|
|
2966
|
+
* no read at all.
|
|
2967
|
+
*/
|
|
2968
|
+
async function quoteOf(client, mint) {
|
|
2969
|
+
if (mint === "So11111111111111111111111111111111111111112") return {
|
|
2970
|
+
mint,
|
|
2971
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS
|
|
2972
|
+
};
|
|
2973
|
+
return {
|
|
2974
|
+
mint,
|
|
2975
|
+
tokenProgram: (await fetchQuoteDisplay(client, mint)).tokenProgram
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* The instructions of a graduated coin's fee collection: create the quote account, collect, and
|
|
2980
|
+
* close it again when the quote is WSOL.
|
|
2981
|
+
*
|
|
2982
|
+
* This is per coin. `collect_creator_fee` pays out both sides of the pair, so anything owed in the
|
|
2983
|
+
* coin itself lands in the creator's coin account and stays there.
|
|
2984
|
+
*
|
|
2985
|
+
* The coin's quote asset comes from its Gabox pool, so a caller never has to name it.
|
|
2986
|
+
*/
|
|
2987
|
+
async function getCollectCreatorFeeInstructions(client, input, ids = raydiumIds(client.cluster)) {
|
|
2988
|
+
const { mint, creator } = input;
|
|
2989
|
+
const quote = input.quote ?? await fetchQuoteBinding(client, mint);
|
|
2990
|
+
const settings = await fetchCurveSettings(client, quote.config, ids);
|
|
2991
|
+
const { address: poolState, pool } = await findCpmmPool(client, {
|
|
2992
|
+
mint,
|
|
2993
|
+
quoteMint: quote.mint,
|
|
2994
|
+
creator: creator.address,
|
|
2995
|
+
cpswapConfig: settings.cpswapConfig
|
|
2996
|
+
});
|
|
2997
|
+
const collect = {
|
|
2998
|
+
programAddress: ids.cpmm,
|
|
2999
|
+
accounts: withSigner(order(CPMM_COLLECT_CREATOR_FEE, {
|
|
3000
|
+
creator: creator.address,
|
|
3001
|
+
authority: ids.cpmmAuthority,
|
|
3002
|
+
pool_state: poolState,
|
|
3003
|
+
amm_config: pool.ammConfig,
|
|
3004
|
+
token_0_vault: pool.token0Vault,
|
|
3005
|
+
token_1_vault: pool.token1Vault,
|
|
3006
|
+
vault_0_mint: pool.token0Mint,
|
|
3007
|
+
vault_1_mint: pool.token1Mint,
|
|
3008
|
+
creator_token_0: await ata(creator.address, pool.token0Mint, pool.token0Program),
|
|
3009
|
+
creator_token_1: await ata(creator.address, pool.token1Mint, pool.token1Program),
|
|
3010
|
+
token_0_program: pool.token0Program,
|
|
3011
|
+
token_1_program: pool.token1Program,
|
|
3012
|
+
associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
|
|
3013
|
+
system_program: SYSTEM_PROGRAM_ADDRESS,
|
|
3014
|
+
creator_fee_share: await cpmmCreatorFeeShare(ids.cpmm, creator.address, pool.ammConfig)
|
|
3015
|
+
}), creator),
|
|
3016
|
+
data: new Uint8Array(CPMM_COLLECT_CREATOR_FEE.discriminator)
|
|
3017
|
+
};
|
|
3018
|
+
return await wrapAround(creator, {
|
|
3019
|
+
mint: quote.mint,
|
|
3020
|
+
tokenProgram: quote.tokenProgram
|
|
3021
|
+
}, [collect]);
|
|
3022
|
+
}
|
|
3023
|
+
/**
|
|
3024
|
+
* Collect one graduated coin's CPMM creator fee.
|
|
3025
|
+
*
|
|
3026
|
+
* A WSOL-quoted coin pays the quote side out as SOL, because the builder closes the WSOL account.
|
|
3027
|
+
* Any other quote arrives as that token.
|
|
3028
|
+
*/
|
|
3029
|
+
async function collectCreatorFee(client, input) {
|
|
3030
|
+
const instructions = await getCollectCreatorFeeInstructions(client, {
|
|
3031
|
+
mint: input.mint,
|
|
3032
|
+
creator: input.creator,
|
|
3033
|
+
...input.quote ? { quote: input.quote } : {}
|
|
3034
|
+
});
|
|
3035
|
+
return await buildMessage(client, input.creator, instructions, {
|
|
3036
|
+
addressLookupTables: input.addressLookupTables,
|
|
3037
|
+
computeUnitLimit: input.computeUnitLimit ?? 55e3,
|
|
3038
|
+
...input.computeUnitPrice === void 0 ? {} : { computeUnitPrice: input.computeUnitPrice }
|
|
3039
|
+
});
|
|
3040
|
+
}
|
|
3041
|
+
/**
|
|
3042
|
+
* Read both places a creator's fees can sit, for one quote asset.
|
|
3043
|
+
*
|
|
3044
|
+
* `quoteMint` defaults to wrapped SOL. LaunchLab keeps one vault per wallet per quote, so a creator
|
|
3045
|
+
* with machines in two quote assets reads this twice.
|
|
3046
|
+
*
|
|
3047
|
+
* `mint` is optional. Without it only the curve vault is read, which is the one number that covers
|
|
3048
|
+
* every coin of that quote at once. With it the coin's CPMM pool is read too, and a coin that has
|
|
3049
|
+
* not graduated reports zeros rather than failing.
|
|
3050
|
+
*/
|
|
3051
|
+
async function fetchCreatorFees(client, input) {
|
|
3052
|
+
const ids = raydiumIds(client.cluster);
|
|
3053
|
+
const quoteMint = input.quoteMint ?? "So11111111111111111111111111111111111111112";
|
|
3054
|
+
const vault = await creatorFeeVaultAddress(ids.launchlab, input.creator, quoteMint);
|
|
3055
|
+
const [vaultAccount] = await readAccounts(client.rpc, [vault]);
|
|
3056
|
+
const curveQuote = vaultAccount ? tokenAccountAmount(vaultAccount.data) : 0n;
|
|
3057
|
+
if (!input.mint) return {
|
|
3058
|
+
quoteMint,
|
|
3059
|
+
curveQuote,
|
|
3060
|
+
cpmmQuote: 0n,
|
|
3061
|
+
cpmmTokens: 0n
|
|
3062
|
+
};
|
|
3063
|
+
const settings = await fetchCurveSettings(client, await launchlabGlobalConfig(ids.launchlab, quoteMint), ids);
|
|
3064
|
+
try {
|
|
3065
|
+
const { pool } = await findCpmmPool(client, {
|
|
3066
|
+
mint: input.mint,
|
|
3067
|
+
quoteMint,
|
|
3068
|
+
creator: input.creator,
|
|
3069
|
+
cpswapConfig: settings.cpswapConfig
|
|
3070
|
+
});
|
|
3071
|
+
const [token0] = sortedMints(input.mint, quoteMint);
|
|
3072
|
+
const baseIsToken0 = token0 === input.mint;
|
|
3073
|
+
return {
|
|
3074
|
+
quoteMint,
|
|
3075
|
+
curveQuote,
|
|
3076
|
+
cpmmQuote: baseIsToken0 ? pool.creatorFeesToken1 : pool.creatorFeesToken0,
|
|
3077
|
+
cpmmTokens: baseIsToken0 ? pool.creatorFeesToken0 : pool.creatorFeesToken1
|
|
3078
|
+
};
|
|
3079
|
+
} catch {
|
|
3080
|
+
return {
|
|
3081
|
+
quoteMint,
|
|
3082
|
+
curveQuote,
|
|
3083
|
+
cpmmQuote: 0n,
|
|
3084
|
+
cpmmTokens: 0n
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
//#endregion
|
|
3089
|
+
//#region src/raydium/trade.ts
|
|
3090
|
+
/**
|
|
3091
|
+
* A venue trade a wallet sends on its own, outside any Gabox instruction.
|
|
3092
|
+
*
|
|
3093
|
+
* Pack buys and token sales go through `buy_pack` and `sell_tokens`: they take the venue's account
|
|
3094
|
+
* list as `remainingAccounts` and the Gabox program does the call. This file is for the one trade
|
|
3095
|
+
* Gabox never makes, a plain `buy_exact_in` on the curve. Anyone may trade a Gabox coin directly,
|
|
3096
|
+
* and the devnet end-to-end test uses this to buy the rest of the curve and force graduation.
|
|
3097
|
+
*
|
|
3098
|
+
* The account list and the discriminator both come from `abi.ts`, which codegen took from
|
|
3099
|
+
* Raydium's IDL. Nothing here retypes either.
|
|
3100
|
+
*/
|
|
3101
|
+
const u64 = getU64Encoder();
|
|
3102
|
+
/**
|
|
3103
|
+
* `buy_exact_in` on the curve: spend exactly `quoteIn` of the quote token and take whatever coins
|
|
3104
|
+
* it buys.
|
|
3105
|
+
*
|
|
3106
|
+
* LaunchLab caps the trade at what the curve has left to sell, so a spend larger than the rest of
|
|
3107
|
+
* the raise still succeeds and graduates the coin. Fund the user's quote ATA first. On a WSOL pool
|
|
3108
|
+
* that means wrapping the SOL and closing the account afterwards, the same way the Gabox builders
|
|
3109
|
+
* do.
|
|
3110
|
+
*/
|
|
3111
|
+
async function getCurveBuyExactInInstruction(client, input, ids = raydiumIds(client.cluster)) {
|
|
3112
|
+
if (input.quoteIn <= 0n) throw new Error("quoteIn must be positive");
|
|
3113
|
+
if (input.minTokensOut < 0n) throw new Error("minTokensOut must not be negative");
|
|
3114
|
+
const quoteMint = input.quoteMint ?? "So11111111111111111111111111111111111111112";
|
|
3115
|
+
const quoteTokenProgram = input.quoteTokenProgram ?? "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
3116
|
+
const poolState = await launchlabPoolAddress(ids.launchlab, input.mint, quoteMint);
|
|
3117
|
+
const accounts = launchlabBuyExactInAccounts({
|
|
3118
|
+
launchlab: ids.launchlab,
|
|
3119
|
+
launchlabAuthority: ids.launchlabAuthority,
|
|
3120
|
+
launchlabEventAuthority: ids.launchlabEventAuthority,
|
|
3121
|
+
globalConfig: await launchlabGlobalConfig(ids.launchlab, quoteMint),
|
|
3122
|
+
platformConfig: ids.gaboxPlatform,
|
|
3123
|
+
poolState,
|
|
3124
|
+
mint: input.mint,
|
|
3125
|
+
quoteMint,
|
|
3126
|
+
baseVault: await launchlabVaultAddress(ids.launchlab, poolState, input.mint),
|
|
3127
|
+
quoteVault: await launchlabVaultAddress(ids.launchlab, poolState, quoteMint),
|
|
3128
|
+
user: input.user.address,
|
|
3129
|
+
userBaseToken: await ata(input.user.address, input.mint),
|
|
3130
|
+
userQuoteToken: await ata(input.user.address, quoteMint, quoteTokenProgram),
|
|
3131
|
+
quoteTokenProgram,
|
|
3132
|
+
platformFeeVault: await platformFeeVaultAddress(ids.launchlab, ids.gaboxPlatform, quoteMint),
|
|
3133
|
+
creatorFeeVault: await creatorFeeVaultAddress(ids.launchlab, input.creator, quoteMint)
|
|
3134
|
+
});
|
|
3135
|
+
return {
|
|
3136
|
+
programAddress: ids.launchlab,
|
|
3137
|
+
accounts: attachSigner(accounts, input.user),
|
|
3138
|
+
data: new Uint8Array([
|
|
3139
|
+
...LAUNCHLAB_BUY_EXACT_IN.discriminator,
|
|
3140
|
+
...u64.encode(input.quoteIn),
|
|
3141
|
+
...u64.encode(input.minTokensOut),
|
|
3142
|
+
...u64.encode(SHARE_FEE_RATE)
|
|
3143
|
+
])
|
|
3144
|
+
};
|
|
3145
|
+
}
|
|
3146
|
+
/** Give the signer to the slot the ABI already marks as signing. Roles never change here. */
|
|
3147
|
+
function attachSigner(accounts, signer) {
|
|
3148
|
+
let matched = false;
|
|
3149
|
+
const withSigner = accounts.map((meta) => {
|
|
3150
|
+
if (!(meta.role === 2 || meta.role === 3) || meta.address !== signer.address) return meta;
|
|
3151
|
+
matched = true;
|
|
3152
|
+
return {
|
|
3153
|
+
...meta,
|
|
3154
|
+
signer
|
|
3155
|
+
};
|
|
3156
|
+
});
|
|
3157
|
+
if (!matched) throw new Error(`${signer.address} is not the signing account of this trade. Build the account list with the same wallet you are going to sign with.`);
|
|
3158
|
+
return withSigner;
|
|
3159
|
+
}
|
|
3160
|
+
//#endregion
|
|
3161
|
+
//#region src/raydium/index.ts
|
|
3162
|
+
var raydium_exports = /* @__PURE__ */ __exportAll({
|
|
3163
|
+
ASSOCIATED_TOKEN_PROGRAM_ADDRESS: () => ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
|
|
3164
|
+
CONSTANT_CURVE_TAG: () => 0,
|
|
3165
|
+
CPMM_AMM_CONFIG_DISCRIMINATOR: () => CPMM_AMM_CONFIG_DISCRIMINATOR,
|
|
3166
|
+
CPMM_AMM_CONFIG_HEAD_SIZE: () => 116,
|
|
3167
|
+
CPMM_COLLECT_CREATOR_FEE: () => CPMM_COLLECT_CREATOR_FEE,
|
|
3168
|
+
CPMM_POOL_OFFSETS: () => CPMM_POOL_OFFSETS,
|
|
3169
|
+
CPMM_POOL_STATE_DISCRIMINATOR: () => CPMM_POOL_STATE_DISCRIMINATOR,
|
|
3170
|
+
CPMM_POOL_STATE_HEAD_SIZE: () => 413,
|
|
3171
|
+
CPMM_POOL_STATE_SIZE: () => 637,
|
|
3172
|
+
CPMM_SEEDS: () => CPMM_SEEDS,
|
|
3173
|
+
CPMM_SWAP_BASE_INPUT: () => CPMM_SWAP_BASE_INPUT,
|
|
3174
|
+
CPMM_SWAP_BASE_OUTPUT: () => CPMM_SWAP_BASE_OUTPUT,
|
|
3175
|
+
CREATOR_FEE_ON_QUOTE: () => 0,
|
|
3176
|
+
LAUNCHLAB_BUY_EXACT_IN: () => LAUNCHLAB_BUY_EXACT_IN,
|
|
3177
|
+
LAUNCHLAB_BUY_EXACT_OUT: () => LAUNCHLAB_BUY_EXACT_OUT,
|
|
3178
|
+
LAUNCHLAB_CLAIM_CREATOR_FEE: () => LAUNCHLAB_CLAIM_CREATOR_FEE,
|
|
3179
|
+
LAUNCHLAB_GLOBAL_CONFIG_DISCRIMINATOR: () => LAUNCHLAB_GLOBAL_CONFIG_DISCRIMINATOR,
|
|
3180
|
+
LAUNCHLAB_GLOBAL_CONFIG_HEAD_SIZE: () => 115,
|
|
3181
|
+
LAUNCHLAB_INITIALIZE: () => LAUNCHLAB_INITIALIZE,
|
|
3182
|
+
LAUNCHLAB_PLATFORM_CONFIG_DISCRIMINATOR: () => LAUNCHLAB_PLATFORM_CONFIG_DISCRIMINATOR,
|
|
3183
|
+
LAUNCHLAB_PLATFORM_CONFIG_HEAD_SIZE: () => 940,
|
|
3184
|
+
LAUNCHLAB_POOL_STATE_DISCRIMINATOR: () => LAUNCHLAB_POOL_STATE_DISCRIMINATOR,
|
|
3185
|
+
LAUNCHLAB_POOL_STATE_HEAD_SIZE: () => 367,
|
|
3186
|
+
LAUNCHLAB_SEEDS: () => LAUNCHLAB_SEEDS,
|
|
3187
|
+
LAUNCHLAB_SELL_EXACT_IN: () => LAUNCHLAB_SELL_EXACT_IN,
|
|
3188
|
+
LAUNCHLAB_STATUS_FUND: () => 0,
|
|
3189
|
+
LAUNCHLAB_STATUS_MIGRATE: () => 1,
|
|
3190
|
+
LAUNCHLAB_STATUS_TRADE: () => 2,
|
|
3191
|
+
LAUNCH_DECIMALS: () => 6,
|
|
3192
|
+
LAUNCH_SUPPLY: () => LAUNCH_SUPPLY,
|
|
3193
|
+
LAUNCH_TOTAL_BASE_SELL: () => LAUNCH_TOTAL_BASE_SELL,
|
|
3194
|
+
METADATA_LIMITS: () => METADATA_LIMITS,
|
|
3195
|
+
METADATA_SEED: () => METADATA_SEED,
|
|
3196
|
+
METAPLEX_PROGRAM_ADDRESS: () => METAPLEX_PROGRAM_ADDRESS,
|
|
3197
|
+
MIGRATE_TO_CPMM: () => 1,
|
|
3198
|
+
PLATFORM_ADMIN: () => PLATFORM_ADMIN,
|
|
3199
|
+
RATE_DENOMINATOR: () => RATE_DENOMINATOR,
|
|
3200
|
+
RAYDIUM_DEVNET_IDS: () => RAYDIUM_DEVNET_IDS,
|
|
3201
|
+
RAYDIUM_IDS: () => RAYDIUM_IDS,
|
|
3202
|
+
RAYDIUM_MAINNET_IDS: () => RAYDIUM_MAINNET_IDS,
|
|
3203
|
+
RENT_SYSVAR_ADDRESS: () => RENT_SYSVAR_ADDRESS,
|
|
3204
|
+
SHARE_FEE_RATE: () => SHARE_FEE_RATE,
|
|
3205
|
+
SYSTEM_PROGRAM_ADDRESS: () => SYSTEM_PROGRAM_ADDRESS,
|
|
3206
|
+
TOKEN_2022_PROGRAM_ADDRESS: () => TOKEN_2022_PROGRAM_ADDRESS,
|
|
3207
|
+
TOKEN_PROGRAM_ADDRESS: () => TOKEN_PROGRAM_ADDRESS,
|
|
3208
|
+
WSOL_MINT: () => WSOL_MINT,
|
|
3209
|
+
ata: () => ata,
|
|
3210
|
+
ceilDiv: () => ceilDiv,
|
|
3211
|
+
ceilDivRate: () => ceilDivRate,
|
|
3212
|
+
claimCreatorFee: () => claimCreatorFee,
|
|
3213
|
+
collectCreatorFee: () => collectCreatorFee,
|
|
3214
|
+
compareAddresses: () => compareAddresses,
|
|
3215
|
+
cpmmAuthority: () => cpmmAuthority,
|
|
3216
|
+
cpmmBuyAccounts: () => cpmmBuyAccounts,
|
|
3217
|
+
cpmmCreatorFeeShare: () => cpmmCreatorFeeShare,
|
|
3218
|
+
cpmmPoolAddress: () => cpmmPoolAddress,
|
|
3219
|
+
cpmmPoolMatches: () => cpmmPoolMatches,
|
|
3220
|
+
cpmmSellAccounts: () => cpmmSellAccounts,
|
|
3221
|
+
cpmmSwapBaseInput: () => cpmmSwapBaseInput,
|
|
3222
|
+
cpmmSwapBaseOutput: () => cpmmSwapBaseOutput,
|
|
3223
|
+
creatorFeeOnInput: () => creatorFeeOnInput,
|
|
3224
|
+
creatorFeeVaultAddress: () => creatorFeeVaultAddress,
|
|
3225
|
+
creatorFeeVaultAuthority: () => creatorFeeVaultAuthority,
|
|
3226
|
+
curveBuyExactIn: () => curveBuyExactIn,
|
|
3227
|
+
curveBuyExactOut: () => curveBuyExactOut,
|
|
3228
|
+
curveQuote: () => curveQuote,
|
|
3229
|
+
curveSellExactIn: () => curveSellExactIn,
|
|
3230
|
+
decodeBase64: () => decodeBase64,
|
|
3231
|
+
decodeCpmmAmmConfig: () => decodeCpmmAmmConfig,
|
|
3232
|
+
decodeCpmmPool: () => decodeCpmmPool,
|
|
3233
|
+
decodeLaunchlabGlobalConfig: () => decodeLaunchlabGlobalConfig,
|
|
3234
|
+
decodeLaunchlabPlatformConfig: () => decodeLaunchlabPlatformConfig,
|
|
3235
|
+
decodeLaunchlabPool: () => decodeLaunchlabPool,
|
|
3236
|
+
fetchCreatorFees: () => fetchCreatorFees,
|
|
3237
|
+
fetchCurveSettings: () => fetchCurveSettings,
|
|
3238
|
+
fetchQuoteAsset: () => fetchQuoteAsset,
|
|
3239
|
+
fetchQuoteBinding: () => fetchQuoteBinding,
|
|
3240
|
+
fetchQuoteConfig: () => fetchQuoteConfig,
|
|
3241
|
+
fetchQuoteDisplay: () => fetchQuoteDisplay,
|
|
3242
|
+
findCpmmPool: () => findCpmmPool,
|
|
3243
|
+
getClaimCreatorFeeInstructions: () => getClaimCreatorFeeInstructions,
|
|
3244
|
+
getCollectCreatorFeeInstructions: () => getCollectCreatorFeeInstructions,
|
|
3245
|
+
getCurveBuyExactInInstruction: () => getCurveBuyExactInInstruction,
|
|
3246
|
+
getLaunchInstruction: () => getLaunchInstruction,
|
|
3247
|
+
initialCurve: () => initialCurve,
|
|
3248
|
+
isQuoteSupported: () => isQuoteSupported,
|
|
3249
|
+
launchlabAuthority: () => launchlabAuthority,
|
|
3250
|
+
launchlabBuyAccounts: () => launchlabBuyAccounts,
|
|
3251
|
+
launchlabBuyExactInAccounts: () => launchlabBuyExactInAccounts,
|
|
3252
|
+
launchlabEventAuthority: () => launchlabEventAuthority,
|
|
3253
|
+
launchlabGlobalConfig: () => launchlabGlobalConfig,
|
|
3254
|
+
launchlabPlatformConfig: () => launchlabPlatformConfig,
|
|
3255
|
+
launchlabPoolAddress: () => launchlabPoolAddress,
|
|
3256
|
+
launchlabSellAccounts: () => launchlabSellAccounts,
|
|
3257
|
+
launchlabVaultAddress: () => launchlabVaultAddress,
|
|
3258
|
+
metadataAddress: () => metadataAddress,
|
|
3259
|
+
metaplexSymbol: () => metaplexSymbol,
|
|
3260
|
+
mintDecimals: () => mintDecimals,
|
|
3261
|
+
newCurveBuyCost: () => newCurveBuyCost,
|
|
3262
|
+
newCurveReserves: () => newCurveReserves,
|
|
3263
|
+
order: () => order,
|
|
3264
|
+
platformFeeVaultAddress: () => platformFeeVaultAddress,
|
|
3265
|
+
preFeeAmount: () => preFeeAmount,
|
|
3266
|
+
quoteAccountFor: () => quoteAccountFor,
|
|
3267
|
+
raydiumIds: () => raydiumIds,
|
|
3268
|
+
readAccounts: () => readAccounts,
|
|
3269
|
+
remainingBase: () => remainingBase,
|
|
3270
|
+
resolveVenue: () => resolveVenue,
|
|
3271
|
+
sellQuote: () => sellQuote,
|
|
3272
|
+
sortedMints: () => sortedMints,
|
|
3273
|
+
token2022Symbol: () => token2022Symbol,
|
|
3274
|
+
tokenAccountAmount: () => tokenAccountAmount,
|
|
3275
|
+
tokenAccountOwnerAndMint: () => tokenAccountOwnerAndMint,
|
|
3276
|
+
totalCurveFeeRate: () => totalCurveFeeRate,
|
|
3277
|
+
wsolAccountFor: () => wsolAccountFor
|
|
3278
|
+
});
|
|
3279
|
+
//#endregion
|
|
3280
|
+
export { computeBudgetInstructions as $, LAUNCHLAB_BUY_EXACT_OUT as $n, launchlabVaultAddress as $t, initialCurve as A, CPMM_SEEDS as An, metaplexSymbol as At, fetchQuoteAsset as B, RATE_DENOMINATOR as Bn, vaultAddress as Bt, ceilDiv as C, PACK_TOKENS as Cn, LAUNCHLAB_PLATFORM_CONFIG_HEAD_SIZE as Ct, curveBuyExactIn as D, VRF_DEFAULT_QUEUE as Dn, decodeLaunchlabGlobalConfig as Dt, cpmmSwapBaseOutput as E, TIMEOUT_SLOTS as En, decodeCpmmPool as Et, cpmmSellAccounts as F, LAUNCHLAB_STATUS_TRADE as Fn, activityAddress as Ft, readAccounts as G, sortedMints as Gn, cpmmPoolAddress as Gt, fetchQuoteDisplay as H, SHARE_FEE_RATE as Hn, ata as Ht, launchlabBuyAccounts as I, LAUNCH_DECIMALS as In, associatedTokenAddress as It, COMPUTE_BUDGET_PROGRAM_ADDRESS as J, CPMM_COLLECT_CREATOR_FEE as Jn, launchlabAuthority as Jt, BUY_PACK_COMPUTE_UNITS as K, ASSOCIATED_TOKEN_PROGRAM_ADDRESS as Kn, creatorFeeVaultAddress as Kt, launchlabBuyExactInAccounts as L, METADATA_LIMITS as Ln, drawAddress as Lt, remainingBase as M, LAUNCHLAB_SEEDS as Mn, token2022Symbol as Mt, totalCurveFeeRate as N, LAUNCHLAB_STATUS_FUND as Nn, tokenAccountAmount as Nt, curveBuyExactOut as O, VRF_PROGRAM_ADDRESS as On, decodeLaunchlabPlatformConfig as Ot, cpmmBuyAccounts as P, LAUNCHLAB_STATUS_MIGRATE as Pn, tokenAccountOwnerAndMint as Pt, REDEEM_COMPUTE_UNITS as Q, LAUNCHLAB_BUY_EXACT_IN as Qn, launchlabPoolAddress as Qt, launchlabSellAccounts as R, METADATA_SEED as Rn, poolAddress as Rt, wsolAccountFor as S, MAX_ATTEMPTS as Sn, LAUNCHLAB_GLOBAL_CONFIG_HEAD_SIZE as St, cpmmSwapBaseInput as T, SLOT_HASHES_SYSVAR as Tn, decodeCpmmAmmConfig as Tt, isQuoteSupported as U, compareAddresses as Un, cpmmAuthority as Ut, fetchQuoteConfig as V, RAYDIUM_IDS as Vn, vrfIdentityAddress as Vt, decodeBase64 as W, raydiumIds as Wn, cpmmCreatorFeeShare as Wt, DEFAULT_COMPUTE_UNIT_LIMIT as X, CPMM_SWAP_BASE_INPUT as Xn, launchlabGlobalConfig as Xt, CREATE_MACHINE_COMPUTE_UNITS as Y, CPMM_POOL_STATE_DISCRIMINATOR as Yn, launchlabEventAuthority as Yt, MAX_COMPUTE_UNIT_LIMIT as Z, CPMM_SWAP_BASE_OUTPUT as Zn, launchlabPlatformConfig as Zt, newCurveBuyCost as _, validatePack as _n, tiersOf as _t, fetchCreatorFees as a, MAX_MULTIPLIER_BPS as an, LAUNCHLAB_SELL_EXACT_IN as ar, POOL_MINT_OFFSET as at, resolveVenue as b, IDENTITY_SEED as bn, CPMM_POOL_STATE_HEAD_SIZE as bt, getLaunchInstruction as c, TIERS as cn, METAPLEX_PROGRAM_ADDRESS as cr, fetchPoolByMint as ct, cpmmPoolMatches as d, maxMultiplierBps as dn, RAYDIUM_MAINNET_IDS as dr, fetchWalletActivity as dt, metadataAddress as en, LAUNCHLAB_CLAIM_CREATOR_FEE as er, getSetComputeUnitLimitInstruction as et, creatorFeeOnInput as f, quote as fn, RENT_SYSVAR_ADDRESS as fr, listDraws as ft, findCpmmPool as g, uncappedMaximum as gn, WSOL_MINT as gr, quotePool as gt, fetchQuoteBinding as h, tierAmount as hn, TOKEN_PROGRAM_ADDRESS as hr, listPools as ht, collectCreatorFee as i, GaboxMathError as in, LAUNCHLAB_POOL_STATE_DISCRIMINATOR as ir, POOL_CREATOR_OFFSET as it, preFeeAmount as j, CREATOR_FEE_ON_QUOTE as jn, mintDecimals as jt, curveSellExactIn as k, CONSTANT_CURVE_TAG as kn, decodeLaunchlabPool as kt, buildMessage as l, averageMultiplierBps as ln, PLATFORM_ADMIN as lr, fetchPoolInventory as lt, fetchCurveSettings as m, seedTokens as mn, TOKEN_2022_PROGRAM_ADDRESS as mr, listDrawsByPurchaser as mt, getCurveBuyExactInInstruction as n, BPS as nn, LAUNCHLAB_INITIALIZE as nr, DRAW_POOL_OFFSET as nt, getClaimCreatorFeeInstructions as o, MIN_SEED_MULTIPLIER_BPS as on, LAUNCH_SUPPLY as or, fetchDraw as ot, curveQuote as p, resolveReservation as pn, SYSTEM_PROGRAM_ADDRESS as pr, listDrawsByPool as pt, CLAIM_COMPUTE_UNITS as q, CPMM_AMM_CONFIG_DISCRIMINATOR as qn, creatorFeeVaultAuthority as qt, claimCreatorFee as r, DEFAULT_TIERS as rn, LAUNCHLAB_PLATFORM_CONFIG_DISCRIMINATOR as rr, DRAW_PURCHASER_OFFSET as rt, getCollectCreatorFeeInstructions as s, TICKETS as sn, LAUNCH_TOTAL_BASE_SELL as sr, fetchPoolAt as st, raydium_exports as t, platformFeeVaultAddress as tn, LAUNCHLAB_GLOBAL_CONFIG_DISCRIMINATOR as tr, getSetComputeUnitPriceInstruction as tt, withRemainingAccounts as u, choose as un, RAYDIUM_DEVNET_IDS as ur, fetchVaultBalance as ut, newCurveReserves as v, validateTiers as vn, CPMM_AMM_CONFIG_HEAD_SIZE as vt, ceilDivRate as w, RETRY_SLOTS as wn, LAUNCHLAB_POOL_STATE_HEAD_SIZE as wt, sellQuote as x, INSTRUCTIONS_SYSVAR as xn, CPMM_POOL_STATE_SIZE as xt, quoteAccountFor as y, GABOX_PROGRAM_ID as yn, CPMM_POOL_OFFSETS as yt, order as z, MIGRATE_TO_CPMM as zn, scopedVrfIdentityAddress as zt };
|
|
3281
|
+
|
|
3282
|
+
//# sourceMappingURL=raydium-CU-tZzIk.js.map
|