@helium/sus 0.6.2-next.34

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.
@@ -0,0 +1,670 @@
1
+ import { BN, BorshAccountsCoder, BorshInstructionCoder, } from "@coral-xyz/anchor";
2
+ import { decodeIdlAccount } from "@coral-xyz/anchor/dist/cjs/idl";
3
+ import { utf8 } from "@coral-xyz/anchor/dist/cjs/utils/bytes";
4
+ import { getLeafAssetId } from "@metaplex-foundation/mpl-bubblegum";
5
+ import { PROGRAM_ID as MPL_PID, Metadata, } from "@metaplex-foundation/mpl-token-metadata";
6
+ import { sha256 } from "@noble/hashes/sha256";
7
+ import { NATIVE_MINT, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, unpackAccount, unpackMint, unpackMultisig, } from "@solana/spl-token";
8
+ import { PublicKey, SystemProgram, VersionedTransaction, } from "@solana/web3.js";
9
+ import axios from "axios";
10
+ import { inflate } from "pako";
11
+ const BUBBLEGUM_PROGRAM_ID = new PublicKey("BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY");
12
+ const ACCOUNT_COMPRESSION_PROGRAM_ID = new PublicKey("cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK");
13
+ async function getAccountKeys({ connection, transaction, }) {
14
+ const addressLookupTableAccounts = [];
15
+ const { addressTableLookups } = transaction.message;
16
+ if (addressTableLookups.length > 0) {
17
+ // eslint-disable-next-line no-restricted-syntax
18
+ for (const addressTableLookup of addressTableLookups) {
19
+ // eslint-disable-next-line no-await-in-loop
20
+ const result = await connection?.getAddressLookupTable(addressTableLookup.accountKey);
21
+ if (result?.value) {
22
+ addressLookupTableAccounts.push(result?.value);
23
+ }
24
+ }
25
+ }
26
+ return transaction.message.getAccountKeys({
27
+ addressLookupTableAccounts,
28
+ });
29
+ }
30
+ async function getMultipleAccounts({ connection, keys, }) {
31
+ const batchSize = 100;
32
+ const batches = Math.ceil(keys.length / batchSize);
33
+ const results = [];
34
+ for (let i = 0; i < batches; i++) {
35
+ const batchKeys = keys.slice(i * batchSize, (i + 1) * batchSize);
36
+ const batchResults = await connection.getMultipleAccountsInfo(batchKeys);
37
+ results.push(...batchResults);
38
+ }
39
+ return results;
40
+ }
41
+ export async function sus({ connection, wallet, serializedTransactions,
42
+ /// CNFT specific params
43
+ checkCNfts = false, extraSearchAssetParams, cNfts,
44
+ // Cluster for explorer
45
+ cluster = "mainnet-beta", accountBlacklist, }) {
46
+ let assets = cNfts;
47
+ if (checkCNfts) {
48
+ if (!assets) {
49
+ const assetsResponse = await axios.post(connection.rpcEndpoint, {
50
+ jsonrpc: "2.0",
51
+ method: "searchAssets",
52
+ id: "get-assets-op-1",
53
+ params: {
54
+ page: 1,
55
+ // limit to checking 200 assets
56
+ limit: 200,
57
+ compressed: true,
58
+ ownerAddress: wallet.toBase58(),
59
+ ...extraSearchAssetParams,
60
+ },
61
+ headers: {
62
+ "Cache-Control": "no-cache",
63
+ Pragma: "no-cache",
64
+ Expires: "0",
65
+ },
66
+ });
67
+ assets = assetsResponse.data.result?.items;
68
+ }
69
+ }
70
+ const warningsByTx = serializedTransactions.map(() => []);
71
+ const transactions = serializedTransactions.map((t) => VersionedTransaction.deserialize(t));
72
+ const accountKeysByTx = await Promise.all(transactions.map((transaction) => getAccountKeys({ connection, transaction })));
73
+ const simulationAccountsByTx = accountKeysByTx.map((accountKeys, txIndex) => [
74
+ ...new Set(accountKeys.staticAccountKeys
75
+ .filter((_, index) => transactions[txIndex].message.isAccountWritable(index))
76
+ .concat(accountKeys.accountKeysFromLookups
77
+ ? // Only writable accounts will contribute to balance changes
78
+ accountKeys.accountKeysFromLookups.writable
79
+ : [])),
80
+ ].filter((a) => !accountBlacklist?.has(a.toBase58())));
81
+ const allAccounts = [...new Set(simulationAccountsByTx.flat())];
82
+ const fetchedAccounts = await getMultipleAccounts({
83
+ connection,
84
+ keys: allAccounts,
85
+ });
86
+ const fetchedAccountsByAddr = fetchedAccounts.reduce((acc, account, index) => {
87
+ acc[allAccounts[index].toBase58()] = account;
88
+ return acc;
89
+ }, {});
90
+ let { blockhash } = await connection?.getLatestBlockhash("finalized");
91
+ const simulatedTxs = [];
92
+ // Linearly simulate txs so as not to hit rate limits
93
+ for (const [index, transaction] of transactions.entries()) {
94
+ let simulatedTxn = null;
95
+ let tries = 0;
96
+ // Retry until we stop getting blockhashNotFound
97
+ blockhashLoop: while (true) {
98
+ transaction.message.recentBlockhash = blockhash;
99
+ simulatedTxn = await connection.simulateTransaction(transaction, {
100
+ accounts: {
101
+ encoding: "base64",
102
+ addresses: simulationAccountsByTx[index]?.map((account) => account.toBase58()) || [],
103
+ },
104
+ });
105
+ if (isBlockhashNotFound(simulatedTxn)) {
106
+ ({ blockhash } = await connection?.getLatestBlockhash("finalized"));
107
+ tries++;
108
+ if (tries >= 5) {
109
+ simulatedTxs.push(simulatedTxn);
110
+ break blockhashLoop;
111
+ }
112
+ }
113
+ else {
114
+ simulatedTxs.push(simulatedTxn);
115
+ break blockhashLoop;
116
+ }
117
+ }
118
+ }
119
+ const fullAccountsByTxn = simulationAccountsByTx.map((simulationAccounts, transactionIndex) => {
120
+ const simulatedTxn = simulatedTxs[transactionIndex];
121
+ return simulationAccounts.map((account, index) => {
122
+ const post = simulatedTxn.value.accounts?.[index];
123
+ return {
124
+ address: account,
125
+ post: post
126
+ ? {
127
+ ...post,
128
+ owner: new PublicKey(post.owner),
129
+ data: Buffer.from(post.data[0], post.data[1]),
130
+ }
131
+ : undefined,
132
+ pre: fetchedAccountsByAddr[account.toBase58()],
133
+ };
134
+ });
135
+ });
136
+ const instructionProgramIds = transactions
137
+ .flatMap((transaction, index) => transaction.message.compiledInstructions.map((ix) => accountKeysByTx[index].get(ix.programIdIndex) || null))
138
+ .filter(truthy);
139
+ const programKeys = fullAccountsByTxn
140
+ .flat()
141
+ .map((acc) => acc?.pre?.owner || (acc.post ? new PublicKey(acc.post.owner) : null))
142
+ .concat(...instructionProgramIds)
143
+ .filter(truthy);
144
+ const idlKeys = programKeys.map(getIdlKey);
145
+ const idls = (await getMultipleAccounts({ connection, keys: idlKeys }))
146
+ .map((acc, index) => {
147
+ if (acc) {
148
+ return {
149
+ program: programKeys[index],
150
+ idl: decodeIdl(acc),
151
+ };
152
+ }
153
+ })
154
+ .filter(truthy)
155
+ .reduce((acc, { program, idl }) => {
156
+ if (idl) {
157
+ acc[program.toBase58()] = idl;
158
+ }
159
+ return acc;
160
+ }, {});
161
+ const writableAccountsByTxRaw = fullAccountsByTxn.map((accounts) => getDetailedWritableAccountsWithoutTM({
162
+ accounts,
163
+ idls,
164
+ }));
165
+ const tokens = [
166
+ ...new Set(writableAccountsByTxRaw.flatMap((w) => w.tokens).map((t) => t.toBase58())),
167
+ ].map((t) => new PublicKey(t));
168
+ const metadatas = (await fetchMetadatas(connection, tokens)).reduce((acc, m, index) => {
169
+ if (m) {
170
+ acc[tokens[index].toBase58()] = m;
171
+ }
172
+ return acc;
173
+ }, {});
174
+ const writableAccountsByTx = writableAccountsByTxRaw.map(({ withoutMetadata }, index) => {
175
+ const writableAccounts = withoutMetadata.map((acc) => {
176
+ let name = acc.name;
177
+ let metadata;
178
+ // Attempt to take last known type
179
+ const type = (acc.pre.type !== "Unknown" && acc.pre.type) ||
180
+ (acc.post.type !== "Unknown" && acc.post.type) ||
181
+ "Unknown";
182
+ // If token, get the name based on the metadata
183
+ if (type === "Mint") {
184
+ metadata = metadatas[acc.address.toBase58()];
185
+ if (metadata) {
186
+ name = `${metadata.symbol} Mint`;
187
+ }
188
+ else {
189
+ name = `Unknown Mint`;
190
+ }
191
+ }
192
+ else if (type === "TokenAccount") {
193
+ metadata =
194
+ metadatas[(acc.pre.parsed?.mint || acc.post.parsed?.mint).toBase58()];
195
+ if (metadata) {
196
+ name = `${metadata.symbol} Token Account`;
197
+ }
198
+ else {
199
+ name = `Unknown Token Account`;
200
+ }
201
+ }
202
+ return {
203
+ ...acc,
204
+ name,
205
+ metadata,
206
+ };
207
+ });
208
+ writableAccounts.forEach((acc) => {
209
+ if (!acc.changedInSimulation) {
210
+ warningsByTx[index].push({
211
+ severity: "warning",
212
+ shortMessage: "Unchanged",
213
+ message: "Account did not change in simulation but was labeled as writable. The behavior of the transaction may differ from the simulation.",
214
+ account: acc.address,
215
+ });
216
+ }
217
+ // Catch malicious sol ownwer change
218
+ const sysProg = new PublicKey("11111111111111111111111111111111");
219
+ const postOwner = acc.post.account?.owner || sysProg;
220
+ const preOwner = acc.pre.account?.owner || sysProg;
221
+ const accountOwnerChanged = !preOwner.equals(postOwner);
222
+ if (acc.name === "Native SOL Account" && acc.owner && acc.owner.equals(wallet) && accountOwnerChanged) {
223
+ warningsByTx[index].push({
224
+ severity: "critical",
225
+ shortMessage: "Owner Changed",
226
+ message: `The owner of ${acc.name} changed to ${acc.post.parsed?.owner?.toBase58()}. This gives that wallet full custody of these tokens.`,
227
+ account: acc.address,
228
+ });
229
+ }
230
+ });
231
+ return writableAccounts;
232
+ });
233
+ const instructionsByTx = await Promise.all(transactions.map(async (transaction, index) => {
234
+ const instructions = parseInstructions({
235
+ idls,
236
+ instructions: transaction.message.compiledInstructions.map((ix) => ({
237
+ data: Buffer.from(ix.data),
238
+ programId: accountKeysByTx[index].get(ix.programIdIndex),
239
+ accounts: ix.accountKeyIndexes.map((ix) => ({
240
+ pubkey: accountKeysByTx[index].get(ix),
241
+ isSigner: transaction.message.isAccountSigner(ix),
242
+ isWritable: transaction.message.isAccountWritable(ix),
243
+ })),
244
+ })),
245
+ });
246
+ if (instructions.some((ix) => ix.parsed?.name === "ledgerTransferPositionV0")) {
247
+ warningsByTx[index].push({
248
+ severity: "critical",
249
+ shortMessage: "Theft of Locked HNT",
250
+ message: "This transaction is attempting to steal your locked HNT positions",
251
+ });
252
+ }
253
+ if (instructions.some((ix) => ix.parsed?.name === "updateDestinationV0" ||
254
+ ix.parsed?.name === "updateCompressionDestinationV0")) {
255
+ warningsByTx[index].push({
256
+ severity: "warning",
257
+ shortMessage: "Rewards Destination Changed",
258
+ message: "This transaction will change the destination wallet of your mining rewards",
259
+ });
260
+ }
261
+ if ((await Promise.all(instructions.map((ix) => isBurnHotspot(connection, ix, assets)))).some((isBurn) => isBurn)) {
262
+ warningsByTx[index].push({
263
+ severity: "critical",
264
+ shortMessage: "Hotspot Destroyed",
265
+ message: "This transaction will brick your Hotspot!",
266
+ });
267
+ }
268
+ return instructions;
269
+ }));
270
+ const results = [];
271
+ for (const [index, simulatedTxn] of simulatedTxs.entries()) {
272
+ const warnings = warningsByTx[index];
273
+ const instructions = instructionsByTx[index];
274
+ const writableAccounts = writableAccountsByTx[index];
275
+ const transaction = transactions[index];
276
+ const message = Buffer.from(transaction.message.serialize()).toString("base64");
277
+ const explorerLink = `https://explorer.solana.com/tx/inspector?cluster=${cluster}&message=${encodeURIComponent(message)}`;
278
+ const logs = simulatedTxn.value.logs;
279
+ let result;
280
+ if (simulatedTxn?.value.err) {
281
+ warnings.push({
282
+ severity: "critical",
283
+ shortMessage: "Simulation Failed",
284
+ message: "Transaction failed in simulation",
285
+ });
286
+ result = {
287
+ instructions,
288
+ error: simulatedTxn.value.err,
289
+ logs,
290
+ solFee: 0,
291
+ priorityFee: 0,
292
+ insufficientFunds: isInsufficientBal(simulatedTxn?.value.err),
293
+ explorerLink,
294
+ balanceChanges: [],
295
+ possibleCNftChanges: [],
296
+ writableAccounts,
297
+ rawSimulation: simulatedTxn.value,
298
+ warnings,
299
+ };
300
+ }
301
+ else {
302
+ let solFee = (transaction?.signatures.length || 1) * 5000;
303
+ let priorityFee = 0;
304
+ const fee = (await connection?.getFeeForMessage(transaction.message, "confirmed"))
305
+ .value || solFee;
306
+ priorityFee = fee - solFee;
307
+ const balanceChanges = writableAccounts
308
+ .map((acc) => {
309
+ const type = (acc.pre.type !== "Unknown" && acc.pre.type) ||
310
+ (acc.post.type !== "Unknown" && acc.post.type);
311
+ switch (type) {
312
+ case "TokenAccount":
313
+ if (acc.post.parsed?.delegate && !acc.pre.parsed?.delegate) {
314
+ warnings.push({
315
+ severity: "warning",
316
+ shortMessage: "Withdraw Authority Given",
317
+ message: `Delegation was taken on ${acc.name}. This gives permission to withdraw tokens without the owner's permission.`,
318
+ account: acc.address,
319
+ });
320
+ }
321
+ if (acc.post.parsed &&
322
+ acc.pre.parsed &&
323
+ !acc.post.parsed.owner.equals(acc.pre.parsed.owner)) {
324
+ warnings.push({
325
+ severity: "warning",
326
+ shortMessage: "Owner Changed",
327
+ message: `The owner of ${acc.name} changed to ${acc.post.parsed?.owner?.toBase58()}. This gives that wallet full custody of these tokens.`,
328
+ account: acc.address,
329
+ });
330
+ }
331
+ return {
332
+ owner: acc.post.parsed?.owner || acc.pre.parsed?.owner,
333
+ address: acc.address,
334
+ amount: (acc.post.parsed?.amount || BigInt(0)) -
335
+ (acc.pre.parsed?.amount || BigInt(0)),
336
+ metadata: acc.metadata,
337
+ };
338
+ case "NativeAccount":
339
+ return {
340
+ owner: acc.address,
341
+ address: acc.address,
342
+ amount: BigInt((acc.post.account?.lamports || 0) -
343
+ (acc.pre.account?.lamports || 0)),
344
+ metadata: {
345
+ mint: NATIVE_MINT,
346
+ decimals: 9,
347
+ name: "SOL",
348
+ symbol: "SOL",
349
+ },
350
+ };
351
+ default:
352
+ return null;
353
+ }
354
+ })
355
+ .filter(truthy);
356
+ // Don't count new mints being created, as this might flag on candymachine txs
357
+ if (balanceChanges.filter((b) => b.owner.equals(wallet) && fetchedAccountsByAddr[b.address.toBase58()]).length >= 3) {
358
+ warnings.push({
359
+ severity: "warning",
360
+ shortMessage: "3+ Token Accounts",
361
+ message: "3 or more token accounts are impacted by this transaction. Any token account listed as writable can be emptied by the transaction, is this okay?",
362
+ });
363
+ }
364
+ let possibleCNftChanges = [];
365
+ if (checkCNfts) {
366
+ const possibleMerkles = new Set(writableAccounts
367
+ .filter((acc) => acc.name === "Merkle Tree")
368
+ .map((a) => a.address.toBase58()));
369
+ possibleCNftChanges = (assets || []).filter((item) => item.compression.tree && possibleMerkles.has(item.compression.tree));
370
+ }
371
+ result = {
372
+ instructions,
373
+ logs,
374
+ solFee,
375
+ priorityFee,
376
+ insufficientFunds: false,
377
+ explorerLink,
378
+ balanceChanges,
379
+ possibleCNftChanges,
380
+ writableAccounts,
381
+ rawSimulation: simulatedTxn.value,
382
+ warnings,
383
+ };
384
+ }
385
+ results.push(result);
386
+ }
387
+ return results;
388
+ }
389
+ function isBlockhashNotFound(simulatedTxn) {
390
+ return simulatedTxn?.value.err?.toString() === "BlockhashNotFound";
391
+ }
392
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
393
+ export function isInsufficientBal(e) {
394
+ return (e.toString().includes("Insufficient Balance") ||
395
+ e.toString().includes('"Custom":1') ||
396
+ e.InstructionError?.[1]?.Custom === 1);
397
+ }
398
+ function parseInstructions({ idls, instructions, }) {
399
+ return instructions.map((ix) => {
400
+ const idl = idls[ix.programId.toBase58()];
401
+ if (idl) {
402
+ try {
403
+ const coder = new BorshInstructionCoder(idl);
404
+ const parsed = coder.decode(ix.data, "base58");
405
+ if (parsed) {
406
+ const formatted = coder.format(parsed, ix.accounts);
407
+ if (formatted) {
408
+ return {
409
+ parsed: {
410
+ name: parsed.name,
411
+ programName: idl.name,
412
+ data: parsed.data,
413
+ accounts: formatted.accounts,
414
+ },
415
+ raw: ix,
416
+ };
417
+ }
418
+ }
419
+ }
420
+ catch (e) {
421
+ // Ignore, not a valid ix
422
+ }
423
+ }
424
+ return { raw: ix };
425
+ });
426
+ }
427
+ function getIdlKey(programId) {
428
+ const base = PublicKey.findProgramAddressSync([], programId)[0];
429
+ const buffer = Buffer.concat([
430
+ base.toBuffer(),
431
+ Buffer.from("anchor:idl"),
432
+ programId.toBuffer(),
433
+ ]);
434
+ const publicKeyBytes = sha256(buffer);
435
+ return new PublicKey(publicKeyBytes);
436
+ }
437
+ function decodeIdl(account) {
438
+ try {
439
+ const idlData = decodeIdlAccount(Buffer.from(account.data.subarray(8)));
440
+ const inflatedIdl = inflate(idlData.data);
441
+ return JSON.parse(utf8.decode(inflatedIdl));
442
+ }
443
+ catch (e) {
444
+ // Ignore, not a valid IDL
445
+ }
446
+ }
447
+ export function getDetailedWritableAccountsWithoutTM({ accounts, idls, }) {
448
+ const uniqueTokens = new Set();
449
+ const withoutMetadata = accounts.map(({ address, pre, post }) => {
450
+ let name = "Unknown";
451
+ let type = "Unknown";
452
+ let preParsed = null;
453
+ let postParsed = null;
454
+ let accountOwner = undefined;
455
+ const postData = post && post.data;
456
+ const postAccount = post && postData
457
+ ? {
458
+ executable: post.executable,
459
+ owner: new PublicKey(post.owner),
460
+ lamports: post.lamports,
461
+ data: postData,
462
+ rentEpoch: post.rentEpoch,
463
+ }
464
+ : null;
465
+ const owner = pre?.owner || (post ? new PublicKey(post.owner) : null);
466
+ switch (owner?.toBase58()) {
467
+ case ACCOUNT_COMPRESSION_PROGRAM_ID.toBase58():
468
+ name = "Merkle Tree";
469
+ type = "MerkleTree";
470
+ break;
471
+ case SystemProgram.programId.toBase58():
472
+ name = "Native SOL Account";
473
+ type = "NativeAccount";
474
+ accountOwner = address;
475
+ break;
476
+ case TOKEN_2022_PROGRAM_ID.toBase58():
477
+ ({ parsed: preParsed, type } = decodeTokenStruct(address, pre, TOKEN_2022_PROGRAM_ID) || { type });
478
+ ({ parsed: postParsed, type } = decodeTokenStruct(address, postAccount, TOKEN_2022_PROGRAM_ID) || { type });
479
+ break;
480
+ case TOKEN_PROGRAM_ID.toBase58():
481
+ ({ parsed: preParsed, type } = decodeTokenStruct(address, pre, TOKEN_PROGRAM_ID) || { type });
482
+ ({ parsed: postParsed, type } = decodeTokenStruct(address, postAccount, TOKEN_PROGRAM_ID) || { type });
483
+ break;
484
+ default:
485
+ if (owner) {
486
+ const idl = idls[owner.toBase58()];
487
+ if (idl) {
488
+ const decodedPre = decodeIdlStruct(idl, pre);
489
+ const decodedPost = decodeIdlStruct(idl, postAccount);
490
+ preParsed = decodedPre?.parsed;
491
+ postParsed = decodedPost?.parsed;
492
+ type =
493
+ (decodedPre?.type !== "Unknown" && decodedPre?.type) ||
494
+ (decodedPost?.type !== "Unknown" && decodedPost?.type) ||
495
+ "Unknown";
496
+ name = type;
497
+ }
498
+ }
499
+ break;
500
+ }
501
+ // If token, get the name based on the metadata
502
+ if (new Set([
503
+ TOKEN_2022_PROGRAM_ID.toBase58(),
504
+ TOKEN_PROGRAM_ID.toBase58(),
505
+ ]).has(owner?.toBase58() || "")) {
506
+ if (type === "Mint") {
507
+ uniqueTokens.add(address.toBase58());
508
+ }
509
+ else if (type === "TokenAccount") {
510
+ const mint = (preParsed?.mint || postParsed?.mint).toBase58();
511
+ accountOwner = preParsed?.owner || postParsed?.owner;
512
+ uniqueTokens.add(mint);
513
+ }
514
+ }
515
+ return {
516
+ address,
517
+ name,
518
+ owner: accountOwner,
519
+ pre: {
520
+ type,
521
+ account: pre || null,
522
+ parsed: preParsed,
523
+ },
524
+ post: {
525
+ type,
526
+ account: post || null,
527
+ parsed: postParsed,
528
+ },
529
+ changedInSimulation: pre && postData
530
+ ? !pre.data.equals(postData) ||
531
+ pre.lamports != post.lamports ||
532
+ !pre.owner.equals(new PublicKey(post.owner))
533
+ : true,
534
+ };
535
+ });
536
+ const tokens = [...uniqueTokens].map((t) => new PublicKey(t));
537
+ return {
538
+ withoutMetadata,
539
+ tokens,
540
+ };
541
+ }
542
+ function decodeIdlStruct(idl, account) {
543
+ if (!account) {
544
+ return null;
545
+ }
546
+ try {
547
+ const coder = new BorshAccountsCoder(idl);
548
+ const descriminator = account.data.slice(0, 8);
549
+ const type = idl.accounts?.find((account) => BorshAccountsCoder.accountDiscriminator(account.name).equals(descriminator))?.name;
550
+ if (type) {
551
+ return {
552
+ type,
553
+ parsed: coder.decode(type, account.data),
554
+ };
555
+ }
556
+ }
557
+ catch (e) {
558
+ // Ignore, not a valid account
559
+ }
560
+ return null;
561
+ }
562
+ function decodeTokenStruct(address, account, programId) {
563
+ if (!account) {
564
+ return null;
565
+ }
566
+ try {
567
+ return {
568
+ type: "TokenAccount",
569
+ parsed: unpackAccount(address, account, programId),
570
+ };
571
+ }
572
+ catch (e) {
573
+ // Not an account
574
+ }
575
+ try {
576
+ return {
577
+ type: "Mint",
578
+ parsed: unpackMint(address, account, programId),
579
+ };
580
+ }
581
+ catch (e) {
582
+ // Not an account
583
+ }
584
+ try {
585
+ return {
586
+ type: "Multisig",
587
+ parsed: unpackMultisig(address, account, programId),
588
+ };
589
+ }
590
+ catch (e) {
591
+ // Not an account
592
+ }
593
+ return null;
594
+ }
595
+ export function getMetadataId(mint) {
596
+ return PublicKey.findProgramAddressSync([Buffer.from("metadata", "utf-8"), MPL_PID.toBuffer(), mint.toBuffer()], MPL_PID)[0];
597
+ }
598
+ export async function fetchMetadatas(connection, tokens) {
599
+ const metadatas = tokens.map(getMetadataId);
600
+ const all = await getMultipleAccounts({
601
+ keys: [...metadatas, ...tokens],
602
+ connection,
603
+ });
604
+ const metadataAccounts = all.slice(0, metadatas.length);
605
+ const mintAccounts = all.slice(metadatas.length, metadatas.length * 2);
606
+ return metadataAccounts.map((acc, index) => {
607
+ try {
608
+ const mint = unpackMint(tokens[index], mintAccounts[index]);
609
+ if (acc) {
610
+ const collectable = Metadata.fromAccountInfo(acc)[0];
611
+ return {
612
+ name: collectable.data.name.replace(/\0/g, ""),
613
+ symbol: collectable.data.symbol.replace(/\0/g, ""),
614
+ uri: collectable.data.uri.replace(/\0/g, ""),
615
+ decimals: mint.decimals,
616
+ mint: tokens[index],
617
+ };
618
+ }
619
+ return {
620
+ mint: tokens[index],
621
+ decimals: mint.decimals,
622
+ };
623
+ }
624
+ catch (e) {
625
+ // Ignore, not a valid mint
626
+ }
627
+ return null;
628
+ });
629
+ }
630
+ const truthy = (value) => !!value;
631
+ async function isBurnHotspot(connection, ix, assets) {
632
+ if (ix.raw.programId.equals(BUBBLEGUM_PROGRAM_ID) &&
633
+ ix.parsed?.name === "burn") {
634
+ const tree = ix.parsed?.accounts.find((acc) => acc.name === "Merkle Tree")?.pubkey;
635
+ if (tree) {
636
+ const index = ix.parsed?.data?.index;
637
+ const assetId = await getLeafAssetId(tree, new BN(index));
638
+ let asset;
639
+ if (assets) {
640
+ asset = assets.find(a => a.id === assetId.toBase58());
641
+ }
642
+ else {
643
+ const assetResponse = await axios.post(connection.rpcEndpoint, {
644
+ jsonrpc: "2.0",
645
+ method: "getAsset",
646
+ id: "get-asset-op-1",
647
+ params: {
648
+ id: assetId.toBase58(),
649
+ },
650
+ headers: {
651
+ "Cache-Control": "no-cache",
652
+ Pragma: "no-cache",
653
+ Expires: "0",
654
+ },
655
+ });
656
+ asset = assetResponse.data.result;
657
+ }
658
+ return (asset &&
659
+ asset.creators[0]?.address == HELIUM_ENTITY_CREATOR.toBase58() &&
660
+ asset.creators[0]?.verified);
661
+ }
662
+ }
663
+ return false;
664
+ }
665
+ const DAO = PublicKey.findProgramAddressSync([
666
+ Buffer.from("dao", "utf-8"),
667
+ new PublicKey("hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux").toBuffer(),
668
+ ], new PublicKey("hdaoVTCqhfHHo75XdAMxBKdUqvq1i5bF23sisBqVgGR"))[0];
669
+ const HELIUM_ENTITY_CREATOR = PublicKey.findProgramAddressSync([Buffer.from("entity_creator", "utf-8"), DAO.toBuffer()], new PublicKey("hemjuPXBpNvggtaUnN1MwT3wrdhttKEfosTcc2P9Pg8"))[0];
670
+ //# sourceMappingURL=index.js.map