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