@gabox-labs/sdk 0.1.1 → 0.6.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 +249 -1
- package/README.md +116 -458
- package/dist/generated/index.d.ts +334 -755
- package/dist/generated/index.js +2403 -338
- package/dist/generated/index.js.map +1 -1
- package/dist/index-BDfGvmgF.d.ts +934 -0
- package/dist/index.d.ts +211 -472
- package/dist/index.js +539 -1093
- 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-B-l9V3O-.js +2604 -0
- package/dist/raydium-B-l9V3O-.js.map +1 -0
- package/llms.txt +4 -2
- package/package.json +11 -16
- package/skills/gabox-sdk/SKILL.md +40 -141
- package/skills/gabox-sdk/references/api.md +102 -145
- package/dist/gaboxV2-CV2XltqC.js +0 -3347
- 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,2604 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import { AccountRole, Endian, addEncoderSizePrefix, appendTransactionMessageInstructions, compileTransaction, compressTransactionMessageUsingAddressLookupTables, createTransactionMessage, fixDecoderSize, getAddressDecoder, getAddressEncoder, getBase64Encoder, getBooleanDecoder, getBytesDecoder, getProgramDerivedAddress, getStructDecoder, getStructEncoder, getTransactionEncoder, getU16Decoder, getU16Encoder, getU32Encoder, getU64Decoder, getU64Encoder, getU8Decoder, getU8Encoder, getUtf8Encoder, pipe, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash } from "@solana/kit";
|
|
3
|
+
import { getCloseAccountInstruction, getCreateAssociatedTokenIdempotentInstruction } from "@solana-program/token";
|
|
4
|
+
//#region src/raydium/abi.ts
|
|
5
|
+
const RAYDIUM_MAINNET_IDS = {
|
|
6
|
+
launchlab: "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj",
|
|
7
|
+
launchlabAuthority: "WLHv2UAZm6z4KyaaELi5pjdbJh6RESMva1Rnn8pJVVh",
|
|
8
|
+
launchlabEventAuthority: "2DPAtwB8L12vrMRExbLuyGnC7n2J5LNoZQSejeQGpwkr",
|
|
9
|
+
solGlobalConfig: "6s1xP3hpbAfFoNtUNF8mfHsjr2Bd97JxFJRWLbL6aHuX",
|
|
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
|
+
solGlobalConfig: "7ZR4zD7PYfY2XxoG1Gxcy2EgEeGYrpxrwzPuwdUBssEt",
|
|
20
|
+
gaboxPlatform: "DdEeCPXbCAzHE2PZSoR3RZng4WA4bSztrezQznrJ4ooB",
|
|
21
|
+
cpmm: "DRaycpLY18LhpbydsBWbVJtxpNv9oXPgjRSfpF2bWpYb",
|
|
22
|
+
cpmmAuthority: "CXniRufdq5xL8t8jZAPxsPZDpuudwuJSPWnbcD5Y5Nxq",
|
|
23
|
+
launchQuoteRaise: 3000000000n
|
|
24
|
+
};
|
|
25
|
+
/** Wrapped SOL. The only quote Gabox accepts today, on both venues. */
|
|
26
|
+
const WSOL_MINT = "So11111111111111111111111111111111111111112";
|
|
27
|
+
/** Metaplex Token Metadata, which LaunchLab's create instruction writes to. */
|
|
28
|
+
const METAPLEX_PROGRAM_ADDRESS = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
|
|
29
|
+
/**
|
|
30
|
+
* `PLATFORM_ADMIN` in the program's constants.rs. The Gabox platform config PDA derives from this
|
|
31
|
+
* wallet, and LaunchLab pays the platform share of every curve trade to it.
|
|
32
|
+
*/
|
|
33
|
+
const PLATFORM_ADMIN = "5WcPTEQ59UqpQzjZUPbU8QRGCbj7NeQNLDa7DbsLkLKT";
|
|
34
|
+
/** The coin supply every Gabox launch mints: 1,000,000,000 coins at 6 decimals. */
|
|
35
|
+
const LAUNCH_SUPPLY = 1000000000000000n;
|
|
36
|
+
/** The part of the supply the LaunchLab curve sells: 793,100,000 coins. */
|
|
37
|
+
const LAUNCH_TOTAL_BASE_SELL = 793100000000000n;
|
|
38
|
+
/** Classic SPL Token. LaunchLab pins it on both sides of every Gabox launch and trade. */
|
|
39
|
+
const TOKEN_PROGRAM_ADDRESS = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
40
|
+
const ASSOCIATED_TOKEN_PROGRAM_ADDRESS = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
41
|
+
const SYSTEM_PROGRAM_ADDRESS = "11111111111111111111111111111111";
|
|
42
|
+
/** The rent sysvar. LaunchLab's create instruction still takes it. */
|
|
43
|
+
const RENT_SYSVAR_ADDRESS = "SysvarRent111111111111111111111111111111111";
|
|
44
|
+
/**
|
|
45
|
+
* The first eight bytes of each Raydium account this SDK decodes. `adapter.ts` checks them before
|
|
46
|
+
* it reads a field, so an account of the wrong type fails loudly instead of decoding as rubbish.
|
|
47
|
+
*
|
|
48
|
+
* LaunchLab and CPMM give their pool account the same eight bytes, because Anchor derives a
|
|
49
|
+
* discriminator from the struct name alone and both are called `PoolState`. The two programs own
|
|
50
|
+
* different accounts, so the pair never collides in practice.
|
|
51
|
+
*/
|
|
52
|
+
const LAUNCHLAB_POOL_STATE_DISCRIMINATOR = new Uint8Array([
|
|
53
|
+
247,
|
|
54
|
+
237,
|
|
55
|
+
227,
|
|
56
|
+
245,
|
|
57
|
+
215,
|
|
58
|
+
195,
|
|
59
|
+
222,
|
|
60
|
+
70
|
|
61
|
+
]);
|
|
62
|
+
const LAUNCHLAB_GLOBAL_CONFIG_DISCRIMINATOR = new Uint8Array([
|
|
63
|
+
149,
|
|
64
|
+
8,
|
|
65
|
+
156,
|
|
66
|
+
202,
|
|
67
|
+
160,
|
|
68
|
+
252,
|
|
69
|
+
176,
|
|
70
|
+
217
|
|
71
|
+
]);
|
|
72
|
+
const LAUNCHLAB_PLATFORM_CONFIG_DISCRIMINATOR = new Uint8Array([
|
|
73
|
+
160,
|
|
74
|
+
78,
|
|
75
|
+
128,
|
|
76
|
+
0,
|
|
77
|
+
248,
|
|
78
|
+
83,
|
|
79
|
+
230,
|
|
80
|
+
160
|
|
81
|
+
]);
|
|
82
|
+
const CPMM_POOL_STATE_DISCRIMINATOR = new Uint8Array([
|
|
83
|
+
247,
|
|
84
|
+
237,
|
|
85
|
+
227,
|
|
86
|
+
245,
|
|
87
|
+
215,
|
|
88
|
+
195,
|
|
89
|
+
222,
|
|
90
|
+
70
|
|
91
|
+
]);
|
|
92
|
+
const CPMM_AMM_CONFIG_DISCRIMINATOR = new Uint8Array([
|
|
93
|
+
218,
|
|
94
|
+
244,
|
|
95
|
+
33,
|
|
96
|
+
104,
|
|
97
|
+
203,
|
|
98
|
+
203,
|
|
99
|
+
43,
|
|
100
|
+
111
|
|
101
|
+
]);
|
|
102
|
+
/**
|
|
103
|
+
* `initialize_v2` on `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj` — cross-checked against interfaces.json.
|
|
104
|
+
*/
|
|
105
|
+
const LAUNCHLAB_INITIALIZE = {
|
|
106
|
+
name: "initialize_v2",
|
|
107
|
+
discriminator: new Uint8Array([
|
|
108
|
+
67,
|
|
109
|
+
153,
|
|
110
|
+
175,
|
|
111
|
+
39,
|
|
112
|
+
218,
|
|
113
|
+
16,
|
|
114
|
+
38,
|
|
115
|
+
32
|
|
116
|
+
]),
|
|
117
|
+
accounts: [
|
|
118
|
+
{
|
|
119
|
+
name: "payer",
|
|
120
|
+
writable: true,
|
|
121
|
+
signer: true
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "creator",
|
|
125
|
+
writable: false,
|
|
126
|
+
signer: false
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "global_config",
|
|
130
|
+
writable: false,
|
|
131
|
+
signer: false
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "platform_config",
|
|
135
|
+
writable: false,
|
|
136
|
+
signer: false
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "authority",
|
|
140
|
+
writable: false,
|
|
141
|
+
signer: false
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "pool_state",
|
|
145
|
+
writable: true,
|
|
146
|
+
signer: false
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "base_mint",
|
|
150
|
+
writable: true,
|
|
151
|
+
signer: true
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: "quote_mint",
|
|
155
|
+
writable: false,
|
|
156
|
+
signer: false
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "base_vault",
|
|
160
|
+
writable: true,
|
|
161
|
+
signer: false
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "quote_vault",
|
|
165
|
+
writable: true,
|
|
166
|
+
signer: false
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "metadata_account",
|
|
170
|
+
writable: true,
|
|
171
|
+
signer: false
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "base_token_program",
|
|
175
|
+
writable: false,
|
|
176
|
+
signer: false
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: "quote_token_program",
|
|
180
|
+
writable: false,
|
|
181
|
+
signer: false
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "metadata_program",
|
|
185
|
+
writable: false,
|
|
186
|
+
signer: false
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "system_program",
|
|
190
|
+
writable: false,
|
|
191
|
+
signer: false
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "rent_program",
|
|
195
|
+
writable: false,
|
|
196
|
+
signer: false
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "event_authority",
|
|
200
|
+
writable: false,
|
|
201
|
+
signer: false
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "program",
|
|
205
|
+
writable: false,
|
|
206
|
+
signer: false
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* `buy_exact_in` on `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj` — not in interfaces.json (the program never builds it).
|
|
212
|
+
* The last three accounts are not in the IDL. LaunchLab reads them from the remaining
|
|
213
|
+
* accounts, in this order.
|
|
214
|
+
*/
|
|
215
|
+
const LAUNCHLAB_BUY_EXACT_IN = {
|
|
216
|
+
name: "buy_exact_in",
|
|
217
|
+
discriminator: new Uint8Array([
|
|
218
|
+
250,
|
|
219
|
+
234,
|
|
220
|
+
13,
|
|
221
|
+
123,
|
|
222
|
+
213,
|
|
223
|
+
156,
|
|
224
|
+
19,
|
|
225
|
+
236
|
|
226
|
+
]),
|
|
227
|
+
accounts: [
|
|
228
|
+
{
|
|
229
|
+
name: "payer",
|
|
230
|
+
writable: true,
|
|
231
|
+
signer: true
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: "authority",
|
|
235
|
+
writable: false,
|
|
236
|
+
signer: false
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: "global_config",
|
|
240
|
+
writable: false,
|
|
241
|
+
signer: false
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "platform_config",
|
|
245
|
+
writable: false,
|
|
246
|
+
signer: false
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: "pool_state",
|
|
250
|
+
writable: true,
|
|
251
|
+
signer: false
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "user_base_token",
|
|
255
|
+
writable: true,
|
|
256
|
+
signer: false
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: "user_quote_token",
|
|
260
|
+
writable: true,
|
|
261
|
+
signer: false
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "base_vault",
|
|
265
|
+
writable: true,
|
|
266
|
+
signer: false
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: "quote_vault",
|
|
270
|
+
writable: true,
|
|
271
|
+
signer: false
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: "base_token_mint",
|
|
275
|
+
writable: false,
|
|
276
|
+
signer: false
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: "quote_token_mint",
|
|
280
|
+
writable: false,
|
|
281
|
+
signer: false
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: "base_token_program",
|
|
285
|
+
writable: false,
|
|
286
|
+
signer: false
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "quote_token_program",
|
|
290
|
+
writable: false,
|
|
291
|
+
signer: false
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: "event_authority",
|
|
295
|
+
writable: false,
|
|
296
|
+
signer: false
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "program",
|
|
300
|
+
writable: false,
|
|
301
|
+
signer: false
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "system_program",
|
|
305
|
+
writable: false,
|
|
306
|
+
signer: false
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: "platform_fee_vault",
|
|
310
|
+
writable: true,
|
|
311
|
+
signer: false
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: "creator_fee_vault",
|
|
315
|
+
writable: true,
|
|
316
|
+
signer: false
|
|
317
|
+
}
|
|
318
|
+
]
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* `buy_exact_out` on `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj` — cross-checked against interfaces.json.
|
|
322
|
+
* The last three accounts are not in the IDL. LaunchLab reads them from the remaining
|
|
323
|
+
* accounts, in this order.
|
|
324
|
+
*/
|
|
325
|
+
const LAUNCHLAB_BUY_EXACT_OUT = {
|
|
326
|
+
name: "buy_exact_out",
|
|
327
|
+
discriminator: new Uint8Array([
|
|
328
|
+
24,
|
|
329
|
+
211,
|
|
330
|
+
116,
|
|
331
|
+
40,
|
|
332
|
+
105,
|
|
333
|
+
3,
|
|
334
|
+
153,
|
|
335
|
+
56
|
|
336
|
+
]),
|
|
337
|
+
accounts: [
|
|
338
|
+
{
|
|
339
|
+
name: "payer",
|
|
340
|
+
writable: true,
|
|
341
|
+
signer: true
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
name: "authority",
|
|
345
|
+
writable: false,
|
|
346
|
+
signer: false
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: "global_config",
|
|
350
|
+
writable: false,
|
|
351
|
+
signer: false
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
name: "platform_config",
|
|
355
|
+
writable: false,
|
|
356
|
+
signer: false
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: "pool_state",
|
|
360
|
+
writable: true,
|
|
361
|
+
signer: false
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: "user_base_token",
|
|
365
|
+
writable: true,
|
|
366
|
+
signer: false
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
name: "user_quote_token",
|
|
370
|
+
writable: true,
|
|
371
|
+
signer: false
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: "base_vault",
|
|
375
|
+
writable: true,
|
|
376
|
+
signer: false
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: "quote_vault",
|
|
380
|
+
writable: true,
|
|
381
|
+
signer: false
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
name: "base_token_mint",
|
|
385
|
+
writable: false,
|
|
386
|
+
signer: false
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: "quote_token_mint",
|
|
390
|
+
writable: false,
|
|
391
|
+
signer: false
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
name: "base_token_program",
|
|
395
|
+
writable: false,
|
|
396
|
+
signer: false
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: "quote_token_program",
|
|
400
|
+
writable: false,
|
|
401
|
+
signer: false
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
name: "event_authority",
|
|
405
|
+
writable: false,
|
|
406
|
+
signer: false
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
name: "program",
|
|
410
|
+
writable: false,
|
|
411
|
+
signer: false
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: "system_program",
|
|
415
|
+
writable: false,
|
|
416
|
+
signer: false
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
name: "platform_fee_vault",
|
|
420
|
+
writable: true,
|
|
421
|
+
signer: false
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: "creator_fee_vault",
|
|
425
|
+
writable: true,
|
|
426
|
+
signer: false
|
|
427
|
+
}
|
|
428
|
+
]
|
|
429
|
+
};
|
|
430
|
+
/**
|
|
431
|
+
* `sell_exact_in` on `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj` — cross-checked against interfaces.json.
|
|
432
|
+
* The last three accounts are not in the IDL. LaunchLab reads them from the remaining
|
|
433
|
+
* accounts, in this order.
|
|
434
|
+
*/
|
|
435
|
+
const LAUNCHLAB_SELL_EXACT_IN = {
|
|
436
|
+
name: "sell_exact_in",
|
|
437
|
+
discriminator: new Uint8Array([
|
|
438
|
+
149,
|
|
439
|
+
39,
|
|
440
|
+
222,
|
|
441
|
+
155,
|
|
442
|
+
211,
|
|
443
|
+
124,
|
|
444
|
+
152,
|
|
445
|
+
26
|
|
446
|
+
]),
|
|
447
|
+
accounts: [
|
|
448
|
+
{
|
|
449
|
+
name: "payer",
|
|
450
|
+
writable: true,
|
|
451
|
+
signer: true
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
name: "authority",
|
|
455
|
+
writable: false,
|
|
456
|
+
signer: false
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
name: "global_config",
|
|
460
|
+
writable: false,
|
|
461
|
+
signer: false
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: "platform_config",
|
|
465
|
+
writable: false,
|
|
466
|
+
signer: false
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
name: "pool_state",
|
|
470
|
+
writable: true,
|
|
471
|
+
signer: false
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
name: "user_base_token",
|
|
475
|
+
writable: true,
|
|
476
|
+
signer: false
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: "user_quote_token",
|
|
480
|
+
writable: true,
|
|
481
|
+
signer: false
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
name: "base_vault",
|
|
485
|
+
writable: true,
|
|
486
|
+
signer: false
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
name: "quote_vault",
|
|
490
|
+
writable: true,
|
|
491
|
+
signer: false
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
name: "base_token_mint",
|
|
495
|
+
writable: false,
|
|
496
|
+
signer: false
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
name: "quote_token_mint",
|
|
500
|
+
writable: false,
|
|
501
|
+
signer: false
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
name: "base_token_program",
|
|
505
|
+
writable: false,
|
|
506
|
+
signer: false
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
name: "quote_token_program",
|
|
510
|
+
writable: false,
|
|
511
|
+
signer: false
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: "event_authority",
|
|
515
|
+
writable: false,
|
|
516
|
+
signer: false
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
name: "program",
|
|
520
|
+
writable: false,
|
|
521
|
+
signer: false
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
name: "system_program",
|
|
525
|
+
writable: false,
|
|
526
|
+
signer: false
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
name: "platform_fee_vault",
|
|
530
|
+
writable: true,
|
|
531
|
+
signer: false
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
name: "creator_fee_vault",
|
|
535
|
+
writable: true,
|
|
536
|
+
signer: false
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
};
|
|
540
|
+
/**
|
|
541
|
+
* `claim_creator_fee` on `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj` — not in interfaces.json (the program never builds it).
|
|
542
|
+
*/
|
|
543
|
+
const LAUNCHLAB_CLAIM_CREATOR_FEE = {
|
|
544
|
+
name: "claim_creator_fee",
|
|
545
|
+
discriminator: new Uint8Array([
|
|
546
|
+
26,
|
|
547
|
+
97,
|
|
548
|
+
138,
|
|
549
|
+
203,
|
|
550
|
+
132,
|
|
551
|
+
171,
|
|
552
|
+
141,
|
|
553
|
+
252
|
|
554
|
+
]),
|
|
555
|
+
accounts: [
|
|
556
|
+
{
|
|
557
|
+
name: "creator",
|
|
558
|
+
writable: true,
|
|
559
|
+
signer: true
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
name: "fee_vault_authority",
|
|
563
|
+
writable: false,
|
|
564
|
+
signer: false
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
name: "creator_fee_vault",
|
|
568
|
+
writable: true,
|
|
569
|
+
signer: false
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
name: "recipient_token_account",
|
|
573
|
+
writable: true,
|
|
574
|
+
signer: false
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
name: "quote_mint",
|
|
578
|
+
writable: false,
|
|
579
|
+
signer: false
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
name: "token_program",
|
|
583
|
+
writable: false,
|
|
584
|
+
signer: false
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
name: "system_program",
|
|
588
|
+
writable: false,
|
|
589
|
+
signer: false
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
name: "associated_token_program",
|
|
593
|
+
writable: false,
|
|
594
|
+
signer: false
|
|
595
|
+
}
|
|
596
|
+
]
|
|
597
|
+
};
|
|
598
|
+
/**
|
|
599
|
+
* `swap_base_output` on `CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C` — cross-checked against interfaces.json.
|
|
600
|
+
*/
|
|
601
|
+
const CPMM_SWAP_BASE_OUTPUT = {
|
|
602
|
+
name: "swap_base_output",
|
|
603
|
+
discriminator: new Uint8Array([
|
|
604
|
+
55,
|
|
605
|
+
217,
|
|
606
|
+
98,
|
|
607
|
+
86,
|
|
608
|
+
163,
|
|
609
|
+
74,
|
|
610
|
+
180,
|
|
611
|
+
173
|
|
612
|
+
]),
|
|
613
|
+
accounts: [
|
|
614
|
+
{
|
|
615
|
+
name: "payer",
|
|
616
|
+
writable: false,
|
|
617
|
+
signer: true
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
name: "authority",
|
|
621
|
+
writable: false,
|
|
622
|
+
signer: false
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
name: "amm_config",
|
|
626
|
+
writable: false,
|
|
627
|
+
signer: false
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
name: "pool_state",
|
|
631
|
+
writable: true,
|
|
632
|
+
signer: false
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
name: "input_token_account",
|
|
636
|
+
writable: true,
|
|
637
|
+
signer: false
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
name: "output_token_account",
|
|
641
|
+
writable: true,
|
|
642
|
+
signer: false
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
name: "input_vault",
|
|
646
|
+
writable: true,
|
|
647
|
+
signer: false
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
name: "output_vault",
|
|
651
|
+
writable: true,
|
|
652
|
+
signer: false
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
name: "input_token_program",
|
|
656
|
+
writable: false,
|
|
657
|
+
signer: false
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
name: "output_token_program",
|
|
661
|
+
writable: false,
|
|
662
|
+
signer: false
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
name: "input_token_mint",
|
|
666
|
+
writable: false,
|
|
667
|
+
signer: false
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
name: "output_token_mint",
|
|
671
|
+
writable: false,
|
|
672
|
+
signer: false
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
name: "observation_state",
|
|
676
|
+
writable: true,
|
|
677
|
+
signer: false
|
|
678
|
+
}
|
|
679
|
+
]
|
|
680
|
+
};
|
|
681
|
+
/**
|
|
682
|
+
* `swap_base_input` on `CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C` — cross-checked against interfaces.json.
|
|
683
|
+
*/
|
|
684
|
+
const CPMM_SWAP_BASE_INPUT = {
|
|
685
|
+
name: "swap_base_input",
|
|
686
|
+
discriminator: new Uint8Array([
|
|
687
|
+
143,
|
|
688
|
+
190,
|
|
689
|
+
90,
|
|
690
|
+
218,
|
|
691
|
+
196,
|
|
692
|
+
30,
|
|
693
|
+
51,
|
|
694
|
+
222
|
|
695
|
+
]),
|
|
696
|
+
accounts: [
|
|
697
|
+
{
|
|
698
|
+
name: "payer",
|
|
699
|
+
writable: false,
|
|
700
|
+
signer: true
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
name: "authority",
|
|
704
|
+
writable: false,
|
|
705
|
+
signer: false
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
name: "amm_config",
|
|
709
|
+
writable: false,
|
|
710
|
+
signer: false
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
name: "pool_state",
|
|
714
|
+
writable: true,
|
|
715
|
+
signer: false
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
name: "input_token_account",
|
|
719
|
+
writable: true,
|
|
720
|
+
signer: false
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
name: "output_token_account",
|
|
724
|
+
writable: true,
|
|
725
|
+
signer: false
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
name: "input_vault",
|
|
729
|
+
writable: true,
|
|
730
|
+
signer: false
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
name: "output_vault",
|
|
734
|
+
writable: true,
|
|
735
|
+
signer: false
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
name: "input_token_program",
|
|
739
|
+
writable: false,
|
|
740
|
+
signer: false
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
name: "output_token_program",
|
|
744
|
+
writable: false,
|
|
745
|
+
signer: false
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
name: "input_token_mint",
|
|
749
|
+
writable: false,
|
|
750
|
+
signer: false
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
name: "output_token_mint",
|
|
754
|
+
writable: false,
|
|
755
|
+
signer: false
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
name: "observation_state",
|
|
759
|
+
writable: true,
|
|
760
|
+
signer: false
|
|
761
|
+
}
|
|
762
|
+
]
|
|
763
|
+
};
|
|
764
|
+
/**
|
|
765
|
+
* `collect_creator_fee` on `CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C` — not in interfaces.json (the program never builds it).
|
|
766
|
+
* The last account is not in the IDL snapshot; see `CPMM_CREATOR_FEE_SHARE_ACCOUNT` in
|
|
767
|
+
* scripts/codegen.mjs. Derive it as PDA(["creator_fee_share", creator, amm_config]).
|
|
768
|
+
*/
|
|
769
|
+
const CPMM_COLLECT_CREATOR_FEE = {
|
|
770
|
+
name: "collect_creator_fee",
|
|
771
|
+
discriminator: new Uint8Array([
|
|
772
|
+
20,
|
|
773
|
+
22,
|
|
774
|
+
86,
|
|
775
|
+
123,
|
|
776
|
+
198,
|
|
777
|
+
28,
|
|
778
|
+
219,
|
|
779
|
+
132
|
|
780
|
+
]),
|
|
781
|
+
accounts: [
|
|
782
|
+
{
|
|
783
|
+
name: "creator",
|
|
784
|
+
writable: true,
|
|
785
|
+
signer: true
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
name: "authority",
|
|
789
|
+
writable: false,
|
|
790
|
+
signer: false
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
name: "pool_state",
|
|
794
|
+
writable: true,
|
|
795
|
+
signer: false
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
name: "amm_config",
|
|
799
|
+
writable: false,
|
|
800
|
+
signer: false
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
name: "token_0_vault",
|
|
804
|
+
writable: true,
|
|
805
|
+
signer: false
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
name: "token_1_vault",
|
|
809
|
+
writable: true,
|
|
810
|
+
signer: false
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
name: "vault_0_mint",
|
|
814
|
+
writable: false,
|
|
815
|
+
signer: false
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
name: "vault_1_mint",
|
|
819
|
+
writable: false,
|
|
820
|
+
signer: false
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
name: "creator_token_0",
|
|
824
|
+
writable: true,
|
|
825
|
+
signer: false
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
name: "creator_token_1",
|
|
829
|
+
writable: true,
|
|
830
|
+
signer: false
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
name: "token_0_program",
|
|
834
|
+
writable: false,
|
|
835
|
+
signer: false
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
name: "token_1_program",
|
|
839
|
+
writable: false,
|
|
840
|
+
signer: false
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
name: "associated_token_program",
|
|
844
|
+
writable: false,
|
|
845
|
+
signer: false
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
name: "system_program",
|
|
849
|
+
writable: false,
|
|
850
|
+
signer: false
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
name: "creator_fee_share",
|
|
854
|
+
writable: false,
|
|
855
|
+
signer: false
|
|
856
|
+
}
|
|
857
|
+
]
|
|
858
|
+
};
|
|
859
|
+
//#endregion
|
|
860
|
+
//#region src/raydium/ids.ts
|
|
861
|
+
/**
|
|
862
|
+
* The Raydium addresses and launch numbers Gabox pins, per cluster.
|
|
863
|
+
*
|
|
864
|
+
* Mainnet and devnet run different Raydium deployments. Every program id and every PDA differs, and
|
|
865
|
+
* so does the quote the curve must raise before a coin graduates. So nothing here is a global: a
|
|
866
|
+
* caller passes the cluster, which comes from `createClient({ cluster })`.
|
|
867
|
+
*
|
|
868
|
+
* None of these strings is typed in this file. `scripts/codegen.mjs` reads them out of the Rust
|
|
869
|
+
* program's `programs/gabox/src/venue/ids.rs` and writes `abi.ts`. The Rust file is the source of
|
|
870
|
+
* truth, because the program checks every one of these addresses on chain.
|
|
871
|
+
* `test/raydium-pdas.test.ts` derives each PDA again and compares.
|
|
872
|
+
*/
|
|
873
|
+
const ADDRESS_ENCODER = getAddressEncoder();
|
|
874
|
+
/**
|
|
875
|
+
* The decimals every Gabox coin is launched with. `venue/launch.rs` refuses any other value, and
|
|
876
|
+
* `tokens.rs` refuses a pool mint that does not have exactly this many.
|
|
877
|
+
*/
|
|
878
|
+
const LAUNCH_DECIMALS = 6;
|
|
879
|
+
/**
|
|
880
|
+
* The Raydium deployment of each cluster.
|
|
881
|
+
*
|
|
882
|
+
* `localnet` uses the mainnet set. A local validator normally clones the mainnet Raydium programs,
|
|
883
|
+
* and the program's own default `anchor build` pins the mainnet addresses too; the `devnet` cargo
|
|
884
|
+
* feature is what swaps them. Pass your own ids to a builder if your validator clones devnet.
|
|
885
|
+
*/
|
|
886
|
+
const RAYDIUM_IDS = {
|
|
887
|
+
"mainnet-beta": RAYDIUM_MAINNET_IDS,
|
|
888
|
+
devnet: RAYDIUM_DEVNET_IDS,
|
|
889
|
+
localnet: RAYDIUM_MAINNET_IDS
|
|
890
|
+
};
|
|
891
|
+
/** The Raydium deployment one cluster trades on. Throws for a cluster this SDK does not know. */
|
|
892
|
+
function raydiumIds(cluster) {
|
|
893
|
+
const ids = RAYDIUM_IDS[cluster];
|
|
894
|
+
if (!ids) throw new Error(`unknown cluster ${JSON.stringify(cluster)}; expected 'devnet', 'mainnet-beta' or 'localnet'`);
|
|
895
|
+
return ids;
|
|
896
|
+
}
|
|
897
|
+
/** LaunchLab's PDA seeds, and the two Gabox coins are always sorted against. */
|
|
898
|
+
const LAUNCHLAB_SEEDS = {
|
|
899
|
+
/** `["vault_auth_seed"]`. */
|
|
900
|
+
authority: "vault_auth_seed",
|
|
901
|
+
/** `["__event_authority"]`. */
|
|
902
|
+
eventAuthority: "__event_authority",
|
|
903
|
+
/** `["global_config", quote_mint, u8 curve_type, u16 big-endian index]`. */
|
|
904
|
+
globalConfig: "global_config",
|
|
905
|
+
/** `["platform_config", platform_admin]`. */
|
|
906
|
+
platformConfig: "platform_config",
|
|
907
|
+
/** `["pool", base_mint, quote_mint]`. */
|
|
908
|
+
pool: "pool",
|
|
909
|
+
/** `["pool_vault", pool_state, mint]`. */
|
|
910
|
+
vault: "pool_vault",
|
|
911
|
+
/** `["creator_fee_vault_auth_seed"]`, the authority over every creator fee vault. */
|
|
912
|
+
creatorFeeVaultAuthority: "creator_fee_vault_auth_seed"
|
|
913
|
+
};
|
|
914
|
+
/** CPMM's PDA seeds. */
|
|
915
|
+
const CPMM_SEEDS = {
|
|
916
|
+
/** `["vault_and_lp_mint_auth_seed"]`. */
|
|
917
|
+
authority: "vault_and_lp_mint_auth_seed",
|
|
918
|
+
/** `["pool", amm_config, token_0_mint, token_1_mint]`, the mints sorted by byte order. */
|
|
919
|
+
pool: "pool",
|
|
920
|
+
/** `["pool_vault", pool_state, mint]`. */
|
|
921
|
+
vault: "pool_vault",
|
|
922
|
+
/** `["observation", pool_state]`. */
|
|
923
|
+
observation: "observation",
|
|
924
|
+
/** `["creator_fee_share", creator, amm_config]`. */
|
|
925
|
+
creatorFeeShare: "creator_fee_share"
|
|
926
|
+
};
|
|
927
|
+
/** Metaplex's metadata PDA seed: `["metadata", metaplex, mint]`. */
|
|
928
|
+
const METADATA_SEED = "metadata";
|
|
929
|
+
/**
|
|
930
|
+
* Every rate LaunchLab and CPMM state is out of 1,000,000, not out of 10,000. A `trade_fee_rate`
|
|
931
|
+
* of 5,000 is 0.5%.
|
|
932
|
+
*/
|
|
933
|
+
const RATE_DENOMINATOR = 1000000n;
|
|
934
|
+
/** The share-fee receiver Gabox never passes, so its rate is always zero on every trade. */
|
|
935
|
+
const SHARE_FEE_RATE = 0n;
|
|
936
|
+
/** `CurveParams::Constant`, the only curve shape Gabox launches. */
|
|
937
|
+
const CONSTANT_CURVE_TAG = 0;
|
|
938
|
+
/** `migrate_type` 1: the coin graduates into a CPMM pool, not into an AMM pool. */
|
|
939
|
+
const MIGRATE_TO_CPMM = 1;
|
|
940
|
+
/** `AmmCreatorFeeOn::QuoteToken`, so the CPMM creator fee is paid in WSOL. */
|
|
941
|
+
const CREATOR_FEE_ON_QUOTE = 0;
|
|
942
|
+
/** LaunchLab pool `status`: still selling on the curve. */
|
|
943
|
+
const LAUNCHLAB_STATUS_FUND = 0;
|
|
944
|
+
/** LaunchLab pool `status`: the raise is complete and Raydium's bot is migrating the coin. */
|
|
945
|
+
const LAUNCHLAB_STATUS_MIGRATE = 1;
|
|
946
|
+
/** LaunchLab pool `status`: the coin has graduated and trades on its CPMM pool. */
|
|
947
|
+
const LAUNCHLAB_STATUS_TRADE = 2;
|
|
948
|
+
/** Metaplex's own limits on the three strings a creator picks, in UTF-8 bytes. */
|
|
949
|
+
const METADATA_LIMITS = {
|
|
950
|
+
name: 32,
|
|
951
|
+
symbol: 10,
|
|
952
|
+
uri: 200
|
|
953
|
+
};
|
|
954
|
+
/** Raydium sorts a CPMM pair by raw address byte order. */
|
|
955
|
+
function sortedMints(a, b) {
|
|
956
|
+
return compareAddresses(a, b) <= 0 ? [a, b] : [b, a];
|
|
957
|
+
}
|
|
958
|
+
/** Compare two addresses the way the runtime does: by their 32 raw bytes, not by base58 text. */
|
|
959
|
+
function compareAddresses(a, b) {
|
|
960
|
+
const left = ADDRESS_ENCODER.encode(a);
|
|
961
|
+
const right = ADDRESS_ENCODER.encode(b);
|
|
962
|
+
for (let i = 0; i < 32; i++) {
|
|
963
|
+
const difference = (left[i] ?? 0) - (right[i] ?? 0);
|
|
964
|
+
if (difference !== 0) return difference;
|
|
965
|
+
}
|
|
966
|
+
return 0;
|
|
967
|
+
}
|
|
968
|
+
//#endregion
|
|
969
|
+
//#region src/raydium/pdas.ts
|
|
970
|
+
/**
|
|
971
|
+
* Every Raydium address a Gabox transaction derives.
|
|
972
|
+
*
|
|
973
|
+
* All of them are program-derived, so each one takes the program it lives under. Mainnet and devnet
|
|
974
|
+
* run different Raydium programs, so the same seeds give different addresses on the two clusters.
|
|
975
|
+
* Pass `raydiumIds(client.cluster)` and read the program out of it.
|
|
976
|
+
*
|
|
977
|
+
* `venue/trade.rs` and `venue/launch.rs` derive the same addresses on chain and refuse any account
|
|
978
|
+
* that does not match. `test/raydium-pdas.test.ts` checks every one of these against the pinned
|
|
979
|
+
* table in `abi.ts`, for both clusters.
|
|
980
|
+
*/
|
|
981
|
+
const address$1 = getAddressEncoder();
|
|
982
|
+
const utf8 = getUtf8Encoder();
|
|
983
|
+
const u8$1 = getU8Encoder();
|
|
984
|
+
const u16be = getU16Encoder({ endian: Endian.Big });
|
|
985
|
+
const derive = async (programAddress, seeds) => (await getProgramDerivedAddress({
|
|
986
|
+
programAddress,
|
|
987
|
+
seeds
|
|
988
|
+
}))[0];
|
|
989
|
+
/** `["vault_auth_seed"]`. LaunchLab signs its own vault transfers with this PDA. */
|
|
990
|
+
const launchlabAuthority = async (launchlab) => await derive(launchlab, [utf8.encode(LAUNCHLAB_SEEDS.authority)]);
|
|
991
|
+
/** `["__event_authority"]`. */
|
|
992
|
+
const launchlabEventAuthority = async (launchlab) => await derive(launchlab, [utf8.encode(LAUNCHLAB_SEEDS.eventAuthority)]);
|
|
993
|
+
/**
|
|
994
|
+
* `["global_config", quote_mint, u8 curve_type, u16 big-endian index]`. The curve settings for one
|
|
995
|
+
* quote asset. Gabox uses curve type 0 (constant product) and index 0.
|
|
996
|
+
*/
|
|
997
|
+
const launchlabGlobalConfig = async (launchlab, quoteMint, curveType = 0, index = 0) => await derive(launchlab, [
|
|
998
|
+
utf8.encode(LAUNCHLAB_SEEDS.globalConfig),
|
|
999
|
+
address$1.encode(quoteMint),
|
|
1000
|
+
u8$1.encode(curveType),
|
|
1001
|
+
u16be.encode(index)
|
|
1002
|
+
]);
|
|
1003
|
+
/** `["platform_config", platform_admin]`. The Gabox platform on LaunchLab. */
|
|
1004
|
+
const launchlabPlatformConfig = async (launchlab, platformAdmin) => await derive(launchlab, [utf8.encode(LAUNCHLAB_SEEDS.platformConfig), address$1.encode(platformAdmin)]);
|
|
1005
|
+
/** `["pool", base_mint, quote_mint]`. The curve pool of one coin. */
|
|
1006
|
+
const launchlabPoolAddress = async (launchlab, baseMint, quoteMint) => await derive(launchlab, [
|
|
1007
|
+
utf8.encode(LAUNCHLAB_SEEDS.pool),
|
|
1008
|
+
address$1.encode(baseMint),
|
|
1009
|
+
address$1.encode(quoteMint)
|
|
1010
|
+
]);
|
|
1011
|
+
/** `["pool_vault", pool_state, mint]`. One side of a curve pool's reserves. */
|
|
1012
|
+
const launchlabVaultAddress = async (launchlab, poolState, mint) => await derive(launchlab, [
|
|
1013
|
+
utf8.encode(LAUNCHLAB_SEEDS.vault),
|
|
1014
|
+
address$1.encode(poolState),
|
|
1015
|
+
address$1.encode(mint)
|
|
1016
|
+
]);
|
|
1017
|
+
/**
|
|
1018
|
+
* `[platform_config, quote_mint]`. LaunchLab pays the platform share of every curve trade here.
|
|
1019
|
+
*
|
|
1020
|
+
* The seeds carry no text prefix. That is Raydium's own derivation, not a mistake.
|
|
1021
|
+
*/
|
|
1022
|
+
const platformFeeVaultAddress = async (launchlab, platformConfig, quoteMint) => await derive(launchlab, [address$1.encode(platformConfig), address$1.encode(quoteMint)]);
|
|
1023
|
+
/**
|
|
1024
|
+
* `[creator, quote_mint]`. LaunchLab pays the creator share of every curve trade here.
|
|
1025
|
+
*
|
|
1026
|
+
* One vault per wallet per quote asset, not one per coin. A creator with several coins collects
|
|
1027
|
+
* all of them with a single `claim_creator_fee`.
|
|
1028
|
+
*/
|
|
1029
|
+
const creatorFeeVaultAddress = async (launchlab, creator, quoteMint) => await derive(launchlab, [address$1.encode(creator), address$1.encode(quoteMint)]);
|
|
1030
|
+
/** `["creator_fee_vault_auth_seed"]`. The authority `claim_creator_fee` signs the payout with. */
|
|
1031
|
+
const creatorFeeVaultAuthority = async (launchlab) => await derive(launchlab, [utf8.encode(LAUNCHLAB_SEEDS.creatorFeeVaultAuthority)]);
|
|
1032
|
+
/** `["metadata", metaplex, mint]` under Metaplex. LaunchLab's create instruction writes it. */
|
|
1033
|
+
const metadataAddress = async (mint) => await derive(METAPLEX_PROGRAM_ADDRESS, [
|
|
1034
|
+
utf8.encode(METADATA_SEED),
|
|
1035
|
+
address$1.encode(METAPLEX_PROGRAM_ADDRESS),
|
|
1036
|
+
address$1.encode(mint)
|
|
1037
|
+
]);
|
|
1038
|
+
/** `["vault_and_lp_mint_auth_seed"]`. */
|
|
1039
|
+
const cpmmAuthority = async (cpmm) => await derive(cpmm, [utf8.encode(CPMM_SEEDS.authority)]);
|
|
1040
|
+
/**
|
|
1041
|
+
* `["pool", amm_config, token_0, token_1]`, with the two mints sorted by raw byte order.
|
|
1042
|
+
*
|
|
1043
|
+
* This is where Raydium migrates a coin when the address is free. It is only a first guess: when the
|
|
1044
|
+
* account is taken, the migration lands somewhere else entirely. And the address alone proves
|
|
1045
|
+
* nothing, because anyone can open a CPMM pool for any pair under any config. `findCpmmPool` tries
|
|
1046
|
+
* this address, checks the account data, and scans for the real pool when it does not match.
|
|
1047
|
+
*
|
|
1048
|
+
* There are no vault or observation derivations here on purpose. `venue/trade.rs` reads those three
|
|
1049
|
+
* addresses out of the pool account, and so does this SDK: a migrated pool names its own vaults and
|
|
1050
|
+
* its own oracle, wherever it sits.
|
|
1051
|
+
*/
|
|
1052
|
+
const cpmmPoolAddress = async (cpmm, ammConfig, mintA, mintB) => {
|
|
1053
|
+
const [token0, token1] = sortedMints(mintA, mintB);
|
|
1054
|
+
return await derive(cpmm, [
|
|
1055
|
+
utf8.encode(CPMM_SEEDS.pool),
|
|
1056
|
+
address$1.encode(ammConfig),
|
|
1057
|
+
address$1.encode(token0),
|
|
1058
|
+
address$1.encode(token1)
|
|
1059
|
+
]);
|
|
1060
|
+
};
|
|
1061
|
+
/**
|
|
1062
|
+
* `["creator_fee_share", creator, amm_config]`. Where CPMM tracks what one creator is owed under
|
|
1063
|
+
* one fee tier.
|
|
1064
|
+
*
|
|
1065
|
+
* `collect_creator_fee` takes this account after the fourteen the mainnet IDL lists. That snapshot
|
|
1066
|
+
* predates the account; Raydium's devnet build declares it and stops with `AccountNotEnoughKeys`
|
|
1067
|
+
* (3005) when it is missing. One account per creator per fee tier, not one per pool.
|
|
1068
|
+
*/
|
|
1069
|
+
const cpmmCreatorFeeShare = async (cpmm, creator, ammConfig) => await derive(cpmm, [
|
|
1070
|
+
utf8.encode(CPMM_SEEDS.creatorFeeShare),
|
|
1071
|
+
address$1.encode(creator),
|
|
1072
|
+
address$1.encode(ammConfig)
|
|
1073
|
+
]);
|
|
1074
|
+
/** An associated token account. Off-curve owners are allowed: pool PDAs are all off-curve. */
|
|
1075
|
+
const ata = async (owner, mint, tokenProgram = TOKEN_PROGRAM_ADDRESS) => await derive(ASSOCIATED_TOKEN_PROGRAM_ADDRESS, [
|
|
1076
|
+
address$1.encode(owner),
|
|
1077
|
+
address$1.encode(tokenProgram),
|
|
1078
|
+
address$1.encode(mint)
|
|
1079
|
+
]);
|
|
1080
|
+
//#endregion
|
|
1081
|
+
//#region src/raydium/adapter.ts
|
|
1082
|
+
/**
|
|
1083
|
+
* Reading Raydium's accounts.
|
|
1084
|
+
*
|
|
1085
|
+
* Every decoder here is written from the field list in the two IDL snapshots under `idl/`. Nothing
|
|
1086
|
+
* is guessed: each struct is laid out in Borsh order with no padding between fields, so the offsets
|
|
1087
|
+
* follow from the field sizes alone. Each decoder checks the account's eight-byte discriminator
|
|
1088
|
+
* first, so a wrong account fails loudly instead of decoding as plausible rubbish.
|
|
1089
|
+
*
|
|
1090
|
+
* `test/raydium-decode.test.ts` builds one byte array per decoder by hand and reads it back.
|
|
1091
|
+
*
|
|
1092
|
+
* Only the fields Gabox needs are decoded. The trailing padding of each account is skipped, which
|
|
1093
|
+
* is also why a longer account than the one recorded here still decodes: Raydium only ever appends
|
|
1094
|
+
* into its own padding.
|
|
1095
|
+
*
|
|
1096
|
+
* Kit's codecs, never `Buffer`. This module is vendored into a Next.js app, and `Buffer` does not
|
|
1097
|
+
* exist in a browser.
|
|
1098
|
+
*/
|
|
1099
|
+
const DISCRIMINATOR = 8;
|
|
1100
|
+
const u8 = getU8Decoder();
|
|
1101
|
+
const u16 = getU16Decoder();
|
|
1102
|
+
const u64$1 = getU64Decoder();
|
|
1103
|
+
const addressAt = getAddressDecoder();
|
|
1104
|
+
const bool = getBooleanDecoder();
|
|
1105
|
+
/** Refuse an account that is too short or carries another type's discriminator. */
|
|
1106
|
+
function checked(data, discriminator, what) {
|
|
1107
|
+
if (data.length < discriminator.length) throw new Error(`not a ${what}: ${data.length} bytes is shorter than a discriminator`);
|
|
1108
|
+
for (const [i, byte] of discriminator.entries()) if (data[i] !== byte) throw new Error(`not a ${what}: the discriminator does not match`);
|
|
1109
|
+
return data;
|
|
1110
|
+
}
|
|
1111
|
+
/** Refuse a truncated account before a decoder reads past its end. */
|
|
1112
|
+
function atLeast(data, size, what) {
|
|
1113
|
+
if (data.length < size) throw new Error(`not a ${what}: ${data.length} bytes, expected at least ${size}`);
|
|
1114
|
+
return data;
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* The `amount` field of an SPL token account: bytes 64 to 72, little-endian.
|
|
1118
|
+
*
|
|
1119
|
+
* Both venues and every Gabox account use classic SPL Token, whose account is 165 bytes. The
|
|
1120
|
+
* Token-2022 layout shares those first 165 bytes, so this reader covers a Token-2022 account too.
|
|
1121
|
+
*/
|
|
1122
|
+
function tokenAccountAmount(data) {
|
|
1123
|
+
if (data.length < 72) throw new Error("not a token account: fewer than 72 bytes");
|
|
1124
|
+
return u64$1.decode(data, 64);
|
|
1125
|
+
}
|
|
1126
|
+
/** The `mint` and `owner` of an SPL token account: bytes 0 to 32 and 32 to 64. */
|
|
1127
|
+
function tokenAccountOwnerAndMint(data) {
|
|
1128
|
+
if (data.length < 64) throw new Error("not a token account: fewer than 64 bytes");
|
|
1129
|
+
return {
|
|
1130
|
+
mint: addressAt.decode(data, 0),
|
|
1131
|
+
owner: addressAt.decode(data, 32)
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
/** Offsets follow the `PoolState` field list in `idl/raydium_launchpad.json`. */
|
|
1135
|
+
const LAUNCHLAB_POOL_HEAD = getStructDecoder([
|
|
1136
|
+
["epoch", u64$1],
|
|
1137
|
+
["authBump", u8],
|
|
1138
|
+
["status", u8],
|
|
1139
|
+
["baseDecimals", u8],
|
|
1140
|
+
["quoteDecimals", u8],
|
|
1141
|
+
["migrateType", u8],
|
|
1142
|
+
["supply", u64$1],
|
|
1143
|
+
["totalBaseSell", u64$1],
|
|
1144
|
+
["virtualBase", u64$1],
|
|
1145
|
+
["virtualQuote", u64$1],
|
|
1146
|
+
["realBase", u64$1],
|
|
1147
|
+
["realQuote", u64$1],
|
|
1148
|
+
["totalQuoteFundRaising", u64$1],
|
|
1149
|
+
["quoteProtocolFee", u64$1],
|
|
1150
|
+
["platformFee", u64$1],
|
|
1151
|
+
["migrateFee", u64$1],
|
|
1152
|
+
["vestingTotalLockedAmount", u64$1],
|
|
1153
|
+
["vestingCliffPeriod", u64$1],
|
|
1154
|
+
["vestingUnlockPeriod", u64$1],
|
|
1155
|
+
["vestingStartTime", u64$1],
|
|
1156
|
+
["vestingAllocatedShareAmount", u64$1],
|
|
1157
|
+
["globalConfig", addressAt],
|
|
1158
|
+
["platformConfig", addressAt],
|
|
1159
|
+
["baseMint", addressAt],
|
|
1160
|
+
["quoteMint", addressAt],
|
|
1161
|
+
["baseVault", addressAt],
|
|
1162
|
+
["quoteVault", addressAt],
|
|
1163
|
+
["creator", addressAt],
|
|
1164
|
+
["tokenProgramFlag", u8],
|
|
1165
|
+
["ammCreatorFeeOn", u8]
|
|
1166
|
+
]);
|
|
1167
|
+
/** The bytes the decoder above reads, discriminator included. The account itself is longer. */
|
|
1168
|
+
const LAUNCHLAB_POOL_STATE_HEAD_SIZE = 367;
|
|
1169
|
+
function decodeLaunchlabPool(data) {
|
|
1170
|
+
checked(data, LAUNCHLAB_POOL_STATE_DISCRIMINATOR, "LaunchLab pool");
|
|
1171
|
+
atLeast(data, 367, "LaunchLab pool");
|
|
1172
|
+
const head = LAUNCHLAB_POOL_HEAD.decode(data, DISCRIMINATOR);
|
|
1173
|
+
return {
|
|
1174
|
+
status: head.status,
|
|
1175
|
+
baseDecimals: head.baseDecimals,
|
|
1176
|
+
quoteDecimals: head.quoteDecimals,
|
|
1177
|
+
migrateType: head.migrateType,
|
|
1178
|
+
supply: head.supply,
|
|
1179
|
+
totalBaseSell: head.totalBaseSell,
|
|
1180
|
+
virtualBase: head.virtualBase,
|
|
1181
|
+
virtualQuote: head.virtualQuote,
|
|
1182
|
+
realBase: head.realBase,
|
|
1183
|
+
realQuote: head.realQuote,
|
|
1184
|
+
totalQuoteFundRaising: head.totalQuoteFundRaising,
|
|
1185
|
+
migrateFee: head.migrateFee,
|
|
1186
|
+
globalConfig: head.globalConfig,
|
|
1187
|
+
platformConfig: head.platformConfig,
|
|
1188
|
+
baseMint: head.baseMint,
|
|
1189
|
+
quoteMint: head.quoteMint,
|
|
1190
|
+
baseVault: head.baseVault,
|
|
1191
|
+
quoteVault: head.quoteVault,
|
|
1192
|
+
creator: head.creator,
|
|
1193
|
+
ammCreatorFeeOn: head.ammCreatorFeeOn
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
const LAUNCHLAB_GLOBAL_CONFIG_HEAD = getStructDecoder([
|
|
1197
|
+
["epoch", u64$1],
|
|
1198
|
+
["curveType", u8],
|
|
1199
|
+
["index", u16],
|
|
1200
|
+
["migrateFee", u64$1],
|
|
1201
|
+
["tradeFeeRate", u64$1],
|
|
1202
|
+
["maxShareFeeRate", u64$1],
|
|
1203
|
+
["minBaseSupply", u64$1],
|
|
1204
|
+
["maxLockRate", u64$1],
|
|
1205
|
+
["minBaseSellRate", u64$1],
|
|
1206
|
+
["minBaseMigrateRate", u64$1],
|
|
1207
|
+
["minQuoteFundRaising", u64$1],
|
|
1208
|
+
["quoteMint", addressAt]
|
|
1209
|
+
]);
|
|
1210
|
+
const LAUNCHLAB_GLOBAL_CONFIG_HEAD_SIZE = 115;
|
|
1211
|
+
function decodeLaunchlabGlobalConfig(data) {
|
|
1212
|
+
checked(data, LAUNCHLAB_GLOBAL_CONFIG_DISCRIMINATOR, "LaunchLab global config");
|
|
1213
|
+
atLeast(data, 115, "LaunchLab global config");
|
|
1214
|
+
const head = LAUNCHLAB_GLOBAL_CONFIG_HEAD.decode(data, DISCRIMINATOR);
|
|
1215
|
+
return {
|
|
1216
|
+
curveType: head.curveType,
|
|
1217
|
+
index: head.index,
|
|
1218
|
+
migrateFee: head.migrateFee,
|
|
1219
|
+
tradeFeeRate: head.tradeFeeRate,
|
|
1220
|
+
quoteMint: head.quoteMint
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* The fixed-size head of `PlatformConfig`, before its `curve_params` vector. The three text fields
|
|
1225
|
+
* are fixed byte arrays, so this is the same length for every platform.
|
|
1226
|
+
*/
|
|
1227
|
+
const LAUNCHLAB_PLATFORM_CONFIG_HEAD = getStructDecoder([
|
|
1228
|
+
["epoch", u64$1],
|
|
1229
|
+
["platformFeeWallet", addressAt],
|
|
1230
|
+
["platformNftWallet", addressAt],
|
|
1231
|
+
["platformScale", u64$1],
|
|
1232
|
+
["creatorScale", u64$1],
|
|
1233
|
+
["burnScale", u64$1],
|
|
1234
|
+
["feeRate", u64$1]
|
|
1235
|
+
]);
|
|
1236
|
+
const PLATFORM_CPSWAP_CONFIG_OFFSET = 688;
|
|
1237
|
+
const PLATFORM_CREATOR_FEE_RATE_OFFSET = 720;
|
|
1238
|
+
const PLATFORM_CP_CREATOR_OFFSET = 800;
|
|
1239
|
+
/** Everything before `curve_params`, discriminator included. */
|
|
1240
|
+
const LAUNCHLAB_PLATFORM_CONFIG_HEAD_SIZE = 940;
|
|
1241
|
+
function decodeLaunchlabPlatformConfig(data) {
|
|
1242
|
+
checked(data, LAUNCHLAB_PLATFORM_CONFIG_DISCRIMINATOR, "LaunchLab platform config");
|
|
1243
|
+
atLeast(data, 940, "LaunchLab platform config");
|
|
1244
|
+
const head = LAUNCHLAB_PLATFORM_CONFIG_HEAD.decode(data, DISCRIMINATOR);
|
|
1245
|
+
return {
|
|
1246
|
+
platformFeeWallet: head.platformFeeWallet,
|
|
1247
|
+
feeRate: head.feeRate,
|
|
1248
|
+
creatorFeeRate: u64$1.decode(data, PLATFORM_CREATOR_FEE_RATE_OFFSET),
|
|
1249
|
+
cpswapConfig: addressAt.decode(data, PLATFORM_CPSWAP_CONFIG_OFFSET),
|
|
1250
|
+
platformCpCreator: addressAt.decode(data, PLATFORM_CP_CREATOR_OFFSET)
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
const CPMM_POOL_HEAD = getStructDecoder([
|
|
1254
|
+
["ammConfig", addressAt],
|
|
1255
|
+
["poolCreator", addressAt],
|
|
1256
|
+
["token0Vault", addressAt],
|
|
1257
|
+
["token1Vault", addressAt],
|
|
1258
|
+
["lpMint", addressAt],
|
|
1259
|
+
["token0Mint", addressAt],
|
|
1260
|
+
["token1Mint", addressAt],
|
|
1261
|
+
["token0Program", addressAt],
|
|
1262
|
+
["token1Program", addressAt],
|
|
1263
|
+
["observationKey", addressAt],
|
|
1264
|
+
["authBump", u8],
|
|
1265
|
+
["status", u8],
|
|
1266
|
+
["lpMintDecimals", u8],
|
|
1267
|
+
["mint0Decimals", u8],
|
|
1268
|
+
["mint1Decimals", u8],
|
|
1269
|
+
["lpSupply", u64$1],
|
|
1270
|
+
["protocolFeesToken0", u64$1],
|
|
1271
|
+
["protocolFeesToken1", u64$1],
|
|
1272
|
+
["fundFeesToken0", u64$1],
|
|
1273
|
+
["fundFeesToken1", u64$1],
|
|
1274
|
+
["openTime", u64$1],
|
|
1275
|
+
["recentEpoch", u64$1],
|
|
1276
|
+
["creatorFeeOn", u8],
|
|
1277
|
+
["enableCreatorFee", bool],
|
|
1278
|
+
["padding1", fixDecoderSize(getBytesDecoder(), 6)],
|
|
1279
|
+
["creatorFeesToken0", u64$1],
|
|
1280
|
+
["creatorFeesToken1", u64$1]
|
|
1281
|
+
]);
|
|
1282
|
+
const CPMM_POOL_STATE_HEAD_SIZE = 413;
|
|
1283
|
+
/**
|
|
1284
|
+
* The full byte length of a CPMM `PoolState`, trailing padding included. A `getProgramAccounts`
|
|
1285
|
+
* scan filters on it, so it has to be the whole account and not only the part decoded above.
|
|
1286
|
+
*/
|
|
1287
|
+
const CPMM_POOL_STATE_SIZE = 637;
|
|
1288
|
+
/**
|
|
1289
|
+
* Byte offsets a `getProgramAccounts` scan matches on. They are the same offsets `venue/trade.rs`
|
|
1290
|
+
* reads the fields at, so a pool that passes the scan passes the program's own check.
|
|
1291
|
+
*/
|
|
1292
|
+
const CPMM_POOL_OFFSETS = {
|
|
1293
|
+
ammConfig: 8,
|
|
1294
|
+
poolCreator: 40,
|
|
1295
|
+
token0Mint: 168,
|
|
1296
|
+
token1Mint: 200,
|
|
1297
|
+
enableCreatorFee: 390
|
|
1298
|
+
};
|
|
1299
|
+
function decodeCpmmPool(data) {
|
|
1300
|
+
checked(data, CPMM_POOL_STATE_DISCRIMINATOR, "CPMM pool");
|
|
1301
|
+
atLeast(data, 413, "CPMM pool");
|
|
1302
|
+
const head = CPMM_POOL_HEAD.decode(data, DISCRIMINATOR);
|
|
1303
|
+
return {
|
|
1304
|
+
ammConfig: head.ammConfig,
|
|
1305
|
+
poolCreator: head.poolCreator,
|
|
1306
|
+
token0Vault: head.token0Vault,
|
|
1307
|
+
token1Vault: head.token1Vault,
|
|
1308
|
+
token0Mint: head.token0Mint,
|
|
1309
|
+
token1Mint: head.token1Mint,
|
|
1310
|
+
token0Program: head.token0Program,
|
|
1311
|
+
token1Program: head.token1Program,
|
|
1312
|
+
observationKey: head.observationKey,
|
|
1313
|
+
status: head.status,
|
|
1314
|
+
mint0Decimals: head.mint0Decimals,
|
|
1315
|
+
mint1Decimals: head.mint1Decimals,
|
|
1316
|
+
protocolFeesToken0: head.protocolFeesToken0,
|
|
1317
|
+
protocolFeesToken1: head.protocolFeesToken1,
|
|
1318
|
+
fundFeesToken0: head.fundFeesToken0,
|
|
1319
|
+
fundFeesToken1: head.fundFeesToken1,
|
|
1320
|
+
creatorFeesToken0: head.creatorFeesToken0,
|
|
1321
|
+
creatorFeesToken1: head.creatorFeesToken1,
|
|
1322
|
+
openTime: head.openTime,
|
|
1323
|
+
creatorFeeOn: head.creatorFeeOn,
|
|
1324
|
+
enableCreatorFee: head.enableCreatorFee
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
const CPMM_AMM_CONFIG_HEAD = getStructDecoder([
|
|
1328
|
+
["bump", u8],
|
|
1329
|
+
["disableCreatePool", bool],
|
|
1330
|
+
["index", u16],
|
|
1331
|
+
["tradeFeeRate", u64$1],
|
|
1332
|
+
["protocolFeeRate", u64$1],
|
|
1333
|
+
["fundFeeRate", u64$1],
|
|
1334
|
+
["createPoolFee", u64$1],
|
|
1335
|
+
["protocolOwner", addressAt],
|
|
1336
|
+
["fundOwner", addressAt],
|
|
1337
|
+
["creatorFeeRate", u64$1]
|
|
1338
|
+
]);
|
|
1339
|
+
const CPMM_AMM_CONFIG_HEAD_SIZE = 116;
|
|
1340
|
+
function decodeCpmmAmmConfig(data) {
|
|
1341
|
+
checked(data, CPMM_AMM_CONFIG_DISCRIMINATOR, "CPMM amm config");
|
|
1342
|
+
atLeast(data, 116, "CPMM amm config");
|
|
1343
|
+
const head = CPMM_AMM_CONFIG_HEAD.decode(data, DISCRIMINATOR);
|
|
1344
|
+
return {
|
|
1345
|
+
index: head.index,
|
|
1346
|
+
disableCreatePool: head.disableCreatePool,
|
|
1347
|
+
tradeFeeRate: head.tradeFeeRate,
|
|
1348
|
+
protocolFeeRate: head.protocolFeeRate,
|
|
1349
|
+
fundFeeRate: head.fundFeeRate,
|
|
1350
|
+
creatorFeeRate: head.creatorFeeRate
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
//#endregion
|
|
1354
|
+
//#region src/compute.ts
|
|
1355
|
+
/**
|
|
1356
|
+
* The two `ComputeBudget` instructions the transaction builders prepend.
|
|
1357
|
+
*
|
|
1358
|
+
* # Why every gabox transaction needs one
|
|
1359
|
+
*
|
|
1360
|
+
* The default budget is 200,000 compute units for the whole transaction. `buy_pack` alone does a
|
|
1361
|
+
* CPI into Raydium (which does its own CPIs into the token program and may create two fee vaults),
|
|
1362
|
+
* a `transfer_checked`, an account init, and a CPI into MagicBlock's VRF program that allocates a
|
|
1363
|
+
* request account. `createMachine` adds LaunchLab's `initialize_v2` to that, which mints the coin
|
|
1364
|
+
* and writes its Metaplex metadata. Neither fits in the default, and nothing on chain can raise its
|
|
1365
|
+
* own budget.
|
|
1366
|
+
*
|
|
1367
|
+
* # The numbers are measured
|
|
1368
|
+
*
|
|
1369
|
+
* Each figure below is about 1.3x the largest `computeUnitsConsumed` the devnet end-to-end test saw
|
|
1370
|
+
* over three runs on 2026-09-18, across both venues. That matters because the prioritisation fee is
|
|
1371
|
+
* `price x requested limit`: a request three times too large costs three times too much on every
|
|
1372
|
+
* pack. A caller may still override any of them.
|
|
1373
|
+
*
|
|
1374
|
+
* The three runs did not agree. A curve pack cost 164,717, then 167,723, then 181,225 units for the
|
|
1375
|
+
* same work, so the venue's own cost moves with state this SDK does not control. The 1.3x margin is
|
|
1376
|
+
* there to absorb that, and the largest of the three is always the basis.
|
|
1377
|
+
*
|
|
1378
|
+
* Re-measure after a change to a builder or to a Raydium program. `test/devnet.e2e.test.ts` prints
|
|
1379
|
+
* `CU <label>: <units> | <signature>` for every transaction it sends, which is where these came
|
|
1380
|
+
* from.
|
|
1381
|
+
*/
|
|
1382
|
+
const COMPUTE_BUDGET_PROGRAM_ADDRESS = "ComputeBudget111111111111111111111111111111";
|
|
1383
|
+
/** The runtime's per-transaction ceiling. A larger request is rejected outright. */
|
|
1384
|
+
const MAX_COMPUTE_UNIT_LIMIT = 14e5;
|
|
1385
|
+
/** What an instruction gets when no `SetComputeUnitLimit` is present. */
|
|
1386
|
+
const DEFAULT_COMPUTE_UNIT_LIMIT = 2e5;
|
|
1387
|
+
/**
|
|
1388
|
+
* LaunchLab `initialize_v2`, the WSOL wrap, pool initialization with a seed buy, and the WSOL
|
|
1389
|
+
* close, in one transaction. Measured at `293,004`, `264,249` and `268,749`. The first run is the
|
|
1390
|
+
* worst case: that creator had no LaunchLab fee vault yet, so the transaction created one.
|
|
1391
|
+
*/
|
|
1392
|
+
const CREATE_MACHINE_COMPUTE_UNITS = 385e3;
|
|
1393
|
+
/**
|
|
1394
|
+
* A WSOL wrap, a venue buy, an escrow transfer, a draw init, a VRF request and the WSOL close.
|
|
1395
|
+
* Measured at `164,717`, `167,723` and `181,225` on the curve, and `116,332`, `113,342` and
|
|
1396
|
+
* `119,334` on CPMM. The curve buy is dearer because a coin's first trade also creates the platform
|
|
1397
|
+
* and creator fee vaults.
|
|
1398
|
+
*/
|
|
1399
|
+
const BUY_PACK_COMPUTE_UNITS = 24e4;
|
|
1400
|
+
/**
|
|
1401
|
+
* A WSOL wrap, a venue sale, and the WSOL close. Measured at `93,827`, `93,827` and `101,327` on
|
|
1402
|
+
* the curve, and `61,041`, `64,041` and `67,041` on CPMM.
|
|
1403
|
+
*/
|
|
1404
|
+
const REDEEM_COMPUTE_UNITS = 135e3;
|
|
1405
|
+
/**
|
|
1406
|
+
* A creator fee claim: create the WSOL account, claim or collect, close it again. Measured at
|
|
1407
|
+
* `31,319` every run for the LaunchLab claim, and `40,959` every run for the CPMM collect.
|
|
1408
|
+
*/
|
|
1409
|
+
const CLAIM_COMPUTE_UNITS = 55e3;
|
|
1410
|
+
/** `ComputeBudgetInstruction`'s discriminants. Positional and append-only upstream. */
|
|
1411
|
+
const SET_COMPUTE_UNIT_LIMIT = 2;
|
|
1412
|
+
const SET_COMPUTE_UNIT_PRICE = 3;
|
|
1413
|
+
/** `[u8 discriminant, u32 units]` — five bytes. */
|
|
1414
|
+
const LIMIT_ENCODER = getStructEncoder([["discriminant", getU8Encoder()], ["units", getU32Encoder()]]);
|
|
1415
|
+
/** `[u8 discriminant, u64 microLamports]` — nine bytes. */
|
|
1416
|
+
const PRICE_ENCODER = getStructEncoder([["discriminant", getU8Encoder()], ["microLamports", getU64Encoder()]]);
|
|
1417
|
+
function getSetComputeUnitLimitInstruction(units) {
|
|
1418
|
+
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}`);
|
|
1419
|
+
return {
|
|
1420
|
+
programAddress: COMPUTE_BUDGET_PROGRAM_ADDRESS,
|
|
1421
|
+
accounts: [],
|
|
1422
|
+
data: LIMIT_ENCODER.encode({
|
|
1423
|
+
discriminant: SET_COMPUTE_UNIT_LIMIT,
|
|
1424
|
+
units
|
|
1425
|
+
})
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* `SetComputeUnitPrice(microLamports)` — the priority fee, per compute unit.
|
|
1430
|
+
*
|
|
1431
|
+
* No default, deliberately. The fee that lands a transaction is a property of the network at the
|
|
1432
|
+
* moment you send it. A hardcoded price is either money burnt on an idle chain or a transaction
|
|
1433
|
+
* that quietly stops landing under load. Sample `getRecentPrioritizationFees`, or take it from
|
|
1434
|
+
* config.
|
|
1435
|
+
*/
|
|
1436
|
+
function getSetComputeUnitPriceInstruction(microLamports) {
|
|
1437
|
+
const price = BigInt(microLamports);
|
|
1438
|
+
if (price < 0n) throw new Error(`compute unit price must not be negative, got ${price}`);
|
|
1439
|
+
return {
|
|
1440
|
+
programAddress: COMPUTE_BUDGET_PROGRAM_ADDRESS,
|
|
1441
|
+
accounts: [],
|
|
1442
|
+
data: PRICE_ENCODER.encode({
|
|
1443
|
+
discriminant: SET_COMPUTE_UNIT_PRICE,
|
|
1444
|
+
microLamports: price
|
|
1445
|
+
})
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
/** The compute budget prefix a builder prepends: a limit, and a price only when one is asked for. */
|
|
1449
|
+
function computeBudgetInstructions(units, microLamports) {
|
|
1450
|
+
const instructions = [getSetComputeUnitLimitInstruction(units)];
|
|
1451
|
+
if (microLamports !== void 0) instructions.push(getSetComputeUnitPriceInstruction(microLamports));
|
|
1452
|
+
return instructions;
|
|
1453
|
+
}
|
|
1454
|
+
//#endregion
|
|
1455
|
+
//#region src/raydium/accounts.ts
|
|
1456
|
+
/**
|
|
1457
|
+
* The ordered account list of every Raydium trade Gabox forwards.
|
|
1458
|
+
*
|
|
1459
|
+
* # Why these are built here
|
|
1460
|
+
*
|
|
1461
|
+
* `venue/trade.rs` accepts four trade instructions and no others: LaunchLab's `buy_exact_out` and
|
|
1462
|
+
* `sell_exact_in`, CPMM's `swap_base_output` and `swap_base_input`. It takes the venue's own
|
|
1463
|
+
* account list as `remaining_accounts` and walks it position by position. So the lists are
|
|
1464
|
+
* assembled here, from `abi.ts`, which codegen generated from the same Raydium IDLs the program's
|
|
1465
|
+
* `venue/abi.rs` came from.
|
|
1466
|
+
*
|
|
1467
|
+
* # How to read the output
|
|
1468
|
+
*
|
|
1469
|
+
* Each function returns `AccountMeta[]` in ABI order, with the role each slot's own ABI gives it.
|
|
1470
|
+
* That list goes into `initialize_pool`, `buy_pack` or `sell_tokens` as `remainingAccounts`,
|
|
1471
|
+
* unchanged. The program then requires every writable slot to be writable in the outer transaction,
|
|
1472
|
+
* requires the one signer slot to be the purchaser, and checks every address by name.
|
|
1473
|
+
*
|
|
1474
|
+
* # CPMM names its accounts by direction
|
|
1475
|
+
*
|
|
1476
|
+
* CPMM calls its four pairs `input_*` and `output_*`. A buy sends quote and receives the coin; a
|
|
1477
|
+
* sell is the other way round. So the two builders below fill the same four names with opposite
|
|
1478
|
+
* addresses. LaunchLab names its accounts by role instead, and uses one list for both sides.
|
|
1479
|
+
*/
|
|
1480
|
+
/**
|
|
1481
|
+
* Turn `{name -> address}` into the ABI's ordered `AccountMeta[]`.
|
|
1482
|
+
*
|
|
1483
|
+
* The map is keyed by the IDL's own account names, so a missing entry names the slot it belongs to
|
|
1484
|
+
* rather than failing as an off-by-one further down. The role comes from the ABI table alone, never
|
|
1485
|
+
* from the caller, which is what keeps this list in step with `venue/abi.rs`.
|
|
1486
|
+
*/
|
|
1487
|
+
function order(abi, byName) {
|
|
1488
|
+
return abi.accounts.map((spec) => {
|
|
1489
|
+
const address = byName[spec.name];
|
|
1490
|
+
if (address === void 0) throw new Error(`${abi.name}: no address given for the "${spec.name}" account`);
|
|
1491
|
+
return {
|
|
1492
|
+
address,
|
|
1493
|
+
role: spec.signer ? spec.writable ? AccountRole.WRITABLE_SIGNER : AccountRole.READONLY_SIGNER : spec.writable ? AccountRole.WRITABLE : AccountRole.READONLY
|
|
1494
|
+
};
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
function launchlabTradeAccounts(abi, input) {
|
|
1498
|
+
return order(abi, {
|
|
1499
|
+
payer: input.user,
|
|
1500
|
+
authority: input.launchlabAuthority,
|
|
1501
|
+
global_config: input.globalConfig,
|
|
1502
|
+
platform_config: input.platformConfig,
|
|
1503
|
+
pool_state: input.poolState,
|
|
1504
|
+
user_base_token: input.userBaseToken,
|
|
1505
|
+
user_quote_token: input.userQuoteToken,
|
|
1506
|
+
base_vault: input.baseVault,
|
|
1507
|
+
quote_vault: input.quoteVault,
|
|
1508
|
+
base_token_mint: input.mint,
|
|
1509
|
+
quote_token_mint: input.quoteMint,
|
|
1510
|
+
base_token_program: TOKEN_PROGRAM_ADDRESS,
|
|
1511
|
+
quote_token_program: TOKEN_PROGRAM_ADDRESS,
|
|
1512
|
+
event_authority: input.launchlabEventAuthority,
|
|
1513
|
+
program: input.launchlab,
|
|
1514
|
+
system_program: SYSTEM_PROGRAM_ADDRESS,
|
|
1515
|
+
platform_fee_vault: input.platformFeeVault,
|
|
1516
|
+
creator_fee_vault: input.creatorFeeVault
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
/** `buy_exact_out`: 18 accounts. The first argument is the coins wanted, the second the cost cap. */
|
|
1520
|
+
const launchlabBuyAccounts = (input) => launchlabTradeAccounts(LAUNCHLAB_BUY_EXACT_OUT, input);
|
|
1521
|
+
/** `sell_exact_in`: the same 18 accounts. `venue/trade.rs` uses one table for both sides. */
|
|
1522
|
+
const launchlabSellAccounts = (input) => launchlabTradeAccounts(LAUNCHLAB_SELL_EXACT_IN, input);
|
|
1523
|
+
/**
|
|
1524
|
+
* `buy_exact_in`: the same 18 accounts again. Gabox never forwards this instruction; a wallet sends
|
|
1525
|
+
* it on its own, to buy the curve out with a fixed spend.
|
|
1526
|
+
*/
|
|
1527
|
+
const launchlabBuyExactInAccounts = (input) => launchlabTradeAccounts(LAUNCHLAB_BUY_EXACT_IN, input);
|
|
1528
|
+
/** A buy sends the quote and receives the coin. A sell is the other way round. */
|
|
1529
|
+
function cpmmTradeAccounts(abi, input, side) {
|
|
1530
|
+
const buying = side === "buy";
|
|
1531
|
+
return order(abi, {
|
|
1532
|
+
payer: input.user,
|
|
1533
|
+
authority: input.cpmmAuthority,
|
|
1534
|
+
amm_config: input.ammConfig,
|
|
1535
|
+
pool_state: input.poolState,
|
|
1536
|
+
input_token_account: buying ? input.userQuoteToken : input.userBaseToken,
|
|
1537
|
+
output_token_account: buying ? input.userBaseToken : input.userQuoteToken,
|
|
1538
|
+
input_vault: buying ? input.quoteVault : input.baseVault,
|
|
1539
|
+
output_vault: buying ? input.baseVault : input.quoteVault,
|
|
1540
|
+
input_token_program: TOKEN_PROGRAM_ADDRESS,
|
|
1541
|
+
output_token_program: TOKEN_PROGRAM_ADDRESS,
|
|
1542
|
+
input_token_mint: buying ? input.quoteMint : input.mint,
|
|
1543
|
+
output_token_mint: buying ? input.mint : input.quoteMint,
|
|
1544
|
+
observation_state: input.observationState
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
/** `swap_base_output`: 13 accounts. Note the argument order is `(max_amount_in, amount_out)`. */
|
|
1548
|
+
const cpmmBuyAccounts = (input) => cpmmTradeAccounts(CPMM_SWAP_BASE_OUTPUT, input, "buy");
|
|
1549
|
+
/** `swap_base_input`: 13 accounts, with the four pairs swapped. */
|
|
1550
|
+
const cpmmSellAccounts = (input) => cpmmTradeAccounts(CPMM_SWAP_BASE_INPUT, input, "sell");
|
|
1551
|
+
//#endregion
|
|
1552
|
+
//#region src/raydium/curve.ts
|
|
1553
|
+
/**
|
|
1554
|
+
* Raydium's own price math, ported to bigint.
|
|
1555
|
+
*
|
|
1556
|
+
* Every function here is a line-by-line port of Raydium's TypeScript, which is itself the same math
|
|
1557
|
+
* their on-chain programs run. The point is that a buyer sees the exact number the venue will
|
|
1558
|
+
* charge, not an estimate: `buy_pack` passes the pack size as an exact-tokens-out amount, and the
|
|
1559
|
+
* program then requires the token delta to equal one pack.
|
|
1560
|
+
*
|
|
1561
|
+
* Sources, read on 2026-09-18:
|
|
1562
|
+
*
|
|
1563
|
+
* - `raydium/launchpad/curve/constantProductCurve.ts` and `curve.ts` (LaunchLab)
|
|
1564
|
+
* - `raydium/cpmm/curve/calculator.ts`, `constantProduct.ts` and `fee.ts` (CPMM)
|
|
1565
|
+
*
|
|
1566
|
+
* Raydium uses `BN`, which truncates toward zero on division. Every value here is a non-negative
|
|
1567
|
+
* bigint, where `/` truncates the same way, so a plain `/` is the faithful port of their `.div()`.
|
|
1568
|
+
* Where they round up they call a ceiling helper, and so does this file. Nothing is approximated
|
|
1569
|
+
* and no step is reordered: a division that happens before a subtraction there happens before it
|
|
1570
|
+
* here, because the two orders give different answers.
|
|
1571
|
+
*
|
|
1572
|
+
* Every rate is out of 1,000,000, not out of 10,000.
|
|
1573
|
+
*/
|
|
1574
|
+
/** `ceilDiv(a, b, denominator)` in Raydium's code: `ceil(a * b / denominator)`. */
|
|
1575
|
+
function ceilDivRate(amount, rate, denominator = RATE_DENOMINATOR) {
|
|
1576
|
+
if (rate === 0n) return 0n;
|
|
1577
|
+
return ceilDiv(amount * rate, denominator);
|
|
1578
|
+
}
|
|
1579
|
+
/** `ceil(numerator / denominator)` for non-negative values. */
|
|
1580
|
+
function ceilDiv(numerator, denominator) {
|
|
1581
|
+
if (denominator === 0n) throw new Error("divide by zero");
|
|
1582
|
+
return (numerator + denominator - 1n) / denominator;
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* The amount before a fee was taken, given the amount after it: `ceil(post * 1e6 / (1e6 - rate))`.
|
|
1586
|
+
*
|
|
1587
|
+
* Raydium calls this `calculatePreFee` on the curve and `calculatePreFeeAmount` on CPMM. Both are
|
|
1588
|
+
* the same expression.
|
|
1589
|
+
*/
|
|
1590
|
+
function preFeeAmount(postFeeAmount, rate) {
|
|
1591
|
+
if (rate === 0n) return postFeeAmount;
|
|
1592
|
+
if (rate >= 1000000n) throw new Error("a fee rate of 100% or more has no pre-fee amount");
|
|
1593
|
+
return ceilDiv(postFeeAmount * RATE_DENOMINATOR, RATE_DENOMINATOR - rate);
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* The whole fee a curve trade pays. Gabox never passes a share-fee receiver, so the fourth rate
|
|
1597
|
+
* Raydium supports is always zero and is left out here.
|
|
1598
|
+
*/
|
|
1599
|
+
function totalCurveFeeRate(rates) {
|
|
1600
|
+
const total = rates.tradeFeeRate + rates.platformFeeRate + rates.creatorFeeRate;
|
|
1601
|
+
if (total > 1000000n) throw new Error("the total curve fee rate exceeds 1,000,000");
|
|
1602
|
+
return total;
|
|
1603
|
+
}
|
|
1604
|
+
/** Coins the curve still has to sell. A buy cannot take more than this. */
|
|
1605
|
+
function remainingBase(pool) {
|
|
1606
|
+
return pool.totalBaseSell > pool.realBase ? pool.totalBaseSell - pool.realBase : 0n;
|
|
1607
|
+
}
|
|
1608
|
+
/** The two sides of the constant product, as the curve sees them right now. */
|
|
1609
|
+
const quoteSide = (pool) => pool.virtualQuote + pool.realQuote;
|
|
1610
|
+
const baseSide = (pool) => pool.virtualBase - pool.realBase;
|
|
1611
|
+
/**
|
|
1612
|
+
* Quote needed to buy exactly `tokens` coins on the curve, fees included.
|
|
1613
|
+
*
|
|
1614
|
+
* This is `Curve.buyExactOut` with no token transfer fee, which is always the case for Gabox: both
|
|
1615
|
+
* mints are classic SPL Token and carry no transfer-fee extension.
|
|
1616
|
+
*
|
|
1617
|
+
* Raydium caps the amount at what the curve has left to sell and still returns a price, so this
|
|
1618
|
+
* does the same. A caller that needs the whole `tokens` must check `remainingBase` first;
|
|
1619
|
+
* `resolveVenue` does.
|
|
1620
|
+
*/
|
|
1621
|
+
function curveBuyExactOut(pool, rates, tokens) {
|
|
1622
|
+
if (tokens <= 0n) throw new Error("tokens must be positive");
|
|
1623
|
+
const remaining = remainingBase(pool);
|
|
1624
|
+
const amount = tokens > remaining ? remaining : tokens;
|
|
1625
|
+
if (amount === 0n) throw new Error("the curve has no coins left to sell");
|
|
1626
|
+
return preFeeAmount(curveQuoteInForBaseOut(pool, amount), totalCurveFeeRate(rates));
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Coins `quote` buys on the curve right now, fees already taken off the spend.
|
|
1630
|
+
*
|
|
1631
|
+
* This is `Curve.buyExactIn`. It is what a raw `buy_exact_in` pays out, which the devnet end-to-end
|
|
1632
|
+
* test uses to push a curve to graduation.
|
|
1633
|
+
*/
|
|
1634
|
+
function curveBuyExactIn(pool, rates, quote) {
|
|
1635
|
+
if (quote <= 0n) throw new Error("quote must be positive");
|
|
1636
|
+
const spend = quote - ceilDivRate(quote, totalCurveFeeRate(rates));
|
|
1637
|
+
if (spend <= 0n) throw new Error("the whole spend would go to fees");
|
|
1638
|
+
const tokens = curveBaseOutForQuoteIn(pool, spend);
|
|
1639
|
+
const remaining = remainingBase(pool);
|
|
1640
|
+
return tokens > remaining ? remaining : tokens;
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Quote a sale of exactly `tokens` coins returns, net of fees. This is `Curve.sellExactIn`.
|
|
1644
|
+
*/
|
|
1645
|
+
function curveSellExactIn(pool, rates, tokens) {
|
|
1646
|
+
if (tokens <= 0n) throw new Error("tokens must be positive");
|
|
1647
|
+
const gross = curveQuoteOutForBaseIn(pool, tokens);
|
|
1648
|
+
const fee = ceilDivRate(gross, totalCurveFeeRate(rates));
|
|
1649
|
+
return gross > fee ? gross - fee : 0n;
|
|
1650
|
+
}
|
|
1651
|
+
/** `getAmountIn` on the buy side: `ceil(quoteSide * out / (baseSide - out))`. */
|
|
1652
|
+
function curveQuoteInForBaseOut(pool, tokens) {
|
|
1653
|
+
const output = baseSide(pool);
|
|
1654
|
+
if (tokens >= output) throw new Error("the curve does not hold that many coins");
|
|
1655
|
+
return ceilDiv(quoteSide(pool) * tokens, output - tokens);
|
|
1656
|
+
}
|
|
1657
|
+
/** `getAmountOut` on the buy side: `floor(in * baseSide / (quoteSide + in))`. */
|
|
1658
|
+
function curveBaseOutForQuoteIn(pool, quote) {
|
|
1659
|
+
return quote * baseSide(pool) / (quoteSide(pool) + quote);
|
|
1660
|
+
}
|
|
1661
|
+
/** `getAmountOut` on the sell side: `floor(in * quoteSide / (baseSide + in))`. */
|
|
1662
|
+
function curveQuoteOutForBaseIn(pool, tokens) {
|
|
1663
|
+
return tokens * quoteSide(pool) / (baseSide(pool) + tokens);
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* The virtual reserves LaunchLab gives a coin that does not exist yet.
|
|
1667
|
+
*
|
|
1668
|
+
* This is `LaunchConstantProductCurve.getInitParam`. `createMachine` needs it because the seed buy
|
|
1669
|
+
* runs in the same transaction that creates the coin: there is no pool account to read yet, and the
|
|
1670
|
+
* reserves follow from the pinned launch shape alone.
|
|
1671
|
+
*
|
|
1672
|
+
* Checked against Raydium's own published numbers for the mainnet Gabox launch shape (supply
|
|
1673
|
+
* 1,000,000,000 coins, 793,100,000 sold on the curve, 85 SOL raised, no migrate fee):
|
|
1674
|
+
* `virtualBase` 1,073,025,605,596,382 and `virtualQuote` 30,000,852,951.
|
|
1675
|
+
*/
|
|
1676
|
+
function initialCurve(params) {
|
|
1677
|
+
const { supply, totalBaseSell, quoteRaise } = params;
|
|
1678
|
+
const locked = params.totalLockedAmount ?? 0n;
|
|
1679
|
+
const migrateFee = params.migrateFee ?? 0n;
|
|
1680
|
+
if (supply <= totalBaseSell) throw new Error("the supply must be larger than the curve sells");
|
|
1681
|
+
const migrateBase = supply - totalBaseSell - locked;
|
|
1682
|
+
if (migrateBase <= 0n) throw new Error("nothing would be left to seed the pool at graduation");
|
|
1683
|
+
const raiseLessMigrateFee = quoteRaise - migrateFee;
|
|
1684
|
+
if (raiseLessMigrateFee <= 0n) throw new Error("the migrate fee eats the whole raise");
|
|
1685
|
+
const numerator = raiseLessMigrateFee * totalBaseSell * totalBaseSell / migrateBase;
|
|
1686
|
+
const denominator = raiseLessMigrateFee * totalBaseSell / migrateBase - quoteRaise;
|
|
1687
|
+
if (denominator <= 0n) throw new Error("these launch parameters have no valid starting price");
|
|
1688
|
+
return {
|
|
1689
|
+
virtualBase: numerator / denominator,
|
|
1690
|
+
virtualQuote: quoteRaise * quoteRaise / denominator,
|
|
1691
|
+
realBase: 0n,
|
|
1692
|
+
realQuote: 0n,
|
|
1693
|
+
totalBaseSell
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Quote a CPMM buy of exactly `tokens` coins costs, both fees included.
|
|
1698
|
+
*
|
|
1699
|
+
* This is `CurveCalculator.swapBaseOutput`, which is what `swap_base_output` charges.
|
|
1700
|
+
*/
|
|
1701
|
+
function cpmmSwapBaseOutput(sides, rates, amountOut) {
|
|
1702
|
+
if (amountOut <= 0n) throw new Error("amountOut must be positive");
|
|
1703
|
+
const actualOut = rates.creatorFeeOnInput ? amountOut : preFeeAmount(amountOut, rates.creatorFeeRate);
|
|
1704
|
+
if (actualOut >= sides.outputReserve) throw new Error("the pool does not hold that much of the output token");
|
|
1705
|
+
return preFeeAmount(ceilDiv(sides.inputReserve * actualOut, sides.outputReserve - actualOut), rates.creatorFeeOnInput ? rates.tradeFeeRate + rates.creatorFeeRate : rates.tradeFeeRate);
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* What a CPMM swap of exactly `amountIn` returns, net of both fees.
|
|
1709
|
+
*
|
|
1710
|
+
* This is `CurveCalculator.swapBaseInput`, which is what `swap_base_input` pays out.
|
|
1711
|
+
*/
|
|
1712
|
+
function cpmmSwapBaseInput(sides, rates, amountIn) {
|
|
1713
|
+
if (amountIn <= 0n) throw new Error("amountIn must be positive");
|
|
1714
|
+
const tradeFee = ceilDivRate(amountIn, rates.tradeFeeRate);
|
|
1715
|
+
const creatorFeeIn = rates.creatorFeeOnInput ? ceilDivRate(amountIn, rates.creatorFeeRate) : 0n;
|
|
1716
|
+
const lessFees = amountIn - tradeFee - creatorFeeIn;
|
|
1717
|
+
if (lessFees <= 0n) throw new Error("the whole input would go to fees");
|
|
1718
|
+
const swapped = lessFees * sides.outputReserve / (sides.inputReserve + lessFees);
|
|
1719
|
+
if (rates.creatorFeeOnInput) return swapped;
|
|
1720
|
+
const creatorFeeOut = ceilDivRate(swapped, rates.creatorFeeRate);
|
|
1721
|
+
return swapped > creatorFeeOut ? swapped - creatorFeeOut : 0n;
|
|
1722
|
+
}
|
|
1723
|
+
//#endregion
|
|
1724
|
+
//#region src/raydium/venue.ts
|
|
1725
|
+
/**
|
|
1726
|
+
* Reading the venue: which one a coin trades on right now, what a pack costs there, and the account
|
|
1727
|
+
* list for the trade.
|
|
1728
|
+
*
|
|
1729
|
+
* `resolveVenue` reads every account the answer depends on, decodes them, and hands back a plain
|
|
1730
|
+
* object whose methods are pure. A caller that wants to re-price a different token count calls
|
|
1731
|
+
* `quoteBuy` again with no further reads.
|
|
1732
|
+
*
|
|
1733
|
+
* # Choosing the venue
|
|
1734
|
+
*
|
|
1735
|
+
* The LaunchLab pool's `status` is the whole rule:
|
|
1736
|
+
*
|
|
1737
|
+
* - `0`: the curve is still selling, so trade LaunchLab.
|
|
1738
|
+
* - `1`: the raise is finished and Raydium's own bot is moving the coin into a CPMM pool. Nothing
|
|
1739
|
+
* trades. This throws, and the message says to retry in a moment. On devnet the move usually
|
|
1740
|
+
* finishes within a minute.
|
|
1741
|
+
* - `2`: the coin graduated, so trade its CPMM pool.
|
|
1742
|
+
*
|
|
1743
|
+
* A coin can graduate between the read and the send. A pack buy then fails and no draw is created,
|
|
1744
|
+
* which is the intended outcome: refresh and retry.
|
|
1745
|
+
*
|
|
1746
|
+
* # Finding the migrated CPMM pool
|
|
1747
|
+
*
|
|
1748
|
+
* The pool is normally at `["pool", cpswap_config, token_0, token_1]` under CPMM, where
|
|
1749
|
+
* `cpswap_config` is the fee tier the Gabox platform config names. It is not always: when that
|
|
1750
|
+
* address is already taken, Raydium migrates into a random account instead. So this module tries
|
|
1751
|
+
* the derived address first and falls back to a filtered `getProgramAccounts` scan.
|
|
1752
|
+
*
|
|
1753
|
+
* Either way the pool has to prove itself from its own data, which is what `venue/trade.rs` checks
|
|
1754
|
+
* on chain as well: CPMM owns it, it carries the `PoolState` discriminator, its `pool_creator` is
|
|
1755
|
+
* the coin creator, its two mints are the sorted pair, and `enable_creator_fee` is true. That last
|
|
1756
|
+
* flag is the one that cannot be forged. A plain CPMM `initialize` always leaves it false; only a
|
|
1757
|
+
* creator Raydium has permissioned, such as LaunchLab's migration, can set it.
|
|
1758
|
+
*/
|
|
1759
|
+
const base64$1 = getBase64Encoder();
|
|
1760
|
+
async function readAccounts(rpc, addresses) {
|
|
1761
|
+
const { value } = await rpc.getMultipleAccounts(addresses, {
|
|
1762
|
+
encoding: "base64",
|
|
1763
|
+
commitment: "confirmed"
|
|
1764
|
+
}).send();
|
|
1765
|
+
return value.map((account) => {
|
|
1766
|
+
if (!account) return null;
|
|
1767
|
+
const [encoded] = account.data;
|
|
1768
|
+
return {
|
|
1769
|
+
data: new Uint8Array(base64$1.encode(encoded)),
|
|
1770
|
+
owner: account.owner
|
|
1771
|
+
};
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* The user's WSOL associated token account.
|
|
1776
|
+
*
|
|
1777
|
+
* Both venues settle in WSOL, never in native SOL. This account must exist and hold enough before a
|
|
1778
|
+
* pack is bought, and sale proceeds land in it. Every builder in `tx/` wraps the SOL it needs into
|
|
1779
|
+
* this account and closes it again in the same transaction.
|
|
1780
|
+
*/
|
|
1781
|
+
async function wsolAccountFor(user) {
|
|
1782
|
+
return await ata(user, WSOL_MINT);
|
|
1783
|
+
}
|
|
1784
|
+
/** Read the two pinned config accounts and take the four numbers a quote needs out of them. */
|
|
1785
|
+
async function fetchCurveSettings(client, ids = raydiumIds(client.cluster)) {
|
|
1786
|
+
const [globalAccount, platformAccount] = await readAccounts(client.rpc, [ids.solGlobalConfig, ids.gaboxPlatform]);
|
|
1787
|
+
if (!globalAccount) throw new Error(`LaunchLab's WSOL global config is missing at ${ids.solGlobalConfig}`);
|
|
1788
|
+
if (!platformAccount) throw new Error(`the Gabox platform config is missing at ${ids.gaboxPlatform}`);
|
|
1789
|
+
return settingsFrom(globalAccount.data, platformAccount.data);
|
|
1790
|
+
}
|
|
1791
|
+
function settingsFrom(globalData, platformData) {
|
|
1792
|
+
const global = decodeLaunchlabGlobalConfig(globalData);
|
|
1793
|
+
const platform = decodeLaunchlabPlatformConfig(platformData);
|
|
1794
|
+
return {
|
|
1795
|
+
rates: {
|
|
1796
|
+
tradeFeeRate: global.tradeFeeRate,
|
|
1797
|
+
platformFeeRate: platform.feeRate,
|
|
1798
|
+
creatorFeeRate: platform.creatorFeeRate
|
|
1799
|
+
},
|
|
1800
|
+
migrateFee: global.migrateFee,
|
|
1801
|
+
cpswapConfig: platform.cpswapConfig
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
async function resolveVenue(client, options) {
|
|
1805
|
+
const ids = raydiumIds(client.cluster);
|
|
1806
|
+
const { mint, user } = options;
|
|
1807
|
+
const quoteMint = WSOL_MINT;
|
|
1808
|
+
const curvePool = await launchlabPoolAddress(ids.launchlab, mint, quoteMint);
|
|
1809
|
+
const [poolAccount, globalAccount, platformAccount] = await readAccounts(client.rpc, [
|
|
1810
|
+
curvePool,
|
|
1811
|
+
ids.solGlobalConfig,
|
|
1812
|
+
ids.gaboxPlatform
|
|
1813
|
+
]);
|
|
1814
|
+
if (!poolAccount) throw new Error(`no LaunchLab pool at ${curvePool}. The coin was not launched on LaunchLab with WSOL as its quote, so it has no Gabox venue.`);
|
|
1815
|
+
if (!globalAccount) throw new Error(`LaunchLab's WSOL global config is missing at ${ids.solGlobalConfig}`);
|
|
1816
|
+
if (!platformAccount) throw new Error(`the Gabox platform config is missing at ${ids.gaboxPlatform}`);
|
|
1817
|
+
const pool = decodeLaunchlabPool(poolAccount.data);
|
|
1818
|
+
if (pool.baseMint !== mint || pool.quoteMint !== quoteMint) throw new Error(`the LaunchLab pool at ${curvePool} is not the pool of ${mint}`);
|
|
1819
|
+
const settings = settingsFrom(globalAccount.data, platformAccount.data);
|
|
1820
|
+
const kind = options.venue ?? (pool.status === 2 ? "cpmm" : "launchlab");
|
|
1821
|
+
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.`);
|
|
1822
|
+
if (kind === "launchlab") {
|
|
1823
|
+
if (pool.status !== 0) throw new Error(`${mint} no longer trades on its LaunchLab curve (status ${pool.status}). Use the CPMM venue.`);
|
|
1824
|
+
return await launchlabVenue(ids, pool, settings.rates, {
|
|
1825
|
+
mint,
|
|
1826
|
+
quoteMint,
|
|
1827
|
+
user,
|
|
1828
|
+
poolState: curvePool
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
return await cpmmVenue(client, ids, pool, settings.cpswapConfig, {
|
|
1832
|
+
mint,
|
|
1833
|
+
quoteMint,
|
|
1834
|
+
user
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
async function launchlabVenue(ids, pool, rates, where) {
|
|
1838
|
+
const { mint, quoteMint, user, poolState } = where;
|
|
1839
|
+
const shared = {
|
|
1840
|
+
launchlab: ids.launchlab,
|
|
1841
|
+
launchlabAuthority: ids.launchlabAuthority,
|
|
1842
|
+
launchlabEventAuthority: ids.launchlabEventAuthority,
|
|
1843
|
+
globalConfig: ids.solGlobalConfig,
|
|
1844
|
+
platformConfig: ids.gaboxPlatform,
|
|
1845
|
+
poolState,
|
|
1846
|
+
mint,
|
|
1847
|
+
quoteMint,
|
|
1848
|
+
baseVault: await launchlabVaultAddress(ids.launchlab, poolState, mint),
|
|
1849
|
+
quoteVault: await launchlabVaultAddress(ids.launchlab, poolState, quoteMint),
|
|
1850
|
+
user,
|
|
1851
|
+
userBaseToken: await ata(user, mint),
|
|
1852
|
+
userQuoteToken: await ata(user, quoteMint),
|
|
1853
|
+
platformFeeVault: await platformFeeVaultAddress(ids.launchlab, ids.gaboxPlatform, quoteMint),
|
|
1854
|
+
creatorFeeVault: await creatorFeeVaultAddress(ids.launchlab, pool.creator, quoteMint)
|
|
1855
|
+
};
|
|
1856
|
+
const reserves = {
|
|
1857
|
+
virtualBase: pool.virtualBase,
|
|
1858
|
+
virtualQuote: pool.virtualQuote,
|
|
1859
|
+
realBase: pool.realBase,
|
|
1860
|
+
realQuote: pool.realQuote,
|
|
1861
|
+
totalBaseSell: pool.totalBaseSell
|
|
1862
|
+
};
|
|
1863
|
+
const remaining = remainingBase(reserves);
|
|
1864
|
+
return {
|
|
1865
|
+
kind: "launchlab",
|
|
1866
|
+
program: ids.launchlab,
|
|
1867
|
+
mint,
|
|
1868
|
+
quoteMint,
|
|
1869
|
+
poolState,
|
|
1870
|
+
status: pool.status,
|
|
1871
|
+
creator: pool.creator,
|
|
1872
|
+
ammConfig: null,
|
|
1873
|
+
remainingCurveBase: remaining,
|
|
1874
|
+
buyAccounts: launchlabBuyAccounts(shared),
|
|
1875
|
+
sellAccounts: launchlabSellAccounts(shared),
|
|
1876
|
+
quoteBuy: (tokens) => {
|
|
1877
|
+
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.`);
|
|
1878
|
+
return curveBuyExactOut(reserves, rates, tokens);
|
|
1879
|
+
},
|
|
1880
|
+
quoteSell: (tokens) => curveSellExactIn(reserves, rates, tokens)
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Every check the program makes on a CPMM pool account, in one place.
|
|
1885
|
+
*
|
|
1886
|
+
* `enable_creator_fee` is the one that identifies the migrated pool. Anyone can call CPMM's own
|
|
1887
|
+
* `initialize` and open a pool for the same pair with the same creator, but that pool always has
|
|
1888
|
+
* the flag false. Only a creator Raydium has permissioned can set it, and LaunchLab's migration is
|
|
1889
|
+
* one of those.
|
|
1890
|
+
*/
|
|
1891
|
+
function cpmmPoolMatches(pool, expect) {
|
|
1892
|
+
return pool.enableCreatorFee && pool.poolCreator === expect.creator && pool.token0Mint === expect.token0 && pool.token1Mint === expect.token1;
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* The CPMM pool LaunchLab migrated a coin into.
|
|
1896
|
+
*
|
|
1897
|
+
* Two steps, because the pool is not always at a derived address. Raydium migrates into
|
|
1898
|
+
* `["pool", cpswap_config, token_0, token_1]` when that account is free, and into a random account
|
|
1899
|
+
* when it is not. So: try the derived address, and scan for the pool when it does not hold one.
|
|
1900
|
+
*
|
|
1901
|
+
* The scan filters on the account length and on the three fields at their fixed offsets, so the
|
|
1902
|
+
* RPC does the work and returns at most a handful of accounts. Every candidate still has to pass
|
|
1903
|
+
* `cpmmPoolMatches`, and more than one match is an error rather than a guess.
|
|
1904
|
+
*/
|
|
1905
|
+
async function findCpmmPool(client, options) {
|
|
1906
|
+
const ids = raydiumIds(client.cluster);
|
|
1907
|
+
const { mint, creator, cpswapConfig } = options;
|
|
1908
|
+
const quoteMint = options.quoteMint ?? "So11111111111111111111111111111111111111112";
|
|
1909
|
+
const [token0, token1] = sortedMints(mint, quoteMint);
|
|
1910
|
+
const expect = {
|
|
1911
|
+
creator,
|
|
1912
|
+
token0,
|
|
1913
|
+
token1
|
|
1914
|
+
};
|
|
1915
|
+
const derived = await cpmmPoolAddress(ids.cpmm, cpswapConfig, mint, quoteMint);
|
|
1916
|
+
const [account] = await readAccounts(client.rpc, [derived]);
|
|
1917
|
+
if (account && account.owner === ids.cpmm) {
|
|
1918
|
+
const pool = decodeCpmmPoolOrNull(account.data);
|
|
1919
|
+
if (pool && cpmmPoolMatches(pool, expect)) return {
|
|
1920
|
+
address: derived,
|
|
1921
|
+
pool
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
const rows = await client.rpc.getProgramAccounts(ids.cpmm, {
|
|
1925
|
+
encoding: "base64",
|
|
1926
|
+
commitment: "confirmed",
|
|
1927
|
+
filters: [
|
|
1928
|
+
{ dataSize: BigInt(637) },
|
|
1929
|
+
memcmp(CPMM_POOL_OFFSETS.poolCreator, creator),
|
|
1930
|
+
memcmp(CPMM_POOL_OFFSETS.token0Mint, token0),
|
|
1931
|
+
memcmp(CPMM_POOL_OFFSETS.token1Mint, token1)
|
|
1932
|
+
]
|
|
1933
|
+
}).send();
|
|
1934
|
+
const matches = [];
|
|
1935
|
+
for (const { pubkey, account: row } of rows) {
|
|
1936
|
+
const pool = decodeCpmmPoolOrNull(new Uint8Array(base64$1.encode(row.data[0])));
|
|
1937
|
+
if (pool && cpmmPoolMatches(pool, expect)) matches.push({
|
|
1938
|
+
address: pubkey,
|
|
1939
|
+
pool
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
const [only] = matches;
|
|
1943
|
+
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.`);
|
|
1944
|
+
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.`);
|
|
1945
|
+
return only;
|
|
1946
|
+
}
|
|
1947
|
+
/** A `getProgramAccounts` byte filter. The bytes of an address are its own base58 text. */
|
|
1948
|
+
function memcmp(offset, value) {
|
|
1949
|
+
return { memcmp: {
|
|
1950
|
+
offset: BigInt(offset),
|
|
1951
|
+
bytes: value,
|
|
1952
|
+
encoding: "base58"
|
|
1953
|
+
} };
|
|
1954
|
+
}
|
|
1955
|
+
/** Decode a candidate account, or give up quietly. A scan row that is not a pool is not an error. */
|
|
1956
|
+
function decodeCpmmPoolOrNull(data) {
|
|
1957
|
+
try {
|
|
1958
|
+
return decodeCpmmPool(data);
|
|
1959
|
+
} catch {
|
|
1960
|
+
return null;
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
async function cpmmVenue(client, ids, curve, cpswapConfig, where) {
|
|
1964
|
+
const { mint, quoteMint, user } = where;
|
|
1965
|
+
const { address: poolState, pool } = await findCpmmPool(client, {
|
|
1966
|
+
mint,
|
|
1967
|
+
quoteMint,
|
|
1968
|
+
creator: curve.creator,
|
|
1969
|
+
cpswapConfig
|
|
1970
|
+
});
|
|
1971
|
+
const baseIsToken0 = pool.token0Mint === mint;
|
|
1972
|
+
const baseVault = baseIsToken0 ? pool.token0Vault : pool.token1Vault;
|
|
1973
|
+
const quoteVault = baseIsToken0 ? pool.token1Vault : pool.token0Vault;
|
|
1974
|
+
const [configAccount, baseVaultAccount, quoteVaultAccount] = await readAccounts(client.rpc, [
|
|
1975
|
+
pool.ammConfig,
|
|
1976
|
+
baseVault,
|
|
1977
|
+
quoteVault
|
|
1978
|
+
]);
|
|
1979
|
+
if (!configAccount) throw new Error(`the CPMM pool at ${poolState} names a fee tier that is not on chain`);
|
|
1980
|
+
if (!baseVaultAccount || !quoteVaultAccount) throw new Error(`the CPMM pool at ${poolState} has no reserve accounts`);
|
|
1981
|
+
const config = decodeCpmmAmmConfig(configAccount.data);
|
|
1982
|
+
const owedBase = baseIsToken0 ? pool.protocolFeesToken0 + pool.fundFeesToken0 + pool.creatorFeesToken0 : pool.protocolFeesToken1 + pool.fundFeesToken1 + pool.creatorFeesToken1;
|
|
1983
|
+
const owedQuote = baseIsToken0 ? pool.protocolFeesToken1 + pool.fundFeesToken1 + pool.creatorFeesToken1 : pool.protocolFeesToken0 + pool.fundFeesToken0 + pool.creatorFeesToken0;
|
|
1984
|
+
const baseReserve = tokenAccountAmount(baseVaultAccount.data) - owedBase;
|
|
1985
|
+
const quoteReserve = tokenAccountAmount(quoteVaultAccount.data) - owedQuote;
|
|
1986
|
+
if (baseReserve <= 0n || quoteReserve <= 0n) throw new Error(`the CPMM pool at ${poolState} has no tradable reserves`);
|
|
1987
|
+
const shared = {
|
|
1988
|
+
cpmmAuthority: ids.cpmmAuthority,
|
|
1989
|
+
ammConfig: pool.ammConfig,
|
|
1990
|
+
poolState,
|
|
1991
|
+
mint,
|
|
1992
|
+
quoteMint,
|
|
1993
|
+
baseVault,
|
|
1994
|
+
quoteVault,
|
|
1995
|
+
observationState: pool.observationKey,
|
|
1996
|
+
user,
|
|
1997
|
+
userBaseToken: await ata(user, mint),
|
|
1998
|
+
userQuoteToken: await ata(user, quoteMint)
|
|
1999
|
+
};
|
|
2000
|
+
const creatorFeeRate = pool.enableCreatorFee ? config.creatorFeeRate : 0n;
|
|
2001
|
+
const buySides = {
|
|
2002
|
+
inputReserve: quoteReserve,
|
|
2003
|
+
outputReserve: baseReserve
|
|
2004
|
+
};
|
|
2005
|
+
const sellSides = {
|
|
2006
|
+
inputReserve: baseReserve,
|
|
2007
|
+
outputReserve: quoteReserve
|
|
2008
|
+
};
|
|
2009
|
+
const buyRates = {
|
|
2010
|
+
tradeFeeRate: config.tradeFeeRate,
|
|
2011
|
+
creatorFeeRate,
|
|
2012
|
+
creatorFeeOnInput: creatorFeeOnInput(pool, quoteMint)
|
|
2013
|
+
};
|
|
2014
|
+
const sellRates = {
|
|
2015
|
+
tradeFeeRate: config.tradeFeeRate,
|
|
2016
|
+
creatorFeeRate,
|
|
2017
|
+
creatorFeeOnInput: creatorFeeOnInput(pool, mint)
|
|
2018
|
+
};
|
|
2019
|
+
return {
|
|
2020
|
+
kind: "cpmm",
|
|
2021
|
+
program: ids.cpmm,
|
|
2022
|
+
mint,
|
|
2023
|
+
quoteMint,
|
|
2024
|
+
poolState,
|
|
2025
|
+
status: curve.status,
|
|
2026
|
+
creator: pool.poolCreator,
|
|
2027
|
+
ammConfig: pool.ammConfig,
|
|
2028
|
+
remainingCurveBase: 0n,
|
|
2029
|
+
buyAccounts: cpmmBuyAccounts(shared),
|
|
2030
|
+
sellAccounts: cpmmSellAccounts(shared),
|
|
2031
|
+
quoteBuy: (tokens) => cpmmSwapBaseOutput(buySides, buyRates, tokens),
|
|
2032
|
+
quoteSell: (tokens) => cpmmSwapBaseInput(sellSides, sellRates, tokens)
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* Does this side's creator fee come off the token going in?
|
|
2037
|
+
*
|
|
2038
|
+
* `creator_fee_on` is 0 when the fee follows the input token, 1 when it is always token 0, and 2
|
|
2039
|
+
* when it is always token 1. LaunchLab migrates a Gabox coin with `AmmCreatorFeeOn::QuoteToken`, so
|
|
2040
|
+
* the pool charges the creator fee in WSOL only: on the input of a buy, on the output of a sale.
|
|
2041
|
+
*/
|
|
2042
|
+
function creatorFeeOnInput(pool, inputMint) {
|
|
2043
|
+
if (pool.creatorFeeOn === 0) return true;
|
|
2044
|
+
const inputIsToken0 = pool.token0Mint === inputMint;
|
|
2045
|
+
return pool.creatorFeeOn === 1 ? inputIsToken0 : !inputIsToken0;
|
|
2046
|
+
}
|
|
2047
|
+
/**
|
|
2048
|
+
* WSOL an exact-coins-out buy of `tokens` costs right now, on whichever venue the coin trades.
|
|
2049
|
+
* Pass `pool.packTokens` for the pack price.
|
|
2050
|
+
*
|
|
2051
|
+
* Use `resolveVenue` when you also need the account list, which every transaction builder does.
|
|
2052
|
+
* This is for a price display.
|
|
2053
|
+
*/
|
|
2054
|
+
async function curveQuote(client, mint, tokens, options = {}) {
|
|
2055
|
+
return (await resolveVenue(client, {
|
|
2056
|
+
mint,
|
|
2057
|
+
user: options.user ?? mint,
|
|
2058
|
+
...options.venue ? { venue: options.venue } : {}
|
|
2059
|
+
})).quoteBuy(tokens);
|
|
2060
|
+
}
|
|
2061
|
+
/** WSOL a sale of `tokens` returns right now, net of the venue's fees. */
|
|
2062
|
+
async function sellQuote(client, mint, tokens, options = {}) {
|
|
2063
|
+
return (await resolveVenue(client, {
|
|
2064
|
+
mint,
|
|
2065
|
+
user: options.user ?? mint,
|
|
2066
|
+
...options.venue ? { venue: options.venue } : {}
|
|
2067
|
+
})).quoteSell(tokens);
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* WSOL an exact-coins-out buy of `tokens` would cost on a curve that does not exist yet, fees
|
|
2071
|
+
* included.
|
|
2072
|
+
*
|
|
2073
|
+
* This is what the seed costs. `initialize_pool` buys it in the same transaction that creates the
|
|
2074
|
+
* coin, so the curve is a brand-new LaunchLab curve with the pinned Gabox launch shape at that
|
|
2075
|
+
* moment. Exact, unless Raydium changes a fee rate between this read and the send.
|
|
2076
|
+
*/
|
|
2077
|
+
async function newCurveBuyCost(client, tokens) {
|
|
2078
|
+
if (tokens <= 0n) return 0n;
|
|
2079
|
+
const ids = raydiumIds(client.cluster);
|
|
2080
|
+
const settings = await fetchCurveSettings(client, ids);
|
|
2081
|
+
return curveBuyExactOut(newCurveReserves(ids, settings.migrateFee), settings.rates, tokens);
|
|
2082
|
+
}
|
|
2083
|
+
/** The starting reserves of a brand-new Gabox curve on this cluster. */
|
|
2084
|
+
function newCurveReserves(ids, migrateFee) {
|
|
2085
|
+
return initialCurve({
|
|
2086
|
+
supply: LAUNCH_SUPPLY,
|
|
2087
|
+
totalBaseSell: LAUNCH_TOTAL_BASE_SELL,
|
|
2088
|
+
quoteRaise: ids.launchQuoteRaise,
|
|
2089
|
+
migrateFee
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
//#endregion
|
|
2093
|
+
//#region src/tx/message.ts
|
|
2094
|
+
/**
|
|
2095
|
+
* Assembling a transaction message.
|
|
2096
|
+
*
|
|
2097
|
+
* Every builder in this directory ends here: compute budget first, then the program instructions,
|
|
2098
|
+
* with a fee payer and a blockhash lifetime. The result is a message a wallet can sign and send —
|
|
2099
|
+
* nothing in this SDK signs or sends anything itself.
|
|
2100
|
+
*/
|
|
2101
|
+
/**
|
|
2102
|
+
* Build the message. One RPC read, for the blockhash.
|
|
2103
|
+
*
|
|
2104
|
+
* The blockhash expires in about a minute, so build the message when the user is ready to sign
|
|
2105
|
+
* rather than when the page loads.
|
|
2106
|
+
*/
|
|
2107
|
+
async function buildMessage(client, feePayer, instructions, options) {
|
|
2108
|
+
const { value: latestBlockhash } = await client.rpc.getLatestBlockhash({ commitment: "confirmed" }).send();
|
|
2109
|
+
const budget = computeBudgetInstructions(options.computeUnitLimit, options.computeUnitPrice);
|
|
2110
|
+
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));
|
|
2111
|
+
const size = getTransactionEncoder().encode(compileTransaction(message)).length;
|
|
2112
|
+
if (size > 1232) throw new Error(`Transaction is ${size} bytes; Solana allows 1232. Shorten metadata or supply additional address lookup tables.`);
|
|
2113
|
+
return message;
|
|
2114
|
+
}
|
|
2115
|
+
/** Append `remainingAccounts` to a generated instruction, which is how a venue's list is passed. */
|
|
2116
|
+
function withRemainingAccounts(instruction, remaining) {
|
|
2117
|
+
return {
|
|
2118
|
+
...instruction,
|
|
2119
|
+
accounts: [...instruction.accounts ?? [], ...remaining]
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
//#endregion
|
|
2123
|
+
//#region src/raydium/launch.ts
|
|
2124
|
+
/**
|
|
2125
|
+
* LaunchLab's `initialize_v2`, built by hand.
|
|
2126
|
+
*
|
|
2127
|
+
* Put it **before** `initialize_pool` in the transaction. `venue/launch.rs` reads the Instructions
|
|
2128
|
+
* sysvar and looks for this instruction by discriminator, then checks all 18 of its accounts and
|
|
2129
|
+
* every one of its arguments. The order matters for a second reason too: the mint account has to
|
|
2130
|
+
* exist and deserialize before Anchor validates the Gabox accounts.
|
|
2131
|
+
*
|
|
2132
|
+
* # The launch shape is pinned
|
|
2133
|
+
*
|
|
2134
|
+
* A creator picks the name, the symbol and the metadata URI. Nothing else. Every other argument is
|
|
2135
|
+
* fixed, and the program refuses any other value:
|
|
2136
|
+
*
|
|
2137
|
+
* | Argument | Value |
|
|
2138
|
+
* | --- | --- |
|
|
2139
|
+
* | `decimals` | 6 |
|
|
2140
|
+
* | curve | `Constant` |
|
|
2141
|
+
* | `supply` | 1,000,000,000 coins |
|
|
2142
|
+
* | `total_base_sell` | 793,100,000 coins |
|
|
2143
|
+
* | `total_quote_fund_raising` | 85 SOL on mainnet, 3 SOL on devnet |
|
|
2144
|
+
* | `migrate_type` | 1, which means graduate into a CPMM pool |
|
|
2145
|
+
* | `VestingParams` | all three fields 0 |
|
|
2146
|
+
* | `AmmCreatorFeeOn` | `QuoteToken` |
|
|
2147
|
+
*
|
|
2148
|
+
* The instruction data must end exactly after the last argument. Trailing bytes are refused.
|
|
2149
|
+
*
|
|
2150
|
+
* # The argument layout
|
|
2151
|
+
*
|
|
2152
|
+
* Borsh, in IDL order: `MintParams { decimals, name, symbol, uri }`, then the `CurveParams` enum
|
|
2153
|
+
* tag and its `ConstantCurve` fields, then `VestingParams`, then the `AmmCreatorFeeOn` enum tag.
|
|
2154
|
+
* An Anchor enum tag is one byte.
|
|
2155
|
+
*/
|
|
2156
|
+
const borshString = () => addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder());
|
|
2157
|
+
const ARGS_ENCODER = getStructEncoder([
|
|
2158
|
+
["decimals", getU8Encoder()],
|
|
2159
|
+
["name", borshString()],
|
|
2160
|
+
["symbol", borshString()],
|
|
2161
|
+
["uri", borshString()],
|
|
2162
|
+
["curveTag", getU8Encoder()],
|
|
2163
|
+
["supply", getU64Encoder()],
|
|
2164
|
+
["totalBaseSell", getU64Encoder()],
|
|
2165
|
+
["totalQuoteFundRaising", getU64Encoder()],
|
|
2166
|
+
["migrateType", getU8Encoder()],
|
|
2167
|
+
["totalLockedAmount", getU64Encoder()],
|
|
2168
|
+
["cliffPeriod", getU64Encoder()],
|
|
2169
|
+
["unlockPeriod", getU64Encoder()],
|
|
2170
|
+
["ammFeeOn", getU8Encoder()]
|
|
2171
|
+
]);
|
|
2172
|
+
/** Metaplex refuses a longer string, so refuse it here rather than on chain. */
|
|
2173
|
+
function bounded(value, limit, what) {
|
|
2174
|
+
const bytes = new TextEncoder().encode(value).length;
|
|
2175
|
+
if (bytes === 0) throw new Error(`${what} must not be empty`);
|
|
2176
|
+
if (bytes > limit) throw new Error(`${what} is ${bytes} UTF-8 bytes; Metaplex allows at most ${limit}`);
|
|
2177
|
+
return value;
|
|
2178
|
+
}
|
|
2179
|
+
/**
|
|
2180
|
+
* Build `initialize_v2` for one cluster's LaunchLab. `ids` is `raydiumIds(client.cluster)`.
|
|
2181
|
+
*
|
|
2182
|
+
* Two slots sign: the mint keypair, which LaunchLab takes as a signer rather than deriving, and the
|
|
2183
|
+
* creator, who pays for everything.
|
|
2184
|
+
*/
|
|
2185
|
+
async function getLaunchInstruction(input, ids) {
|
|
2186
|
+
const { mint, creator } = input;
|
|
2187
|
+
const name = bounded(input.name, METADATA_LIMITS.name, "name");
|
|
2188
|
+
const symbol = bounded(input.symbol, METADATA_LIMITS.symbol, "symbol");
|
|
2189
|
+
const uri = bounded(input.uri, METADATA_LIMITS.uri, "uri");
|
|
2190
|
+
const quoteMint = WSOL_MINT;
|
|
2191
|
+
const poolState = await launchlabPoolAddress(ids.launchlab, mint.address, quoteMint);
|
|
2192
|
+
const metas = order(LAUNCHLAB_INITIALIZE, {
|
|
2193
|
+
payer: creator.address,
|
|
2194
|
+
creator: creator.address,
|
|
2195
|
+
global_config: ids.solGlobalConfig,
|
|
2196
|
+
platform_config: ids.gaboxPlatform,
|
|
2197
|
+
authority: ids.launchlabAuthority,
|
|
2198
|
+
pool_state: poolState,
|
|
2199
|
+
base_mint: mint.address,
|
|
2200
|
+
quote_mint: quoteMint,
|
|
2201
|
+
base_vault: await launchlabVaultAddress(ids.launchlab, poolState, mint.address),
|
|
2202
|
+
quote_vault: await launchlabVaultAddress(ids.launchlab, poolState, quoteMint),
|
|
2203
|
+
metadata_account: await metadataAddress(mint.address),
|
|
2204
|
+
base_token_program: TOKEN_PROGRAM_ADDRESS,
|
|
2205
|
+
quote_token_program: TOKEN_PROGRAM_ADDRESS,
|
|
2206
|
+
metadata_program: METAPLEX_PROGRAM_ADDRESS,
|
|
2207
|
+
system_program: SYSTEM_PROGRAM_ADDRESS,
|
|
2208
|
+
rent_program: RENT_SYSVAR_ADDRESS,
|
|
2209
|
+
event_authority: ids.launchlabEventAuthority,
|
|
2210
|
+
program: ids.launchlab
|
|
2211
|
+
});
|
|
2212
|
+
const signerFor = /* @__PURE__ */ new Map([[mint.address, mint], [creator.address, creator]]);
|
|
2213
|
+
const accounts = metas.map((meta) => {
|
|
2214
|
+
const signs = meta.role === 2 || meta.role === 3;
|
|
2215
|
+
const signer = signerFor.get(meta.address);
|
|
2216
|
+
return signs && signer ? {
|
|
2217
|
+
...meta,
|
|
2218
|
+
signer
|
|
2219
|
+
} : meta;
|
|
2220
|
+
});
|
|
2221
|
+
const data = new Uint8Array([...LAUNCHLAB_INITIALIZE.discriminator, ...ARGS_ENCODER.encode({
|
|
2222
|
+
decimals: 6,
|
|
2223
|
+
name,
|
|
2224
|
+
symbol,
|
|
2225
|
+
uri,
|
|
2226
|
+
curveTag: 0,
|
|
2227
|
+
supply: LAUNCH_SUPPLY,
|
|
2228
|
+
totalBaseSell: LAUNCH_TOTAL_BASE_SELL,
|
|
2229
|
+
totalQuoteFundRaising: ids.launchQuoteRaise,
|
|
2230
|
+
migrateType: 1,
|
|
2231
|
+
totalLockedAmount: 0n,
|
|
2232
|
+
cliffPeriod: 0n,
|
|
2233
|
+
unlockPeriod: 0n,
|
|
2234
|
+
ammFeeOn: 0
|
|
2235
|
+
})]);
|
|
2236
|
+
return {
|
|
2237
|
+
programAddress: ids.launchlab,
|
|
2238
|
+
accounts,
|
|
2239
|
+
data
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
//#endregion
|
|
2243
|
+
//#region src/raydium/claim.ts
|
|
2244
|
+
/**
|
|
2245
|
+
* Collecting the creator's share of trading.
|
|
2246
|
+
*
|
|
2247
|
+
* Gabox charges no fee at all. The creator's income is the share Raydium pays out of its own
|
|
2248
|
+
* revenue, and it sits in two different places depending on where the coin trades:
|
|
2249
|
+
*
|
|
2250
|
+
* - **On the curve.** LaunchLab pays 0.5% of every trade into `[creator, WSOL]` under LaunchLab.
|
|
2251
|
+
* One vault per wallet, not one per coin, so a single `claim_creator_fee` sweeps every coin
|
|
2252
|
+
* that creator launched.
|
|
2253
|
+
* - **After graduation.** The CPMM pool keeps the creator's fee inside the pool account, and
|
|
2254
|
+
* `collect_creator_fee` pays it out. That is per pool, so it takes one call per coin.
|
|
2255
|
+
*
|
|
2256
|
+
* Both instructions pay into the creator's WSOL associated token account. Every builder here closes
|
|
2257
|
+
* that account afterwards, so the creator ends up with plain SOL in their wallet.
|
|
2258
|
+
*
|
|
2259
|
+
* **Closing unwraps everything.** If the creator already held WSOL in that account before the
|
|
2260
|
+
* claim, closing turns that into SOL too. Nothing is lost, but the balance moves.
|
|
2261
|
+
*/
|
|
2262
|
+
const base64 = getBase64Encoder();
|
|
2263
|
+
/**
|
|
2264
|
+
* Create the creator's WSOL account, if it is missing, and close it again at the end.
|
|
2265
|
+
*
|
|
2266
|
+
* Raydium's own instructions create the account when they need to, but an idempotent create here
|
|
2267
|
+
* costs nothing and makes the flow the same whichever instruction runs. The close is what turns the
|
|
2268
|
+
* WSOL into SOL.
|
|
2269
|
+
*/
|
|
2270
|
+
async function wrapAround(creator, middle) {
|
|
2271
|
+
const account = await ata(creator.address, WSOL_MINT);
|
|
2272
|
+
return [
|
|
2273
|
+
getCreateAssociatedTokenIdempotentInstruction({
|
|
2274
|
+
payer: creator,
|
|
2275
|
+
ata: account,
|
|
2276
|
+
owner: creator.address,
|
|
2277
|
+
mint: WSOL_MINT,
|
|
2278
|
+
tokenProgram: TOKEN_PROGRAM_ADDRESS
|
|
2279
|
+
}),
|
|
2280
|
+
...middle,
|
|
2281
|
+
getCloseAccountInstruction({
|
|
2282
|
+
account,
|
|
2283
|
+
destination: creator.address,
|
|
2284
|
+
owner: creator
|
|
2285
|
+
})
|
|
2286
|
+
];
|
|
2287
|
+
}
|
|
2288
|
+
/** Attach a signer to the one slot the ABI marks as signing. Roles never change here. */
|
|
2289
|
+
function withSigner(accounts, signer) {
|
|
2290
|
+
return accounts.map((meta) => (meta.role === AccountRole.READONLY_SIGNER || meta.role === AccountRole.WRITABLE_SIGNER) && meta.address === signer.address ? {
|
|
2291
|
+
...meta,
|
|
2292
|
+
signer
|
|
2293
|
+
} : meta);
|
|
2294
|
+
}
|
|
2295
|
+
/**
|
|
2296
|
+
* The instructions of a curve fee claim: create the WSOL account, claim, close it.
|
|
2297
|
+
*
|
|
2298
|
+
* This sweeps **every** coin this wallet launched, because LaunchLab keeps one vault per wallet per
|
|
2299
|
+
* quote asset. There is no per-coin version.
|
|
2300
|
+
*/
|
|
2301
|
+
async function getClaimCreatorFeeInstructions(creator, ids) {
|
|
2302
|
+
const quoteMint = WSOL_MINT;
|
|
2303
|
+
return await wrapAround(creator, [{
|
|
2304
|
+
programAddress: ids.launchlab,
|
|
2305
|
+
accounts: withSigner(order(LAUNCHLAB_CLAIM_CREATOR_FEE, {
|
|
2306
|
+
creator: creator.address,
|
|
2307
|
+
fee_vault_authority: await creatorFeeVaultAuthority(ids.launchlab),
|
|
2308
|
+
creator_fee_vault: await creatorFeeVaultAddress(ids.launchlab, creator.address, quoteMint),
|
|
2309
|
+
recipient_token_account: await ata(creator.address, quoteMint),
|
|
2310
|
+
quote_mint: quoteMint,
|
|
2311
|
+
token_program: TOKEN_PROGRAM_ADDRESS,
|
|
2312
|
+
system_program: SYSTEM_PROGRAM_ADDRESS,
|
|
2313
|
+
associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ADDRESS
|
|
2314
|
+
}), creator),
|
|
2315
|
+
data: new Uint8Array(LAUNCHLAB_CLAIM_CREATOR_FEE.discriminator)
|
|
2316
|
+
}]);
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* Claim the curve creator fee and receive it as SOL.
|
|
2320
|
+
*
|
|
2321
|
+
* One transaction, three instructions. It sweeps every coin this wallet launched on LaunchLab.
|
|
2322
|
+
*/
|
|
2323
|
+
async function claimCreatorFee(client, input) {
|
|
2324
|
+
const ids = raydiumIds(client.cluster);
|
|
2325
|
+
const instructions = await getClaimCreatorFeeInstructions(input.creator, ids);
|
|
2326
|
+
return await buildMessage(client, input.creator, instructions, {
|
|
2327
|
+
addressLookupTables: input.addressLookupTables,
|
|
2328
|
+
computeUnitLimit: input.computeUnitLimit ?? 55e3,
|
|
2329
|
+
...input.computeUnitPrice === void 0 ? {} : { computeUnitPrice: input.computeUnitPrice }
|
|
2330
|
+
});
|
|
2331
|
+
}
|
|
2332
|
+
/**
|
|
2333
|
+
* The instructions of a graduated coin's fee collection: create the WSOL account, collect, close.
|
|
2334
|
+
*
|
|
2335
|
+
* This is per coin. `collect_creator_fee` pays out both sides of the pair, so anything owed in the
|
|
2336
|
+
* coin itself lands in the creator's coin account and stays there.
|
|
2337
|
+
*/
|
|
2338
|
+
async function getCollectCreatorFeeInstructions(client, input, ids = raydiumIds(client.cluster)) {
|
|
2339
|
+
const { mint, creator } = input;
|
|
2340
|
+
const quoteMint = WSOL_MINT;
|
|
2341
|
+
const settings = await fetchCurveSettings(client, ids);
|
|
2342
|
+
const { address: poolState, pool } = await findCpmmPool(client, {
|
|
2343
|
+
mint,
|
|
2344
|
+
quoteMint,
|
|
2345
|
+
creator: creator.address,
|
|
2346
|
+
cpswapConfig: settings.cpswapConfig
|
|
2347
|
+
});
|
|
2348
|
+
return await wrapAround(creator, [{
|
|
2349
|
+
programAddress: ids.cpmm,
|
|
2350
|
+
accounts: withSigner(order(CPMM_COLLECT_CREATOR_FEE, {
|
|
2351
|
+
creator: creator.address,
|
|
2352
|
+
authority: ids.cpmmAuthority,
|
|
2353
|
+
pool_state: poolState,
|
|
2354
|
+
amm_config: pool.ammConfig,
|
|
2355
|
+
token_0_vault: pool.token0Vault,
|
|
2356
|
+
token_1_vault: pool.token1Vault,
|
|
2357
|
+
vault_0_mint: pool.token0Mint,
|
|
2358
|
+
vault_1_mint: pool.token1Mint,
|
|
2359
|
+
creator_token_0: await ata(creator.address, pool.token0Mint, pool.token0Program),
|
|
2360
|
+
creator_token_1: await ata(creator.address, pool.token1Mint, pool.token1Program),
|
|
2361
|
+
token_0_program: pool.token0Program,
|
|
2362
|
+
token_1_program: pool.token1Program,
|
|
2363
|
+
associated_token_program: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
|
|
2364
|
+
system_program: SYSTEM_PROGRAM_ADDRESS,
|
|
2365
|
+
creator_fee_share: await cpmmCreatorFeeShare(ids.cpmm, creator.address, pool.ammConfig)
|
|
2366
|
+
}), creator),
|
|
2367
|
+
data: new Uint8Array(CPMM_COLLECT_CREATOR_FEE.discriminator)
|
|
2368
|
+
}]);
|
|
2369
|
+
}
|
|
2370
|
+
/** Collect one graduated coin's CPMM creator fee and receive the WSOL side as SOL. */
|
|
2371
|
+
async function collectCreatorFee(client, input) {
|
|
2372
|
+
const instructions = await getCollectCreatorFeeInstructions(client, {
|
|
2373
|
+
mint: input.mint,
|
|
2374
|
+
creator: input.creator
|
|
2375
|
+
});
|
|
2376
|
+
return await buildMessage(client, input.creator, instructions, {
|
|
2377
|
+
addressLookupTables: input.addressLookupTables,
|
|
2378
|
+
computeUnitLimit: input.computeUnitLimit ?? 55e3,
|
|
2379
|
+
...input.computeUnitPrice === void 0 ? {} : { computeUnitPrice: input.computeUnitPrice }
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Read both places a creator's fees can sit.
|
|
2384
|
+
*
|
|
2385
|
+
* `mint` is optional. Without it only the curve vault is read, which is the one number that covers
|
|
2386
|
+
* every coin at once. With it the coin's CPMM pool is read too, and a coin that has not graduated
|
|
2387
|
+
* reports zeros rather than failing.
|
|
2388
|
+
*/
|
|
2389
|
+
async function fetchCreatorFees(client, input) {
|
|
2390
|
+
const ids = raydiumIds(client.cluster);
|
|
2391
|
+
const quoteMint = WSOL_MINT;
|
|
2392
|
+
const vault = await creatorFeeVaultAddress(ids.launchlab, input.creator, quoteMint);
|
|
2393
|
+
const { value } = await client.rpc.getAccountInfo(vault, {
|
|
2394
|
+
encoding: "base64",
|
|
2395
|
+
commitment: "confirmed"
|
|
2396
|
+
}).send();
|
|
2397
|
+
const curveLamports = value ? tokenAccountAmount(new Uint8Array(base64.encode(value.data[0]))) : 0n;
|
|
2398
|
+
if (!input.mint) return {
|
|
2399
|
+
curveLamports,
|
|
2400
|
+
cpmmLamports: 0n,
|
|
2401
|
+
cpmmTokens: 0n
|
|
2402
|
+
};
|
|
2403
|
+
const settings = await fetchCurveSettings(client, ids);
|
|
2404
|
+
try {
|
|
2405
|
+
const { pool } = await findCpmmPool(client, {
|
|
2406
|
+
mint: input.mint,
|
|
2407
|
+
quoteMint,
|
|
2408
|
+
creator: input.creator,
|
|
2409
|
+
cpswapConfig: settings.cpswapConfig
|
|
2410
|
+
});
|
|
2411
|
+
const [token0] = sortedMints(input.mint, quoteMint);
|
|
2412
|
+
const baseIsToken0 = token0 === input.mint;
|
|
2413
|
+
return {
|
|
2414
|
+
curveLamports,
|
|
2415
|
+
cpmmLamports: baseIsToken0 ? pool.creatorFeesToken1 : pool.creatorFeesToken0,
|
|
2416
|
+
cpmmTokens: baseIsToken0 ? pool.creatorFeesToken0 : pool.creatorFeesToken1
|
|
2417
|
+
};
|
|
2418
|
+
} catch {
|
|
2419
|
+
return {
|
|
2420
|
+
curveLamports,
|
|
2421
|
+
cpmmLamports: 0n,
|
|
2422
|
+
cpmmTokens: 0n
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
//#endregion
|
|
2427
|
+
//#region src/raydium/trade.ts
|
|
2428
|
+
/**
|
|
2429
|
+
* A venue trade a wallet sends on its own, outside any Gabox instruction.
|
|
2430
|
+
*
|
|
2431
|
+
* Pack buys and token sales go through `buy_pack` and `sell_tokens`: they take the venue's account
|
|
2432
|
+
* list as `remainingAccounts` and the Gabox program does the call. This file is for the one trade
|
|
2433
|
+
* Gabox never makes, a plain `buy_exact_in` on the curve. Anyone may trade a Gabox coin directly,
|
|
2434
|
+
* and the devnet end-to-end test uses this to buy the rest of the curve and force graduation.
|
|
2435
|
+
*
|
|
2436
|
+
* The account list and the discriminator both come from `abi.ts`, which codegen took from
|
|
2437
|
+
* Raydium's IDL. Nothing here retypes either.
|
|
2438
|
+
*/
|
|
2439
|
+
const u64 = getU64Encoder();
|
|
2440
|
+
/**
|
|
2441
|
+
* `buy_exact_in` on the curve: spend exactly `quoteIn` WSOL and take whatever coins it buys.
|
|
2442
|
+
*
|
|
2443
|
+
* LaunchLab caps the trade at what the curve has left to sell, so a spend larger than the rest of
|
|
2444
|
+
* the raise still succeeds and graduates the coin. Wrap the SOL into the user's WSOL ATA first and
|
|
2445
|
+
* close it afterwards, the same way the Gabox builders do.
|
|
2446
|
+
*/
|
|
2447
|
+
async function getCurveBuyExactInInstruction(client, input, ids = raydiumIds(client.cluster)) {
|
|
2448
|
+
if (input.quoteIn <= 0n) throw new Error("quoteIn must be positive");
|
|
2449
|
+
if (input.minTokensOut < 0n) throw new Error("minTokensOut must not be negative");
|
|
2450
|
+
const quoteMint = WSOL_MINT;
|
|
2451
|
+
const poolState = await launchlabPoolAddress(ids.launchlab, input.mint, quoteMint);
|
|
2452
|
+
const accounts = launchlabBuyExactInAccounts({
|
|
2453
|
+
launchlab: ids.launchlab,
|
|
2454
|
+
launchlabAuthority: ids.launchlabAuthority,
|
|
2455
|
+
launchlabEventAuthority: ids.launchlabEventAuthority,
|
|
2456
|
+
globalConfig: ids.solGlobalConfig,
|
|
2457
|
+
platformConfig: ids.gaboxPlatform,
|
|
2458
|
+
poolState,
|
|
2459
|
+
mint: input.mint,
|
|
2460
|
+
quoteMint,
|
|
2461
|
+
baseVault: await launchlabVaultAddress(ids.launchlab, poolState, input.mint),
|
|
2462
|
+
quoteVault: await launchlabVaultAddress(ids.launchlab, poolState, quoteMint),
|
|
2463
|
+
user: input.user.address,
|
|
2464
|
+
userBaseToken: await ata(input.user.address, input.mint),
|
|
2465
|
+
userQuoteToken: await ata(input.user.address, quoteMint),
|
|
2466
|
+
platformFeeVault: await platformFeeVaultAddress(ids.launchlab, ids.gaboxPlatform, quoteMint),
|
|
2467
|
+
creatorFeeVault: await creatorFeeVaultAddress(ids.launchlab, input.creator, quoteMint)
|
|
2468
|
+
});
|
|
2469
|
+
return {
|
|
2470
|
+
programAddress: ids.launchlab,
|
|
2471
|
+
accounts: attachSigner(accounts, input.user),
|
|
2472
|
+
data: new Uint8Array([
|
|
2473
|
+
...LAUNCHLAB_BUY_EXACT_IN.discriminator,
|
|
2474
|
+
...u64.encode(input.quoteIn),
|
|
2475
|
+
...u64.encode(input.minTokensOut),
|
|
2476
|
+
...u64.encode(SHARE_FEE_RATE)
|
|
2477
|
+
])
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
/** Give the signer to the slot the ABI already marks as signing. Roles never change here. */
|
|
2481
|
+
function attachSigner(accounts, signer) {
|
|
2482
|
+
let matched = false;
|
|
2483
|
+
const withSigner = accounts.map((meta) => {
|
|
2484
|
+
if (!(meta.role === 2 || meta.role === 3) || meta.address !== signer.address) return meta;
|
|
2485
|
+
matched = true;
|
|
2486
|
+
return {
|
|
2487
|
+
...meta,
|
|
2488
|
+
signer
|
|
2489
|
+
};
|
|
2490
|
+
});
|
|
2491
|
+
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.`);
|
|
2492
|
+
return withSigner;
|
|
2493
|
+
}
|
|
2494
|
+
//#endregion
|
|
2495
|
+
//#region src/raydium/index.ts
|
|
2496
|
+
var raydium_exports = /* @__PURE__ */ __exportAll({
|
|
2497
|
+
ASSOCIATED_TOKEN_PROGRAM_ADDRESS: () => ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
|
|
2498
|
+
CONSTANT_CURVE_TAG: () => 0,
|
|
2499
|
+
CPMM_AMM_CONFIG_DISCRIMINATOR: () => CPMM_AMM_CONFIG_DISCRIMINATOR,
|
|
2500
|
+
CPMM_AMM_CONFIG_HEAD_SIZE: () => 116,
|
|
2501
|
+
CPMM_COLLECT_CREATOR_FEE: () => CPMM_COLLECT_CREATOR_FEE,
|
|
2502
|
+
CPMM_POOL_OFFSETS: () => CPMM_POOL_OFFSETS,
|
|
2503
|
+
CPMM_POOL_STATE_DISCRIMINATOR: () => CPMM_POOL_STATE_DISCRIMINATOR,
|
|
2504
|
+
CPMM_POOL_STATE_HEAD_SIZE: () => 413,
|
|
2505
|
+
CPMM_POOL_STATE_SIZE: () => 637,
|
|
2506
|
+
CPMM_SEEDS: () => CPMM_SEEDS,
|
|
2507
|
+
CPMM_SWAP_BASE_INPUT: () => CPMM_SWAP_BASE_INPUT,
|
|
2508
|
+
CPMM_SWAP_BASE_OUTPUT: () => CPMM_SWAP_BASE_OUTPUT,
|
|
2509
|
+
CREATOR_FEE_ON_QUOTE: () => 0,
|
|
2510
|
+
LAUNCHLAB_BUY_EXACT_IN: () => LAUNCHLAB_BUY_EXACT_IN,
|
|
2511
|
+
LAUNCHLAB_BUY_EXACT_OUT: () => LAUNCHLAB_BUY_EXACT_OUT,
|
|
2512
|
+
LAUNCHLAB_CLAIM_CREATOR_FEE: () => LAUNCHLAB_CLAIM_CREATOR_FEE,
|
|
2513
|
+
LAUNCHLAB_GLOBAL_CONFIG_DISCRIMINATOR: () => LAUNCHLAB_GLOBAL_CONFIG_DISCRIMINATOR,
|
|
2514
|
+
LAUNCHLAB_GLOBAL_CONFIG_HEAD_SIZE: () => 115,
|
|
2515
|
+
LAUNCHLAB_INITIALIZE: () => LAUNCHLAB_INITIALIZE,
|
|
2516
|
+
LAUNCHLAB_PLATFORM_CONFIG_DISCRIMINATOR: () => LAUNCHLAB_PLATFORM_CONFIG_DISCRIMINATOR,
|
|
2517
|
+
LAUNCHLAB_PLATFORM_CONFIG_HEAD_SIZE: () => 940,
|
|
2518
|
+
LAUNCHLAB_POOL_STATE_DISCRIMINATOR: () => LAUNCHLAB_POOL_STATE_DISCRIMINATOR,
|
|
2519
|
+
LAUNCHLAB_POOL_STATE_HEAD_SIZE: () => 367,
|
|
2520
|
+
LAUNCHLAB_SEEDS: () => LAUNCHLAB_SEEDS,
|
|
2521
|
+
LAUNCHLAB_SELL_EXACT_IN: () => LAUNCHLAB_SELL_EXACT_IN,
|
|
2522
|
+
LAUNCHLAB_STATUS_FUND: () => 0,
|
|
2523
|
+
LAUNCHLAB_STATUS_MIGRATE: () => 1,
|
|
2524
|
+
LAUNCHLAB_STATUS_TRADE: () => 2,
|
|
2525
|
+
LAUNCH_DECIMALS: () => 6,
|
|
2526
|
+
LAUNCH_SUPPLY: () => LAUNCH_SUPPLY,
|
|
2527
|
+
LAUNCH_TOTAL_BASE_SELL: () => LAUNCH_TOTAL_BASE_SELL,
|
|
2528
|
+
METADATA_LIMITS: () => METADATA_LIMITS,
|
|
2529
|
+
METADATA_SEED: () => METADATA_SEED,
|
|
2530
|
+
METAPLEX_PROGRAM_ADDRESS: () => METAPLEX_PROGRAM_ADDRESS,
|
|
2531
|
+
MIGRATE_TO_CPMM: () => 1,
|
|
2532
|
+
PLATFORM_ADMIN: () => PLATFORM_ADMIN,
|
|
2533
|
+
RATE_DENOMINATOR: () => RATE_DENOMINATOR,
|
|
2534
|
+
RAYDIUM_DEVNET_IDS: () => RAYDIUM_DEVNET_IDS,
|
|
2535
|
+
RAYDIUM_IDS: () => RAYDIUM_IDS,
|
|
2536
|
+
RAYDIUM_MAINNET_IDS: () => RAYDIUM_MAINNET_IDS,
|
|
2537
|
+
RENT_SYSVAR_ADDRESS: () => RENT_SYSVAR_ADDRESS,
|
|
2538
|
+
SHARE_FEE_RATE: () => SHARE_FEE_RATE,
|
|
2539
|
+
SYSTEM_PROGRAM_ADDRESS: () => SYSTEM_PROGRAM_ADDRESS,
|
|
2540
|
+
TOKEN_PROGRAM_ADDRESS: () => TOKEN_PROGRAM_ADDRESS,
|
|
2541
|
+
WSOL_MINT: () => WSOL_MINT,
|
|
2542
|
+
ata: () => ata,
|
|
2543
|
+
ceilDiv: () => ceilDiv,
|
|
2544
|
+
ceilDivRate: () => ceilDivRate,
|
|
2545
|
+
claimCreatorFee: () => claimCreatorFee,
|
|
2546
|
+
collectCreatorFee: () => collectCreatorFee,
|
|
2547
|
+
compareAddresses: () => compareAddresses,
|
|
2548
|
+
cpmmAuthority: () => cpmmAuthority,
|
|
2549
|
+
cpmmBuyAccounts: () => cpmmBuyAccounts,
|
|
2550
|
+
cpmmCreatorFeeShare: () => cpmmCreatorFeeShare,
|
|
2551
|
+
cpmmPoolAddress: () => cpmmPoolAddress,
|
|
2552
|
+
cpmmPoolMatches: () => cpmmPoolMatches,
|
|
2553
|
+
cpmmSellAccounts: () => cpmmSellAccounts,
|
|
2554
|
+
cpmmSwapBaseInput: () => cpmmSwapBaseInput,
|
|
2555
|
+
cpmmSwapBaseOutput: () => cpmmSwapBaseOutput,
|
|
2556
|
+
creatorFeeOnInput: () => creatorFeeOnInput,
|
|
2557
|
+
creatorFeeVaultAddress: () => creatorFeeVaultAddress,
|
|
2558
|
+
creatorFeeVaultAuthority: () => creatorFeeVaultAuthority,
|
|
2559
|
+
curveBuyExactIn: () => curveBuyExactIn,
|
|
2560
|
+
curveBuyExactOut: () => curveBuyExactOut,
|
|
2561
|
+
curveQuote: () => curveQuote,
|
|
2562
|
+
curveSellExactIn: () => curveSellExactIn,
|
|
2563
|
+
decodeCpmmAmmConfig: () => decodeCpmmAmmConfig,
|
|
2564
|
+
decodeCpmmPool: () => decodeCpmmPool,
|
|
2565
|
+
decodeLaunchlabGlobalConfig: () => decodeLaunchlabGlobalConfig,
|
|
2566
|
+
decodeLaunchlabPlatformConfig: () => decodeLaunchlabPlatformConfig,
|
|
2567
|
+
decodeLaunchlabPool: () => decodeLaunchlabPool,
|
|
2568
|
+
fetchCreatorFees: () => fetchCreatorFees,
|
|
2569
|
+
fetchCurveSettings: () => fetchCurveSettings,
|
|
2570
|
+
findCpmmPool: () => findCpmmPool,
|
|
2571
|
+
getClaimCreatorFeeInstructions: () => getClaimCreatorFeeInstructions,
|
|
2572
|
+
getCollectCreatorFeeInstructions: () => getCollectCreatorFeeInstructions,
|
|
2573
|
+
getCurveBuyExactInInstruction: () => getCurveBuyExactInInstruction,
|
|
2574
|
+
getLaunchInstruction: () => getLaunchInstruction,
|
|
2575
|
+
initialCurve: () => initialCurve,
|
|
2576
|
+
launchlabAuthority: () => launchlabAuthority,
|
|
2577
|
+
launchlabBuyAccounts: () => launchlabBuyAccounts,
|
|
2578
|
+
launchlabBuyExactInAccounts: () => launchlabBuyExactInAccounts,
|
|
2579
|
+
launchlabEventAuthority: () => launchlabEventAuthority,
|
|
2580
|
+
launchlabGlobalConfig: () => launchlabGlobalConfig,
|
|
2581
|
+
launchlabPlatformConfig: () => launchlabPlatformConfig,
|
|
2582
|
+
launchlabPoolAddress: () => launchlabPoolAddress,
|
|
2583
|
+
launchlabSellAccounts: () => launchlabSellAccounts,
|
|
2584
|
+
launchlabVaultAddress: () => launchlabVaultAddress,
|
|
2585
|
+
metadataAddress: () => metadataAddress,
|
|
2586
|
+
newCurveBuyCost: () => newCurveBuyCost,
|
|
2587
|
+
newCurveReserves: () => newCurveReserves,
|
|
2588
|
+
order: () => order,
|
|
2589
|
+
platformFeeVaultAddress: () => platformFeeVaultAddress,
|
|
2590
|
+
preFeeAmount: () => preFeeAmount,
|
|
2591
|
+
raydiumIds: () => raydiumIds,
|
|
2592
|
+
remainingBase: () => remainingBase,
|
|
2593
|
+
resolveVenue: () => resolveVenue,
|
|
2594
|
+
sellQuote: () => sellQuote,
|
|
2595
|
+
sortedMints: () => sortedMints,
|
|
2596
|
+
tokenAccountAmount: () => tokenAccountAmount,
|
|
2597
|
+
tokenAccountOwnerAndMint: () => tokenAccountOwnerAndMint,
|
|
2598
|
+
totalCurveFeeRate: () => totalCurveFeeRate,
|
|
2599
|
+
wsolAccountFor: () => wsolAccountFor
|
|
2600
|
+
});
|
|
2601
|
+
//#endregion
|
|
2602
|
+
export { LAUNCHLAB_PLATFORM_CONFIG_HEAD_SIZE as $, LAUNCH_SUPPLY as $t, remainingBase as A, METADATA_LIMITS as At, COMPUTE_BUDGET_PROGRAM_ADDRESS as B, CPMM_AMM_CONFIG_DISCRIMINATOR as Bt, cpmmSwapBaseInput as C, CPMM_SEEDS as Ct, curveSellExactIn as D, LAUNCHLAB_STATUS_MIGRATE as Dt, curveBuyExactOut as E, LAUNCHLAB_STATUS_FUND as Et, launchlabBuyExactInAccounts as F, SHARE_FEE_RATE as Ft, computeBudgetInstructions as G, LAUNCHLAB_BUY_EXACT_IN as Gt, DEFAULT_COMPUTE_UNIT_LIMIT as H, CPMM_POOL_STATE_DISCRIMINATOR as Ht, launchlabSellAccounts as I, compareAddresses as It, CPMM_AMM_CONFIG_HEAD_SIZE as J, LAUNCHLAB_GLOBAL_CONFIG_DISCRIMINATOR as Jt, getSetComputeUnitLimitInstruction as K, LAUNCHLAB_BUY_EXACT_OUT as Kt, order as L, raydiumIds as Lt, cpmmBuyAccounts as M, MIGRATE_TO_CPMM as Mt, cpmmSellAccounts as N, RATE_DENOMINATOR as Nt, initialCurve as O, LAUNCHLAB_STATUS_TRADE as Ot, launchlabBuyAccounts as P, RAYDIUM_IDS as Pt, LAUNCHLAB_GLOBAL_CONFIG_HEAD_SIZE as Q, LAUNCHLAB_SELL_EXACT_IN as Qt, BUY_PACK_COMPUTE_UNITS as R, sortedMints as Rt, ceilDivRate as S, CONSTANT_CURVE_TAG as St, curveBuyExactIn as T, LAUNCHLAB_SEEDS as Tt, MAX_COMPUTE_UNIT_LIMIT as U, CPMM_SWAP_BASE_INPUT as Ut, CREATE_MACHINE_COMPUTE_UNITS as V, CPMM_COLLECT_CREATOR_FEE as Vt, REDEEM_COMPUTE_UNITS as W, CPMM_SWAP_BASE_OUTPUT as Wt, CPMM_POOL_STATE_HEAD_SIZE as X, LAUNCHLAB_PLATFORM_CONFIG_DISCRIMINATOR as Xt, CPMM_POOL_OFFSETS as Y, LAUNCHLAB_INITIALIZE as Yt, CPMM_POOL_STATE_SIZE as Z, LAUNCHLAB_POOL_STATE_DISCRIMINATOR as Zt, newCurveReserves as _, launchlabPlatformConfig as _t, fetchCreatorFees as a, RENT_SYSVAR_ADDRESS as an, decodeLaunchlabPool as at, wsolAccountFor as b, metadataAddress as bt, getLaunchInstruction as c, WSOL_MINT as cn, ata as ct, cpmmPoolMatches as d, cpmmPoolAddress as dt, LAUNCH_TOTAL_BASE_SELL as en, LAUNCHLAB_POOL_STATE_HEAD_SIZE as et, creatorFeeOnInput as f, creatorFeeVaultAddress as ft, newCurveBuyCost as g, launchlabGlobalConfig as gt, findCpmmPool as h, launchlabEventAuthority as ht, collectCreatorFee as i, RAYDIUM_MAINNET_IDS as in, decodeLaunchlabPlatformConfig as it, totalCurveFeeRate as j, METADATA_SEED as jt, preFeeAmount as k, LAUNCH_DECIMALS as kt, buildMessage as l, cpmmAuthority as lt, fetchCurveSettings as m, launchlabAuthority as mt, getCurveBuyExactInInstruction as n, PLATFORM_ADMIN as nn, decodeCpmmPool as nt, getClaimCreatorFeeInstructions as o, SYSTEM_PROGRAM_ADDRESS as on, tokenAccountAmount as ot, curveQuote as p, creatorFeeVaultAuthority as pt, getSetComputeUnitPriceInstruction as q, LAUNCHLAB_CLAIM_CREATOR_FEE as qt, claimCreatorFee as r, RAYDIUM_DEVNET_IDS as rn, decodeLaunchlabGlobalConfig as rt, getCollectCreatorFeeInstructions as s, TOKEN_PROGRAM_ADDRESS as sn, tokenAccountOwnerAndMint as st, raydium_exports as t, METAPLEX_PROGRAM_ADDRESS as tn, decodeCpmmAmmConfig as tt, withRemainingAccounts as u, cpmmCreatorFeeShare as ut, resolveVenue as v, launchlabPoolAddress as vt, cpmmSwapBaseOutput as w, CREATOR_FEE_ON_QUOTE as wt, ceilDiv as x, platformFeeVaultAddress as xt, sellQuote as y, launchlabVaultAddress as yt, CLAIM_COMPUTE_UNITS as z, ASSOCIATED_TOKEN_PROGRAM_ADDRESS as zt };
|
|
2603
|
+
|
|
2604
|
+
//# sourceMappingURL=raydium-B-l9V3O-.js.map
|