pangu-sdk 0.1.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/LICENSE +21 -0
- package/README.md +217 -0
- package/dist/accounts-BuD-oCcr.d.mts +206 -0
- package/dist/accounts-BuD-oCcr.d.ts +206 -0
- package/dist/dbc.d.mts +505 -0
- package/dist/dbc.d.ts +505 -0
- package/dist/dbc.js +3003 -0
- package/dist/dbc.js.map +1 -0
- package/dist/dbc.mjs +3027 -0
- package/dist/dbc.mjs.map +1 -0
- package/dist/feed-CY-pcUU2.d.mts +269 -0
- package/dist/feed-Zwu1irEx.d.ts +269 -0
- package/dist/index.d.mts +415 -0
- package/dist/index.d.ts +415 -0
- package/dist/index.js +2809 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2803 -0
- package/dist/index.mjs.map +1 -0
- package/dist/price.d.mts +86 -0
- package/dist/price.d.ts +86 -0
- package/dist/price.js +1554 -0
- package/dist/price.js.map +1 -0
- package/dist/price.mjs +1533 -0
- package/dist/price.mjs.map +1 -0
- package/package.json +104 -0
package/dist/dbc.mjs
ADDED
|
@@ -0,0 +1,3027 @@
|
|
|
1
|
+
// src/dbc/template.ts
|
|
2
|
+
import { Keypair } from "@solana/web3.js";
|
|
3
|
+
import {
|
|
4
|
+
CollectFeeMode,
|
|
5
|
+
DynamicBondingCurveClient as DynamicBondingCurveClient2,
|
|
6
|
+
MigrationOption,
|
|
7
|
+
TokenAuthorityOption,
|
|
8
|
+
TokenType,
|
|
9
|
+
buildCurve,
|
|
10
|
+
hasMintAuthority
|
|
11
|
+
} from "@meteora-ag/dynamic-bonding-curve-sdk";
|
|
12
|
+
|
|
13
|
+
// src/constants.ts
|
|
14
|
+
import { PublicKey } from "@solana/web3.js";
|
|
15
|
+
import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
|
|
16
|
+
|
|
17
|
+
// src/idl/pangu.json
|
|
18
|
+
var pangu_default = {
|
|
19
|
+
address: "4Nd46mDiaTSkqXPAXKqT4jkahcz1TxVSdoirbBCAr5qG",
|
|
20
|
+
metadata: {
|
|
21
|
+
name: "pangu",
|
|
22
|
+
version: "0.1.0",
|
|
23
|
+
spec: "0.1.0",
|
|
24
|
+
description: "Rules for a fair first sale of a stock token on Meteora DBC"
|
|
25
|
+
},
|
|
26
|
+
docs: [
|
|
27
|
+
"Pangu holds the rules of a stock token's first sale on Meteora's Dynamic Bonding",
|
|
28
|
+
"Curve: a cap per wallet, an optional issuer-approved buyer list, an optional",
|
|
29
|
+
"ceiling against the real stock's live price, and no wallet-to-wallet movement",
|
|
30
|
+
"until the sale is over. DBC finds the price, Pangu decides who may receive",
|
|
31
|
+
"tokens and how many."
|
|
32
|
+
],
|
|
33
|
+
instructions: [
|
|
34
|
+
{
|
|
35
|
+
name: "approve_buyer",
|
|
36
|
+
discriminator: [
|
|
37
|
+
193,
|
|
38
|
+
6,
|
|
39
|
+
144,
|
|
40
|
+
245,
|
|
41
|
+
24,
|
|
42
|
+
184,
|
|
43
|
+
227,
|
|
44
|
+
19
|
|
45
|
+
],
|
|
46
|
+
accounts: [
|
|
47
|
+
{
|
|
48
|
+
name: "issuer",
|
|
49
|
+
writable: true,
|
|
50
|
+
signer: true,
|
|
51
|
+
relations: [
|
|
52
|
+
"rules"
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "mint",
|
|
57
|
+
relations: [
|
|
58
|
+
"rules"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "rules",
|
|
63
|
+
pda: {
|
|
64
|
+
seeds: [
|
|
65
|
+
{
|
|
66
|
+
kind: "const",
|
|
67
|
+
value: [
|
|
68
|
+
115,
|
|
69
|
+
97,
|
|
70
|
+
108,
|
|
71
|
+
101
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
kind: "account",
|
|
76
|
+
path: "mint"
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "record",
|
|
83
|
+
writable: true,
|
|
84
|
+
pda: {
|
|
85
|
+
seeds: [
|
|
86
|
+
{
|
|
87
|
+
kind: "const",
|
|
88
|
+
value: [
|
|
89
|
+
98,
|
|
90
|
+
117,
|
|
91
|
+
121,
|
|
92
|
+
101,
|
|
93
|
+
114
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
kind: "account",
|
|
98
|
+
path: "mint"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
kind: "arg",
|
|
102
|
+
path: "wallet"
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "system_program",
|
|
109
|
+
address: "11111111111111111111111111111111"
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
args: [
|
|
113
|
+
{
|
|
114
|
+
name: "wallet",
|
|
115
|
+
type: "pubkey"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "close_buyer_record",
|
|
121
|
+
discriminator: [
|
|
122
|
+
27,
|
|
123
|
+
105,
|
|
124
|
+
238,
|
|
125
|
+
144,
|
|
126
|
+
158,
|
|
127
|
+
212,
|
|
128
|
+
40,
|
|
129
|
+
114
|
|
130
|
+
],
|
|
131
|
+
accounts: [
|
|
132
|
+
{
|
|
133
|
+
name: "wallet",
|
|
134
|
+
writable: true,
|
|
135
|
+
signer: true,
|
|
136
|
+
relations: [
|
|
137
|
+
"record"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "mint",
|
|
142
|
+
relations: [
|
|
143
|
+
"record"
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "record",
|
|
148
|
+
writable: true,
|
|
149
|
+
pda: {
|
|
150
|
+
seeds: [
|
|
151
|
+
{
|
|
152
|
+
kind: "const",
|
|
153
|
+
value: [
|
|
154
|
+
98,
|
|
155
|
+
117,
|
|
156
|
+
121,
|
|
157
|
+
101,
|
|
158
|
+
114
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
kind: "account",
|
|
163
|
+
path: "mint"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
kind: "account",
|
|
167
|
+
path: "wallet"
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "rules",
|
|
174
|
+
docs: [
|
|
175
|
+
"offering period is over, and only when the bytes are a SaleRules in a",
|
|
176
|
+
"layout this build reads. Anything else, including rules written by an",
|
|
177
|
+
'older build, reads as "not over" and leaves the two older ways in exactly',
|
|
178
|
+
"as they were, so this account can never stop a close that worked before."
|
|
179
|
+
],
|
|
180
|
+
pda: {
|
|
181
|
+
seeds: [
|
|
182
|
+
{
|
|
183
|
+
kind: "const",
|
|
184
|
+
value: [
|
|
185
|
+
115,
|
|
186
|
+
97,
|
|
187
|
+
108,
|
|
188
|
+
101
|
|
189
|
+
]
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
kind: "account",
|
|
193
|
+
path: "mint"
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
],
|
|
199
|
+
args: []
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "create_sale",
|
|
203
|
+
discriminator: [
|
|
204
|
+
137,
|
|
205
|
+
197,
|
|
206
|
+
124,
|
|
207
|
+
245,
|
|
208
|
+
254,
|
|
209
|
+
35,
|
|
210
|
+
17,
|
|
211
|
+
12
|
|
212
|
+
],
|
|
213
|
+
accounts: [
|
|
214
|
+
{
|
|
215
|
+
name: "issuer",
|
|
216
|
+
writable: true,
|
|
217
|
+
signer: true
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: "pool",
|
|
221
|
+
docs: [
|
|
222
|
+
"owning program, the length and the discriminator before any field is used."
|
|
223
|
+
]
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "mint",
|
|
227
|
+
docs: [
|
|
228
|
+
"owning program is checked there before the bytes are read."
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: "credential",
|
|
233
|
+
docs: [
|
|
234
|
+
"key named in `credential`, owned by the attestation service, and to carry",
|
|
235
|
+
"the credential discriminator. Refused outright in the other modes."
|
|
236
|
+
],
|
|
237
|
+
optional: true
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "schema",
|
|
241
|
+
docs: [
|
|
242
|
+
"key named in `schema`, owned by the attestation service, to belong to",
|
|
243
|
+
"`credential`, and not to be paused. Refused outright in the other modes."
|
|
244
|
+
],
|
|
245
|
+
optional: true
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: "dbc_config",
|
|
249
|
+
docs: [
|
|
250
|
+
"template this pool names, owned by DBC and of the right kind. Its fee mode",
|
|
251
|
+
"and the supply its curve sells decide whether the sale may open at all, and",
|
|
252
|
+
"it names the paying token."
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "quote_mint",
|
|
257
|
+
docs: [
|
|
258
|
+
"Required in every mode. Must be the paying token the template names. It is",
|
|
259
|
+
"stored in the rules, its freeze authority is checked, and on a banded sale",
|
|
260
|
+
"its decimals are stored so the hook can turn a curve price into dollars",
|
|
261
|
+
"without being handed a mint at buy time."
|
|
262
|
+
]
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: "rules",
|
|
266
|
+
writable: true,
|
|
267
|
+
pda: {
|
|
268
|
+
seeds: [
|
|
269
|
+
{
|
|
270
|
+
kind: "const",
|
|
271
|
+
value: [
|
|
272
|
+
115,
|
|
273
|
+
97,
|
|
274
|
+
108,
|
|
275
|
+
101
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
kind: "account",
|
|
280
|
+
path: "mint"
|
|
281
|
+
}
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "extra_account_meta_list",
|
|
287
|
+
docs: [
|
|
288
|
+
"seeds the transfer-hook interface requires, and `init` makes it single-use."
|
|
289
|
+
],
|
|
290
|
+
writable: true,
|
|
291
|
+
pda: {
|
|
292
|
+
seeds: [
|
|
293
|
+
{
|
|
294
|
+
kind: "const",
|
|
295
|
+
value: [
|
|
296
|
+
101,
|
|
297
|
+
120,
|
|
298
|
+
116,
|
|
299
|
+
114,
|
|
300
|
+
97,
|
|
301
|
+
45,
|
|
302
|
+
97,
|
|
303
|
+
99,
|
|
304
|
+
99,
|
|
305
|
+
111,
|
|
306
|
+
117,
|
|
307
|
+
110,
|
|
308
|
+
116,
|
|
309
|
+
45,
|
|
310
|
+
109,
|
|
311
|
+
101,
|
|
312
|
+
116,
|
|
313
|
+
97,
|
|
314
|
+
115
|
|
315
|
+
]
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
kind: "account",
|
|
319
|
+
path: "mint"
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: "system_program",
|
|
326
|
+
address: "11111111111111111111111111111111"
|
|
327
|
+
}
|
|
328
|
+
],
|
|
329
|
+
args: [
|
|
330
|
+
{
|
|
331
|
+
name: "cap",
|
|
332
|
+
type: "u64"
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: "access_mode",
|
|
336
|
+
type: "u8"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
name: "credential",
|
|
340
|
+
type: "pubkey"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: "schema",
|
|
344
|
+
type: "pubkey"
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: "band",
|
|
348
|
+
type: {
|
|
349
|
+
defined: {
|
|
350
|
+
name: "PriceBand"
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: "ends_at",
|
|
356
|
+
type: "i64"
|
|
357
|
+
}
|
|
358
|
+
]
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: "execute",
|
|
362
|
+
discriminator: [
|
|
363
|
+
105,
|
|
364
|
+
37,
|
|
365
|
+
101,
|
|
366
|
+
197,
|
|
367
|
+
75,
|
|
368
|
+
251,
|
|
369
|
+
102,
|
|
370
|
+
26
|
|
371
|
+
],
|
|
372
|
+
accounts: [
|
|
373
|
+
{
|
|
374
|
+
name: "source_token",
|
|
375
|
+
docs: [
|
|
376
|
+
"owning program is checked. Nothing is read from it before that."
|
|
377
|
+
]
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: "mint",
|
|
381
|
+
docs: [
|
|
382
|
+
"the rules account is proven to be the one derived from it."
|
|
383
|
+
],
|
|
384
|
+
relations: [
|
|
385
|
+
"rules"
|
|
386
|
+
]
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: "destination_token",
|
|
390
|
+
docs: [
|
|
391
|
+
"owning program is checked."
|
|
392
|
+
]
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: "authority",
|
|
396
|
+
docs: [
|
|
397
|
+
"because a signature proves nothing about who is allowed to receive tokens."
|
|
398
|
+
]
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: "extra_account_meta_list",
|
|
402
|
+
docs: [
|
|
403
|
+
"Token-2022 resolves the extra accounts from it before the call. Pangu itself",
|
|
404
|
+
"reads nothing out of it, so it is carried, not trusted."
|
|
405
|
+
]
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: "rules",
|
|
409
|
+
writable: true,
|
|
410
|
+
pda: {
|
|
411
|
+
seeds: [
|
|
412
|
+
{
|
|
413
|
+
kind: "const",
|
|
414
|
+
value: [
|
|
415
|
+
115,
|
|
416
|
+
97,
|
|
417
|
+
108,
|
|
418
|
+
101
|
|
419
|
+
]
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
kind: "account",
|
|
423
|
+
path: "mint"
|
|
424
|
+
}
|
|
425
|
+
]
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: "destination_record",
|
|
430
|
+
docs: [
|
|
431
|
+
"program, discriminator, mint and wallet are all checked by hand below."
|
|
432
|
+
],
|
|
433
|
+
writable: true
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
name: "source_record",
|
|
437
|
+
docs: [
|
|
438
|
+
"program, discriminator, mint and wallet are all checked by hand below."
|
|
439
|
+
],
|
|
440
|
+
writable: true
|
|
441
|
+
}
|
|
442
|
+
],
|
|
443
|
+
args: [
|
|
444
|
+
{
|
|
445
|
+
name: "amount",
|
|
446
|
+
type: "u64"
|
|
447
|
+
}
|
|
448
|
+
]
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: "open_buyer_record",
|
|
452
|
+
discriminator: [
|
|
453
|
+
216,
|
|
454
|
+
212,
|
|
455
|
+
35,
|
|
456
|
+
190,
|
|
457
|
+
138,
|
|
458
|
+
99,
|
|
459
|
+
66,
|
|
460
|
+
197
|
|
461
|
+
],
|
|
462
|
+
accounts: [
|
|
463
|
+
{
|
|
464
|
+
name: "wallet",
|
|
465
|
+
writable: true,
|
|
466
|
+
signer: true
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
name: "mint"
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: "rules",
|
|
473
|
+
pda: {
|
|
474
|
+
seeds: [
|
|
475
|
+
{
|
|
476
|
+
kind: "const",
|
|
477
|
+
value: [
|
|
478
|
+
115,
|
|
479
|
+
97,
|
|
480
|
+
108,
|
|
481
|
+
101
|
|
482
|
+
]
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
kind: "account",
|
|
486
|
+
path: "mint"
|
|
487
|
+
}
|
|
488
|
+
]
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: "record",
|
|
493
|
+
writable: true,
|
|
494
|
+
pda: {
|
|
495
|
+
seeds: [
|
|
496
|
+
{
|
|
497
|
+
kind: "const",
|
|
498
|
+
value: [
|
|
499
|
+
98,
|
|
500
|
+
117,
|
|
501
|
+
121,
|
|
502
|
+
101,
|
|
503
|
+
114
|
|
504
|
+
]
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
kind: "account",
|
|
508
|
+
path: "mint"
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
kind: "account",
|
|
512
|
+
path: "wallet"
|
|
513
|
+
}
|
|
514
|
+
]
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
name: "system_program",
|
|
519
|
+
address: "11111111111111111111111111111111"
|
|
520
|
+
}
|
|
521
|
+
],
|
|
522
|
+
args: []
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
name: "revoke_buyer",
|
|
526
|
+
discriminator: [
|
|
527
|
+
78,
|
|
528
|
+
97,
|
|
529
|
+
108,
|
|
530
|
+
126,
|
|
531
|
+
172,
|
|
532
|
+
157,
|
|
533
|
+
228,
|
|
534
|
+
134
|
|
535
|
+
],
|
|
536
|
+
accounts: [
|
|
537
|
+
{
|
|
538
|
+
name: "issuer",
|
|
539
|
+
signer: true,
|
|
540
|
+
relations: [
|
|
541
|
+
"rules"
|
|
542
|
+
]
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: "mint",
|
|
546
|
+
relations: [
|
|
547
|
+
"rules",
|
|
548
|
+
"record"
|
|
549
|
+
]
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
name: "rules",
|
|
553
|
+
pda: {
|
|
554
|
+
seeds: [
|
|
555
|
+
{
|
|
556
|
+
kind: "const",
|
|
557
|
+
value: [
|
|
558
|
+
115,
|
|
559
|
+
97,
|
|
560
|
+
108,
|
|
561
|
+
101
|
|
562
|
+
]
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
kind: "account",
|
|
566
|
+
path: "mint"
|
|
567
|
+
}
|
|
568
|
+
]
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
name: "record",
|
|
573
|
+
writable: true,
|
|
574
|
+
pda: {
|
|
575
|
+
seeds: [
|
|
576
|
+
{
|
|
577
|
+
kind: "const",
|
|
578
|
+
value: [
|
|
579
|
+
98,
|
|
580
|
+
117,
|
|
581
|
+
121,
|
|
582
|
+
101,
|
|
583
|
+
114
|
|
584
|
+
]
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
kind: "account",
|
|
588
|
+
path: "mint"
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
kind: "arg",
|
|
592
|
+
path: "wallet"
|
|
593
|
+
}
|
|
594
|
+
]
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
],
|
|
598
|
+
args: [
|
|
599
|
+
{
|
|
600
|
+
name: "wallet",
|
|
601
|
+
type: "pubkey"
|
|
602
|
+
}
|
|
603
|
+
]
|
|
604
|
+
}
|
|
605
|
+
],
|
|
606
|
+
accounts: [
|
|
607
|
+
{
|
|
608
|
+
name: "BuyerRecord",
|
|
609
|
+
discriminator: [
|
|
610
|
+
107,
|
|
611
|
+
122,
|
|
612
|
+
54,
|
|
613
|
+
31,
|
|
614
|
+
4,
|
|
615
|
+
54,
|
|
616
|
+
209,
|
|
617
|
+
38
|
|
618
|
+
]
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
name: "SaleRules",
|
|
622
|
+
discriminator: [
|
|
623
|
+
39,
|
|
624
|
+
103,
|
|
625
|
+
95,
|
|
626
|
+
70,
|
|
627
|
+
15,
|
|
628
|
+
23,
|
|
629
|
+
201,
|
|
630
|
+
128
|
|
631
|
+
]
|
|
632
|
+
}
|
|
633
|
+
],
|
|
634
|
+
events: [
|
|
635
|
+
{
|
|
636
|
+
name: "Bought",
|
|
637
|
+
discriminator: [
|
|
638
|
+
193,
|
|
639
|
+
56,
|
|
640
|
+
215,
|
|
641
|
+
24,
|
|
642
|
+
156,
|
|
643
|
+
76,
|
|
644
|
+
42,
|
|
645
|
+
104
|
|
646
|
+
]
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
name: "BuyerApproved",
|
|
650
|
+
discriminator: [
|
|
651
|
+
146,
|
|
652
|
+
41,
|
|
653
|
+
51,
|
|
654
|
+
7,
|
|
655
|
+
192,
|
|
656
|
+
96,
|
|
657
|
+
249,
|
|
658
|
+
196
|
|
659
|
+
]
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
name: "BuyerRecordClosed",
|
|
663
|
+
discriminator: [
|
|
664
|
+
175,
|
|
665
|
+
245,
|
|
666
|
+
14,
|
|
667
|
+
122,
|
|
668
|
+
129,
|
|
669
|
+
113,
|
|
670
|
+
23,
|
|
671
|
+
155
|
|
672
|
+
]
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
name: "BuyerRecordOpened",
|
|
676
|
+
discriminator: [
|
|
677
|
+
249,
|
|
678
|
+
239,
|
|
679
|
+
77,
|
|
680
|
+
35,
|
|
681
|
+
176,
|
|
682
|
+
81,
|
|
683
|
+
45,
|
|
684
|
+
85
|
|
685
|
+
]
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
name: "BuyerRevoked",
|
|
689
|
+
discriminator: [
|
|
690
|
+
116,
|
|
691
|
+
129,
|
|
692
|
+
248,
|
|
693
|
+
92,
|
|
694
|
+
255,
|
|
695
|
+
222,
|
|
696
|
+
13,
|
|
697
|
+
59
|
|
698
|
+
]
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
name: "SaleCreated",
|
|
702
|
+
discriminator: [
|
|
703
|
+
164,
|
|
704
|
+
187,
|
|
705
|
+
32,
|
|
706
|
+
35,
|
|
707
|
+
143,
|
|
708
|
+
167,
|
|
709
|
+
235,
|
|
710
|
+
132
|
|
711
|
+
]
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
name: "SoldBack",
|
|
715
|
+
discriminator: [
|
|
716
|
+
153,
|
|
717
|
+
215,
|
|
718
|
+
200,
|
|
719
|
+
12,
|
|
720
|
+
194,
|
|
721
|
+
138,
|
|
722
|
+
190,
|
|
723
|
+
253
|
|
724
|
+
]
|
|
725
|
+
}
|
|
726
|
+
],
|
|
727
|
+
errors: [
|
|
728
|
+
{
|
|
729
|
+
code: 6e3,
|
|
730
|
+
name: "NotTransferring",
|
|
731
|
+
msg: "Token-2022 is not mid-transfer on both token accounts"
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
code: 6001,
|
|
735
|
+
name: "ReceivingAccountOwnerCanChange",
|
|
736
|
+
msg: "the receiving token account's owner can still be changed"
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
code: 6002,
|
|
740
|
+
name: "WrongMint",
|
|
741
|
+
msg: "the account does not belong to this sale's mint"
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
code: 6003,
|
|
745
|
+
name: "WrongBuyerRecord",
|
|
746
|
+
msg: "the buyer record is not the one for this wallet"
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
code: 6004,
|
|
750
|
+
name: "BuyerRecordMissing",
|
|
751
|
+
msg: "this wallet has no buyer record yet"
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
code: 6005,
|
|
755
|
+
name: "NotApproved",
|
|
756
|
+
msg: "this wallet is not on the issuer's approved list"
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
code: 6006,
|
|
760
|
+
name: "CredentialInvalid",
|
|
761
|
+
msg: "the attestation is not valid for this wallet, credential and schema"
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
code: 6007,
|
|
765
|
+
name: "CredentialExpired",
|
|
766
|
+
msg: "the attestation has expired"
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
code: 6008,
|
|
770
|
+
name: "CredentialSignerNotAuthorized",
|
|
771
|
+
msg: "the key that signed the attestation is no longer an authorized signer"
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
code: 6009,
|
|
775
|
+
name: "OverCap",
|
|
776
|
+
msg: "this buy would take the wallet over the per-wallet cap"
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
code: 6010,
|
|
780
|
+
name: "WalletToWalletDuringSale",
|
|
781
|
+
msg: "the token cannot move between wallets while the sale is running"
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
code: 6011,
|
|
785
|
+
name: "PriceStale",
|
|
786
|
+
msg: "the reference price is too old, missing or not a positive number"
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
code: 6012,
|
|
790
|
+
name: "PriceOutsideBand",
|
|
791
|
+
msg: "the price is outside the allowed band"
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
code: 6013,
|
|
795
|
+
name: "WrongPriceAccount",
|
|
796
|
+
msg: "the price account is not the one named in the rules, or does not hold a usable price"
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
code: 6014,
|
|
800
|
+
name: "PriceNotFullyVerified",
|
|
801
|
+
msg: "the price update has not been signed by two thirds of the guardians"
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
code: 6015,
|
|
805
|
+
name: "PriceTooUncertain",
|
|
806
|
+
msg: "the price carries a wider confidence interval than this sale accepts"
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
code: 6016,
|
|
810
|
+
name: "NotPoolCreator",
|
|
811
|
+
msg: "the signer did not create this pool"
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
code: 6017,
|
|
815
|
+
name: "NotAHookPool",
|
|
816
|
+
msg: "the account is not a DBC transfer-hook pool"
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
code: 6018,
|
|
820
|
+
name: "HookProgramMismatch",
|
|
821
|
+
msg: "the mint's transfer hook does not name this program"
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
code: 6019,
|
|
825
|
+
name: "MintAuthorityStillSet",
|
|
826
|
+
msg: "someone can still mint this token, which would go straight past the cap"
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
code: 6020,
|
|
830
|
+
name: "WrongLaunchTemplate",
|
|
831
|
+
msg: "the launch template is not the one this pool was opened on"
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
code: 6021,
|
|
835
|
+
name: "FeesNotInQuoteToken",
|
|
836
|
+
msg: "the launch template collects fees in the sale token instead of the paying token"
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
code: 6022,
|
|
840
|
+
name: "ZeroCap",
|
|
841
|
+
msg: "the cap must be above zero"
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
code: 6023,
|
|
845
|
+
name: "InvalidAccessMode",
|
|
846
|
+
msg: "that access mode is not available"
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
code: 6024,
|
|
850
|
+
name: "InvalidBand",
|
|
851
|
+
msg: "the price band settings are incomplete, out of range, or set on a sale that has no band"
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
code: 6025,
|
|
855
|
+
name: "SaleStillRunning",
|
|
856
|
+
msg: "the sale is still running, the mint still names this program as its hook"
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
code: 6026,
|
|
860
|
+
name: "NotIssuer",
|
|
861
|
+
msg: "only the issuer can do this"
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
code: 6027,
|
|
865
|
+
name: "MathOverflow",
|
|
866
|
+
msg: "the counter would overflow"
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
code: 6028,
|
|
870
|
+
name: "WrongLayoutVersion",
|
|
871
|
+
msg: "these sale rules were written by another layout of the program"
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
code: 6029,
|
|
875
|
+
name: "BandNeedsDollarQuote",
|
|
876
|
+
msg: "a price band needs buyers to pay in a dollar token on this network's list"
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
code: 6030,
|
|
880
|
+
name: "IssuerControlsPayingToken",
|
|
881
|
+
msg: "the issuer can freeze the paying token, which would let them stop sellers being paid"
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
code: 6031,
|
|
885
|
+
name: "CapCoversWholeSale",
|
|
886
|
+
msg: "the cap is at or above everything the curve sells, so it limits nobody"
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
code: 6032,
|
|
890
|
+
name: "EndInThePast",
|
|
891
|
+
msg: "the offering period would end at a time that has already passed"
|
|
892
|
+
}
|
|
893
|
+
],
|
|
894
|
+
types: [
|
|
895
|
+
{
|
|
896
|
+
name: "Bought",
|
|
897
|
+
type: {
|
|
898
|
+
kind: "struct",
|
|
899
|
+
fields: [
|
|
900
|
+
{
|
|
901
|
+
name: "mint",
|
|
902
|
+
type: "pubkey"
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
name: "wallet",
|
|
906
|
+
type: "pubkey"
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
name: "amount",
|
|
910
|
+
type: "u64"
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
name: "net_bought",
|
|
914
|
+
type: "u64"
|
|
915
|
+
}
|
|
916
|
+
]
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
name: "BuyerApproved",
|
|
921
|
+
type: {
|
|
922
|
+
kind: "struct",
|
|
923
|
+
fields: [
|
|
924
|
+
{
|
|
925
|
+
name: "mint",
|
|
926
|
+
type: "pubkey"
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
name: "wallet",
|
|
930
|
+
type: "pubkey"
|
|
931
|
+
}
|
|
932
|
+
]
|
|
933
|
+
}
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
name: "BuyerRecord",
|
|
937
|
+
docs: [
|
|
938
|
+
"One wallet's standing in one sale. Keyed on the wallet that owns the token",
|
|
939
|
+
"account, never on the token account, so extra token accounts share one cap."
|
|
940
|
+
],
|
|
941
|
+
type: {
|
|
942
|
+
kind: "struct",
|
|
943
|
+
fields: [
|
|
944
|
+
{
|
|
945
|
+
name: "mint",
|
|
946
|
+
type: "pubkey"
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
name: "wallet",
|
|
950
|
+
type: "pubkey"
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
name: "approved",
|
|
954
|
+
type: "bool"
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
name: "net_bought",
|
|
958
|
+
docs: [
|
|
959
|
+
"Tokens received from the pool minus tokens sold back to it."
|
|
960
|
+
],
|
|
961
|
+
type: "u64"
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
name: "bump",
|
|
965
|
+
type: "u8"
|
|
966
|
+
}
|
|
967
|
+
]
|
|
968
|
+
}
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
name: "BuyerRecordClosed",
|
|
972
|
+
type: {
|
|
973
|
+
kind: "struct",
|
|
974
|
+
fields: [
|
|
975
|
+
{
|
|
976
|
+
name: "mint",
|
|
977
|
+
type: "pubkey"
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
name: "wallet",
|
|
981
|
+
type: "pubkey"
|
|
982
|
+
}
|
|
983
|
+
]
|
|
984
|
+
}
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
name: "BuyerRecordOpened",
|
|
988
|
+
type: {
|
|
989
|
+
kind: "struct",
|
|
990
|
+
fields: [
|
|
991
|
+
{
|
|
992
|
+
name: "mint",
|
|
993
|
+
type: "pubkey"
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
name: "wallet",
|
|
997
|
+
type: "pubkey"
|
|
998
|
+
}
|
|
999
|
+
]
|
|
1000
|
+
}
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
name: "BuyerRevoked",
|
|
1004
|
+
type: {
|
|
1005
|
+
kind: "struct",
|
|
1006
|
+
fields: [
|
|
1007
|
+
{
|
|
1008
|
+
name: "mint",
|
|
1009
|
+
type: "pubkey"
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
name: "wallet",
|
|
1013
|
+
type: "pubkey"
|
|
1014
|
+
}
|
|
1015
|
+
]
|
|
1016
|
+
}
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
name: "PriceBand",
|
|
1020
|
+
docs: [
|
|
1021
|
+
"The price band settings, passed to `create_sale` as one value.",
|
|
1022
|
+
"",
|
|
1023
|
+
"They travel together because they are only meaningful together: the price",
|
|
1024
|
+
"account's address is derived from the shard id and the feed id, so changing",
|
|
1025
|
+
"either one changes which account the hook must read."
|
|
1026
|
+
],
|
|
1027
|
+
type: {
|
|
1028
|
+
kind: "struct",
|
|
1029
|
+
fields: [
|
|
1030
|
+
{
|
|
1031
|
+
name: "band_bps",
|
|
1032
|
+
docs: [
|
|
1033
|
+
"How far above the live stock price a buy may leave the curve price, in",
|
|
1034
|
+
"basis points. Zero switches the whole band off."
|
|
1035
|
+
],
|
|
1036
|
+
type: "u16"
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
name: "price_account",
|
|
1040
|
+
docs: [
|
|
1041
|
+
"The Pyth price feed account. Must be the address the shard id and the feed",
|
|
1042
|
+
"id below produce under Pyth's price feed program."
|
|
1043
|
+
],
|
|
1044
|
+
type: "pubkey"
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
name: "price_feed_id",
|
|
1048
|
+
docs: [
|
|
1049
|
+
"The Pyth feed carrying the stock price in dollars."
|
|
1050
|
+
],
|
|
1051
|
+
type: {
|
|
1052
|
+
array: [
|
|
1053
|
+
"u8",
|
|
1054
|
+
32
|
|
1055
|
+
]
|
|
1056
|
+
}
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
name: "price_shard",
|
|
1060
|
+
docs: [
|
|
1061
|
+
"Which of Pyth's shards this sale reads. A shard is just a second copy of",
|
|
1062
|
+
"the same feed at a second address, so a sale can depend on a price its own",
|
|
1063
|
+
"refresher keeps fresh rather than on Pyth's sponsored one."
|
|
1064
|
+
],
|
|
1065
|
+
type: "u16"
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
name: "max_price_age_secs",
|
|
1069
|
+
docs: [
|
|
1070
|
+
"How old the published price may be on a buy, in seconds."
|
|
1071
|
+
],
|
|
1072
|
+
type: "u32"
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
name: "max_conf_bps",
|
|
1076
|
+
docs: [
|
|
1077
|
+
"The widest confidence interval this sale will buy against, in basis points",
|
|
1078
|
+
"of the price itself."
|
|
1079
|
+
],
|
|
1080
|
+
type: "u16"
|
|
1081
|
+
}
|
|
1082
|
+
]
|
|
1083
|
+
}
|
|
1084
|
+
},
|
|
1085
|
+
{
|
|
1086
|
+
name: "SaleCreated",
|
|
1087
|
+
type: {
|
|
1088
|
+
kind: "struct",
|
|
1089
|
+
fields: [
|
|
1090
|
+
{
|
|
1091
|
+
name: "mint",
|
|
1092
|
+
type: "pubkey"
|
|
1093
|
+
},
|
|
1094
|
+
{
|
|
1095
|
+
name: "pool",
|
|
1096
|
+
type: "pubkey"
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
name: "issuer",
|
|
1100
|
+
type: "pubkey"
|
|
1101
|
+
},
|
|
1102
|
+
{
|
|
1103
|
+
name: "base_vault",
|
|
1104
|
+
type: "pubkey"
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
name: "cap",
|
|
1108
|
+
type: "u64"
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
name: "access_mode",
|
|
1112
|
+
type: "u8"
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
name: "band_bps",
|
|
1116
|
+
type: "u16"
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
name: "quote_mint",
|
|
1120
|
+
type: "pubkey"
|
|
1121
|
+
},
|
|
1122
|
+
{
|
|
1123
|
+
name: "ends_at",
|
|
1124
|
+
docs: [
|
|
1125
|
+
"Unix seconds when the offering period ends and every rule lifts. Zero",
|
|
1126
|
+
"means the rules hold until graduation."
|
|
1127
|
+
],
|
|
1128
|
+
type: "i64"
|
|
1129
|
+
}
|
|
1130
|
+
]
|
|
1131
|
+
}
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
name: "SaleRules",
|
|
1135
|
+
docs: [
|
|
1136
|
+
"The rules of one sale. Written once at creation and never changed: there is no",
|
|
1137
|
+
"update instruction, which is what makes the cap a promise instead of a setting."
|
|
1138
|
+
],
|
|
1139
|
+
type: {
|
|
1140
|
+
kind: "struct",
|
|
1141
|
+
fields: [
|
|
1142
|
+
{
|
|
1143
|
+
name: "mint",
|
|
1144
|
+
type: "pubkey"
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
name: "pool",
|
|
1148
|
+
type: "pubkey"
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
name: "base_vault",
|
|
1152
|
+
docs: [
|
|
1153
|
+
"The pool's base token vault, read out of the pool account at creation. The",
|
|
1154
|
+
"hook tells a buy from a sell by comparing against this one address."
|
|
1155
|
+
],
|
|
1156
|
+
type: "pubkey"
|
|
1157
|
+
},
|
|
1158
|
+
{
|
|
1159
|
+
name: "issuer",
|
|
1160
|
+
docs: [
|
|
1161
|
+
"The pool creator. The only key that can approve or revoke buyers."
|
|
1162
|
+
],
|
|
1163
|
+
type: "pubkey"
|
|
1164
|
+
},
|
|
1165
|
+
{
|
|
1166
|
+
name: "cap",
|
|
1167
|
+
docs: [
|
|
1168
|
+
"Most tokens one wallet may hold net of sells, in raw units."
|
|
1169
|
+
],
|
|
1170
|
+
type: "u64"
|
|
1171
|
+
},
|
|
1172
|
+
{
|
|
1173
|
+
name: "access_mode",
|
|
1174
|
+
type: "u8"
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
name: "credential",
|
|
1178
|
+
docs: [
|
|
1179
|
+
"Mode 2 only: the attestation credential whose approvals count. Zero",
|
|
1180
|
+
"otherwise. Sits at byte 145 of the account, and the hook's extra accounts",
|
|
1181
|
+
"are derived by reading it there, so it can never move."
|
|
1182
|
+
],
|
|
1183
|
+
type: "pubkey"
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
name: "schema",
|
|
1187
|
+
docs: [
|
|
1188
|
+
"Mode 2 only: the attestation schema that counts. Zero otherwise. Sits at",
|
|
1189
|
+
"byte 177 of the account, read the same way."
|
|
1190
|
+
],
|
|
1191
|
+
type: "pubkey"
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
name: "price_account",
|
|
1195
|
+
docs: [
|
|
1196
|
+
"Band only: the Pyth price feed account, at byte 209. Zero otherwise."
|
|
1197
|
+
],
|
|
1198
|
+
type: "pubkey"
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
name: "price_feed_id",
|
|
1202
|
+
docs: [
|
|
1203
|
+
"Band only: the Pyth feed the price account must carry, at byte 241."
|
|
1204
|
+
],
|
|
1205
|
+
type: {
|
|
1206
|
+
array: [
|
|
1207
|
+
"u8",
|
|
1208
|
+
32
|
|
1209
|
+
]
|
|
1210
|
+
}
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
name: "price_shard",
|
|
1214
|
+
docs: [
|
|
1215
|
+
"Band only: the Pyth shard the price account was derived under."
|
|
1216
|
+
],
|
|
1217
|
+
type: "u16"
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
name: "band_bps",
|
|
1221
|
+
docs: [
|
|
1222
|
+
"Zero means no band."
|
|
1223
|
+
],
|
|
1224
|
+
type: "u16"
|
|
1225
|
+
},
|
|
1226
|
+
{
|
|
1227
|
+
name: "max_price_age_secs",
|
|
1228
|
+
type: "u32"
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
name: "max_conf_bps",
|
|
1232
|
+
type: "u16"
|
|
1233
|
+
},
|
|
1234
|
+
{
|
|
1235
|
+
name: "base_decimals",
|
|
1236
|
+
docs: [
|
|
1237
|
+
"Read off the two mints at creation, so the curve price can be turned into",
|
|
1238
|
+
"dollars per whole token without trusting anything passed at buy time."
|
|
1239
|
+
],
|
|
1240
|
+
type: "u8"
|
|
1241
|
+
},
|
|
1242
|
+
{
|
|
1243
|
+
name: "quote_decimals",
|
|
1244
|
+
type: "u8"
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
name: "buyers",
|
|
1248
|
+
docs: [
|
|
1249
|
+
"Wallets whose record is above zero right now."
|
|
1250
|
+
],
|
|
1251
|
+
type: "u32"
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
name: "total_net_bought",
|
|
1255
|
+
docs: [
|
|
1256
|
+
"Sum of every record, so the app can show the largest holder's share."
|
|
1257
|
+
],
|
|
1258
|
+
type: "u64"
|
|
1259
|
+
},
|
|
1260
|
+
{
|
|
1261
|
+
name: "bump",
|
|
1262
|
+
type: "u8"
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
name: "layout_version",
|
|
1266
|
+
docs: [
|
|
1267
|
+
"Which layout wrote this account, at byte 298. It takes the first of what",
|
|
1268
|
+
"used to be the spare bytes, so the account is the same size and every",
|
|
1269
|
+
"field in front of it sits exactly where it always did. A sale opened",
|
|
1270
|
+
"before this byte existed reads as version 0, which is how a reader tells",
|
|
1271
|
+
"the two apart instead of reading one layout's bytes as the other's."
|
|
1272
|
+
],
|
|
1273
|
+
type: "u8"
|
|
1274
|
+
},
|
|
1275
|
+
{
|
|
1276
|
+
name: "quote_mint",
|
|
1277
|
+
docs: [
|
|
1278
|
+
"The token buyers pay in, read from the launch template at creation, at",
|
|
1279
|
+
"byte 299. Layout 2 onwards; a version 1 account holds zeros here."
|
|
1280
|
+
],
|
|
1281
|
+
type: "pubkey"
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
name: "ends_at",
|
|
1285
|
+
docs: [
|
|
1286
|
+
"Unix seconds at which the offering period ends, at byte 331. From then on",
|
|
1287
|
+
"every rule lifts and the token moves freely, so a curve that never fills",
|
|
1288
|
+
"cannot hold the token in place for good. Zero means no end: the rules hold",
|
|
1289
|
+
"until graduation. Fixed at creation like every other rule."
|
|
1290
|
+
],
|
|
1291
|
+
type: "i64"
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
name: "reserved",
|
|
1295
|
+
type: {
|
|
1296
|
+
array: [
|
|
1297
|
+
"u8",
|
|
1298
|
+
23
|
|
1299
|
+
]
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
]
|
|
1303
|
+
}
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
name: "SoldBack",
|
|
1307
|
+
type: {
|
|
1308
|
+
kind: "struct",
|
|
1309
|
+
fields: [
|
|
1310
|
+
{
|
|
1311
|
+
name: "mint",
|
|
1312
|
+
type: "pubkey"
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
name: "wallet",
|
|
1316
|
+
type: "pubkey"
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
name: "amount",
|
|
1320
|
+
type: "u64"
|
|
1321
|
+
},
|
|
1322
|
+
{
|
|
1323
|
+
name: "net_bought",
|
|
1324
|
+
type: "u64"
|
|
1325
|
+
}
|
|
1326
|
+
]
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
]
|
|
1330
|
+
};
|
|
1331
|
+
|
|
1332
|
+
// src/constants.ts
|
|
1333
|
+
var PANGU_IDL = pangu_default;
|
|
1334
|
+
var PANGU_PROGRAM_ID = new PublicKey(PANGU_IDL.address);
|
|
1335
|
+
var DBC_PROGRAM_ID = new PublicKey(
|
|
1336
|
+
"dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN"
|
|
1337
|
+
);
|
|
1338
|
+
var SAS_PROGRAM_ID = new PublicKey(
|
|
1339
|
+
"22zoJMtdu4tQc2PzL74ZUT7FrwgB1Udec8DdW4yw4BdG"
|
|
1340
|
+
);
|
|
1341
|
+
var PYTH_PRICE_FEED_PROGRAM_ID = new PublicKey(
|
|
1342
|
+
"pythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsT"
|
|
1343
|
+
);
|
|
1344
|
+
var PYTH_RECEIVER_PROGRAM_ID = new PublicKey(
|
|
1345
|
+
"rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ"
|
|
1346
|
+
);
|
|
1347
|
+
var PANGU_SHARD_ID = 7700;
|
|
1348
|
+
var SALE_RULES_LAYOUT_VERSIONS = /* @__PURE__ */ new Set([1, 2]);
|
|
1349
|
+
var ACCESS_MODE = {
|
|
1350
|
+
open: 0,
|
|
1351
|
+
issuerList: 1,
|
|
1352
|
+
verifierCredential: 2
|
|
1353
|
+
};
|
|
1354
|
+
var SEEDS = {
|
|
1355
|
+
sale: "sale",
|
|
1356
|
+
buyer: "buyer",
|
|
1357
|
+
extraAccountMetas: "extra-account-metas",
|
|
1358
|
+
attestation: "attestation",
|
|
1359
|
+
credential: "credential",
|
|
1360
|
+
schema: "schema",
|
|
1361
|
+
dbcTokenVault: "token_vault"
|
|
1362
|
+
};
|
|
1363
|
+
var LIMITS = {
|
|
1364
|
+
/** price.rs MAX_BAND_BPS: half again over the live stock price. */
|
|
1365
|
+
maxBandBps: 5e3,
|
|
1366
|
+
minBandBps: 1,
|
|
1367
|
+
/** price.rs MAX_PRICE_AGE_SECS, and create_sale.rs check_band takes 1..=3600. */
|
|
1368
|
+
maxPriceAgeSecs: 3600,
|
|
1369
|
+
minPriceAgeSecs: 1,
|
|
1370
|
+
/** price.rs MAX_CONF_BPS: ten percent, and check_band takes 1..=1000. */
|
|
1371
|
+
maxConfBps: 1e3,
|
|
1372
|
+
minConfBps: 1,
|
|
1373
|
+
/** price.rs MAX_DECIMALS. */
|
|
1374
|
+
maxDecimals: 18,
|
|
1375
|
+
/** Every Pyth feed id is 32 bytes. */
|
|
1376
|
+
feedIdLength: 32,
|
|
1377
|
+
/** A Pyth shard id is a u16, which is what the address seed holds. */
|
|
1378
|
+
maxShard: 65535,
|
|
1379
|
+
/** u64 raw token units, the widest cap the program can hold. */
|
|
1380
|
+
maxCap: (1n << 64n) - 1n
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
// src/inputs.ts
|
|
1384
|
+
import { PublicKey as PublicKey2 } from "@solana/web3.js";
|
|
1385
|
+
var PanguInputError = class extends Error {
|
|
1386
|
+
constructor(message) {
|
|
1387
|
+
super(message);
|
|
1388
|
+
this.name = "PanguInputError";
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
function requirePublicKey(value, field) {
|
|
1392
|
+
if (!(value instanceof PublicKey2)) {
|
|
1393
|
+
throw new PanguInputError(`${field} must be a PublicKey`);
|
|
1394
|
+
}
|
|
1395
|
+
return value;
|
|
1396
|
+
}
|
|
1397
|
+
function requireRealPublicKey(value, field) {
|
|
1398
|
+
const key = requirePublicKey(value, field);
|
|
1399
|
+
if (key.equals(PublicKey2.default)) {
|
|
1400
|
+
throw new PanguInputError(`${field} must not be the all zero address`);
|
|
1401
|
+
}
|
|
1402
|
+
return key;
|
|
1403
|
+
}
|
|
1404
|
+
function requireAbsent(value, field, reason) {
|
|
1405
|
+
if (value !== void 0 && value !== null) {
|
|
1406
|
+
throw new PanguInputError(`${field} ${reason}`);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
function requireWholeNumber(value, field, low, high) {
|
|
1410
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
1411
|
+
throw new PanguInputError(`${field} must be a whole number`);
|
|
1412
|
+
}
|
|
1413
|
+
if (value < low || value > high) {
|
|
1414
|
+
throw new PanguInputError(`${field} must be between ${low} and ${high}, got ${value}`);
|
|
1415
|
+
}
|
|
1416
|
+
return value;
|
|
1417
|
+
}
|
|
1418
|
+
function requireBigint(value, field) {
|
|
1419
|
+
if (typeof value === "bigint") {
|
|
1420
|
+
return value;
|
|
1421
|
+
}
|
|
1422
|
+
if (typeof value === "number" && Number.isInteger(value)) {
|
|
1423
|
+
return BigInt(value);
|
|
1424
|
+
}
|
|
1425
|
+
throw new PanguInputError(`${field} must be a bigint`);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// src/dbc/budget.ts
|
|
1429
|
+
import { ComputeBudgetProgram } from "@solana/web3.js";
|
|
1430
|
+
var TRANSACTION_SIZE_LIMIT = 1232;
|
|
1431
|
+
var COMPUTE_LIMIT = {
|
|
1432
|
+
/** Measured: 113,000 to 130,000 units for a buy, 83,000 for a sell. */
|
|
1433
|
+
swap: 3e5,
|
|
1434
|
+
/** Measured: 55,192 for the partner claim, 51,742 for the creator claim. */
|
|
1435
|
+
claim: 15e4
|
|
1436
|
+
};
|
|
1437
|
+
function computeLimit(units) {
|
|
1438
|
+
return ComputeBudgetProgram.setComputeUnitLimit({ units });
|
|
1439
|
+
}
|
|
1440
|
+
function transactionBytes(transaction) {
|
|
1441
|
+
const message = transaction.compileMessage();
|
|
1442
|
+
return message.serialize().length + 1 + 64 * message.header.numRequiredSignatures;
|
|
1443
|
+
}
|
|
1444
|
+
function requireOneTransaction(transaction, action) {
|
|
1445
|
+
const bytes = transactionBytes(transaction);
|
|
1446
|
+
if (bytes > TRANSACTION_SIZE_LIMIT) {
|
|
1447
|
+
throw new PanguInputError(
|
|
1448
|
+
`${action} does not fit in one transaction: ${bytes} bytes against the ${TRANSACTION_SIZE_LIMIT} byte limit`
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
return bytes;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// src/dbc/state.ts
|
|
1455
|
+
import {
|
|
1456
|
+
BaseFeeMode,
|
|
1457
|
+
DynamicBondingCurveClient,
|
|
1458
|
+
createDbcProgram,
|
|
1459
|
+
deriveDbcPoolAuthority,
|
|
1460
|
+
getCurrentPoint,
|
|
1461
|
+
getTokenProgram
|
|
1462
|
+
} from "@meteora-ag/dynamic-bonding-curve-sdk";
|
|
1463
|
+
|
|
1464
|
+
// src/accounts.ts
|
|
1465
|
+
import { Buffer } from "buffer";
|
|
1466
|
+
import { getTransferHook, unpackMint } from "@solana/spl-token";
|
|
1467
|
+
|
|
1468
|
+
// src/addresses.ts
|
|
1469
|
+
import { PublicKey as PublicKey3 } from "@solana/web3.js";
|
|
1470
|
+
var HEX = /^[0-9a-fA-F]+$/;
|
|
1471
|
+
function seed(text) {
|
|
1472
|
+
return new TextEncoder().encode(text);
|
|
1473
|
+
}
|
|
1474
|
+
function feedIdBytes(id) {
|
|
1475
|
+
let bytes;
|
|
1476
|
+
if (typeof id === "string") {
|
|
1477
|
+
const text = id.startsWith("0x") || id.startsWith("0X") ? id.slice(2) : id;
|
|
1478
|
+
if (text.length !== LIMITS.feedIdLength * 2 || !HEX.test(text)) {
|
|
1479
|
+
throw new PanguInputError(
|
|
1480
|
+
`a feed id must be ${LIMITS.feedIdLength * 2} hex characters, got "${id}"`
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
bytes = new Uint8Array(LIMITS.feedIdLength);
|
|
1484
|
+
for (let i = 0; i < LIMITS.feedIdLength; i += 1) {
|
|
1485
|
+
bytes[i] = Number.parseInt(text.slice(i * 2, i * 2 + 2), 16);
|
|
1486
|
+
}
|
|
1487
|
+
return bytes;
|
|
1488
|
+
}
|
|
1489
|
+
if (Array.isArray(id)) {
|
|
1490
|
+
if (id.some((byte) => !Number.isInteger(byte) || byte < 0 || byte > 255)) {
|
|
1491
|
+
throw new PanguInputError("a feed id array must hold whole bytes");
|
|
1492
|
+
}
|
|
1493
|
+
bytes = Uint8Array.from(id);
|
|
1494
|
+
} else if (id instanceof Uint8Array) {
|
|
1495
|
+
bytes = id;
|
|
1496
|
+
} else {
|
|
1497
|
+
throw new PanguInputError("a feed id must be hex or 32 bytes");
|
|
1498
|
+
}
|
|
1499
|
+
if (bytes.length !== LIMITS.feedIdLength) {
|
|
1500
|
+
throw new PanguInputError(
|
|
1501
|
+
`a feed id must be ${LIMITS.feedIdLength} bytes, got ${bytes.length}`
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
return bytes;
|
|
1505
|
+
}
|
|
1506
|
+
function feedIdHex(id) {
|
|
1507
|
+
return Array.from(feedIdBytes(id)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1508
|
+
}
|
|
1509
|
+
function saleRulesAddress(mint) {
|
|
1510
|
+
return PublicKey3.findProgramAddressSync(
|
|
1511
|
+
[seed(SEEDS.sale), requirePublicKey(mint, "mint").toBuffer()],
|
|
1512
|
+
PANGU_PROGRAM_ID
|
|
1513
|
+
)[0];
|
|
1514
|
+
}
|
|
1515
|
+
function buyerRecordAddress(mint, wallet) {
|
|
1516
|
+
return PublicKey3.findProgramAddressSync(
|
|
1517
|
+
[
|
|
1518
|
+
seed(SEEDS.buyer),
|
|
1519
|
+
requirePublicKey(mint, "mint").toBuffer(),
|
|
1520
|
+
requirePublicKey(wallet, "wallet").toBuffer()
|
|
1521
|
+
],
|
|
1522
|
+
PANGU_PROGRAM_ID
|
|
1523
|
+
)[0];
|
|
1524
|
+
}
|
|
1525
|
+
function extraAccountListAddress(mint) {
|
|
1526
|
+
return PublicKey3.findProgramAddressSync(
|
|
1527
|
+
[seed(SEEDS.extraAccountMetas), requirePublicKey(mint, "mint").toBuffer()],
|
|
1528
|
+
PANGU_PROGRAM_ID
|
|
1529
|
+
)[0];
|
|
1530
|
+
}
|
|
1531
|
+
function attestationAddress(credential, schema, wallet) {
|
|
1532
|
+
return PublicKey3.findProgramAddressSync(
|
|
1533
|
+
[
|
|
1534
|
+
seed(SEEDS.attestation),
|
|
1535
|
+
requirePublicKey(credential, "credential").toBuffer(),
|
|
1536
|
+
requirePublicKey(schema, "schema").toBuffer(),
|
|
1537
|
+
requirePublicKey(wallet, "wallet").toBuffer()
|
|
1538
|
+
],
|
|
1539
|
+
SAS_PROGRAM_ID
|
|
1540
|
+
)[0];
|
|
1541
|
+
}
|
|
1542
|
+
function priceFeedAddress(feedId, shard = PANGU_SHARD_ID) {
|
|
1543
|
+
const id = feedIdBytes(feedId);
|
|
1544
|
+
const shardId = requireWholeNumber(shard, "shard", 0, LIMITS.maxShard);
|
|
1545
|
+
const seedBytes = new Uint8Array(2);
|
|
1546
|
+
seedBytes[0] = shardId & 255;
|
|
1547
|
+
seedBytes[1] = shardId >> 8 & 255;
|
|
1548
|
+
return PublicKey3.findProgramAddressSync(
|
|
1549
|
+
[seedBytes, id],
|
|
1550
|
+
PYTH_PRICE_FEED_PROGRAM_ID
|
|
1551
|
+
)[0];
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// src/coder.ts
|
|
1555
|
+
import { BorshCoder } from "@anchor-lang/core";
|
|
1556
|
+
var cached = null;
|
|
1557
|
+
function panguCoder() {
|
|
1558
|
+
if (cached === null) {
|
|
1559
|
+
cached = new BorshCoder(PANGU_IDL);
|
|
1560
|
+
}
|
|
1561
|
+
return cached;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// src/accounts.ts
|
|
1565
|
+
var SALE_RULES = "SaleRules";
|
|
1566
|
+
var BUYER_RECORD = "BuyerRecord";
|
|
1567
|
+
var PanguLayoutError = class extends PanguInputError {
|
|
1568
|
+
constructor(message) {
|
|
1569
|
+
super(message);
|
|
1570
|
+
this.name = "PanguLayoutError";
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
function asBuffer(data) {
|
|
1574
|
+
return Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
1575
|
+
}
|
|
1576
|
+
function big(value) {
|
|
1577
|
+
return BigInt(value.toString());
|
|
1578
|
+
}
|
|
1579
|
+
function decodeAccount(name, data) {
|
|
1580
|
+
try {
|
|
1581
|
+
return panguCoder().accounts.decode(name, asBuffer(data));
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1584
|
+
throw new PanguInputError(`these bytes are not a Pangu ${name}: ${reason}`);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
function decodeSale(data) {
|
|
1588
|
+
const raw = decodeAccount(SALE_RULES, data);
|
|
1589
|
+
const size = panguCoder().accounts.size(SALE_RULES);
|
|
1590
|
+
if (data.length !== size) {
|
|
1591
|
+
throw new PanguLayoutError(
|
|
1592
|
+
`a SaleRules account is ${size} bytes and these are ${data.length}, so they were written by another build of the program`
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
if (!SALE_RULES_LAYOUT_VERSIONS.has(raw.layout_version)) {
|
|
1596
|
+
throw new PanguLayoutError(
|
|
1597
|
+
`these are layout version ${raw.layout_version} and this package reads versions ${[...SALE_RULES_LAYOUT_VERSIONS].join(" and ")}, so they were written by another build of the program`
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
const newFields = raw.layout_version >= 2;
|
|
1601
|
+
const endsAt = newFields ? Number(big(raw.ends_at)) : 0;
|
|
1602
|
+
return {
|
|
1603
|
+
mint: raw.mint,
|
|
1604
|
+
pool: raw.pool,
|
|
1605
|
+
baseVault: raw.base_vault,
|
|
1606
|
+
issuer: raw.issuer,
|
|
1607
|
+
cap: big(raw.cap),
|
|
1608
|
+
accessMode: raw.access_mode,
|
|
1609
|
+
credential: raw.credential,
|
|
1610
|
+
schema: raw.schema,
|
|
1611
|
+
priceAccount: raw.price_account,
|
|
1612
|
+
bandBps: raw.band_bps,
|
|
1613
|
+
priceFeedId: feedIdHex(raw.price_feed_id),
|
|
1614
|
+
priceShard: raw.price_shard,
|
|
1615
|
+
maxPriceAgeSecs: raw.max_price_age_secs,
|
|
1616
|
+
maxConfBps: raw.max_conf_bps,
|
|
1617
|
+
baseDecimals: raw.base_decimals,
|
|
1618
|
+
quoteDecimals: raw.quote_decimals,
|
|
1619
|
+
buyers: raw.buyers,
|
|
1620
|
+
totalNetBought: big(raw.total_net_bought),
|
|
1621
|
+
bump: raw.bump,
|
|
1622
|
+
layoutVersion: raw.layout_version,
|
|
1623
|
+
quoteMint: newFields ? raw.quote_mint : null,
|
|
1624
|
+
endsAt: endsAt === 0 ? null : endsAt,
|
|
1625
|
+
reserved: Uint8Array.from(raw.reserved),
|
|
1626
|
+
hasBand: raw.band_bps > 0
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
function decodeBuyerRecord(data) {
|
|
1630
|
+
const raw = decodeAccount(BUYER_RECORD, data);
|
|
1631
|
+
return {
|
|
1632
|
+
mint: raw.mint,
|
|
1633
|
+
wallet: raw.wallet,
|
|
1634
|
+
approved: raw.approved,
|
|
1635
|
+
netBought: big(raw.net_bought),
|
|
1636
|
+
bump: raw.bump
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
function ownedByPangu(owner, address) {
|
|
1640
|
+
if (!owner.equals(PANGU_PROGRAM_ID)) {
|
|
1641
|
+
throw new PanguInputError(
|
|
1642
|
+
`${address.toBase58()} is owned by ${owner.toBase58()}, not by Pangu`
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
async function getSale(connection, mint) {
|
|
1647
|
+
const address = saleRulesAddress(mint);
|
|
1648
|
+
const info = await connection.getAccountInfo(address);
|
|
1649
|
+
if (info === null) {
|
|
1650
|
+
return null;
|
|
1651
|
+
}
|
|
1652
|
+
ownedByPangu(info.owner, address);
|
|
1653
|
+
const sale = decodeSale(info.data);
|
|
1654
|
+
if (sale.baseDecimals === 0) {
|
|
1655
|
+
const decimals = await mintDecimals(connection, sale.mint);
|
|
1656
|
+
if (decimals !== null) {
|
|
1657
|
+
return { ...sale, baseDecimals: decimals };
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
return sale;
|
|
1661
|
+
}
|
|
1662
|
+
async function mintDecimals(connection, mint) {
|
|
1663
|
+
const info = await connection.getAccountInfo(mint);
|
|
1664
|
+
if (info === null || !info.owner.equals(TOKEN_2022_PROGRAM_ID)) {
|
|
1665
|
+
return null;
|
|
1666
|
+
}
|
|
1667
|
+
try {
|
|
1668
|
+
return unpackMint(mint, info, TOKEN_2022_PROGRAM_ID).decimals;
|
|
1669
|
+
} catch {
|
|
1670
|
+
return null;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
async function getBuyerRecord(connection, mint, wallet) {
|
|
1674
|
+
const address = buyerRecordAddress(mint, wallet);
|
|
1675
|
+
const info = await connection.getAccountInfo(address);
|
|
1676
|
+
if (info === null) {
|
|
1677
|
+
return null;
|
|
1678
|
+
}
|
|
1679
|
+
ownedByPangu(info.owner, address);
|
|
1680
|
+
return decodeBuyerRecord(info.data);
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// src/dbc/state.ts
|
|
1684
|
+
var DBC_POOL_AUTHORITY = deriveDbcPoolAuthority();
|
|
1685
|
+
var POOL_BASE_MINT_OFFSET = 136;
|
|
1686
|
+
function dbcProgram(connection) {
|
|
1687
|
+
return createDbcProgram(connection, "confirmed").program;
|
|
1688
|
+
}
|
|
1689
|
+
async function requireSale(connection, mint) {
|
|
1690
|
+
const sale = await getSale(connection, requirePublicKey(mint, "mint"));
|
|
1691
|
+
if (sale === null) {
|
|
1692
|
+
throw new PanguInputError(
|
|
1693
|
+
`${mint.toBase58()} has no Pangu sale, so there is nothing to trade here`
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
return sale;
|
|
1697
|
+
}
|
|
1698
|
+
async function loadConfig(connection, config) {
|
|
1699
|
+
const client = new DynamicBondingCurveClient(connection, "confirmed");
|
|
1700
|
+
const state = await client.state.getPoolConfig(config);
|
|
1701
|
+
if (state === null) {
|
|
1702
|
+
throw new PanguInputError(
|
|
1703
|
+
`${config.toBase58()} is not a Meteora launch template`
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
return state;
|
|
1707
|
+
}
|
|
1708
|
+
async function loadPool(connection, mint) {
|
|
1709
|
+
const sale = await requireSale(connection, mint);
|
|
1710
|
+
return { sale, ...await readMarket(connection, sale.pool) };
|
|
1711
|
+
}
|
|
1712
|
+
async function loadSellPool(connection, mint) {
|
|
1713
|
+
const mintKey = requirePublicKey(mint, "mint");
|
|
1714
|
+
let sale = null;
|
|
1715
|
+
try {
|
|
1716
|
+
sale = await getSale(connection, mintKey);
|
|
1717
|
+
} catch (error) {
|
|
1718
|
+
if (!(error instanceof PanguInputError)) {
|
|
1719
|
+
throw error;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
const pool = sale?.pool ?? await hookPoolSelling(connection, mintKey);
|
|
1723
|
+
const market = await readMarket(connection, pool);
|
|
1724
|
+
if (!market.baseMint.equals(mintKey)) {
|
|
1725
|
+
throw new PanguInputError(
|
|
1726
|
+
`${pool.toBase58()} sells ${market.baseMint.toBase58()}, not ${mintKey.toBase58()}`
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
return { sale, ...market };
|
|
1730
|
+
}
|
|
1731
|
+
async function hookPoolSelling(connection, mint) {
|
|
1732
|
+
const found = await dbcProgram(connection).account.transferHookPool.all([
|
|
1733
|
+
{ memcmp: { offset: POOL_BASE_MINT_OFFSET, bytes: mint.toBase58() } }
|
|
1734
|
+
]);
|
|
1735
|
+
if (found.length === 0) {
|
|
1736
|
+
throw new PanguInputError(
|
|
1737
|
+
`no Meteora transfer hook pool sells ${mint.toBase58()}, so there is nothing to sell into`
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
if (found.length > 1) {
|
|
1741
|
+
throw new PanguInputError(
|
|
1742
|
+
`${found.length} transfer hook pools sell ${mint.toBase58()}, and a sell will not pick one on a guess`
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
return found[0].publicKey;
|
|
1746
|
+
}
|
|
1747
|
+
async function readMarket(connection, pool) {
|
|
1748
|
+
const program = dbcProgram(connection);
|
|
1749
|
+
const poolAccount = await program.account.transferHookPool.fetchNullable(
|
|
1750
|
+
pool,
|
|
1751
|
+
"confirmed"
|
|
1752
|
+
);
|
|
1753
|
+
if (poolAccount === null) {
|
|
1754
|
+
throw new PanguInputError(
|
|
1755
|
+
`${pool.toBase58()} is not a Meteora transfer hook pool`
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
const configState = await loadConfig(connection, poolAccount.poolState.config);
|
|
1759
|
+
if (configState.enableFirstSwapWithMinFee || configState.poolFees.baseFee.baseFeeMode === BaseFeeMode.RateLimiter) {
|
|
1760
|
+
throw new PanguInputError(
|
|
1761
|
+
"this launch template puts the instructions sysvar in the swap's remaining accounts, where Pangu's hook accounts have to be. Open the sale with launchTemplateTransaction."
|
|
1762
|
+
);
|
|
1763
|
+
}
|
|
1764
|
+
return {
|
|
1765
|
+
pool,
|
|
1766
|
+
poolAccount,
|
|
1767
|
+
config: poolAccount.poolState.config,
|
|
1768
|
+
configState,
|
|
1769
|
+
baseMint: poolAccount.poolState.baseMint,
|
|
1770
|
+
quoteMint: configState.quoteMint,
|
|
1771
|
+
baseVault: poolAccount.poolState.baseVault,
|
|
1772
|
+
quoteVault: poolAccount.poolState.quoteVault,
|
|
1773
|
+
quoteProgram: getTokenProgram(configState.quoteTokenFlag),
|
|
1774
|
+
currentPoint: await getCurrentPoint(connection, configState.activationType)
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
async function readyToSign(connection, transaction, payer) {
|
|
1778
|
+
transaction.feePayer = payer;
|
|
1779
|
+
transaction.recentBlockhash = (await connection.getLatestBlockhash("confirmed")).blockhash;
|
|
1780
|
+
return transaction;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// src/dbc/template.ts
|
|
1784
|
+
var FORCED = {
|
|
1785
|
+
tokenType: TokenType.Token2022,
|
|
1786
|
+
tokenUpdateAuthority: TokenAuthorityOption.CreatorUpdateAuthority,
|
|
1787
|
+
collectFeeMode: CollectFeeMode.QuoteToken,
|
|
1788
|
+
migrationOption: MigrationOption.MET_DAMM_V2,
|
|
1789
|
+
transferHookProgram: PANGU_PROGRAM_ID
|
|
1790
|
+
};
|
|
1791
|
+
function forced(given, wanted, field, why) {
|
|
1792
|
+
if (given !== void 0 && given !== wanted) {
|
|
1793
|
+
throw new PanguInputError(
|
|
1794
|
+
`${field} is fixed at ${String(wanted)} for a Pangu sale and cannot be set to ${String(given)}: ${why}`
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
return wanted;
|
|
1798
|
+
}
|
|
1799
|
+
function withoutMintAuthority(given) {
|
|
1800
|
+
if (given === void 0) {
|
|
1801
|
+
return FORCED.tokenUpdateAuthority;
|
|
1802
|
+
}
|
|
1803
|
+
if (hasMintAuthority(given)) {
|
|
1804
|
+
throw new PanguInputError(
|
|
1805
|
+
`curve.token.tokenAuthorityOption ${String(given)} leaves the mint authority alive, and Pangu will not open a sale on a token that can still be minted: use CreatorUpdateAuthority, Immutable or PartnerUpdateAuthority`
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
return given;
|
|
1809
|
+
}
|
|
1810
|
+
function panguCurve(curve) {
|
|
1811
|
+
if (curve === null || typeof curve !== "object") {
|
|
1812
|
+
throw new PanguInputError("curve must be the parameters buildCurve takes");
|
|
1813
|
+
}
|
|
1814
|
+
return {
|
|
1815
|
+
...curve,
|
|
1816
|
+
token: {
|
|
1817
|
+
...curve.token,
|
|
1818
|
+
tokenType: forced(
|
|
1819
|
+
curve.token?.tokenType,
|
|
1820
|
+
FORCED.tokenType,
|
|
1821
|
+
"curve.token.tokenType",
|
|
1822
|
+
"a transfer hook only exists on Token-2022"
|
|
1823
|
+
),
|
|
1824
|
+
tokenAuthorityOption: withoutMintAuthority(curve.token?.tokenAuthorityOption)
|
|
1825
|
+
},
|
|
1826
|
+
fee: {
|
|
1827
|
+
...curve.fee,
|
|
1828
|
+
collectFeeMode: forced(
|
|
1829
|
+
curve.fee?.collectFeeMode,
|
|
1830
|
+
FORCED.collectFeeMode,
|
|
1831
|
+
"curve.fee.collectFeeMode",
|
|
1832
|
+
"fees taken in the sale token would move it past every cap and approval"
|
|
1833
|
+
)
|
|
1834
|
+
},
|
|
1835
|
+
migration: {
|
|
1836
|
+
...curve.migration,
|
|
1837
|
+
migrationOption: forced(
|
|
1838
|
+
curve.migration?.migrationOption,
|
|
1839
|
+
FORCED.migrationOption,
|
|
1840
|
+
"curve.migration.migrationOption",
|
|
1841
|
+
"a Pangu sale graduates to DAMM v2"
|
|
1842
|
+
)
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
async function launchTemplateTransaction(input) {
|
|
1847
|
+
const partner = requireRealPublicKey(input.partner, "partner");
|
|
1848
|
+
const payer = input.payer === void 0 ? partner : requireRealPublicKey(input.payer, "payer");
|
|
1849
|
+
const quoteMint = requireRealPublicKey(input.quoteMint, "quoteMint");
|
|
1850
|
+
forced(
|
|
1851
|
+
input.transferHookProgram?.toBase58(),
|
|
1852
|
+
FORCED.transferHookProgram.toBase58(),
|
|
1853
|
+
"transferHookProgram",
|
|
1854
|
+
"the sale's rules live in Pangu, so Pangu has to be the hook"
|
|
1855
|
+
);
|
|
1856
|
+
const client = new DynamicBondingCurveClient2(input.connection, "confirmed");
|
|
1857
|
+
const config = Keypair.generate();
|
|
1858
|
+
const transaction = await client.partner.createConfigWithTransferHook({
|
|
1859
|
+
config: config.publicKey,
|
|
1860
|
+
feeClaimer: input.feeClaimer ?? partner,
|
|
1861
|
+
leftoverReceiver: input.leftoverReceiver ?? partner,
|
|
1862
|
+
payer,
|
|
1863
|
+
quoteMint,
|
|
1864
|
+
tokenBadge: input.tokenBadge,
|
|
1865
|
+
transferHookProgram: FORCED.transferHookProgram,
|
|
1866
|
+
...buildCurve(panguCurve(input.curve))
|
|
1867
|
+
});
|
|
1868
|
+
await readyToSign(input.connection, transaction, payer);
|
|
1869
|
+
return {
|
|
1870
|
+
transaction,
|
|
1871
|
+
config,
|
|
1872
|
+
bytes: requireOneTransaction(transaction, "the launch template")
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
// src/dbc/open.ts
|
|
1877
|
+
import { Keypair as Keypair2 } from "@solana/web3.js";
|
|
1878
|
+
import {
|
|
1879
|
+
DynamicBondingCurveClient as DynamicBondingCurveClient3,
|
|
1880
|
+
deriveDbcPoolAddress
|
|
1881
|
+
} from "@meteora-ag/dynamic-bonding-curve-sdk";
|
|
1882
|
+
|
|
1883
|
+
// src/instructions.ts
|
|
1884
|
+
import { BN } from "@anchor-lang/core";
|
|
1885
|
+
import {
|
|
1886
|
+
PublicKey as PublicKey4,
|
|
1887
|
+
SystemProgram,
|
|
1888
|
+
TransactionInstruction
|
|
1889
|
+
} from "@solana/web3.js";
|
|
1890
|
+
var ZERO_FEED = Array(LIMITS.feedIdLength).fill(0);
|
|
1891
|
+
var NO_BAND = {
|
|
1892
|
+
band_bps: 0,
|
|
1893
|
+
price_account: PublicKey4.default,
|
|
1894
|
+
price_feed_id: ZERO_FEED,
|
|
1895
|
+
price_shard: 0,
|
|
1896
|
+
max_price_age_secs: 0,
|
|
1897
|
+
max_conf_bps: 0
|
|
1898
|
+
};
|
|
1899
|
+
function meta(pubkey, isSigner, isWritable) {
|
|
1900
|
+
return { pubkey, isSigner, isWritable };
|
|
1901
|
+
}
|
|
1902
|
+
function absentAccount() {
|
|
1903
|
+
return meta(PANGU_PROGRAM_ID, false, false);
|
|
1904
|
+
}
|
|
1905
|
+
function build(name, keys, args) {
|
|
1906
|
+
return new TransactionInstruction({
|
|
1907
|
+
programId: PANGU_PROGRAM_ID,
|
|
1908
|
+
keys,
|
|
1909
|
+
data: panguCoder().instruction.encode(name, args)
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
function bandFields(band, field) {
|
|
1913
|
+
const bps = requireWholeNumber(
|
|
1914
|
+
band.bps,
|
|
1915
|
+
`${field}.bps`,
|
|
1916
|
+
LIMITS.minBandBps,
|
|
1917
|
+
LIMITS.maxBandBps
|
|
1918
|
+
);
|
|
1919
|
+
const priceFeedId = feedIdBytes(band.priceFeedId);
|
|
1920
|
+
if (priceFeedId.every((byte) => byte === 0)) {
|
|
1921
|
+
throw new PanguInputError(`${field}.priceFeedId of all zeros is not a feed`);
|
|
1922
|
+
}
|
|
1923
|
+
const shard = band.shard === void 0 ? PANGU_SHARD_ID : requireWholeNumber(band.shard, `${field}.shard`, 0, LIMITS.maxShard);
|
|
1924
|
+
const maxPriceAgeSecs = requireWholeNumber(
|
|
1925
|
+
band.maxPriceAgeSecs,
|
|
1926
|
+
`${field}.maxPriceAgeSecs`,
|
|
1927
|
+
LIMITS.minPriceAgeSecs,
|
|
1928
|
+
LIMITS.maxPriceAgeSecs
|
|
1929
|
+
);
|
|
1930
|
+
const maxConfBps = requireWholeNumber(
|
|
1931
|
+
band.maxConfBps,
|
|
1932
|
+
`${field}.maxConfBps`,
|
|
1933
|
+
LIMITS.minConfBps,
|
|
1934
|
+
LIMITS.maxConfBps
|
|
1935
|
+
);
|
|
1936
|
+
return {
|
|
1937
|
+
band_bps: bps,
|
|
1938
|
+
// Derived here rather than asked for: the price account is the one program
|
|
1939
|
+
// address this shard and this feed id can produce under Pyth's price feed
|
|
1940
|
+
// program, and create_sale derives the same one and refuses anything else.
|
|
1941
|
+
price_account: priceFeedAddress(priceFeedId, shard),
|
|
1942
|
+
price_feed_id: Array.from(priceFeedId),
|
|
1943
|
+
price_shard: shard,
|
|
1944
|
+
max_price_age_secs: maxPriceAgeSecs,
|
|
1945
|
+
max_conf_bps: maxConfBps
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
function createSaleInstruction(input) {
|
|
1949
|
+
const issuer = requireRealPublicKey(input.issuer, "issuer");
|
|
1950
|
+
const pool = requireRealPublicKey(input.pool, "pool");
|
|
1951
|
+
const mint = requireRealPublicKey(input.mint, "mint");
|
|
1952
|
+
const cap = requireBigint(input.cap, "cap");
|
|
1953
|
+
if (cap <= 0n) {
|
|
1954
|
+
throw new PanguInputError("cap must be above zero, the program refuses a zero cap");
|
|
1955
|
+
}
|
|
1956
|
+
if (cap > LIMITS.maxCap) {
|
|
1957
|
+
throw new PanguInputError("cap must fit in 64 bits");
|
|
1958
|
+
}
|
|
1959
|
+
const accessMode = requireWholeNumber(input.accessMode, "accessMode", 0, 2);
|
|
1960
|
+
const wantsCredential = accessMode === ACCESS_MODE.verifierCredential;
|
|
1961
|
+
let credential = PublicKey4.default;
|
|
1962
|
+
let schema = PublicKey4.default;
|
|
1963
|
+
if (wantsCredential) {
|
|
1964
|
+
if (input.credential === void 0 || input.schema === void 0) {
|
|
1965
|
+
throw new PanguInputError(
|
|
1966
|
+
"access mode 2 needs both a credential and a schema, the program has nothing to check against without them"
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
credential = requireRealPublicKey(input.credential, "credential");
|
|
1970
|
+
schema = requireRealPublicKey(input.schema, "schema");
|
|
1971
|
+
} else {
|
|
1972
|
+
const reason = "belongs to access mode 2, and the program refuses a sale that names it in any other mode";
|
|
1973
|
+
requireAbsent(input.credential, "credential", reason);
|
|
1974
|
+
requireAbsent(input.schema, "schema", reason);
|
|
1975
|
+
}
|
|
1976
|
+
const dbcConfig = requireRealPublicKey(input.dbcConfig, "dbcConfig");
|
|
1977
|
+
const quoteMint = requireRealPublicKey(input.quoteMint, "quoteMint");
|
|
1978
|
+
const band = input.band !== void 0 && input.band !== null ? bandFields(input.band, "band") : NO_BAND;
|
|
1979
|
+
const endsAt = input.endsAt === void 0 ? 0 : requireWholeNumber(input.endsAt, "endsAt", 0, Number.MAX_SAFE_INTEGER);
|
|
1980
|
+
const keys = [
|
|
1981
|
+
meta(issuer, true, true),
|
|
1982
|
+
meta(pool, false, false),
|
|
1983
|
+
meta(mint, false, false),
|
|
1984
|
+
wantsCredential ? meta(credential, false, false) : absentAccount(),
|
|
1985
|
+
wantsCredential ? meta(schema, false, false) : absentAccount(),
|
|
1986
|
+
meta(dbcConfig, false, false),
|
|
1987
|
+
meta(quoteMint, false, false),
|
|
1988
|
+
meta(saleRulesAddress(mint), false, true),
|
|
1989
|
+
meta(extraAccountListAddress(mint), false, true),
|
|
1990
|
+
meta(SystemProgram.programId, false, false)
|
|
1991
|
+
];
|
|
1992
|
+
return build("create_sale", keys, {
|
|
1993
|
+
cap: new BN(cap.toString()),
|
|
1994
|
+
access_mode: accessMode,
|
|
1995
|
+
credential,
|
|
1996
|
+
schema,
|
|
1997
|
+
band,
|
|
1998
|
+
ends_at: new BN(endsAt)
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
function openBuyerRecordInstruction(input) {
|
|
2002
|
+
const wallet = requireRealPublicKey(input.wallet, "wallet");
|
|
2003
|
+
const mint = requireRealPublicKey(input.mint, "mint");
|
|
2004
|
+
return build(
|
|
2005
|
+
"open_buyer_record",
|
|
2006
|
+
[
|
|
2007
|
+
meta(wallet, true, true),
|
|
2008
|
+
meta(mint, false, false),
|
|
2009
|
+
meta(saleRulesAddress(mint), false, false),
|
|
2010
|
+
meta(buyerRecordAddress(mint, wallet), false, true),
|
|
2011
|
+
meta(SystemProgram.programId, false, false)
|
|
2012
|
+
],
|
|
2013
|
+
{}
|
|
2014
|
+
);
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
// src/dbc/open.ts
|
|
2018
|
+
function capFromShare(swapBaseAmount, capShareBps) {
|
|
2019
|
+
const amount = requireBigint(swapBaseAmount, "swapBaseAmount");
|
|
2020
|
+
if (typeof capShareBps === "number" && capShareBps >= 1e4) {
|
|
2021
|
+
throw new PanguInputError(
|
|
2022
|
+
`sale.capShareBps must be below 10000 (100 percent), got ${capShareBps}: a cap covering the whole curve lets one wallet buy everything, and the program refuses it`
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
const bps = requireWholeNumber(capShareBps, "sale.capShareBps", 1, 9999);
|
|
2026
|
+
const cap = amount * BigInt(bps) / 10000n;
|
|
2027
|
+
if (cap <= 0n) {
|
|
2028
|
+
throw new PanguInputError(
|
|
2029
|
+
"that share of this curve rounds down to no tokens at all, raise capShareBps"
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
return cap;
|
|
2033
|
+
}
|
|
2034
|
+
function capOf(terms, config) {
|
|
2035
|
+
const hasCap = terms.cap !== void 0 && terms.cap !== null;
|
|
2036
|
+
const hasShare = terms.capShareBps !== void 0 && terms.capShareBps !== null;
|
|
2037
|
+
if (hasCap === hasShare) {
|
|
2038
|
+
throw new PanguInputError(
|
|
2039
|
+
"a sale needs either cap or capShareBps, and never both, because they would disagree"
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
return hasCap ? requireBigint(terms.cap, "sale.cap") : capFromShare(BigInt(config.swapBaseAmount.toString()), terms.capShareBps);
|
|
2043
|
+
}
|
|
2044
|
+
async function openSaleTransaction(input) {
|
|
2045
|
+
const creator = requireRealPublicKey(input.creator, "creator");
|
|
2046
|
+
const payer = input.payer === void 0 ? creator : requireRealPublicKey(input.payer, "payer");
|
|
2047
|
+
const config = requireRealPublicKey(input.config, "config");
|
|
2048
|
+
const accessMode = requireWholeNumber(input.sale?.accessMode, "sale.accessMode", 0, 2);
|
|
2049
|
+
const configState = await loadConfig(input.connection, config);
|
|
2050
|
+
if (configState.collectFeeMode !== FORCED.collectFeeMode) {
|
|
2051
|
+
throw new PanguInputError(
|
|
2052
|
+
"this template collects fees in the sale token, which would move it past every cap. Open the template with launchTemplateTransaction."
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
const cap = capOf(input.sale, configState);
|
|
2056
|
+
const baseMint = input.baseMint ?? Keypair2.generate();
|
|
2057
|
+
const quoteMint = configState.quoteMint;
|
|
2058
|
+
const pool = deriveDbcPoolAddress(quoteMint, baseMint.publicKey, config);
|
|
2059
|
+
const client = new DynamicBondingCurveClient3(input.connection, "confirmed");
|
|
2060
|
+
const transaction = await client.creator.createPoolWithTransferHook({
|
|
2061
|
+
baseMint: baseMint.publicKey,
|
|
2062
|
+
config,
|
|
2063
|
+
name: input.name,
|
|
2064
|
+
symbol: input.symbol,
|
|
2065
|
+
uri: input.uri,
|
|
2066
|
+
payer,
|
|
2067
|
+
poolCreator: creator,
|
|
2068
|
+
tokenBadge: input.tokenBadge,
|
|
2069
|
+
transferHookProgram: FORCED.transferHookProgram
|
|
2070
|
+
});
|
|
2071
|
+
const band = input.sale.band;
|
|
2072
|
+
transaction.add(
|
|
2073
|
+
createSaleInstruction({
|
|
2074
|
+
issuer: creator,
|
|
2075
|
+
pool,
|
|
2076
|
+
mint: baseMint.publicKey,
|
|
2077
|
+
cap,
|
|
2078
|
+
accessMode,
|
|
2079
|
+
credential: accessMode === ACCESS_MODE.verifierCredential ? input.sale.credential : void 0,
|
|
2080
|
+
schema: accessMode === ACCESS_MODE.verifierCredential ? input.sale.schema : void 0,
|
|
2081
|
+
band,
|
|
2082
|
+
dbcConfig: config,
|
|
2083
|
+
quoteMint,
|
|
2084
|
+
endsAt: input.sale.endsAt
|
|
2085
|
+
})
|
|
2086
|
+
);
|
|
2087
|
+
await readyToSign(input.connection, transaction, payer);
|
|
2088
|
+
return {
|
|
2089
|
+
transaction,
|
|
2090
|
+
baseMint,
|
|
2091
|
+
pool,
|
|
2092
|
+
bytes: requireOneTransaction(transaction, "the pool and the sale's rules")
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// src/dbc/trade.ts
|
|
2097
|
+
import {
|
|
2098
|
+
SystemProgram as SystemProgram2
|
|
2099
|
+
} from "@solana/web3.js";
|
|
2100
|
+
import { BN as BN3 } from "@anchor-lang/core";
|
|
2101
|
+
import {
|
|
2102
|
+
NATIVE_MINT,
|
|
2103
|
+
createAssociatedTokenAccountIdempotentInstruction,
|
|
2104
|
+
createSyncNativeInstruction,
|
|
2105
|
+
getAssociatedTokenAddressSync
|
|
2106
|
+
} from "@solana/spl-token";
|
|
2107
|
+
import { SwapMode } from "@meteora-ag/dynamic-bonding-curve-sdk";
|
|
2108
|
+
|
|
2109
|
+
// src/dbc/quote.ts
|
|
2110
|
+
import { BN as BN2 } from "@anchor-lang/core";
|
|
2111
|
+
import {
|
|
2112
|
+
swapQuoteExactIn,
|
|
2113
|
+
swapQuoteExactOut,
|
|
2114
|
+
swapQuotePartialFill
|
|
2115
|
+
} from "@meteora-ag/dynamic-bonding-curve-sdk";
|
|
2116
|
+
function quoteExactIn(view, swapBaseForQuote, amountIn, slippageBps) {
|
|
2117
|
+
return swapQuoteExactIn(
|
|
2118
|
+
view.poolAccount,
|
|
2119
|
+
view.configState,
|
|
2120
|
+
swapBaseForQuote,
|
|
2121
|
+
new BN2(amountIn.toString()),
|
|
2122
|
+
slippageBps,
|
|
2123
|
+
false,
|
|
2124
|
+
view.currentPoint,
|
|
2125
|
+
// The template Pangu builds leaves the first swap minimum fee off, and
|
|
2126
|
+
// loadPool refuses a template that turns it on.
|
|
2127
|
+
false
|
|
2128
|
+
);
|
|
2129
|
+
}
|
|
2130
|
+
function quoteExactOut(view, swapBaseForQuote, amountOut, slippageBps) {
|
|
2131
|
+
return swapQuoteExactOut(
|
|
2132
|
+
view.poolAccount,
|
|
2133
|
+
view.configState,
|
|
2134
|
+
swapBaseForQuote,
|
|
2135
|
+
new BN2(amountOut.toString()),
|
|
2136
|
+
slippageBps,
|
|
2137
|
+
false,
|
|
2138
|
+
view.currentPoint,
|
|
2139
|
+
false
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
2142
|
+
function quotePartialFill(view, swapBaseForQuote, amountIn, slippageBps) {
|
|
2143
|
+
return swapQuotePartialFill(
|
|
2144
|
+
view.poolAccount,
|
|
2145
|
+
view.configState,
|
|
2146
|
+
swapBaseForQuote,
|
|
2147
|
+
new BN2(amountIn.toString()),
|
|
2148
|
+
slippageBps,
|
|
2149
|
+
false,
|
|
2150
|
+
view.currentPoint,
|
|
2151
|
+
false
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// src/dbc/hook.ts
|
|
2156
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
2157
|
+
import {
|
|
2158
|
+
createExecuteInstruction,
|
|
2159
|
+
getExtraAccountMetas,
|
|
2160
|
+
resolveExtraAccountMeta
|
|
2161
|
+
} from "@solana/spl-token";
|
|
2162
|
+
import { AccountsType } from "@meteora-ag/dynamic-bonding-curve-sdk";
|
|
2163
|
+
function tokenAccountBytes(mint, owner) {
|
|
2164
|
+
const data = Buffer2.alloc(165);
|
|
2165
|
+
mint.toBuffer().copy(data, 0);
|
|
2166
|
+
owner.toBuffer().copy(data, 32);
|
|
2167
|
+
return data;
|
|
2168
|
+
}
|
|
2169
|
+
function chainPlusPending(connection, pending) {
|
|
2170
|
+
if (pending.length === 0) {
|
|
2171
|
+
return connection;
|
|
2172
|
+
}
|
|
2173
|
+
const known = new Map(
|
|
2174
|
+
pending.map((account) => [
|
|
2175
|
+
account.address.toBase58(),
|
|
2176
|
+
{
|
|
2177
|
+
data: tokenAccountBytes(account.mint, account.owner),
|
|
2178
|
+
owner: TOKEN_2022_PROGRAM_ID,
|
|
2179
|
+
executable: false,
|
|
2180
|
+
lamports: 0,
|
|
2181
|
+
rentEpoch: 0
|
|
2182
|
+
}
|
|
2183
|
+
])
|
|
2184
|
+
);
|
|
2185
|
+
const reader = {
|
|
2186
|
+
getAccountInfo: async (address) => known.get(address.toBase58()) ?? await connection.getAccountInfo(address)
|
|
2187
|
+
};
|
|
2188
|
+
return reader;
|
|
2189
|
+
}
|
|
2190
|
+
async function hookAccounts(input) {
|
|
2191
|
+
const validation = extraAccountListAddress(input.mint);
|
|
2192
|
+
const listAccount = await input.connection.getAccountInfo(validation);
|
|
2193
|
+
if (listAccount === null) {
|
|
2194
|
+
throw new PanguInputError(
|
|
2195
|
+
`${input.mint.toBase58()} has no published Pangu account list, so it is not a running Pangu sale`
|
|
2196
|
+
);
|
|
2197
|
+
}
|
|
2198
|
+
const execute = createExecuteInstruction(
|
|
2199
|
+
PANGU_PROGRAM_ID,
|
|
2200
|
+
input.source,
|
|
2201
|
+
input.mint,
|
|
2202
|
+
input.destination,
|
|
2203
|
+
input.authority,
|
|
2204
|
+
validation,
|
|
2205
|
+
input.amount
|
|
2206
|
+
);
|
|
2207
|
+
const reader = chainPlusPending(input.connection, input.pending ?? []);
|
|
2208
|
+
for (const meta2 of getExtraAccountMetas(listAccount)) {
|
|
2209
|
+
execute.keys.push(
|
|
2210
|
+
await resolveExtraAccountMeta(
|
|
2211
|
+
reader,
|
|
2212
|
+
meta2,
|
|
2213
|
+
execute.keys,
|
|
2214
|
+
execute.data,
|
|
2215
|
+
PANGU_PROGRAM_ID
|
|
2216
|
+
)
|
|
2217
|
+
);
|
|
2218
|
+
}
|
|
2219
|
+
return [
|
|
2220
|
+
{ pubkey: validation, isSigner: false, isWritable: false },
|
|
2221
|
+
...execute.keys.slice(5),
|
|
2222
|
+
{ pubkey: PANGU_PROGRAM_ID, isSigner: false, isWritable: false }
|
|
2223
|
+
];
|
|
2224
|
+
}
|
|
2225
|
+
function hookAccountsInfo(accounts) {
|
|
2226
|
+
return {
|
|
2227
|
+
slices: [
|
|
2228
|
+
{ accountsType: AccountsType.TransferHookBase, length: accounts.length }
|
|
2229
|
+
]
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
// src/dbc/trade.ts
|
|
2234
|
+
var DEFAULT_SLIPPAGE_BPS = 100;
|
|
2235
|
+
async function buildSwap(options) {
|
|
2236
|
+
const { connection, view, owner, swapBaseForQuote, amountIn } = options;
|
|
2237
|
+
const partial = options.fill === "partial";
|
|
2238
|
+
const quote = partial ? quotePartialFill(view, swapBaseForQuote, amountIn, options.slippageBps) : quoteExactIn(view, swapBaseForQuote, amountIn, options.slippageBps);
|
|
2239
|
+
const quoted = BigInt(quote.outputAmount.toString());
|
|
2240
|
+
if (options.floor !== void 0 && quoted < options.floor) {
|
|
2241
|
+
throw new PanguInputError(
|
|
2242
|
+
`the market moved: ${options.action} now returns ${quoted} raw units, below the floor of ${options.floor}. Take a fresh quote`
|
|
2243
|
+
);
|
|
2244
|
+
}
|
|
2245
|
+
const minimumAmountOut = options.floor ?? (partial ? 0n : BigInt((quote.minimumAmountOut ?? 0n).toString()));
|
|
2246
|
+
const quoteAta = getAssociatedTokenAddressSync(
|
|
2247
|
+
view.quoteMint,
|
|
2248
|
+
owner,
|
|
2249
|
+
false,
|
|
2250
|
+
view.quoteProgram
|
|
2251
|
+
);
|
|
2252
|
+
const baseAta = getAssociatedTokenAddressSync(
|
|
2253
|
+
view.baseMint,
|
|
2254
|
+
owner,
|
|
2255
|
+
false,
|
|
2256
|
+
TOKEN_2022_PROGRAM_ID
|
|
2257
|
+
);
|
|
2258
|
+
const pre = [computeLimit(COMPUTE_LIMIT.swap)];
|
|
2259
|
+
const pending = [];
|
|
2260
|
+
pre.push(
|
|
2261
|
+
createAssociatedTokenAccountIdempotentInstruction(
|
|
2262
|
+
owner,
|
|
2263
|
+
quoteAta,
|
|
2264
|
+
owner,
|
|
2265
|
+
view.quoteMint,
|
|
2266
|
+
view.quoteProgram
|
|
2267
|
+
)
|
|
2268
|
+
);
|
|
2269
|
+
if (!swapBaseForQuote) {
|
|
2270
|
+
const baseAccount = await connection.getAccountInfo(baseAta);
|
|
2271
|
+
if (baseAccount === null) {
|
|
2272
|
+
pre.push(
|
|
2273
|
+
createAssociatedTokenAccountIdempotentInstruction(
|
|
2274
|
+
owner,
|
|
2275
|
+
baseAta,
|
|
2276
|
+
owner,
|
|
2277
|
+
view.baseMint,
|
|
2278
|
+
TOKEN_2022_PROGRAM_ID
|
|
2279
|
+
)
|
|
2280
|
+
);
|
|
2281
|
+
pending.push({ address: baseAta, mint: view.baseMint, owner });
|
|
2282
|
+
}
|
|
2283
|
+
if (view.quoteMint.equals(NATIVE_MINT)) {
|
|
2284
|
+
pre.push(
|
|
2285
|
+
SystemProgram2.transfer({
|
|
2286
|
+
fromPubkey: owner,
|
|
2287
|
+
toPubkey: quoteAta,
|
|
2288
|
+
lamports: amountIn
|
|
2289
|
+
}),
|
|
2290
|
+
createSyncNativeInstruction(quoteAta, view.quoteProgram)
|
|
2291
|
+
);
|
|
2292
|
+
}
|
|
2293
|
+
const record = await connection.getAccountInfo(
|
|
2294
|
+
buyerRecordAddress(view.baseMint, owner)
|
|
2295
|
+
);
|
|
2296
|
+
if (record === null) {
|
|
2297
|
+
pre.push(openBuyerRecordInstruction({ wallet: owner, mint: view.baseMint }));
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
const hook = await hookAccounts({
|
|
2301
|
+
connection,
|
|
2302
|
+
mint: view.baseMint,
|
|
2303
|
+
source: swapBaseForQuote ? baseAta : view.baseVault,
|
|
2304
|
+
destination: swapBaseForQuote ? view.baseVault : baseAta,
|
|
2305
|
+
authority: swapBaseForQuote ? owner : DBC_POOL_AUTHORITY,
|
|
2306
|
+
amount: swapBaseForQuote ? amountIn : BigInt(quote.outputAmount.toString()),
|
|
2307
|
+
pending
|
|
2308
|
+
});
|
|
2309
|
+
const transaction = await dbcProgram(connection).methods.swap2WithTransferHook(
|
|
2310
|
+
{
|
|
2311
|
+
amount0: new BN3(amountIn.toString()),
|
|
2312
|
+
amount1: new BN3(minimumAmountOut.toString()),
|
|
2313
|
+
swapMode: partial ? SwapMode.PartialFill : SwapMode.ExactIn
|
|
2314
|
+
},
|
|
2315
|
+
hookAccountsInfo(hook)
|
|
2316
|
+
).accountsPartial({
|
|
2317
|
+
poolAuthority: DBC_POOL_AUTHORITY,
|
|
2318
|
+
config: view.config,
|
|
2319
|
+
pool: view.pool,
|
|
2320
|
+
inputTokenAccount: swapBaseForQuote ? baseAta : quoteAta,
|
|
2321
|
+
outputTokenAccount: swapBaseForQuote ? quoteAta : baseAta,
|
|
2322
|
+
baseVault: view.baseVault,
|
|
2323
|
+
quoteVault: view.quoteVault,
|
|
2324
|
+
baseMint: view.baseMint,
|
|
2325
|
+
quoteMint: view.quoteMint,
|
|
2326
|
+
payer: owner,
|
|
2327
|
+
tokenBaseProgram: TOKEN_2022_PROGRAM_ID,
|
|
2328
|
+
tokenQuoteProgram: view.quoteProgram,
|
|
2329
|
+
referralTokenAccount: null
|
|
2330
|
+
}).remainingAccounts(hook).preInstructions(pre).transaction();
|
|
2331
|
+
await readyToSign(connection, transaction, owner);
|
|
2332
|
+
return {
|
|
2333
|
+
transaction,
|
|
2334
|
+
expectedAmountOut: quoted,
|
|
2335
|
+
minimumAmountOut,
|
|
2336
|
+
bytes: requireOneTransaction(transaction, options.action),
|
|
2337
|
+
computeUnitLimit: COMPUTE_LIMIT.swap
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
function slippageOf(value) {
|
|
2341
|
+
return value === void 0 ? DEFAULT_SLIPPAGE_BPS : requireWholeNumber(value, "slippageBps", 0, 1e4);
|
|
2342
|
+
}
|
|
2343
|
+
function floorOf(value, field) {
|
|
2344
|
+
if (value === void 0) {
|
|
2345
|
+
return void 0;
|
|
2346
|
+
}
|
|
2347
|
+
const floor = requireBigint(value, field);
|
|
2348
|
+
if (floor < 0n) {
|
|
2349
|
+
throw new PanguInputError(`${field} must not be below zero`);
|
|
2350
|
+
}
|
|
2351
|
+
return floor;
|
|
2352
|
+
}
|
|
2353
|
+
function amountOf(value, field) {
|
|
2354
|
+
const amount = requireBigint(value, field);
|
|
2355
|
+
if (amount <= 0n) {
|
|
2356
|
+
throw new PanguInputError(`${field} must be above zero`);
|
|
2357
|
+
}
|
|
2358
|
+
return amount;
|
|
2359
|
+
}
|
|
2360
|
+
async function buyTransaction(input) {
|
|
2361
|
+
const buyer = requireRealPublicKey(input.buyer, "buyer");
|
|
2362
|
+
const amountIn = amountOf(input.amountIn, "amountIn");
|
|
2363
|
+
const floor = floorOf(input.minimumAmountOut, "minimumAmountOut");
|
|
2364
|
+
const view = await loadPool(input.connection, input.mint);
|
|
2365
|
+
return buildSwap({
|
|
2366
|
+
connection: input.connection,
|
|
2367
|
+
view,
|
|
2368
|
+
owner: buyer,
|
|
2369
|
+
swapBaseForQuote: false,
|
|
2370
|
+
amountIn,
|
|
2371
|
+
slippageBps: slippageOf(input.slippageBps),
|
|
2372
|
+
action: "the buy",
|
|
2373
|
+
fill: input.fill ?? "exactIn",
|
|
2374
|
+
floor
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
async function sellTransaction(input) {
|
|
2378
|
+
const seller = requireRealPublicKey(input.seller, "seller");
|
|
2379
|
+
const amountIn = amountOf(input.amountIn, "amountIn");
|
|
2380
|
+
const floor = floorOf(input.minimumQuoteOut, "minimumQuoteOut");
|
|
2381
|
+
const view = await loadSellPool(input.connection, input.mint);
|
|
2382
|
+
return buildSwap({
|
|
2383
|
+
connection: input.connection,
|
|
2384
|
+
view,
|
|
2385
|
+
owner: seller,
|
|
2386
|
+
swapBaseForQuote: true,
|
|
2387
|
+
amountIn,
|
|
2388
|
+
slippageBps: slippageOf(input.slippageBps),
|
|
2389
|
+
action: "the sell",
|
|
2390
|
+
floor
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
// src/dbc/preflight.ts
|
|
2395
|
+
import { SYSVAR_CLOCK_PUBKEY as SYSVAR_CLOCK_PUBKEY2 } from "@solana/web3.js";
|
|
2396
|
+
|
|
2397
|
+
// src/band.ts
|
|
2398
|
+
var DOLLAR_SCALE = 10n ** 18n;
|
|
2399
|
+
function pow10(exponent) {
|
|
2400
|
+
return 10n ** BigInt(exponent);
|
|
2401
|
+
}
|
|
2402
|
+
var MIN_EXPONENT = -18;
|
|
2403
|
+
var MAX_EXPONENT = 0;
|
|
2404
|
+
function stockPriceDollars(price, exponent) {
|
|
2405
|
+
const raw = requireBigint(price, "price");
|
|
2406
|
+
if (raw <= 0n) {
|
|
2407
|
+
throw new PanguInputError("a price of zero or less is not a price");
|
|
2408
|
+
}
|
|
2409
|
+
const power = requireWholeNumber(exponent, "exponent", MIN_EXPONENT, MAX_EXPONENT);
|
|
2410
|
+
const shift = 18 + power;
|
|
2411
|
+
return shift >= 0 ? raw * pow10(shift) : raw / pow10(-shift);
|
|
2412
|
+
}
|
|
2413
|
+
function confidenceBps(price, conf) {
|
|
2414
|
+
const raw = requireBigint(price, "price");
|
|
2415
|
+
const doubt = requireBigint(conf, "conf");
|
|
2416
|
+
if (raw <= 0n) {
|
|
2417
|
+
throw new PanguInputError("a price of zero or less has no confidence ratio");
|
|
2418
|
+
}
|
|
2419
|
+
if (doubt < 0n) {
|
|
2420
|
+
throw new PanguInputError("a confidence interval cannot be negative");
|
|
2421
|
+
}
|
|
2422
|
+
return (doubt * 10000n + raw - 1n) / raw;
|
|
2423
|
+
}
|
|
2424
|
+
function curvePriceDollars(sqrtPrice, baseDecimals, quoteDecimals) {
|
|
2425
|
+
const root = requireBigint(sqrtPrice, "sqrtPrice");
|
|
2426
|
+
if (root < 0n) {
|
|
2427
|
+
throw new PanguInputError("sqrtPrice cannot be negative");
|
|
2428
|
+
}
|
|
2429
|
+
const base = requireWholeNumber(baseDecimals, "baseDecimals", 0, LIMITS.maxDecimals);
|
|
2430
|
+
const quote = requireWholeNumber(
|
|
2431
|
+
quoteDecimals,
|
|
2432
|
+
"quoteDecimals",
|
|
2433
|
+
0,
|
|
2434
|
+
LIMITS.maxDecimals
|
|
2435
|
+
);
|
|
2436
|
+
const numerator = root * root * pow10(18 + base);
|
|
2437
|
+
const denominator = (1n << 128n) * pow10(quote);
|
|
2438
|
+
const whole = numerator / denominator;
|
|
2439
|
+
return numerator % denominator === 0n ? whole : whole + 1n;
|
|
2440
|
+
}
|
|
2441
|
+
function priceCeiling(sale, stockPrice) {
|
|
2442
|
+
const bandBps = requireWholeNumber(
|
|
2443
|
+
sale?.bandBps,
|
|
2444
|
+
"sale.bandBps",
|
|
2445
|
+
1,
|
|
2446
|
+
LIMITS.maxBandBps
|
|
2447
|
+
);
|
|
2448
|
+
const price = requireBigint(stockPrice, "stockPrice");
|
|
2449
|
+
if (price <= 0n) {
|
|
2450
|
+
throw new PanguInputError(
|
|
2451
|
+
"a stock price of zero or less is never a ceiling, the program refuses it"
|
|
2452
|
+
);
|
|
2453
|
+
}
|
|
2454
|
+
return price * BigInt(1e4 + bandBps) / 10000n;
|
|
2455
|
+
}
|
|
2456
|
+
function dollars(scaled) {
|
|
2457
|
+
return Number(scaled) / Number(DOLLAR_SCALE);
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
// src/errors.ts
|
|
2461
|
+
var idlErrors = PANGU_IDL.errors ?? [];
|
|
2462
|
+
var PANGU_ERRORS = Object.freeze(
|
|
2463
|
+
idlErrors.map((error) => ({
|
|
2464
|
+
code: error.code,
|
|
2465
|
+
name: error.name,
|
|
2466
|
+
message: error.msg ?? error.name
|
|
2467
|
+
}))
|
|
2468
|
+
);
|
|
2469
|
+
var byCode = new Map(PANGU_ERRORS.map((error) => [error.code, error]));
|
|
2470
|
+
var byName = new Map(PANGU_ERRORS.map((error) => [error.name, error]));
|
|
2471
|
+
var PANGU = PANGU_PROGRAM_ID.toBase58();
|
|
2472
|
+
var EXPLANATIONS = {
|
|
2473
|
+
NotTransferring: "This token only moves through a real transfer, and this was not one.",
|
|
2474
|
+
ReceivingAccountOwnerCanChange: "The account you are buying into could be handed to someone else later, so the sale will not send tokens to it. Use the wallet's ordinary holding account for this token, the one your wallet app makes by itself.",
|
|
2475
|
+
WrongMint: "That account belongs to a different token than this sale.",
|
|
2476
|
+
WrongBuyerRecord: "The buyer record sent with this transfer belongs to another wallet.",
|
|
2477
|
+
BuyerRecordMissing: "This wallet has no record in the sale yet. Open one first, then buy.",
|
|
2478
|
+
NotApproved: "The issuer has not approved this wallet to buy in this sale.",
|
|
2479
|
+
CredentialInvalid: "This wallet does not carry a valid approval from the verifier this sale trusts.",
|
|
2480
|
+
CredentialExpired: "The verifier's approval for this wallet has run out.",
|
|
2481
|
+
CredentialSignerNotAuthorized: "The key that signed this wallet's approval is no longer allowed to sign for that verifier.",
|
|
2482
|
+
OverCap: "This purchase would take your wallet past the limit for this sale.",
|
|
2483
|
+
WalletToWalletDuringSale: "This token cannot be sent from one wallet to another while the sale is running.",
|
|
2484
|
+
PriceStale: "The stock price this sale checks against is too old to use. Refresh it and try again. Outside market hours there is no fresh price to get, so the sale stays shut until the market opens.",
|
|
2485
|
+
PriceOutsideBand: "This purchase would push the price too far above the real stock price.",
|
|
2486
|
+
WrongPriceAccount: "The price account sent with this transfer is not the one this sale names.",
|
|
2487
|
+
PriceNotFullyVerified: "The price update has not been signed by two thirds of Pyth's guardians, and this sale will not price against a half signed number.",
|
|
2488
|
+
PriceTooUncertain: "Pyth's own publishers disagree about this stock's price by more than this sale allows, so there is no ceiling worth measuring against right now.",
|
|
2489
|
+
NotPoolCreator: "Only the wallet that created the pool can open its sale.",
|
|
2490
|
+
NotAHookPool: "That account is not a Meteora bonding curve pool of the kind Pangu works with.",
|
|
2491
|
+
HookProgramMismatch: "This token does not name Pangu as its transfer hook, so Pangu cannot hold its rules.",
|
|
2492
|
+
MintAuthorityStillSet: "This token can still be minted, so Pangu will not open a sale on it. Launch it with the minting power revoked.",
|
|
2493
|
+
WrongLaunchTemplate: "That launch template is not the one this pool was opened on.",
|
|
2494
|
+
FeesNotInQuoteToken: "This sale's template collects fees in the sale token, and Pangu only accepts templates that collect them in the paying token.",
|
|
2495
|
+
ZeroCap: "A sale needs a per-wallet limit above zero.",
|
|
2496
|
+
InvalidAccessMode: "That access mode does not exist, or the settings do not match the mode chosen.",
|
|
2497
|
+
InvalidBand: "The price band settings are incomplete or out of range.",
|
|
2498
|
+
SaleStillRunning: "The sale is still running and this record still counts tokens, so it cannot be closed yet. Sell them back or wait for the sale to finish.",
|
|
2499
|
+
NotIssuer: "Only the issuer of this sale can do that.",
|
|
2500
|
+
MathOverflow: "The sale's counters cannot go any higher.",
|
|
2501
|
+
WrongLayoutVersion: "These sale rules were written by an older build of the program, so this build will not act on them.",
|
|
2502
|
+
BandNeedsDollarQuote: "A price ceiling needs buyers to pay in a dollar token the program recognises (USDC, or on devnet the demo dollar). Turn the ceiling off, or pick a dollar paying token.",
|
|
2503
|
+
IssuerControlsPayingToken: "You hold the freeze authority of the token buyers pay in, which would let you stop sellers being paid. Pick a paying token whose freeze authority is not yours.",
|
|
2504
|
+
CapCoversWholeSale: "The per-wallet limit is as large as everything the curve sells, so one wallet could buy the whole sale. Set a limit below the curve's supply.",
|
|
2505
|
+
EndInThePast: "The end of the offering period has to be later than now. Pick a future time, or leave it at zero for no end."
|
|
2506
|
+
};
|
|
2507
|
+
function explainPanguError(name) {
|
|
2508
|
+
const explanation = EXPLANATIONS[name];
|
|
2509
|
+
return explanation ?? "The transaction was refused, and the reason did not come from this sale's rules.";
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
// src/feed.ts
|
|
2513
|
+
import {
|
|
2514
|
+
SYSVAR_CLOCK_PUBKEY,
|
|
2515
|
+
PublicKey as Web3PublicKey
|
|
2516
|
+
} from "@solana/web3.js";
|
|
2517
|
+
var DISCRIMINATOR = [34, 241, 35, 99, 157, 126, 244, 205];
|
|
2518
|
+
var PARTIAL_TAG = 0;
|
|
2519
|
+
var FULL_TAG = 1;
|
|
2520
|
+
var WRITE_AUTHORITY_OFFSET = 8;
|
|
2521
|
+
var VERIFICATION_OFFSET = 40;
|
|
2522
|
+
var PUBLISH_FUTURE_TOLERANCE_SECS = 60;
|
|
2523
|
+
var CLOCK_UNIX_TIMESTAMP_OFFSET = 32;
|
|
2524
|
+
function slice(data, start, length) {
|
|
2525
|
+
const end = start + length;
|
|
2526
|
+
if (start < 0 || length < 0 || end > data.length) {
|
|
2527
|
+
throw new PanguInputError(
|
|
2528
|
+
"these bytes are not a Pyth price update: they end before the layout does"
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
return data.subarray(start, end);
|
|
2532
|
+
}
|
|
2533
|
+
function hex(data) {
|
|
2534
|
+
return Array.from(data, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2535
|
+
}
|
|
2536
|
+
function unsigned(data, offset, length) {
|
|
2537
|
+
const bytes = slice(data, offset, length);
|
|
2538
|
+
let value = 0n;
|
|
2539
|
+
for (let index = length - 1; index >= 0; index -= 1) {
|
|
2540
|
+
value = value << 8n | BigInt(bytes[index] ?? 0);
|
|
2541
|
+
}
|
|
2542
|
+
return value;
|
|
2543
|
+
}
|
|
2544
|
+
function signed(data, offset, length) {
|
|
2545
|
+
const value = unsigned(data, offset, length);
|
|
2546
|
+
const bits = BigInt(length * 8);
|
|
2547
|
+
return value >= 1n << bits - 1n ? value - (1n << bits) : value;
|
|
2548
|
+
}
|
|
2549
|
+
function decodePriceUpdate(data) {
|
|
2550
|
+
const discriminator = slice(data, 0, DISCRIMINATOR.length);
|
|
2551
|
+
if (DISCRIMINATOR.some((byte, index) => discriminator[index] !== byte)) {
|
|
2552
|
+
throw new PanguInputError("these bytes are not a Pyth price feed account");
|
|
2553
|
+
}
|
|
2554
|
+
const tag = slice(data, VERIFICATION_OFFSET, 1)[0];
|
|
2555
|
+
if (tag !== FULL_TAG && tag !== PARTIAL_TAG) {
|
|
2556
|
+
throw new PanguInputError(
|
|
2557
|
+
`${String(tag)} is not a verification level Pyth writes, so these bytes cannot be read`
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
const message = VERIFICATION_OFFSET + (tag === FULL_TAG ? 1 : 2);
|
|
2561
|
+
return {
|
|
2562
|
+
writeAuthority: new Web3PublicKey(slice(data, WRITE_AUTHORITY_OFFSET, 32)),
|
|
2563
|
+
fullyVerified: tag === FULL_TAG,
|
|
2564
|
+
feedId: hex(slice(data, message, 32)),
|
|
2565
|
+
price: signed(data, message + 32, 8),
|
|
2566
|
+
conf: unsigned(data, message + 40, 8),
|
|
2567
|
+
exponent: Number(signed(data, message + 48, 4)),
|
|
2568
|
+
publishTime: Number(signed(data, message + 52, 8)),
|
|
2569
|
+
prevPublishTime: Number(signed(data, message + 60, 8)),
|
|
2570
|
+
postedSlot: unsigned(data, message + 84, 8)
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
function unusable(address, error, partial = {}) {
|
|
2574
|
+
return {
|
|
2575
|
+
address,
|
|
2576
|
+
price: 0n,
|
|
2577
|
+
priceDollars: 0,
|
|
2578
|
+
publishTime: 0,
|
|
2579
|
+
ageSecs: 0,
|
|
2580
|
+
confBps: 0,
|
|
2581
|
+
fullyVerified: false,
|
|
2582
|
+
usable: false,
|
|
2583
|
+
error,
|
|
2584
|
+
reason: explainPanguError(error),
|
|
2585
|
+
...partial
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2588
|
+
async function readPrice(connection, sale) {
|
|
2589
|
+
if (sale === null || sale === void 0 || !sale.hasBand) {
|
|
2590
|
+
throw new PanguInputError(
|
|
2591
|
+
"this sale has no price band, so there is no price account to read"
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
const address = sale.priceAccount;
|
|
2595
|
+
if (!priceFeedAddress(sale.priceFeedId, sale.priceShard).equals(address)) {
|
|
2596
|
+
return unusable(address, "WrongPriceAccount");
|
|
2597
|
+
}
|
|
2598
|
+
const [info, clock] = await connection.getMultipleAccountsInfo([
|
|
2599
|
+
address,
|
|
2600
|
+
SYSVAR_CLOCK_PUBKEY
|
|
2601
|
+
]);
|
|
2602
|
+
if (info === null || info === void 0) {
|
|
2603
|
+
return unusable(address, "PriceStale");
|
|
2604
|
+
}
|
|
2605
|
+
if (!info.owner.equals(PYTH_RECEIVER_PROGRAM_ID)) {
|
|
2606
|
+
return unusable(address, "WrongPriceAccount");
|
|
2607
|
+
}
|
|
2608
|
+
let update;
|
|
2609
|
+
try {
|
|
2610
|
+
update = decodePriceUpdate(info.data);
|
|
2611
|
+
} catch {
|
|
2612
|
+
return unusable(address, "WrongPriceAccount");
|
|
2613
|
+
}
|
|
2614
|
+
if (!update.fullyVerified) {
|
|
2615
|
+
return unusable(address, "PriceNotFullyVerified", {
|
|
2616
|
+
publishTime: update.publishTime
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
if (update.feedId !== sale.priceFeedId) {
|
|
2620
|
+
return unusable(address, "WrongPriceAccount");
|
|
2621
|
+
}
|
|
2622
|
+
if (update.price <= 0n || update.exponent > 0 || update.exponent < -LIMITS.maxDecimals) {
|
|
2623
|
+
return unusable(
|
|
2624
|
+
address,
|
|
2625
|
+
update.price <= 0n ? "PriceStale" : "WrongPriceAccount",
|
|
2626
|
+
{ fullyVerified: true, publishTime: update.publishTime }
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
2629
|
+
const now = clock !== null && clock !== void 0 && clock.data.length >= CLOCK_UNIX_TIMESTAMP_OFFSET + 8 ? Number(clock.data.readBigInt64LE(CLOCK_UNIX_TIMESTAMP_OFFSET)) : Math.floor(Date.now() / 1e3);
|
|
2630
|
+
const price = stockPriceDollars(update.price, update.exponent);
|
|
2631
|
+
const confBps = Number(confidenceBps(update.price, update.conf));
|
|
2632
|
+
const reading = {
|
|
2633
|
+
address,
|
|
2634
|
+
price,
|
|
2635
|
+
priceDollars: dollars(price),
|
|
2636
|
+
publishTime: update.publishTime,
|
|
2637
|
+
ageSecs: now - update.publishTime,
|
|
2638
|
+
confBps,
|
|
2639
|
+
fullyVerified: true,
|
|
2640
|
+
usable: true,
|
|
2641
|
+
error: null,
|
|
2642
|
+
reason: null
|
|
2643
|
+
};
|
|
2644
|
+
const refuse = (error) => ({
|
|
2645
|
+
...reading,
|
|
2646
|
+
usable: false,
|
|
2647
|
+
error,
|
|
2648
|
+
reason: explainPanguError(error)
|
|
2649
|
+
});
|
|
2650
|
+
if (reading.ageSecs > sale.maxPriceAgeSecs || update.publishTime > now + PUBLISH_FUTURE_TOLERANCE_SECS || price <= 0n) {
|
|
2651
|
+
return refuse("PriceStale");
|
|
2652
|
+
}
|
|
2653
|
+
if (confBps > sale.maxConfBps) {
|
|
2654
|
+
return refuse("PriceTooUncertain");
|
|
2655
|
+
}
|
|
2656
|
+
return reading;
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
// src/dbc/credential.ts
|
|
2660
|
+
import { PublicKey as PublicKey5 } from "@solana/web3.js";
|
|
2661
|
+
var ATTESTATION_DISCRIMINATOR = 2;
|
|
2662
|
+
var CREDENTIAL_DISCRIMINATOR = 0;
|
|
2663
|
+
var NONCE_OFFSET = 1;
|
|
2664
|
+
var ATTESTATION_CREDENTIAL_OFFSET = 33;
|
|
2665
|
+
var ATTESTATION_SCHEMA_OFFSET = 65;
|
|
2666
|
+
var ATTESTATION_DATA_LEN_OFFSET = 97;
|
|
2667
|
+
var ATTESTATION_DATA_OFFSET = 101;
|
|
2668
|
+
var CREDENTIAL_NAME_LEN_OFFSET = 33;
|
|
2669
|
+
var CREDENTIAL_NAME_OFFSET = 37;
|
|
2670
|
+
var PUBKEY_LEN = 32;
|
|
2671
|
+
var MAX_AUTHORIZED_SIGNERS = 64;
|
|
2672
|
+
var NEVER_EXPIRES = 0;
|
|
2673
|
+
function readPubkey(data, offset) {
|
|
2674
|
+
const end = offset + PUBKEY_LEN;
|
|
2675
|
+
if (!Number.isSafeInteger(end) || offset < 0 || end > data.length) {
|
|
2676
|
+
return null;
|
|
2677
|
+
}
|
|
2678
|
+
return new PublicKey5(data.subarray(offset, end));
|
|
2679
|
+
}
|
|
2680
|
+
function readU32(data, offset) {
|
|
2681
|
+
const end = offset + 4;
|
|
2682
|
+
if (!Number.isSafeInteger(end) || offset < 0 || end > data.length) {
|
|
2683
|
+
return null;
|
|
2684
|
+
}
|
|
2685
|
+
return new DataView(data.buffer, data.byteOffset + offset, 4).getUint32(0, true);
|
|
2686
|
+
}
|
|
2687
|
+
function readI64(data, offset) {
|
|
2688
|
+
const end = offset + 8;
|
|
2689
|
+
if (!Number.isSafeInteger(end) || offset < 0 || end > data.length) {
|
|
2690
|
+
return null;
|
|
2691
|
+
}
|
|
2692
|
+
return new DataView(data.buffer, data.byteOffset + offset, 8).getBigInt64(0, true);
|
|
2693
|
+
}
|
|
2694
|
+
function parseAttestation(account) {
|
|
2695
|
+
if (!account.owner.equals(SAS_PROGRAM_ID)) {
|
|
2696
|
+
return null;
|
|
2697
|
+
}
|
|
2698
|
+
const data = account.data;
|
|
2699
|
+
if (data.length === 0 || data[0] !== ATTESTATION_DISCRIMINATOR) {
|
|
2700
|
+
return null;
|
|
2701
|
+
}
|
|
2702
|
+
const nonce = readPubkey(data, NONCE_OFFSET);
|
|
2703
|
+
const credential = readPubkey(data, ATTESTATION_CREDENTIAL_OFFSET);
|
|
2704
|
+
const schema = readPubkey(data, ATTESTATION_SCHEMA_OFFSET);
|
|
2705
|
+
const blobLength = readU32(data, ATTESTATION_DATA_LEN_OFFSET);
|
|
2706
|
+
if (nonce === null || credential === null || schema === null || blobLength === null) {
|
|
2707
|
+
return null;
|
|
2708
|
+
}
|
|
2709
|
+
const signerOffset = ATTESTATION_DATA_OFFSET + blobLength;
|
|
2710
|
+
const signer = readPubkey(data, signerOffset);
|
|
2711
|
+
const expiry = readI64(data, signerOffset + PUBKEY_LEN);
|
|
2712
|
+
if (signer === null || expiry === null) {
|
|
2713
|
+
return null;
|
|
2714
|
+
}
|
|
2715
|
+
return { nonce, credential, schema, signer, expiry };
|
|
2716
|
+
}
|
|
2717
|
+
function listsSigner(account, signer) {
|
|
2718
|
+
if (!account.owner.equals(SAS_PROGRAM_ID)) {
|
|
2719
|
+
return null;
|
|
2720
|
+
}
|
|
2721
|
+
const data = account.data;
|
|
2722
|
+
if (data.length === 0 || data[0] !== CREDENTIAL_DISCRIMINATOR) {
|
|
2723
|
+
return null;
|
|
2724
|
+
}
|
|
2725
|
+
const nameLength = readU32(data, CREDENTIAL_NAME_LEN_OFFSET);
|
|
2726
|
+
if (nameLength === null) {
|
|
2727
|
+
return null;
|
|
2728
|
+
}
|
|
2729
|
+
const countOffset = CREDENTIAL_NAME_OFFSET + nameLength;
|
|
2730
|
+
const count = readU32(data, countOffset);
|
|
2731
|
+
if (count === null || count > MAX_AUTHORIZED_SIGNERS) {
|
|
2732
|
+
return null;
|
|
2733
|
+
}
|
|
2734
|
+
let offset = countOffset + 4;
|
|
2735
|
+
for (let index = 0; index < count; index += 1) {
|
|
2736
|
+
const key = readPubkey(data, offset);
|
|
2737
|
+
if (key === null) {
|
|
2738
|
+
return null;
|
|
2739
|
+
}
|
|
2740
|
+
if (key.equals(signer)) {
|
|
2741
|
+
return true;
|
|
2742
|
+
}
|
|
2743
|
+
offset += PUBKEY_LEN;
|
|
2744
|
+
}
|
|
2745
|
+
return false;
|
|
2746
|
+
}
|
|
2747
|
+
function credentialRefusal(rules, wallet, state) {
|
|
2748
|
+
if (state.attestation === null || state.credential === null) {
|
|
2749
|
+
return "CredentialInvalid";
|
|
2750
|
+
}
|
|
2751
|
+
const attested = parseAttestation(state.attestation);
|
|
2752
|
+
if (attested === null) {
|
|
2753
|
+
return "CredentialInvalid";
|
|
2754
|
+
}
|
|
2755
|
+
if (!attested.credential.equals(rules.credential) || !attested.schema.equals(rules.schema) || !attested.nonce.equals(wallet)) {
|
|
2756
|
+
return "CredentialInvalid";
|
|
2757
|
+
}
|
|
2758
|
+
const expiry = attested.expiry;
|
|
2759
|
+
if (expiry !== BigInt(NEVER_EXPIRES) && expiry <= BigInt(Math.floor(state.now))) {
|
|
2760
|
+
return "CredentialExpired";
|
|
2761
|
+
}
|
|
2762
|
+
const authorized = listsSigner(state.credential, attested.signer);
|
|
2763
|
+
if (authorized === null) {
|
|
2764
|
+
return "CredentialInvalid";
|
|
2765
|
+
}
|
|
2766
|
+
return authorized ? null : "CredentialSignerNotAuthorized";
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
// src/dbc/preflight.ts
|
|
2770
|
+
function refused(error, capRoom, extra = {}) {
|
|
2771
|
+
return {
|
|
2772
|
+
ok: false,
|
|
2773
|
+
error,
|
|
2774
|
+
reason: explainPanguError(error),
|
|
2775
|
+
capRoom,
|
|
2776
|
+
curvePrice: null,
|
|
2777
|
+
ceiling: null,
|
|
2778
|
+
price: null,
|
|
2779
|
+
recordOpensInThisBuy: false,
|
|
2780
|
+
...extra
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
async function preflightBuy(input) {
|
|
2784
|
+
const buyer = requireRealPublicKey(input.buyer, "buyer");
|
|
2785
|
+
const amountOut = requireBigint(input.amountOut, "amountOut");
|
|
2786
|
+
if (amountOut <= 0n) {
|
|
2787
|
+
throw new PanguInputError("amountOut must be above zero");
|
|
2788
|
+
}
|
|
2789
|
+
const openingRecord = input.openingRecord === true;
|
|
2790
|
+
const view = await loadPool(input.connection, input.mint);
|
|
2791
|
+
const sale = view.sale;
|
|
2792
|
+
const existing = await getBuyerRecord(input.connection, sale.mint, buyer);
|
|
2793
|
+
if (existing === null && !openingRecord) {
|
|
2794
|
+
return refused("BuyerRecordMissing", sale.cap);
|
|
2795
|
+
}
|
|
2796
|
+
const recordOpensInThisBuy = existing === null;
|
|
2797
|
+
const record = existing ?? { approved: false, netBought: 0n };
|
|
2798
|
+
const capRoom = max(sale.cap - record.netBought, 0n);
|
|
2799
|
+
const opened = { recordOpensInThisBuy };
|
|
2800
|
+
if (sale.accessMode === ACCESS_MODE.issuerList && !record.approved) {
|
|
2801
|
+
return refused("NotApproved", capRoom, opened);
|
|
2802
|
+
}
|
|
2803
|
+
if (sale.accessMode === ACCESS_MODE.verifierCredential) {
|
|
2804
|
+
const refusal = await credentialCheck(input.connection, sale, buyer);
|
|
2805
|
+
if (refusal !== null) {
|
|
2806
|
+
return refused(refusal, capRoom, opened);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
let price = null;
|
|
2810
|
+
let curvePrice = null;
|
|
2811
|
+
let ceiling = null;
|
|
2812
|
+
if (sale.hasBand) {
|
|
2813
|
+
price = await readPrice(input.connection, sale);
|
|
2814
|
+
if (!price.usable) {
|
|
2815
|
+
return refused(price.error ?? "PriceStale", capRoom, { price, ...opened });
|
|
2816
|
+
}
|
|
2817
|
+
const quote = quoteExactOut(view, false, amountOut, 0);
|
|
2818
|
+
curvePrice = curvePriceDollars(
|
|
2819
|
+
BigInt(quote.nextSqrtPrice.toString()),
|
|
2820
|
+
sale.baseDecimals,
|
|
2821
|
+
sale.quoteDecimals
|
|
2822
|
+
);
|
|
2823
|
+
ceiling = priceCeiling(sale, price.price);
|
|
2824
|
+
if (curvePrice > ceiling) {
|
|
2825
|
+
return refused("PriceOutsideBand", capRoom, { curvePrice, ceiling, price, ...opened });
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
if (amountOut > capRoom) {
|
|
2829
|
+
return refused("OverCap", capRoom, { curvePrice, ceiling, price, ...opened });
|
|
2830
|
+
}
|
|
2831
|
+
return {
|
|
2832
|
+
ok: true,
|
|
2833
|
+
error: null,
|
|
2834
|
+
reason: null,
|
|
2835
|
+
capRoom,
|
|
2836
|
+
curvePrice,
|
|
2837
|
+
ceiling,
|
|
2838
|
+
price,
|
|
2839
|
+
recordOpensInThisBuy
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
function max(a, b) {
|
|
2843
|
+
return a > b ? a : b;
|
|
2844
|
+
}
|
|
2845
|
+
var CLOCK_UNIX_TIMESTAMP_OFFSET2 = 32;
|
|
2846
|
+
async function credentialCheck(connection, sale, buyer) {
|
|
2847
|
+
const read = await connection.getMultipleAccountsInfo([
|
|
2848
|
+
sale.credential,
|
|
2849
|
+
attestationAddress(sale.credential, sale.schema, buyer),
|
|
2850
|
+
SYSVAR_CLOCK_PUBKEY2
|
|
2851
|
+
]);
|
|
2852
|
+
const credential = read[0] ?? null;
|
|
2853
|
+
const attestation = read[1] ?? null;
|
|
2854
|
+
const clock = read[2] ?? null;
|
|
2855
|
+
const stamp = clock !== null && clock.data.length >= CLOCK_UNIX_TIMESTAMP_OFFSET2 + 8 ? Number(clock.data.readBigInt64LE(CLOCK_UNIX_TIMESTAMP_OFFSET2)) : Math.floor(Date.now() / 1e3);
|
|
2856
|
+
return credentialRefusal(sale, buyer, {
|
|
2857
|
+
credential: credential === null ? null : { owner: credential.owner, data: credential.data },
|
|
2858
|
+
attestation: attestation === null ? null : { owner: attestation.owner, data: attestation.data },
|
|
2859
|
+
now: stamp
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
// src/dbc/fees.ts
|
|
2864
|
+
import { BN as BN4 } from "@anchor-lang/core";
|
|
2865
|
+
import {
|
|
2866
|
+
createAssociatedTokenAccountIdempotentInstruction as createAssociatedTokenAccountIdempotentInstruction2,
|
|
2867
|
+
getAssociatedTokenAddressSync as getAssociatedTokenAddressSync2
|
|
2868
|
+
} from "@solana/spl-token";
|
|
2869
|
+
var EVERYTHING = new BN4("18446744073709551615");
|
|
2870
|
+
async function claimFeesTransaction(input) {
|
|
2871
|
+
if (input.who !== "partner" && input.who !== "creator") {
|
|
2872
|
+
throw new PanguInputError('who must be "partner" or "creator"');
|
|
2873
|
+
}
|
|
2874
|
+
const view = await loadPool(input.connection, input.mint);
|
|
2875
|
+
const claimer = input.who === "partner" ? view.configState.feeClaimer : view.poolAccount.poolState.creator;
|
|
2876
|
+
const baseAta = getAssociatedTokenAddressSync2(
|
|
2877
|
+
view.baseMint,
|
|
2878
|
+
claimer,
|
|
2879
|
+
false,
|
|
2880
|
+
TOKEN_2022_PROGRAM_ID
|
|
2881
|
+
);
|
|
2882
|
+
const quoteAta = getAssociatedTokenAddressSync2(
|
|
2883
|
+
view.quoteMint,
|
|
2884
|
+
claimer,
|
|
2885
|
+
false,
|
|
2886
|
+
view.quoteProgram
|
|
2887
|
+
);
|
|
2888
|
+
const pending = [];
|
|
2889
|
+
const pre = [computeLimit(COMPUTE_LIMIT.claim)];
|
|
2890
|
+
if (await input.connection.getAccountInfo(baseAta) === null) {
|
|
2891
|
+
pending.push({ address: baseAta, mint: view.baseMint, owner: claimer });
|
|
2892
|
+
}
|
|
2893
|
+
pre.push(
|
|
2894
|
+
createAssociatedTokenAccountIdempotentInstruction2(
|
|
2895
|
+
claimer,
|
|
2896
|
+
baseAta,
|
|
2897
|
+
claimer,
|
|
2898
|
+
view.baseMint,
|
|
2899
|
+
TOKEN_2022_PROGRAM_ID
|
|
2900
|
+
),
|
|
2901
|
+
createAssociatedTokenAccountIdempotentInstruction2(
|
|
2902
|
+
claimer,
|
|
2903
|
+
quoteAta,
|
|
2904
|
+
claimer,
|
|
2905
|
+
view.quoteMint,
|
|
2906
|
+
view.quoteProgram
|
|
2907
|
+
)
|
|
2908
|
+
);
|
|
2909
|
+
const hook = await hookAccounts({
|
|
2910
|
+
connection: input.connection,
|
|
2911
|
+
mint: view.baseMint,
|
|
2912
|
+
source: view.baseVault,
|
|
2913
|
+
destination: baseAta,
|
|
2914
|
+
authority: DBC_POOL_AUTHORITY,
|
|
2915
|
+
amount: 0n,
|
|
2916
|
+
pending
|
|
2917
|
+
});
|
|
2918
|
+
const shared = {
|
|
2919
|
+
poolAuthority: DBC_POOL_AUTHORITY,
|
|
2920
|
+
pool: view.pool,
|
|
2921
|
+
tokenAAccount: baseAta,
|
|
2922
|
+
tokenBAccount: quoteAta,
|
|
2923
|
+
baseVault: view.baseVault,
|
|
2924
|
+
quoteVault: view.quoteVault,
|
|
2925
|
+
baseMint: view.baseMint,
|
|
2926
|
+
quoteMint: view.quoteMint,
|
|
2927
|
+
tokenBaseProgram: TOKEN_2022_PROGRAM_ID,
|
|
2928
|
+
tokenQuoteProgram: view.quoteProgram
|
|
2929
|
+
};
|
|
2930
|
+
const program = dbcProgram(input.connection);
|
|
2931
|
+
const builder = input.who === "partner" ? program.methods.claimTradingFee2(new BN4(0), EVERYTHING, hookAccountsInfo(hook)).accountsPartial({ ...shared, config: view.config, feeClaimer: claimer }) : program.methods.claimCreatorTradingFee2(new BN4(0), EVERYTHING, hookAccountsInfo(hook)).accountsPartial({ ...shared, creator: claimer });
|
|
2932
|
+
const transaction = await builder.remainingAccounts(hook).preInstructions(pre).transaction();
|
|
2933
|
+
await readyToSign(input.connection, transaction, claimer);
|
|
2934
|
+
return {
|
|
2935
|
+
transaction,
|
|
2936
|
+
claimer,
|
|
2937
|
+
bytes: requireOneTransaction(transaction, `the ${input.who} fee claim`),
|
|
2938
|
+
computeUnitLimit: COMPUTE_LIMIT.claim
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
// src/dbc/graduate.ts
|
|
2943
|
+
import {
|
|
2944
|
+
DAMM_V2_MIGRATION_FEE_ADDRESS,
|
|
2945
|
+
DynamicBondingCurveClient as DynamicBondingCurveClient4,
|
|
2946
|
+
deriveDammV2PoolAddress
|
|
2947
|
+
} from "@meteora-ag/dynamic-bonding-curve-sdk";
|
|
2948
|
+
function dammConfigOf(view) {
|
|
2949
|
+
const option = view.configState.migrationFeeOption;
|
|
2950
|
+
const config = DAMM_V2_MIGRATION_FEE_ADDRESS[option];
|
|
2951
|
+
if (config === void 0) {
|
|
2952
|
+
throw new PanguInputError(
|
|
2953
|
+
`this template names migration fee option ${option}, which DAMM v2 has no config for`
|
|
2954
|
+
);
|
|
2955
|
+
}
|
|
2956
|
+
return config;
|
|
2957
|
+
}
|
|
2958
|
+
async function graduateTransaction(input) {
|
|
2959
|
+
const payer = requireRealPublicKey(input.payer, "payer");
|
|
2960
|
+
const view = await loadPool(input.connection, input.mint);
|
|
2961
|
+
const state = view.poolAccount.poolState;
|
|
2962
|
+
if (state.isMigrated !== 0) {
|
|
2963
|
+
throw new PanguInputError("this sale has already been migrated to DAMM v2");
|
|
2964
|
+
}
|
|
2965
|
+
if (state.quoteReserve.lt(view.configState.migrationQuoteThreshold)) {
|
|
2966
|
+
throw new PanguInputError(
|
|
2967
|
+
"the curve is not full yet, so there is nothing to migrate"
|
|
2968
|
+
);
|
|
2969
|
+
}
|
|
2970
|
+
const dammConfig = dammConfigOf(view);
|
|
2971
|
+
const client = new DynamicBondingCurveClient4(input.connection, "confirmed");
|
|
2972
|
+
const { transaction, firstPositionNftKeypair, secondPositionNftKeypair } = await client.migration.migrateToDammV2({
|
|
2973
|
+
pool: view.pool,
|
|
2974
|
+
dammConfig,
|
|
2975
|
+
payer
|
|
2976
|
+
});
|
|
2977
|
+
await readyToSign(input.connection, transaction, payer);
|
|
2978
|
+
return {
|
|
2979
|
+
transaction,
|
|
2980
|
+
signers: [firstPositionNftKeypair, secondPositionNftKeypair],
|
|
2981
|
+
dammPool: deriveDammV2PoolAddress(dammConfig, view.baseMint, view.quoteMint),
|
|
2982
|
+
bytes: requireOneTransaction(transaction, "the migration to DAMM v2")
|
|
2983
|
+
};
|
|
2984
|
+
}
|
|
2985
|
+
async function saleProgress(connection, mint) {
|
|
2986
|
+
const view = await loadPool(connection, mint);
|
|
2987
|
+
const quoteRaised = BigInt(view.poolAccount.poolState.quoteReserve.toString());
|
|
2988
|
+
const threshold = BigInt(view.configState.migrationQuoteThreshold.toString());
|
|
2989
|
+
const dammPool = deriveDammV2PoolAddress(
|
|
2990
|
+
dammConfigOf(view),
|
|
2991
|
+
view.baseMint,
|
|
2992
|
+
view.quoteMint
|
|
2993
|
+
);
|
|
2994
|
+
const exists = await connection.getAccountInfo(dammPool);
|
|
2995
|
+
return {
|
|
2996
|
+
quoteRaised,
|
|
2997
|
+
threshold,
|
|
2998
|
+
percent: threshold === 0n ? 0 : Math.min(1, Number(quoteRaised * 1000000n / threshold) / 1e6),
|
|
2999
|
+
graduated: quoteRaised >= threshold,
|
|
3000
|
+
dammPool: exists === null ? null : dammPool
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
export {
|
|
3004
|
+
COMPUTE_LIMIT,
|
|
3005
|
+
DBC_POOL_AUTHORITY,
|
|
3006
|
+
FORCED,
|
|
3007
|
+
TRANSACTION_SIZE_LIMIT,
|
|
3008
|
+
buyTransaction,
|
|
3009
|
+
capFromShare,
|
|
3010
|
+
claimFeesTransaction,
|
|
3011
|
+
dbcProgram,
|
|
3012
|
+
graduateTransaction,
|
|
3013
|
+
hookAccounts,
|
|
3014
|
+
hookAccountsInfo,
|
|
3015
|
+
launchTemplateTransaction,
|
|
3016
|
+
loadPool,
|
|
3017
|
+
loadSellPool,
|
|
3018
|
+
openSaleTransaction,
|
|
3019
|
+
panguCurve,
|
|
3020
|
+
preflightBuy,
|
|
3021
|
+
requireOneTransaction,
|
|
3022
|
+
requireSale,
|
|
3023
|
+
saleProgress,
|
|
3024
|
+
sellTransaction,
|
|
3025
|
+
transactionBytes
|
|
3026
|
+
};
|
|
3027
|
+
//# sourceMappingURL=dbc.mjs.map
|