@helium/sus 0.6.24-next.8 → 0.6.30

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