solana-privacy-scanner-core 0.1.0 → 0.1.2
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.cjs +98 -78
- package/dist/index.cjs.map +2 -2
- package/dist/index.js +98 -78
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -50,15 +50,14 @@ __export(index_exports, {
|
|
|
50
50
|
});
|
|
51
51
|
module.exports = __toCommonJS(index_exports);
|
|
52
52
|
|
|
53
|
-
// src/rpc/client.
|
|
53
|
+
// src/rpc/client.ts
|
|
54
54
|
var import_web3 = require("@solana/web3.js");
|
|
55
55
|
var RateLimiter = class {
|
|
56
|
-
maxConcurrency;
|
|
57
|
-
activeRequests = 0;
|
|
58
|
-
queue = [];
|
|
59
56
|
constructor(maxConcurrency) {
|
|
60
57
|
this.maxConcurrency = maxConcurrency;
|
|
61
58
|
}
|
|
59
|
+
activeRequests = 0;
|
|
60
|
+
queue = [];
|
|
62
61
|
async acquire() {
|
|
63
62
|
if (this.activeRequests < this.maxConcurrency) {
|
|
64
63
|
this.activeRequests++;
|
|
@@ -92,14 +91,16 @@ var RPCClient = class {
|
|
|
92
91
|
connection;
|
|
93
92
|
config;
|
|
94
93
|
rateLimiter;
|
|
95
|
-
constructor(
|
|
94
|
+
constructor(configOrUrl) {
|
|
95
|
+
const config = typeof configOrUrl === "string" ? { rpcUrl: configOrUrl } : configOrUrl;
|
|
96
|
+
const rpcUrl = config.rpcUrl.trim();
|
|
96
97
|
this.config = {
|
|
97
98
|
maxRetries: config.maxRetries ?? 3,
|
|
98
99
|
retryDelay: config.retryDelay ?? 1e3,
|
|
99
100
|
timeout: config.timeout ?? 3e4,
|
|
100
101
|
maxConcurrency: config.maxConcurrency ?? 10,
|
|
101
102
|
debug: config.debug ?? false,
|
|
102
|
-
rpcUrl
|
|
103
|
+
rpcUrl
|
|
103
104
|
};
|
|
104
105
|
const connectionConfig = {
|
|
105
106
|
commitment: "confirmed",
|
|
@@ -130,7 +131,10 @@ var RPCClient = class {
|
|
|
130
131
|
} catch (error) {
|
|
131
132
|
lastError = error;
|
|
132
133
|
if (this.config.debug) {
|
|
133
|
-
console.error(
|
|
134
|
+
console.error(
|
|
135
|
+
`[RPCClient] Error in ${operationName} (attempt ${attempt + 1}/${this.config.maxRetries + 1}):`,
|
|
136
|
+
error
|
|
137
|
+
);
|
|
134
138
|
}
|
|
135
139
|
if (attempt < this.config.maxRetries) {
|
|
136
140
|
const delay = this.config.retryDelay * Math.pow(2, attempt);
|
|
@@ -139,7 +143,9 @@ var RPCClient = class {
|
|
|
139
143
|
}
|
|
140
144
|
}
|
|
141
145
|
this.rateLimiter.release();
|
|
142
|
-
throw new Error(
|
|
146
|
+
throw new Error(
|
|
147
|
+
`RPC operation ${operationName} failed after ${this.config.maxRetries + 1} attempts: ${lastError?.message}`
|
|
148
|
+
);
|
|
143
149
|
}
|
|
144
150
|
/**
|
|
145
151
|
* Get the underlying Solana Connection
|
|
@@ -161,20 +167,29 @@ var RPCClient = class {
|
|
|
161
167
|
* Get signatures for an address with retry and rate limiting
|
|
162
168
|
*/
|
|
163
169
|
async getSignaturesForAddress(address, options) {
|
|
164
|
-
return this.executeWithRetry(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
170
|
+
return this.executeWithRetry(
|
|
171
|
+
async () => {
|
|
172
|
+
const { PublicKey } = await import("@solana/web3.js");
|
|
173
|
+
return this.connection.getSignaturesForAddress(
|
|
174
|
+
new PublicKey(address),
|
|
175
|
+
options
|
|
176
|
+
);
|
|
177
|
+
},
|
|
178
|
+
`getSignaturesForAddress(${address})`
|
|
179
|
+
);
|
|
168
180
|
}
|
|
169
181
|
/**
|
|
170
182
|
* Get transaction details with retry and rate limiting
|
|
171
183
|
*/
|
|
172
184
|
async getTransaction(signature, options) {
|
|
173
|
-
return this.executeWithRetry(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
return this.executeWithRetry(
|
|
186
|
+
async () => {
|
|
187
|
+
return this.connection.getTransaction(signature, {
|
|
188
|
+
maxSupportedTransactionVersion: options?.maxSupportedTransactionVersion ?? 0
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
`getTransaction(${signature})`
|
|
192
|
+
);
|
|
178
193
|
}
|
|
179
194
|
/**
|
|
180
195
|
* Get multiple transactions in parallel (respects rate limiting)
|
|
@@ -187,54 +202,69 @@ var RPCClient = class {
|
|
|
187
202
|
* Get token accounts by owner with retry and rate limiting
|
|
188
203
|
*/
|
|
189
204
|
async getTokenAccountsByOwner(ownerAddress, mintAddress) {
|
|
190
|
-
return this.executeWithRetry(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
205
|
+
return this.executeWithRetry(
|
|
206
|
+
async () => {
|
|
207
|
+
const { PublicKey } = await import("@solana/web3.js");
|
|
208
|
+
const owner = new PublicKey(ownerAddress);
|
|
209
|
+
const TOKEN_PROGRAM_ID = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
|
210
|
+
if (mintAddress) {
|
|
211
|
+
const mint = new PublicKey(mintAddress);
|
|
212
|
+
return this.connection.getTokenAccountsByOwner(owner, { mint });
|
|
213
|
+
} else {
|
|
214
|
+
return this.connection.getTokenAccountsByOwner(owner, {
|
|
215
|
+
programId: TOKEN_PROGRAM_ID
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
`getTokenAccountsByOwner(${ownerAddress})`
|
|
220
|
+
);
|
|
203
221
|
}
|
|
204
222
|
/**
|
|
205
223
|
* Get program accounts with retry and rate limiting
|
|
206
224
|
*/
|
|
207
225
|
async getProgramAccounts(programId, config) {
|
|
208
|
-
return this.executeWithRetry(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
226
|
+
return this.executeWithRetry(
|
|
227
|
+
async () => {
|
|
228
|
+
const { PublicKey } = await import("@solana/web3.js");
|
|
229
|
+
return this.connection.getProgramAccounts(new PublicKey(programId), config);
|
|
230
|
+
},
|
|
231
|
+
`getProgramAccounts(${programId})`
|
|
232
|
+
);
|
|
212
233
|
}
|
|
213
234
|
/**
|
|
214
235
|
* Get account info with retry and rate limiting
|
|
215
236
|
*/
|
|
216
237
|
async getAccountInfo(address) {
|
|
217
|
-
return this.executeWithRetry(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
238
|
+
return this.executeWithRetry(
|
|
239
|
+
async () => {
|
|
240
|
+
const { PublicKey } = await import("@solana/web3.js");
|
|
241
|
+
return this.connection.getAccountInfo(new PublicKey(address));
|
|
242
|
+
},
|
|
243
|
+
`getAccountInfo(${address})`
|
|
244
|
+
);
|
|
221
245
|
}
|
|
222
246
|
/**
|
|
223
247
|
* Get multiple account infos in parallel (respects rate limiting)
|
|
224
248
|
*/
|
|
225
249
|
async getMultipleAccountsInfo(addresses) {
|
|
226
|
-
return this.executeWithRetry(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
250
|
+
return this.executeWithRetry(
|
|
251
|
+
async () => {
|
|
252
|
+
const { PublicKey } = await import("@solana/web3.js");
|
|
253
|
+
const pubkeys = addresses.map((addr) => new PublicKey(addr));
|
|
254
|
+
return this.connection.getMultipleAccountsInfo(pubkeys);
|
|
255
|
+
},
|
|
256
|
+
`getMultipleAccountsInfo(${addresses.length} addresses)`
|
|
257
|
+
);
|
|
231
258
|
}
|
|
232
259
|
/**
|
|
233
260
|
* Check if the RPC connection is healthy
|
|
234
261
|
*/
|
|
235
262
|
async healthCheck() {
|
|
236
263
|
try {
|
|
237
|
-
const version = await this.executeWithRetry(
|
|
264
|
+
const version = await this.executeWithRetry(
|
|
265
|
+
() => this.connection.getVersion(),
|
|
266
|
+
"healthCheck"
|
|
267
|
+
);
|
|
238
268
|
return !!version;
|
|
239
269
|
} catch {
|
|
240
270
|
return false;
|
|
@@ -242,7 +272,7 @@ var RPCClient = class {
|
|
|
242
272
|
}
|
|
243
273
|
};
|
|
244
274
|
|
|
245
|
-
// src/collectors/index.
|
|
275
|
+
// src/collectors/index.ts
|
|
246
276
|
async function collectWalletData(client, address, options = {}) {
|
|
247
277
|
const maxSignatures = options.maxSignatures ?? 100;
|
|
248
278
|
const includeTokenAccounts = options.includeTokenAccounts ?? true;
|
|
@@ -343,7 +373,7 @@ async function collectProgramData(client, programId, options = {}) {
|
|
|
343
373
|
};
|
|
344
374
|
}
|
|
345
375
|
|
|
346
|
-
// src/normalizer/index.
|
|
376
|
+
// src/normalizer/index.ts
|
|
347
377
|
var PROGRAM_IDS = {
|
|
348
378
|
SYSTEM: "11111111111111111111111111111111",
|
|
349
379
|
TOKEN: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
|
@@ -374,14 +404,11 @@ function extractSOLTransfers(tx, signature) {
|
|
|
374
404
|
continue;
|
|
375
405
|
}
|
|
376
406
|
const diff = post - pre;
|
|
377
|
-
if (diff === 0)
|
|
378
|
-
continue;
|
|
407
|
+
if (diff === 0) continue;
|
|
379
408
|
const account = accountKeys[i];
|
|
380
|
-
if (!account)
|
|
381
|
-
continue;
|
|
409
|
+
if (!account) continue;
|
|
382
410
|
const address = typeof account === "string" ? account : account.pubkey?.toString();
|
|
383
|
-
if (!address)
|
|
384
|
-
continue;
|
|
411
|
+
if (!address) continue;
|
|
385
412
|
if (diff > 0) {
|
|
386
413
|
for (let j = 0; j < accountKeys.length; j++) {
|
|
387
414
|
const preSender = preBalances[j];
|
|
@@ -391,11 +418,9 @@ function extractSOLTransfers(tx, signature) {
|
|
|
391
418
|
}
|
|
392
419
|
if (postSender < preSender) {
|
|
393
420
|
const sender = accountKeys[j];
|
|
394
|
-
if (!sender)
|
|
395
|
-
continue;
|
|
421
|
+
if (!sender) continue;
|
|
396
422
|
const senderAddress = typeof sender === "string" ? sender : sender.pubkey?.toString();
|
|
397
|
-
if (!senderAddress)
|
|
398
|
-
continue;
|
|
423
|
+
if (!senderAddress) continue;
|
|
399
424
|
transfers.push({
|
|
400
425
|
from: senderAddress,
|
|
401
426
|
to: address,
|
|
@@ -421,7 +446,9 @@ function extractSPLTransfers(tx, signature) {
|
|
|
421
446
|
const postTokenBalances = tx.meta.postTokenBalances;
|
|
422
447
|
const balanceChanges = /* @__PURE__ */ new Map();
|
|
423
448
|
for (const post of postTokenBalances) {
|
|
424
|
-
const pre = preTokenBalances.find(
|
|
449
|
+
const pre = preTokenBalances.find(
|
|
450
|
+
(p) => p.accountIndex === post.accountIndex && p.mint === post.mint
|
|
451
|
+
);
|
|
425
452
|
const preAmount = pre?.uiTokenAmount.uiAmount ?? 0;
|
|
426
453
|
const postAmount = post.uiTokenAmount.uiAmount ?? 0;
|
|
427
454
|
const change = postAmount - preAmount;
|
|
@@ -442,20 +469,16 @@ function extractSPLTransfers(tx, signature) {
|
|
|
442
469
|
return;
|
|
443
470
|
}
|
|
444
471
|
const account = accountKeys[accountIndex];
|
|
445
|
-
if (!account)
|
|
446
|
-
return;
|
|
472
|
+
if (!account) return;
|
|
447
473
|
const address = typeof account === "string" ? account : account.pubkey?.toString();
|
|
448
|
-
if (!address)
|
|
449
|
-
return;
|
|
474
|
+
if (!address) return;
|
|
450
475
|
if (info.change > 0) {
|
|
451
476
|
balanceChanges.forEach((senderInfo, senderIndex) => {
|
|
452
477
|
if (senderInfo.mint === info.mint && senderInfo.change < 0 && senderIndex !== accountIndex && senderIndex < accountKeys.length) {
|
|
453
478
|
const sender = accountKeys[senderIndex];
|
|
454
|
-
if (!sender)
|
|
455
|
-
return;
|
|
479
|
+
if (!sender) return;
|
|
456
480
|
const senderAddress = typeof sender === "string" ? sender : sender.pubkey?.toString();
|
|
457
|
-
if (!senderAddress)
|
|
458
|
-
return;
|
|
481
|
+
if (!senderAddress) return;
|
|
459
482
|
transfers.push({
|
|
460
483
|
from: senderAddress,
|
|
461
484
|
to: address,
|
|
@@ -563,8 +586,7 @@ function normalizeWalletData(rawData, labelProvider) {
|
|
|
563
586
|
const allTransfers = [];
|
|
564
587
|
const allInstructions = [];
|
|
565
588
|
for (const rawTx of rawData.transactions) {
|
|
566
|
-
if (!rawTx.transaction)
|
|
567
|
-
continue;
|
|
589
|
+
if (!rawTx.transaction) continue;
|
|
568
590
|
try {
|
|
569
591
|
const solTransfers = extractSOLTransfers(rawTx.transaction, rawTx.signature);
|
|
570
592
|
const splTransfers = extractSPLTransfers(rawTx.transaction, rawTx.signature);
|
|
@@ -644,8 +666,7 @@ function normalizeProgramData(rawData, labelProvider) {
|
|
|
644
666
|
const allInstructions = [];
|
|
645
667
|
const counterparties = /* @__PURE__ */ new Set();
|
|
646
668
|
for (const rawTx of rawData.relatedTransactions) {
|
|
647
|
-
if (!rawTx.transaction)
|
|
648
|
-
continue;
|
|
669
|
+
if (!rawTx.transaction) continue;
|
|
649
670
|
try {
|
|
650
671
|
const solTransfers = extractSOLTransfers(rawTx.transaction, rawTx.signature);
|
|
651
672
|
const splTransfers = extractSPLTransfers(rawTx.transaction, rawTx.signature);
|
|
@@ -679,7 +700,7 @@ function normalizeProgramData(rawData, labelProvider) {
|
|
|
679
700
|
};
|
|
680
701
|
}
|
|
681
702
|
|
|
682
|
-
// src/heuristics/counterparty-reuse.
|
|
703
|
+
// src/heuristics/counterparty-reuse.ts
|
|
683
704
|
function detectCounterpartyReuse(context) {
|
|
684
705
|
if (context.targetType !== "wallet") {
|
|
685
706
|
return null;
|
|
@@ -690,8 +711,7 @@ function detectCounterpartyReuse(context) {
|
|
|
690
711
|
const interactionCounts = /* @__PURE__ */ new Map();
|
|
691
712
|
for (const transfer of context.transfers) {
|
|
692
713
|
const counterparty = transfer.from === context.target ? transfer.to : transfer.from;
|
|
693
|
-
if (counterparty === context.target)
|
|
694
|
-
continue;
|
|
714
|
+
if (counterparty === context.target) continue;
|
|
695
715
|
interactionCounts.set(counterparty, (interactionCounts.get(counterparty) || 0) + 1);
|
|
696
716
|
}
|
|
697
717
|
const reusedCounterparties = Array.from(interactionCounts.entries()).filter(([_, count]) => count >= 3).sort((a, b) => b[1] - a[1]);
|
|
@@ -724,7 +744,7 @@ function detectCounterpartyReuse(context) {
|
|
|
724
744
|
};
|
|
725
745
|
}
|
|
726
746
|
|
|
727
|
-
// src/heuristics/amount-reuse.
|
|
747
|
+
// src/heuristics/amount-reuse.ts
|
|
728
748
|
function detectAmountReuse(context) {
|
|
729
749
|
if (context.transfers.length < 3) {
|
|
730
750
|
return null;
|
|
@@ -781,7 +801,7 @@ function detectAmountReuse(context) {
|
|
|
781
801
|
};
|
|
782
802
|
}
|
|
783
803
|
|
|
784
|
-
// src/heuristics/timing-patterns.
|
|
804
|
+
// src/heuristics/timing-patterns.ts
|
|
785
805
|
function detectTimingPatterns(context) {
|
|
786
806
|
if (!context.timeRange.earliest || !context.timeRange.latest) {
|
|
787
807
|
return null;
|
|
@@ -833,7 +853,7 @@ function detectTimingPatterns(context) {
|
|
|
833
853
|
};
|
|
834
854
|
}
|
|
835
855
|
|
|
836
|
-
// src/heuristics/known-entity.
|
|
856
|
+
// src/heuristics/known-entity.ts
|
|
837
857
|
function detectKnownEntityInteraction(context) {
|
|
838
858
|
if (context.labels.size === 0) {
|
|
839
859
|
return null;
|
|
@@ -887,7 +907,7 @@ function detectKnownEntityInteraction(context) {
|
|
|
887
907
|
};
|
|
888
908
|
}
|
|
889
909
|
|
|
890
|
-
// src/heuristics/balance-traceability.
|
|
910
|
+
// src/heuristics/balance-traceability.ts
|
|
891
911
|
function detectBalanceTraceability(context) {
|
|
892
912
|
if (context.targetType !== "wallet" || context.transfers.length < 2) {
|
|
893
913
|
return null;
|
|
@@ -948,7 +968,7 @@ function detectBalanceTraceability(context) {
|
|
|
948
968
|
};
|
|
949
969
|
}
|
|
950
970
|
|
|
951
|
-
// src/scanner/index.
|
|
971
|
+
// src/scanner/index.ts
|
|
952
972
|
var REPORT_VERSION = "1.0.0";
|
|
953
973
|
var HEURISTICS = [
|
|
954
974
|
detectCounterpartyReuse,
|
|
@@ -1039,7 +1059,7 @@ function generateReport(context) {
|
|
|
1039
1059
|
};
|
|
1040
1060
|
}
|
|
1041
1061
|
|
|
1042
|
-
// src/labels/provider.
|
|
1062
|
+
// src/labels/provider.ts
|
|
1043
1063
|
var import_fs = require("fs");
|
|
1044
1064
|
var import_url = require("url");
|
|
1045
1065
|
var import_path = require("path");
|