Files
nick-doc/02 - Data Models/ScannerBalanceWatch.md

131 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: ScannerBalanceWatch (Scanner DB model)
tags: [data-model, scanner, payment]
created: 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
```sql
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 |
| `24h48h` | 10 min |
| `48h72h` | 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:
```json
{
"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](../01%20-%20Architecture/Scanner%20Architecture.md)
- [Scanner API](../03%20-%20API%20Reference/Scanner%20API.md)
- [Payment Flow - Scanner](../04%20-%20Flows/Payment%20Flow%20-%20Scanner.md)
- [ScannerIntent](ScannerIntent.md)
- [Payment](Payment.md) — backend MongoDB/DTO model that stores payment metadata