5.1 KiB
title, tags, created
| title | tags | created | |||
|---|---|---|---|---|---|
| ScannerBalanceWatch (Scanner DB model) |
|
2026-06-03 |
ScannerBalanceWatch
SQLite row in the AMN Pay Scanner's balance_watches table. One row represents a direct-address token balance watch requested by the backend for non-smart-contract payment detection.
This is scanner-internal state. It is not a Mongoose model and lives in the scanner SQLite database (/data/scanner.db).
Schema
CREATE TABLE balance_watches (
watch_id TEXT PRIMARY KEY,
chain_id INTEGER NOT NULL,
chain_type TEXT NOT NULL DEFAULT 'evm',
token_address TEXT NOT NULL,
token_symbol TEXT,
decimals INTEGER NOT NULL DEFAULT 0,
address TEXT NOT NULL,
baseline_balance TEXT NOT NULL,
current_balance TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'watching',
callback_url TEXT NOT NULL,
callback_secret TEXT NOT NULL,
last_checked_at DATETIME,
next_check_at DATETIME NOT NULL,
change_count INTEGER NOT NULL DEFAULT 0,
last_notified_at DATETIME,
expires_at DATETIME NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_balance_watches_status_next
ON balance_watches(status, next_check_at);
CREATE INDEX idx_balance_watches_chain_status
ON balance_watches(chain_id, status);
Fields
| Field | Type | Description |
|---|---|---|
watch_id |
TEXT PK | Caller-supplied or scanner-generated idempotency key. Backend should use a payment-scoped value such as <paymentId>-balance-c56-USDT when it wants webhook correlation by prefix. |
chain_id |
INTEGER | Numeric EVM chain ID. Direct balance reads currently support EVM ERC-20 only. |
chain_type |
TEXT | Currently evm for balance watches. Kept for future Tron/TON support. |
token_address |
TEXT | ERC-20 token contract address, normalized to lowercase 0x hex. |
token_symbol |
TEXT NULL | Optional token symbol resolved from tokens.json. |
decimals |
INTEGER | Token decimals resolved from registry when available. |
address |
TEXT | Watched holder address, normalized to lowercase 0x hex. |
baseline_balance |
TEXT | Base-unit integer string captured or supplied when the watch is created. |
current_balance |
TEXT | Last scanner-accepted base-unit balance. For changed balances, this advances only after webhook delivery succeeds. |
status |
TEXT | Watch lifecycle state. |
callback_url |
TEXT | Backend webhook endpoint. Validated with the same callback URL guard as scanner intents. |
callback_secret |
TEXT | HMAC-SHA256 key for the balance watch webhook signature. Never returned in API responses. |
last_checked_at |
DATETIME NULL | Last time the scanner attempted a balance read. |
next_check_at |
DATETIME | Scheduler due time. |
change_count |
INTEGER | Count of successfully delivered balance-change notifications. |
last_notified_at |
DATETIME NULL | Time of the last successful balance-change notification. |
expires_at |
DATETIME | Hard stop timestamp, currently 7 days after creation. |
created_at / updated_at |
DATETIME | UTC timestamps. |
Status values
| Status | Description |
|---|---|
watching |
Scheduler polls the address/token when next_check_at is due. |
stopped |
Backend explicitly stopped the watch after payment success, cancellation, or manual resolution. |
expired |
Scanner stopped the watch automatically after 7 days. |
Polling cadence
Age from created_at |
Interval |
|---|---|
< 24h |
5 min |
24h–48h |
10 min |
48h–72h |
20 min |
> 72h |
40 min until expiry |
BALANCE_WATCH_TICK_SEC controls how often the scheduler queries for due watches. BALANCE_WATCH_BATCH_SIZE controls how many due watches are processed per tick.
Webhook semantics
When current_balance changes, scanner sends:
{
"eventType": "balance_changed",
"watchId": "6840fabc-balance-c56-USDT",
"chainId": 56,
"chainType": "evm",
"address": "0x...",
"tokenAddress": "0x...",
"tokenSymbol": "USDT",
"decimals": 18,
"previousBalance": "25000000000000000000",
"currentBalance": "35000000000000000000",
"delta": "10000000000000000000",
"changeCount": 1,
"checkedAt": "2026-06-03T10:05:00Z",
"status": "balance_changed"
}
The backend must not treat this webhook alone as final escrow funding. It should compare delta or (currentBalance - baselineBalance) to the expected amount, apply token/chain/address checks, persist evidence, and stop the watch when the payment is accepted or cancelled.
Related
- Scanner Architecture
- Scanner API
- Payment Flow - Scanner
- ScannerIntent
- Payment — backend MongoDB/DTO model that stores payment metadata