tx-indexer 0.5.0 → 0.5.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/README.md +12 -0
- package/dist/{client-iLW2_DnL.d.ts → client-DdzTiKZ4.d.ts} +25 -9
- package/dist/client.d.ts +1 -1
- package/dist/client.js +479 -97
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +5 -6
- package/dist/index.js +479 -97
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -49,6 +49,57 @@ function getTokenInfo(mint) {
|
|
|
49
49
|
return TOKEN_INFO[mint];
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// ../solana/src/constants/program-ids.ts
|
|
53
|
+
var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
|
|
54
|
+
var COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111";
|
|
55
|
+
var STAKE_PROGRAM_ID = "Stake11111111111111111111111111111111111111";
|
|
56
|
+
var STAKE_POOL_PROGRAM_ID = "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy";
|
|
57
|
+
var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
58
|
+
var TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
|
|
59
|
+
var ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
60
|
+
var SPL_MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
|
|
61
|
+
var MEMO_V1_PROGRAM_ID = "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo";
|
|
62
|
+
var JUPITER_V6_PROGRAM_ID = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4";
|
|
63
|
+
var JUPITER_V4_PROGRAM_ID = "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB";
|
|
64
|
+
var JUPITER_ORDER_ENGINE_PROGRAM_ID = "61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH";
|
|
65
|
+
var RAYDIUM_PROGRAM_ID = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
|
|
66
|
+
var RAYDIUM_CLMM_PROGRAM_ID = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK";
|
|
67
|
+
var RAYDIUM_CPMM_PROGRAM_ID = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C";
|
|
68
|
+
var RAYDIUM_STABLE_PROGRAM_ID = "5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h";
|
|
69
|
+
var ORCA_WHIRLPOOL_PROGRAM_ID = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
|
|
70
|
+
var ORCA_TOKEN_SWAP_V1_PROGRAM_ID = "9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP";
|
|
71
|
+
var OPENBOOK_V2_PROGRAM_ID = "opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb";
|
|
72
|
+
var PHOENIX_PROGRAM_ID = "PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY";
|
|
73
|
+
var SABER_STABLE_SWAP_PROGRAM_ID = "SSwpkEEcbUqx4vtoEByFjSkhKdCT862DNVb52nZg1UZ";
|
|
74
|
+
var MERCURIAL_STABLE_SWAP_PROGRAM_ID = "MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky";
|
|
75
|
+
var METEORA_DLMM_PROGRAM_ID = "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo";
|
|
76
|
+
var METEORA_POOLS_PROGRAM_ID = "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB";
|
|
77
|
+
var PUMPFUN_AMM_PROGRAM_ID = "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA";
|
|
78
|
+
var PUMPFUN_BONDING_CURVE_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
|
|
79
|
+
var LIFINITY_V2_PROGRAM_ID = "2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c";
|
|
80
|
+
var METAPLEX_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
|
|
81
|
+
var CANDY_MACHINE_V3_PROGRAM_ID = "CndyV3LdqHUfDLmE5naZjVN8rBZz4tqhdefbAnjHG3JR";
|
|
82
|
+
var CANDY_GUARD_PROGRAM_ID = "Guard1JwRhJkVH6XZhzoYxeBVQe872VH6QggF4BWmS9g";
|
|
83
|
+
var BUBBLEGUM_PROGRAM_ID = "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY";
|
|
84
|
+
var MAGIC_EDEN_CANDY_MACHINE_ID = "CMZYPASGWeTz7RNGHaRJfCq2XQ5pYK6nDvVQxzkH51zb";
|
|
85
|
+
var WORMHOLE_PROGRAM_ID = "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth";
|
|
86
|
+
var WORMHOLE_TOKEN_BRIDGE_ID = "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb";
|
|
87
|
+
var DEGODS_BRIDGE_PROGRAM_ID = "35iLrpYNNR9ygHLcvE1xKFHbHq6paHthrF6wSovdWgGu";
|
|
88
|
+
var DEBRIDGE_PROGRAM_ID = "DEbrdGj3HsRsAzx6uH4MKyREKxVAfBydijLUF3ygsFfh";
|
|
89
|
+
var ALLBRIDGE_PROGRAM_ID = "BrdgN2RPzEMWF96ZbnnJaUtQDQx7VRXYaHHbYCBvceWB";
|
|
90
|
+
var PAYAI_FACILITATOR = "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4";
|
|
91
|
+
var KNOWN_FACILITATORS = [PAYAI_FACILITATOR];
|
|
92
|
+
function detectFacilitator(accountKeys) {
|
|
93
|
+
for (const facilitator of KNOWN_FACILITATORS) {
|
|
94
|
+
if (accountKeys.includes(facilitator)) {
|
|
95
|
+
if (facilitator === PAYAI_FACILITATOR) {
|
|
96
|
+
return "payai";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
52
103
|
// ../solana/src/fetcher/balances.ts
|
|
53
104
|
async function fetchWalletBalance(rpc, walletAddress, tokenMints) {
|
|
54
105
|
const balanceResponse = await rpc.getBalance(walletAddress).send();
|
|
@@ -66,12 +117,17 @@ async function fetchWalletBalance(rpc, walletAddress, tokenMints) {
|
|
|
66
117
|
}
|
|
67
118
|
async function fetchTokenAccounts(rpc, walletAddress) {
|
|
68
119
|
const accountsMap = /* @__PURE__ */ new Map();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
120
|
+
const tokenPrograms = [TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID];
|
|
121
|
+
const responses = await Promise.all(
|
|
122
|
+
tokenPrograms.map(
|
|
123
|
+
(programId) => rpc.getTokenAccountsByOwner(
|
|
124
|
+
walletAddress,
|
|
125
|
+
{ programId: address(programId) },
|
|
126
|
+
{ encoding: "jsonParsed" }
|
|
127
|
+
).send().catch(() => ({ value: [] }))
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
for (const response of responses) {
|
|
75
131
|
for (const account of response.value) {
|
|
76
132
|
const parsedInfo = account.account.data.parsed.info;
|
|
77
133
|
const mint = parsedInfo.mint;
|
|
@@ -88,8 +144,6 @@ async function fetchTokenAccounts(rpc, walletAddress) {
|
|
|
88
144
|
symbol
|
|
89
145
|
});
|
|
90
146
|
}
|
|
91
|
-
} catch (error) {
|
|
92
|
-
console.error("Error fetching token accounts:", error);
|
|
93
147
|
}
|
|
94
148
|
return accountsMap;
|
|
95
149
|
}
|
|
@@ -133,44 +187,6 @@ function extractProgramIds(transaction) {
|
|
|
133
187
|
return Array.from(programIds);
|
|
134
188
|
}
|
|
135
189
|
|
|
136
|
-
// ../solana/src/constants/program-ids.ts
|
|
137
|
-
var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
|
|
138
|
-
var COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111";
|
|
139
|
-
var STAKE_PROGRAM_ID = "Stake11111111111111111111111111111111111111";
|
|
140
|
-
var STAKE_POOL_PROGRAM_ID = "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy";
|
|
141
|
-
var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
142
|
-
var ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
143
|
-
var SPL_MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
|
|
144
|
-
var MEMO_V1_PROGRAM_ID = "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo";
|
|
145
|
-
var JUPITER_V6_PROGRAM_ID = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4";
|
|
146
|
-
var JUPITER_V4_PROGRAM_ID = "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB";
|
|
147
|
-
var RAYDIUM_PROGRAM_ID = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
|
|
148
|
-
var ORCA_WHIRLPOOL_PROGRAM_ID = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
|
|
149
|
-
var METAPLEX_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
|
|
150
|
-
var CANDY_MACHINE_V3_PROGRAM_ID = "CndyV3LdqHUfDLmE5naZjVN8rBZz4tqhdefbAnjHG3JR";
|
|
151
|
-
var CANDY_GUARD_PROGRAM_ID = "Guard1JwRhJkVH6XZhzoYxeBVQe872VH6QggF4BWmS9g";
|
|
152
|
-
var BUBBLEGUM_PROGRAM_ID = "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY";
|
|
153
|
-
var MAGIC_EDEN_CANDY_MACHINE_ID = "CMZYPASGWeTz7RNGHaRJfCq2XQ5pYK6nDvVQxzkH51zb";
|
|
154
|
-
var WORMHOLE_PROGRAM_ID = "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth";
|
|
155
|
-
var WORMHOLE_TOKEN_BRIDGE_ID = "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb";
|
|
156
|
-
var DEGODS_BRIDGE_PROGRAM_ID = "35iLrpYNNR9ygHLcvE1xKFHbHq6paHthrF6wSovdWgGu";
|
|
157
|
-
var DEBRIDGE_PROGRAM_ID = "DEbrdGj3HsRsAzx6uH4MKyREKxVAfBydijLUF3ygsFfh";
|
|
158
|
-
var ALLBRIDGE_PROGRAM_ID = "BrdgN2RPzEMWF96ZbnnJaUtQDQx7VRXYaHHbYCBvceWB";
|
|
159
|
-
var PAYAI_FACILITATOR = "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4";
|
|
160
|
-
var KNOWN_FACILITATORS = [
|
|
161
|
-
PAYAI_FACILITATOR
|
|
162
|
-
];
|
|
163
|
-
function detectFacilitator(accountKeys) {
|
|
164
|
-
for (const facilitator of KNOWN_FACILITATORS) {
|
|
165
|
-
if (accountKeys.includes(facilitator)) {
|
|
166
|
-
if (facilitator === PAYAI_FACILITATOR) {
|
|
167
|
-
return "payai";
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
190
|
// ../../node_modules/.bun/base-x@5.0.1/node_modules/base-x/src/esm/index.js
|
|
175
191
|
function base(ALPHABET2) {
|
|
176
192
|
if (ALPHABET2.length >= 255) {
|
|
@@ -380,6 +396,189 @@ function isSolanaPayTransaction(programIds, memo) {
|
|
|
380
396
|
return hasMemoProgram && memo !== null && memo !== void 0;
|
|
381
397
|
}
|
|
382
398
|
|
|
399
|
+
// ../solana/src/rpc/retry.ts
|
|
400
|
+
var DEFAULT_CONFIG = {
|
|
401
|
+
maxAttempts: 3,
|
|
402
|
+
baseDelayMs: 1e3,
|
|
403
|
+
maxDelayMs: 1e4
|
|
404
|
+
};
|
|
405
|
+
function isRetryableError(error) {
|
|
406
|
+
if (error instanceof Error) {
|
|
407
|
+
const message = error.message.toLowerCase();
|
|
408
|
+
return message.includes("timeout") || message.includes("econnreset") || message.includes("econnrefused") || message.includes("socket hang up") || message.includes("network") || message.includes("429") || message.includes("rate limit") || message.includes("too many requests") || message.includes("503") || message.includes("502") || message.includes("504");
|
|
409
|
+
}
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
function calculateDelay(attempt, baseDelayMs, maxDelayMs) {
|
|
413
|
+
const exponentialDelay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
414
|
+
const jitter = Math.random() * 0.3 * exponentialDelay;
|
|
415
|
+
return Math.min(exponentialDelay + jitter, maxDelayMs);
|
|
416
|
+
}
|
|
417
|
+
function sleep(ms) {
|
|
418
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
419
|
+
}
|
|
420
|
+
async function withRetry(fn, config = {}) {
|
|
421
|
+
const { maxAttempts, baseDelayMs, maxDelayMs } = {
|
|
422
|
+
...DEFAULT_CONFIG,
|
|
423
|
+
...config
|
|
424
|
+
};
|
|
425
|
+
let lastError;
|
|
426
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
427
|
+
try {
|
|
428
|
+
return await fn();
|
|
429
|
+
} catch (error) {
|
|
430
|
+
lastError = error;
|
|
431
|
+
const isLastAttempt = attempt === maxAttempts;
|
|
432
|
+
const shouldRetry = !isLastAttempt && isRetryableError(error);
|
|
433
|
+
if (!shouldRetry) {
|
|
434
|
+
throw error;
|
|
435
|
+
}
|
|
436
|
+
const delay = calculateDelay(attempt, baseDelayMs, maxDelayMs);
|
|
437
|
+
await sleep(delay);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
throw lastError;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ../../node_modules/.bun/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
|
|
444
|
+
var Node = class {
|
|
445
|
+
value;
|
|
446
|
+
next;
|
|
447
|
+
constructor(value) {
|
|
448
|
+
this.value = value;
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
var Queue = class {
|
|
452
|
+
#head;
|
|
453
|
+
#tail;
|
|
454
|
+
#size;
|
|
455
|
+
constructor() {
|
|
456
|
+
this.clear();
|
|
457
|
+
}
|
|
458
|
+
enqueue(value) {
|
|
459
|
+
const node = new Node(value);
|
|
460
|
+
if (this.#head) {
|
|
461
|
+
this.#tail.next = node;
|
|
462
|
+
this.#tail = node;
|
|
463
|
+
} else {
|
|
464
|
+
this.#head = node;
|
|
465
|
+
this.#tail = node;
|
|
466
|
+
}
|
|
467
|
+
this.#size++;
|
|
468
|
+
}
|
|
469
|
+
dequeue() {
|
|
470
|
+
const current = this.#head;
|
|
471
|
+
if (!current) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
this.#head = this.#head.next;
|
|
475
|
+
this.#size--;
|
|
476
|
+
if (!this.#head) {
|
|
477
|
+
this.#tail = void 0;
|
|
478
|
+
}
|
|
479
|
+
return current.value;
|
|
480
|
+
}
|
|
481
|
+
peek() {
|
|
482
|
+
if (!this.#head) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
return this.#head.value;
|
|
486
|
+
}
|
|
487
|
+
clear() {
|
|
488
|
+
this.#head = void 0;
|
|
489
|
+
this.#tail = void 0;
|
|
490
|
+
this.#size = 0;
|
|
491
|
+
}
|
|
492
|
+
get size() {
|
|
493
|
+
return this.#size;
|
|
494
|
+
}
|
|
495
|
+
*[Symbol.iterator]() {
|
|
496
|
+
let current = this.#head;
|
|
497
|
+
while (current) {
|
|
498
|
+
yield current.value;
|
|
499
|
+
current = current.next;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
*drain() {
|
|
503
|
+
while (this.#head) {
|
|
504
|
+
yield this.dequeue();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// ../../node_modules/.bun/p-limit@6.2.0/node_modules/p-limit/index.js
|
|
510
|
+
function pLimit(concurrency) {
|
|
511
|
+
validateConcurrency(concurrency);
|
|
512
|
+
const queue = new Queue();
|
|
513
|
+
let activeCount = 0;
|
|
514
|
+
const resumeNext = () => {
|
|
515
|
+
if (activeCount < concurrency && queue.size > 0) {
|
|
516
|
+
queue.dequeue()();
|
|
517
|
+
activeCount++;
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
const next = () => {
|
|
521
|
+
activeCount--;
|
|
522
|
+
resumeNext();
|
|
523
|
+
};
|
|
524
|
+
const run = async (function_, resolve, arguments_) => {
|
|
525
|
+
const result = (async () => function_(...arguments_))();
|
|
526
|
+
resolve(result);
|
|
527
|
+
try {
|
|
528
|
+
await result;
|
|
529
|
+
} catch {
|
|
530
|
+
}
|
|
531
|
+
next();
|
|
532
|
+
};
|
|
533
|
+
const enqueue = (function_, resolve, arguments_) => {
|
|
534
|
+
new Promise((internalResolve) => {
|
|
535
|
+
queue.enqueue(internalResolve);
|
|
536
|
+
}).then(
|
|
537
|
+
run.bind(void 0, function_, resolve, arguments_)
|
|
538
|
+
);
|
|
539
|
+
(async () => {
|
|
540
|
+
await Promise.resolve();
|
|
541
|
+
if (activeCount < concurrency) {
|
|
542
|
+
resumeNext();
|
|
543
|
+
}
|
|
544
|
+
})();
|
|
545
|
+
};
|
|
546
|
+
const generator = (function_, ...arguments_) => new Promise((resolve) => {
|
|
547
|
+
enqueue(function_, resolve, arguments_);
|
|
548
|
+
});
|
|
549
|
+
Object.defineProperties(generator, {
|
|
550
|
+
activeCount: {
|
|
551
|
+
get: () => activeCount
|
|
552
|
+
},
|
|
553
|
+
pendingCount: {
|
|
554
|
+
get: () => queue.size
|
|
555
|
+
},
|
|
556
|
+
clearQueue: {
|
|
557
|
+
value() {
|
|
558
|
+
queue.clear();
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
concurrency: {
|
|
562
|
+
get: () => concurrency,
|
|
563
|
+
set(newConcurrency) {
|
|
564
|
+
validateConcurrency(newConcurrency);
|
|
565
|
+
concurrency = newConcurrency;
|
|
566
|
+
queueMicrotask(() => {
|
|
567
|
+
while (activeCount < concurrency && queue.size > 0) {
|
|
568
|
+
resumeNext();
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
return generator;
|
|
575
|
+
}
|
|
576
|
+
function validateConcurrency(concurrency) {
|
|
577
|
+
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
|
|
578
|
+
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
383
582
|
// ../solana/src/fetcher/transactions.ts
|
|
384
583
|
async function fetchWalletSignatures(rpc, walletAddress, config = {}) {
|
|
385
584
|
const { limit = 100, before, until } = config;
|
|
@@ -398,12 +597,16 @@ async function fetchWalletSignatures(rpc, walletAddress, config = {}) {
|
|
|
398
597
|
memo: sig.memo || null
|
|
399
598
|
}));
|
|
400
599
|
}
|
|
401
|
-
async function fetchTransaction(rpc, signature2,
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
600
|
+
async function fetchTransaction(rpc, signature2, options = {}) {
|
|
601
|
+
const { commitment = "confirmed", retry } = options;
|
|
602
|
+
const response = await withRetry(
|
|
603
|
+
() => rpc.getTransaction(signature2, {
|
|
604
|
+
commitment,
|
|
605
|
+
maxSupportedTransactionVersion: 0,
|
|
606
|
+
encoding: "json"
|
|
607
|
+
}).send(),
|
|
608
|
+
retry
|
|
609
|
+
);
|
|
407
610
|
if (!response) {
|
|
408
611
|
return null;
|
|
409
612
|
}
|
|
@@ -450,10 +653,29 @@ async function fetchTransaction(rpc, signature2, commitment = "confirmed") {
|
|
|
450
653
|
memo
|
|
451
654
|
};
|
|
452
655
|
}
|
|
453
|
-
async function fetchTransactionsBatch(rpc, signatures,
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
656
|
+
async function fetchTransactionsBatch(rpc, signatures, options = {}) {
|
|
657
|
+
const {
|
|
658
|
+
commitment = "confirmed",
|
|
659
|
+
concurrency = 10,
|
|
660
|
+
retry,
|
|
661
|
+
onFetchError
|
|
662
|
+
} = options;
|
|
663
|
+
if (signatures.length === 0) {
|
|
664
|
+
return [];
|
|
665
|
+
}
|
|
666
|
+
const limit = pLimit(concurrency);
|
|
667
|
+
const safeFetch = async (sig) => {
|
|
668
|
+
try {
|
|
669
|
+
return await fetchTransaction(rpc, sig, { commitment, retry });
|
|
670
|
+
} catch (error) {
|
|
671
|
+
onFetchError?.(
|
|
672
|
+
sig,
|
|
673
|
+
error instanceof Error ? error : new Error(String(error))
|
|
674
|
+
);
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
const promises = signatures.map((sig) => limit(() => safeFetch(sig)));
|
|
457
679
|
const results = await Promise.all(promises);
|
|
458
680
|
return results.filter((tx) => tx !== null);
|
|
459
681
|
}
|
|
@@ -598,7 +820,7 @@ function transactionToLegs(tx) {
|
|
|
598
820
|
amountRaw: change.change.toString().replace("-", ""),
|
|
599
821
|
amountUi: Math.abs(change.changeUi)
|
|
600
822
|
},
|
|
601
|
-
role: determineSolRole(change, tx
|
|
823
|
+
role: determineSolRole(change, tx)
|
|
602
824
|
});
|
|
603
825
|
}
|
|
604
826
|
const networkFee = totalSolDebits - totalSolCredits;
|
|
@@ -655,13 +877,8 @@ function transactionToLegs(tx) {
|
|
|
655
877
|
}
|
|
656
878
|
return legs;
|
|
657
879
|
}
|
|
658
|
-
function determineSolRole(change, tx,
|
|
659
|
-
const isFeePayer = feePayer ? change.address.toLowerCase() === feePayer : false;
|
|
880
|
+
function determineSolRole(change, tx, _feePayer) {
|
|
660
881
|
const isPositive = change.change > 0n;
|
|
661
|
-
const amountSol = Math.abs(change.changeUi);
|
|
662
|
-
if (isFeePayer && !isPositive && amountSol < 0.01) {
|
|
663
|
-
return "fee";
|
|
664
|
-
}
|
|
665
882
|
if (isPositive) {
|
|
666
883
|
if (tx.protocol?.id === "stake") {
|
|
667
884
|
return "reward";
|
|
@@ -721,6 +938,7 @@ var TransferClassifier = class {
|
|
|
721
938
|
|
|
722
939
|
// ../classification/src/protocols/detector.ts
|
|
723
940
|
var KNOWN_PROGRAMS = {
|
|
941
|
+
// Jupiter aggregator
|
|
724
942
|
[JUPITER_V6_PROGRAM_ID]: {
|
|
725
943
|
id: "jupiter",
|
|
726
944
|
name: "Jupiter"
|
|
@@ -729,10 +947,19 @@ var KNOWN_PROGRAMS = {
|
|
|
729
947
|
id: "jupiter-v4",
|
|
730
948
|
name: "Jupiter V4"
|
|
731
949
|
},
|
|
950
|
+
[JUPITER_ORDER_ENGINE_PROGRAM_ID]: {
|
|
951
|
+
id: "jupiter-limit-order",
|
|
952
|
+
name: "Jupiter Limit Order"
|
|
953
|
+
},
|
|
954
|
+
// Core token programs
|
|
732
955
|
[TOKEN_PROGRAM_ID]: {
|
|
733
956
|
id: "spl-token",
|
|
734
957
|
name: "Token Program"
|
|
735
958
|
},
|
|
959
|
+
[TOKEN_2022_PROGRAM_ID]: {
|
|
960
|
+
id: "token-2022",
|
|
961
|
+
name: "Token-2022 Program"
|
|
962
|
+
},
|
|
736
963
|
[SYSTEM_PROGRAM_ID]: {
|
|
737
964
|
id: "system",
|
|
738
965
|
name: "System Program"
|
|
@@ -745,25 +972,86 @@ var KNOWN_PROGRAMS = {
|
|
|
745
972
|
id: "associated-token",
|
|
746
973
|
name: "Associated Token Program"
|
|
747
974
|
},
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
975
|
+
// Memo programs
|
|
976
|
+
[SPL_MEMO_PROGRAM_ID]: {
|
|
977
|
+
id: "memo",
|
|
978
|
+
name: "Memo Program"
|
|
751
979
|
},
|
|
752
|
-
[
|
|
753
|
-
id: "
|
|
754
|
-
name: "
|
|
980
|
+
[MEMO_V1_PROGRAM_ID]: {
|
|
981
|
+
id: "memo-v1",
|
|
982
|
+
name: "Memo Program V1"
|
|
755
983
|
},
|
|
984
|
+
// Raydium AMMs
|
|
756
985
|
[RAYDIUM_PROGRAM_ID]: {
|
|
757
986
|
id: "raydium",
|
|
758
987
|
name: "Raydium"
|
|
759
988
|
},
|
|
760
|
-
[
|
|
761
|
-
id: "
|
|
762
|
-
name: "
|
|
989
|
+
[RAYDIUM_CLMM_PROGRAM_ID]: {
|
|
990
|
+
id: "raydium-clmm",
|
|
991
|
+
name: "Raydium CLMM"
|
|
763
992
|
},
|
|
764
|
-
[
|
|
765
|
-
id: "
|
|
766
|
-
name: "
|
|
993
|
+
[RAYDIUM_CPMM_PROGRAM_ID]: {
|
|
994
|
+
id: "raydium-cpmm",
|
|
995
|
+
name: "Raydium CPMM"
|
|
996
|
+
},
|
|
997
|
+
[RAYDIUM_STABLE_PROGRAM_ID]: {
|
|
998
|
+
id: "raydium-stable",
|
|
999
|
+
name: "Raydium Stable"
|
|
1000
|
+
},
|
|
1001
|
+
// Orca
|
|
1002
|
+
[ORCA_WHIRLPOOL_PROGRAM_ID]: {
|
|
1003
|
+
id: "orca-whirlpool",
|
|
1004
|
+
name: "Orca Whirlpool"
|
|
1005
|
+
},
|
|
1006
|
+
[ORCA_TOKEN_SWAP_V1_PROGRAM_ID]: {
|
|
1007
|
+
id: "orca-v1",
|
|
1008
|
+
name: "Orca Token Swap V1"
|
|
1009
|
+
},
|
|
1010
|
+
// CLOBs (Central Limit Order Books)
|
|
1011
|
+
[OPENBOOK_V2_PROGRAM_ID]: {
|
|
1012
|
+
id: "openbook",
|
|
1013
|
+
name: "OpenBook"
|
|
1014
|
+
},
|
|
1015
|
+
[PHOENIX_PROGRAM_ID]: {
|
|
1016
|
+
id: "phoenix",
|
|
1017
|
+
name: "Phoenix"
|
|
1018
|
+
},
|
|
1019
|
+
// Stableswap protocols
|
|
1020
|
+
[SABER_STABLE_SWAP_PROGRAM_ID]: {
|
|
1021
|
+
id: "saber",
|
|
1022
|
+
name: "Saber"
|
|
1023
|
+
},
|
|
1024
|
+
[MERCURIAL_STABLE_SWAP_PROGRAM_ID]: {
|
|
1025
|
+
id: "mercurial",
|
|
1026
|
+
name: "Mercurial"
|
|
1027
|
+
},
|
|
1028
|
+
// Meteora
|
|
1029
|
+
[METEORA_DLMM_PROGRAM_ID]: {
|
|
1030
|
+
id: "meteora-dlmm",
|
|
1031
|
+
name: "Meteora DLMM"
|
|
1032
|
+
},
|
|
1033
|
+
[METEORA_POOLS_PROGRAM_ID]: {
|
|
1034
|
+
id: "meteora-pools",
|
|
1035
|
+
name: "Meteora Pools"
|
|
1036
|
+
},
|
|
1037
|
+
// Pump.fun
|
|
1038
|
+
[PUMPFUN_AMM_PROGRAM_ID]: {
|
|
1039
|
+
id: "pumpfun",
|
|
1040
|
+
name: "Pump.fun"
|
|
1041
|
+
},
|
|
1042
|
+
[PUMPFUN_BONDING_CURVE_PROGRAM_ID]: {
|
|
1043
|
+
id: "pumpfun-bonding",
|
|
1044
|
+
name: "Pump.fun Bonding Curve"
|
|
1045
|
+
},
|
|
1046
|
+
// Lifinity
|
|
1047
|
+
[LIFINITY_V2_PROGRAM_ID]: {
|
|
1048
|
+
id: "lifinity",
|
|
1049
|
+
name: "Lifinity"
|
|
1050
|
+
},
|
|
1051
|
+
// NFT programs
|
|
1052
|
+
[METAPLEX_PROGRAM_ID]: {
|
|
1053
|
+
id: "metaplex",
|
|
1054
|
+
name: "Metaplex"
|
|
767
1055
|
},
|
|
768
1056
|
[CANDY_GUARD_PROGRAM_ID]: {
|
|
769
1057
|
id: "candy-guard",
|
|
@@ -781,6 +1069,16 @@ var KNOWN_PROGRAMS = {
|
|
|
781
1069
|
id: "magic-eden-candy-machine",
|
|
782
1070
|
name: "Nft Candy Machine Program (Magic Eden)"
|
|
783
1071
|
},
|
|
1072
|
+
// Staking programs
|
|
1073
|
+
[STAKE_PROGRAM_ID]: {
|
|
1074
|
+
id: "stake",
|
|
1075
|
+
name: "Stake Program"
|
|
1076
|
+
},
|
|
1077
|
+
[STAKE_POOL_PROGRAM_ID]: {
|
|
1078
|
+
id: "stake-pool",
|
|
1079
|
+
name: "Stake Pool Program"
|
|
1080
|
+
},
|
|
1081
|
+
// Bridge programs
|
|
784
1082
|
[WORMHOLE_PROGRAM_ID]: {
|
|
785
1083
|
id: "wormhole",
|
|
786
1084
|
name: "Wormhole"
|
|
@@ -803,27 +1101,79 @@ var KNOWN_PROGRAMS = {
|
|
|
803
1101
|
}
|
|
804
1102
|
};
|
|
805
1103
|
var PRIORITY_ORDER = [
|
|
1104
|
+
// Bridge protocols (highest priority - cross-chain operations)
|
|
806
1105
|
"wormhole",
|
|
807
1106
|
"wormhole-token-bridge",
|
|
808
1107
|
"degods-bridge",
|
|
809
1108
|
"debridge",
|
|
810
1109
|
"allbridge",
|
|
1110
|
+
// DEX aggregators (route through multiple DEXes)
|
|
811
1111
|
"jupiter",
|
|
812
1112
|
"jupiter-v4",
|
|
1113
|
+
"jupiter-limit-order",
|
|
1114
|
+
// AMMs and DEXes
|
|
813
1115
|
"raydium",
|
|
1116
|
+
"raydium-clmm",
|
|
1117
|
+
"raydium-cpmm",
|
|
1118
|
+
"raydium-stable",
|
|
814
1119
|
"orca-whirlpool",
|
|
1120
|
+
"orca-v1",
|
|
1121
|
+
"meteora-dlmm",
|
|
1122
|
+
"meteora-pools",
|
|
1123
|
+
"lifinity",
|
|
1124
|
+
"pumpfun",
|
|
1125
|
+
"pumpfun-bonding",
|
|
1126
|
+
// CLOBs
|
|
1127
|
+
"openbook",
|
|
1128
|
+
"phoenix",
|
|
1129
|
+
// Stableswap
|
|
1130
|
+
"saber",
|
|
1131
|
+
"mercurial",
|
|
1132
|
+
// NFT
|
|
815
1133
|
"metaplex",
|
|
1134
|
+
"candy-guard",
|
|
1135
|
+
"candy-machine-v3",
|
|
1136
|
+
"bubblegum",
|
|
1137
|
+
"magic-eden-candy-machine",
|
|
1138
|
+
// Staking
|
|
816
1139
|
"stake",
|
|
1140
|
+
"stake-pool",
|
|
1141
|
+
// Infrastructure (lowest priority)
|
|
1142
|
+
"memo",
|
|
1143
|
+
"memo-v1",
|
|
817
1144
|
"associated-token",
|
|
818
1145
|
"spl-token",
|
|
1146
|
+
"token-2022",
|
|
819
1147
|
"compute-budget",
|
|
820
1148
|
"system"
|
|
821
1149
|
];
|
|
822
1150
|
var DEX_PROTOCOL_IDS2 = /* @__PURE__ */ new Set([
|
|
1151
|
+
// Jupiter aggregator
|
|
823
1152
|
"jupiter",
|
|
824
1153
|
"jupiter-v4",
|
|
1154
|
+
"jupiter-limit-order",
|
|
1155
|
+
// Raydium AMMs
|
|
825
1156
|
"raydium",
|
|
826
|
-
"
|
|
1157
|
+
"raydium-clmm",
|
|
1158
|
+
"raydium-cpmm",
|
|
1159
|
+
"raydium-stable",
|
|
1160
|
+
// Orca
|
|
1161
|
+
"orca-whirlpool",
|
|
1162
|
+
"orca-v1",
|
|
1163
|
+
// CLOBs
|
|
1164
|
+
"openbook",
|
|
1165
|
+
"phoenix",
|
|
1166
|
+
// Stableswap
|
|
1167
|
+
"saber",
|
|
1168
|
+
"mercurial",
|
|
1169
|
+
// Meteora
|
|
1170
|
+
"meteora-dlmm",
|
|
1171
|
+
"meteora-pools",
|
|
1172
|
+
// Pump.fun
|
|
1173
|
+
"pumpfun",
|
|
1174
|
+
"pumpfun-bonding",
|
|
1175
|
+
// Lifinity
|
|
1176
|
+
"lifinity"
|
|
827
1177
|
]);
|
|
828
1178
|
var NFT_MINT_PROTOCOL_IDS = /* @__PURE__ */ new Set([
|
|
829
1179
|
"metaplex",
|
|
@@ -872,41 +1222,71 @@ function detectProtocol(programIds) {
|
|
|
872
1222
|
}
|
|
873
1223
|
|
|
874
1224
|
// ../classification/src/classifiers/swap-classifier.ts
|
|
1225
|
+
function findSwapPair(tokensOut, tokensIn) {
|
|
1226
|
+
let bestPair = null;
|
|
1227
|
+
let bestScore = 0;
|
|
1228
|
+
for (const out of tokensOut) {
|
|
1229
|
+
for (const inLeg of tokensIn) {
|
|
1230
|
+
if (out.amount.token.symbol !== inLeg.amount.token.symbol) {
|
|
1231
|
+
const score = Math.max(out.amount.amountUi, inLeg.amount.amountUi);
|
|
1232
|
+
if (score > bestScore) {
|
|
1233
|
+
bestScore = score;
|
|
1234
|
+
bestPair = { initiatorOut: out, initiatorIn: inLeg };
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
return bestPair;
|
|
1240
|
+
}
|
|
875
1241
|
var SwapClassifier = class {
|
|
876
1242
|
name = "swap";
|
|
877
1243
|
priority = 80;
|
|
878
1244
|
classify(context) {
|
|
879
|
-
const { legs, tx } = context;
|
|
880
|
-
const
|
|
881
|
-
(leg) => leg.role === "fee" && leg.side === "debit"
|
|
882
|
-
);
|
|
883
|
-
const initiator = feeLeg?.accountId.replace("external:", "") ?? null;
|
|
1245
|
+
const { legs, tx, walletAddress } = context;
|
|
1246
|
+
const initiator = tx.accountKeys?.[0] ?? null;
|
|
884
1247
|
if (!initiator) {
|
|
885
1248
|
return null;
|
|
886
1249
|
}
|
|
887
1250
|
const initiatorAccountId = `external:${initiator}`;
|
|
888
|
-
const
|
|
1251
|
+
const initiatorTokensOut = legs.filter(
|
|
889
1252
|
(leg) => leg.accountId === initiatorAccountId && leg.side === "debit" && (leg.role === "sent" || leg.role === "protocol_deposit")
|
|
890
1253
|
);
|
|
891
|
-
const
|
|
1254
|
+
const initiatorTokensIn = legs.filter(
|
|
892
1255
|
(leg) => leg.accountId === initiatorAccountId && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
|
|
893
1256
|
);
|
|
894
|
-
if (
|
|
1257
|
+
if (initiatorTokensOut.length === 0 || initiatorTokensIn.length === 0) {
|
|
895
1258
|
return null;
|
|
896
1259
|
}
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
if (tokenOut.amount.token.symbol === tokenIn.amount.token.symbol) {
|
|
1260
|
+
const swapPair = findSwapPair(initiatorTokensOut, initiatorTokensIn);
|
|
1261
|
+
if (!swapPair) {
|
|
900
1262
|
return null;
|
|
901
1263
|
}
|
|
1264
|
+
const { initiatorOut, initiatorIn } = swapPair;
|
|
1265
|
+
let tokenOut = initiatorOut;
|
|
1266
|
+
let tokenIn = initiatorIn;
|
|
1267
|
+
let perspectiveWallet = initiator;
|
|
1268
|
+
if (walletAddress) {
|
|
1269
|
+
const walletAccountId = `external:${walletAddress}`;
|
|
1270
|
+
const walletOut = legs.find(
|
|
1271
|
+
(leg) => leg.accountId === walletAccountId && leg.side === "debit" && (leg.role === "sent" || leg.role === "protocol_deposit")
|
|
1272
|
+
);
|
|
1273
|
+
const walletIn = legs.find(
|
|
1274
|
+
(leg) => leg.accountId === walletAccountId && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
|
|
1275
|
+
);
|
|
1276
|
+
if (walletOut && walletIn && walletOut.amount.token.symbol !== walletIn.amount.token.symbol) {
|
|
1277
|
+
tokenOut = walletOut;
|
|
1278
|
+
tokenIn = walletIn;
|
|
1279
|
+
perspectiveWallet = walletAddress;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
902
1282
|
const hasDexProtocol = isDexProtocolById(tx.protocol?.id);
|
|
903
1283
|
const confidence = hasDexProtocol ? 0.95 : 0.75;
|
|
904
1284
|
return {
|
|
905
1285
|
primaryType: "swap",
|
|
906
1286
|
primaryAmount: tokenOut.amount,
|
|
907
1287
|
secondaryAmount: tokenIn.amount,
|
|
908
|
-
sender:
|
|
909
|
-
receiver:
|
|
1288
|
+
sender: perspectiveWallet,
|
|
1289
|
+
receiver: perspectiveWallet,
|
|
910
1290
|
counterparty: null,
|
|
911
1291
|
confidence,
|
|
912
1292
|
isRelevant: true,
|
|
@@ -1248,8 +1628,8 @@ var ClassificationService = class {
|
|
|
1248
1628
|
this.classifiers.push(classifier);
|
|
1249
1629
|
this.classifiers.sort((a, b) => b.priority - a.priority);
|
|
1250
1630
|
}
|
|
1251
|
-
classify(legs, tx) {
|
|
1252
|
-
const context = { legs, tx };
|
|
1631
|
+
classify(legs, tx, walletAddress) {
|
|
1632
|
+
const context = { legs, tx, walletAddress };
|
|
1253
1633
|
for (const classifier of this.classifiers) {
|
|
1254
1634
|
const result = classifier.classify(context);
|
|
1255
1635
|
if (result && result.isRelevant) {
|
|
@@ -1270,19 +1650,19 @@ var ClassificationService = class {
|
|
|
1270
1650
|
}
|
|
1271
1651
|
};
|
|
1272
1652
|
var classificationService = new ClassificationService();
|
|
1273
|
-
function classifyTransaction(legs, tx) {
|
|
1274
|
-
return classificationService.classify(legs, tx);
|
|
1653
|
+
function classifyTransaction(legs, tx, walletAddress) {
|
|
1654
|
+
return classificationService.classify(legs, tx, walletAddress);
|
|
1275
1655
|
}
|
|
1276
1656
|
|
|
1277
1657
|
// ../domain/src/tx/spam-filter.ts
|
|
1278
|
-
var
|
|
1658
|
+
var DEFAULT_CONFIG2 = {
|
|
1279
1659
|
minSolAmount: 1e-3,
|
|
1280
1660
|
minTokenAmountUsd: 0.01,
|
|
1281
1661
|
minConfidence: 0.5,
|
|
1282
1662
|
allowFailed: false
|
|
1283
1663
|
};
|
|
1284
1664
|
function isSpamTransaction(tx, classification, config = {}) {
|
|
1285
|
-
const cfg = { ...
|
|
1665
|
+
const cfg = { ...DEFAULT_CONFIG2, ...config };
|
|
1286
1666
|
if (!cfg.allowFailed && tx.err) {
|
|
1287
1667
|
return true;
|
|
1288
1668
|
}
|
|
@@ -1448,10 +1828,11 @@ function createIndexer(options) {
|
|
|
1448
1828
|
client.rpc,
|
|
1449
1829
|
signatureObjects
|
|
1450
1830
|
);
|
|
1831
|
+
const walletAddressStr2 = walletAddress.toString();
|
|
1451
1832
|
const classified = transactions.map((tx) => {
|
|
1452
1833
|
tx.protocol = detectProtocol(tx.programIds);
|
|
1453
1834
|
const legs = transactionToLegs(tx);
|
|
1454
|
-
const classification = classifyTransaction(legs, tx);
|
|
1835
|
+
const classification = classifyTransaction(legs, tx, walletAddressStr2);
|
|
1455
1836
|
return { tx, classification, legs };
|
|
1456
1837
|
});
|
|
1457
1838
|
return enrichBatch(classified);
|
|
@@ -1460,6 +1841,7 @@ function createIndexer(options) {
|
|
|
1460
1841
|
let currentBefore = before;
|
|
1461
1842
|
const MAX_ITERATIONS = 10;
|
|
1462
1843
|
let iteration = 0;
|
|
1844
|
+
const walletAddressStr = walletAddress.toString();
|
|
1463
1845
|
while (accumulated.length < limit && iteration < MAX_ITERATIONS) {
|
|
1464
1846
|
iteration++;
|
|
1465
1847
|
const batchSize = iteration === 1 ? limit : limit * 2;
|
|
@@ -1481,7 +1863,7 @@ function createIndexer(options) {
|
|
|
1481
1863
|
const classified = transactions.map((tx) => {
|
|
1482
1864
|
tx.protocol = detectProtocol(tx.programIds);
|
|
1483
1865
|
const legs = transactionToLegs(tx);
|
|
1484
|
-
const classification = classifyTransaction(legs, tx);
|
|
1866
|
+
const classification = classifyTransaction(legs, tx, walletAddressStr);
|
|
1485
1867
|
return { tx, classification, legs };
|
|
1486
1868
|
});
|
|
1487
1869
|
const nonSpam = filterSpamTransactions(classified, spamConfig);
|