Skip to content

Instantly share code, notes, and snippets.

@gayleQN
Last active October 16, 2025 18:58
Show Gist options
  • Select an option

  • Save gayleQN/a82ff9d6027c83206534778b35dca493 to your computer and use it in GitHub Desktop.

Select an option

Save gayleQN/a82ff9d6027c83206534778b35dca493 to your computer and use it in GitHub Desktop.
Solana Wallet Monitoring with Key-Value Store
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 };
}
}
@gayleQN
Copy link
Author

gayleQN commented Oct 16, 2025

Updated to use qnContainsListItems for bulk lookups

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment