Last active
October 27, 2025 16:09
-
-
Save gayleQN/ce5fb7c31032cccc050679d8bc3f48b8 to your computer and use it in GitHub Desktop.
EVM Wallet Monitoring with Key-Value Store List
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
| async function main(stream) { | |
| const LIST_NAME = "wallets"; | |
| const TRANSFER_TOPIC = | |
| "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; | |
| // --- helpers --- | |
| const toArray = (x) => (Array.isArray(x) ? x : []); | |
| const lower = (s) => (typeof s === "string" ? s.toLowerCase() : null); | |
| const normalizeAddr = (s) => (s ? lower(s) : null); | |
| const topicToAddr = (topic) => { | |
| if (typeof topic !== "string") return null; | |
| const hex = topic.startsWith("0x") ? topic.slice(2) : topic; | |
| if (hex.length < 40) return null; | |
| return "0x" + hex.slice(-40).toLowerCase(); | |
| }; | |
| const isTransferLog = (log) => | |
| lower(log?.topics?.[0]) === TRANSFER_TOPIC; | |
| try { | |
| const blocks = toArray(stream?.data); | |
| const matchingTransactions = []; | |
| const matchingReceipts = []; | |
| for (const block of blocks) { | |
| const txs = toArray(block?.block?.transactions); | |
| const receipts = toArray(block?.receipts); | |
| // Collect all candidate addresses for this block | |
| const candidates = []; | |
| // tx from/to | |
| for (const tx of txs) { | |
| const f = normalizeAddr(tx?.from); | |
| const t = normalizeAddr(tx?.to); | |
| if (f) candidates.push(f); | |
| if (t) candidates.push(t); | |
| } | |
| // ERC20 Transfer indexed topics (from/to) | |
| for (const receipt of receipts) { | |
| for (const log of toArray(receipt?.logs)) { | |
| if (!isTransferLog(log)) continue; | |
| const fromIdx = topicToAddr(log?.topics?.[1]); | |
| const toIdx = topicToAddr(log?.topics?.[2]); | |
| if (fromIdx) candidates.push(fromIdx); | |
| if (toIdx) candidates.push(toIdx); | |
| } | |
| } | |
| // Batch check once per block | |
| const uniq = Array.from(new Set(candidates)); | |
| const batchResults = | |
| uniq.length > 0 | |
| ? await qnLib.qnContainsListItems(LIST_NAME, uniq) | |
| : []; | |
| const hitMap = new Map(uniq.map((a, i) => [a, !!batchResults[i]])); | |
| // Match transactions | |
| for (const tx of txs) { | |
| const f = normalizeAddr(tx?.from); | |
| const t = normalizeAddr(tx?.to); | |
| if ((f && hitMap.get(f)) || (t && hitMap.get(t))) { | |
| matchingTransactions.push(tx); | |
| } | |
| } | |
| // Match receipts (any matching indexed from/to in logs) | |
| for (const receipt of receipts) { | |
| const logs = toArray(receipt?.logs); | |
| let anyHit = false; | |
| for (const log of logs) { | |
| if (!isTransferLog(log)) continue; | |
| const fromIdx = topicToAddr(log?.topics?.[1]); | |
| const toIdx = topicToAddr(log?.topics?.[2]); | |
| if ((fromIdx && hitMap.get(fromIdx)) || (toIdx && hitMap.get(toIdx))) { | |
| anyHit = true; | |
| break; | |
| } | |
| } | |
| if (anyHit) matchingReceipts.push(receipt); | |
| } | |
| } | |
| if (matchingTransactions.length === 0 && matchingReceipts.length === 0) { | |
| return null; | |
| } | |
| return { | |
| transactions: matchingTransactions, | |
| receipts: matchingReceipts, | |
| }; | |
| } catch (e) { | |
| return { error: e?.message || String(e) }; | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Updated to use
qnContainsListItemsfor bulk matching