One scanner. Three data paths.
The Node scanner reads eth_getBlockByNumber, eth_getLogs and receipts for relevant direct transfers. It verifies the chain ID and block hash. A user timer triggers ticks with a shared lease, bounded retries, exponential backoff and a 45-second budget. Each tick scans up to 1,200 blocks per chain, two blocks behind the reported head. Slow RPCs produce a visible delayed state.
Detection rules.
| Signal | Rule |
|---|---|
| Approval | ERC-20 Approval with three topics, owner equals watched address, nonzero amount. At least 2^255 raw units is flagged as unlimited. |
| Operator access | ApprovalForAll with owner equals watched address and approved=true. |
| Outflow | Standard Transfer / TransferSingle / TransferBatch logs, or a successful direct native send. Internal native traces are not scanned. |
| Poisoning | Incoming native value <= 10^12 wei, or token value <= 1000 raw units, with first 4 and last 4 hex characters matching a prior observed recipient. The addresses must differ. |
| Threat intelligence | ScamSniffer address matches. MetaMask domain checks are separate; no on-chain website attribution is invented. |
State and privacy.
The local file store uses exclusive locks, atomic rename and compare-and-swap updates. The Blob adapter uses a private blob and ETag conditions with the same read, write and transaction interface. A random browser capability is hashed in the store; inbox reads and deletion require that capability. Watches are independent even when they share an address.
The active store holds up to 200 watches, 500 alerts per watch, and the last 2,000 observed recipients and spenders per chain. Choose 7, 30 or 90 days; the entire watch expires. The server stores no email, private wallet key or IP address. New burner and stealth keys stay encrypted in browser storage. A hosting or push provider may retain its own transport metadata. Unwatch removes the active record; provider backups and delivered notifications have separate retention.
Delivery and replay.
VAPID keys are generated server-side and kept in a private environment file outside the served site. Web Push payloads use standard encrypted transport. An outbox retries transient failures with backoff; 404 and 410 remove expired subscriptions. Acceptance by the push service is recorded separately from browser display. Deduplication keys include chain, transaction hash, log index and event type.
Block and receipt hashes are cross-checked. A detected parent-hash mismatch rewinds the affected watch by up to eight blocks and drops affected inbox entries. Previously delivered notifications cannot be retracted. Deep reorganisations and old recipient history are not fully reconstructed.
Run it. Inspect it.
These commands run from the SENTR folder. The fork proof starts local Anvil nodes against Robinhood and Ethereum, grants an approval, sends to a recipient, then sends lookalike dust and checks the next tick. Its local push receiver verifies the VAPID signature and decrypts the authenticated payload. It does not submit a transaction to a public chain.
npm ci --ignore-scripts npm test npm run test:fork node scripts/setup-local.mjs node --env-file=../../ops/sentr-runtime/sentr.env api/server.mjs python3 -m http.server 8786
The build report distinguishes completed evidence from launch prerequisites. Local tests are not an independent security audit.
BUILD-REPORT.md ↗Taking the local build live.
Choose a private shared Blob store and configure BLOB_READ_WRITE_TOKEN, SENTR_STORE=blob, the three VAPID settings and the HTTPS site origin in the API and watcher environments. Keep private runtime data outside the static site. Install the supplied user service and timer for the watcher, expose the API through the same HTTPS origin, and repeat the push and deletion checks. The local build does not deploy itself.
Client-owned transaction signing.
The main wallet is selected through EIP-6963. SENTR builds calldata locally, checks the account and chain, simulates calls and reads balances. EIP-5792 capabilities select wallet_sendCalls when available; otherwise each transaction waits for a receipt before the next request. Rejections and uncertain submissions stop progress. Receipts remain local and are downloadable.
Stealth address math is ported from the frozen YONDER implementation: secp256k1 ECDH, Keccak of the compressed shared point, public-key addition and private-key recovery. Fresh keys use WebCrypto randomness. The vault uses AES-256-GCM with PBKDF2-SHA256 at 310,000 iterations, a random salt and a fresh IV. No announcer call or main-wallet signature is used to create these keys.
The v2 browser proof runs real transactions on Robinhood and Ethereum Anvil forks with an injected EIP-6963 provider. Its EIP-5792 adapter executes real ordered, non-atomic transactions. This proves the app protocol and chain effects, not compatibility with every commercial wallet or an atomic smart-account implementation.
npm test python3 -m http.server 8786 --bind 127.0.0.1 # In a second terminal: npm run test:fork:v2
Partner runtime and signing.
The stateless API validates allow-listed Pimlico methods and keeps its key in the server environment. XMTP runs in a persistent Node worker beside the scanner. The scanner stores pending delivery with each new alert; the worker retries, rechecks consent and records the message ID. A wallet-signed, expiring challenge binds opt-in to the selected watch and inbox. Only one sender process may open its database.
The 4663 fork proof uses the official EntryPoint v0.8 and Simple7702 account with a local test bundler and funded paymaster. This is separate from the public Pimlico read-only probe. No public-chain transaction is claimed.