Last active
October 16, 2025 18:58
-
-
Save gayleQN/a82ff9d6027c83206534778b35dca493 to your computer and use it in GitHub Desktop.
Solana Wallet Monitoring with Key-Value Store
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const tokenPrograms = new Set([ | |
| 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', | |
| 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', | |
| ]); | |
| const ignorablePrograms = new Set([ | |
| 'Vote111111111111111111111111111111111111111', | |
| 'ComputeBudget111111111111111111111111111111', | |
| 'AddressLookupTab1e1111111111111111111111111', | |
| ]); | |
| const LIST_NAME = 'solanawallets'; | |
| function shouldSkipTransaction(tx) { | |
| const instructions = tx?.transaction?.message?.instructions || []; | |
| return instructions.length > 0 && instructions.every(inst => ignorablePrograms.has(inst?.programId)); | |
| } | |
| async function main(stream) { | |
| try { | |
| const blocks = Array.isArray(stream?.data) ? stream.data : []; | |
| const results = []; | |
| for (const block of blocks) { | |
| const txs = Array.isArray(block?.transactions) ? block.transactions : []; | |
| if (txs.length === 0) continue; | |
| const candidates = []; | |
| for (const tx of txs) { | |
| if (shouldSkipTransaction(tx)) continue; | |
| const keys = tx?.transaction?.message?.accountKeys || []; | |
| const pre = tx?.meta?.preBalances || []; | |
| const post = tx?.meta?.postBalances || []; | |
| const n = Math.min(pre.length, post.length, keys.length); | |
| for (let i = 0; i < n; i++) { | |
| if (BigInt(pre[i]) !== BigInt(post[i])) { | |
| const addr = (typeof keys[i] === 'string') ? keys[i] : (keys[i]?.pubkey || null); | |
| if (addr) candidates.push(addr); | |
| } | |
| } | |
| const topLevel = tx?.transaction?.message?.instructions || []; | |
| const inner = (tx?.meta?.innerInstructions || []).flatMap(i => i?.instructions || []); | |
| const instructions = [...topLevel, ...inner]; | |
| for (const inst of instructions) { | |
| if (!inst || !tokenPrograms.has(inst.programId)) continue; | |
| const info = inst?.parsed?.info || {}; | |
| for (const addr of [info.source, info.destination, info.authority, info.multisigAuthority]) { | |
| if (typeof addr === 'string' && addr) candidates.push(addr); | |
| } | |
| } | |
| } | |
| // -------- Batch lookup once per block (new) -------- | |
| const uniq = Array.from(new Set(candidates)); | |
| const batch = uniq.length ? await qnLib.qnContainsListItems(LIST_NAME, uniq) : []; | |
| const hitMap = new Map(uniq.map((a, i) => [a, !!batch[i]])); | |
| const matchedTransactions = []; | |
| for (const tx of txs) { | |
| if (shouldSkipTransaction(tx)) continue; | |
| const matches = new Set(); | |
| const keys = tx?.transaction?.message?.accountKeys || []; | |
| const pre = tx?.meta?.preBalances || []; | |
| const post = tx?.meta?.postBalances || []; | |
| // Native SOL balance-change matches | |
| const n = Math.min(pre.length, post.length, keys.length); | |
| for (let i = 0; i < n; i++) { | |
| if (BigInt(pre[i]) !== BigInt(post[i])) { | |
| const addr = (typeof keys[i] === 'string') ? keys[i] : (keys[i]?.pubkey || null); | |
| if (addr && hitMap.get(addr)) matches.add(addr); | |
| } | |
| } | |
| // Token transfer matches (parsed.info only) | |
| const topLevel = tx?.transaction?.message?.instructions || []; | |
| const inner = (tx?.meta?.innerInstructions || []).flatMap(i => i?.instructions || []); | |
| const instructions = [...topLevel, ...inner]; | |
| for (const inst of instructions) { | |
| if (!inst || !tokenPrograms.has(inst.programId)) continue; | |
| const info = inst?.parsed?.info || {}; | |
| for (const addr of [info.source, info.destination, info.authority, info.multisigAuthority]) { | |
| if (typeof addr === 'string' && addr && hitMap.get(addr)) { | |
| matches.add(addr); | |
| } | |
| } | |
| } | |
| if (matches.size > 0) { | |
| matchedTransactions.push({ wallets: Array.from(matches), raw: tx }); | |
| } | |
| } | |
| if (matchedTransactions.length > 0) { | |
| results.push({ | |
| block: { ...block, transactions: undefined }, | |
| transactions: matchedTransactions, | |
| }); | |
| } | |
| } | |
| return results.length > 0 ? results : null; | |
| } catch (error) { | |
| return { error: error?.message || String(error), stack: error?.stack }; | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Updated to use
qnContainsListItemsfor bulk lookups