---
title: ScannerIntent (Scanner DB model)
tags: [data-model, scanner, payment]
created: 2026-05-30
---

# ScannerIntent

SQLite row in the AMN Pay Scanner's `intents` table. One row per payment intent registered by the backend. This is internal scanner state — it is not a Mongoose model and lives in a separate SQLite database (`/data/scanner.db`).

---

## Schema

```sql
CREATE TABLE intents (
    intent_id              TEXT PRIMARY KEY,
    chain_id               INTEGER NOT NULL,
    chain_type             TEXT NOT NULL DEFAULT 'evm',
    token_address          TEXT NOT NULL,
    destination            TEXT NOT NULL,
    amount                 TEXT NOT NULL,
    payment_reference      TEXT NOT NULL,
    topic_ref              TEXT,
    status                 TEXT NOT NULL DEFAULT 'pending',
    callback_url           TEXT NOT NULL,
    callback_secret        TEXT NOT NULL,
    confirmations_required INTEGER NOT NULL DEFAULT 12,
    tx_hash                TEXT,
    log_index              INTEGER,
    block_number           INTEGER,
    confirmations          INTEGER NOT NULL DEFAULT 0,
    salt                   TEXT NOT NULL,
    webhook_delivered_at   TEXT,
    created_at             DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at             DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```

---

## Fields

| Field | Type | Description |
|---|---|---|
| `intent_id` | TEXT PK | Caller-supplied unique ID (typically the backend Payment `_id`) |
| `chain_id` | INTEGER | Numeric chain ID. EVM standard (56, 137, 1, 42161, 8453), Tron (728126428), TON (1100) |
| `chain_type` | TEXT | `evm` / `tron` / `ton`. Determines which worker handles this intent |
| `token_address` | TEXT | ERC20 / TRC20 contract address. EVM/Tron: lowercase `0x` hex. TON: exact base64url |
| `destination` | TEXT | Recipient wallet address. EVM/Tron: lowercase `0x` hex. TON: base64url (case-sensitive) |
| `amount` | TEXT | Required amount in smallest token unit (wei / 10^decimals), stored as base-10 integer string |
| `payment_reference` | TEXT | 8-byte hex EVM payment reference (`0x` + 16 hex chars). Derived as `last8(keccak256(intentId + salt + destination))` |
| `topic_ref` | TEXT | `keccak256(paymentReferenceBytes)` — matches `Topics[1]` in EVM logs. Pre-computed for indexed DB lookup. NULL for Tron/TON |
| `status` | TEXT | Intent lifecycle state (see below) |
| `callback_url` | TEXT | URL the scanner POSTs to on confirmation |
| `callback_secret` | TEXT | HMAC-SHA256 key for webhook signature. Never returned in API responses |
| `confirmations_required` | INTEGER | Accepted confirmation floor for the intent. Defaults to chain config and cannot be lowered below the chain floor by the create-intent request |
| `tx_hash` | TEXT NULL | Transaction hash once a matching transfer is detected |
| `log_index` | INTEGER NULL | Log position within the transaction (EVM only; 0 for Tron/TON) |
| `block_number` | INTEGER NULL | Block number (EVM/Tron) or Unix timestamp seconds (TON) when the tx was seen |
| `confirmations` | INTEGER | Current confirmation depth while `confirming`; capped at `confirmations_required` once the intent is `confirmed` |
| `salt` | TEXT | 32-byte random hex. Combined with `intent_id` and `destination` to derive `payment_reference`. Prevents reference collisions across retried payments |
| `webhook_delivered_at` | TEXT NULL | RFC3339 timestamp when the webhook was successfully delivered. Used for startup crash recovery |
| `created_at` / `updated_at` | DATETIME | UTC timestamps |

---

## Status values

| Status | Description |
|---|---|
| `pending` | Registered; scanner is watching for a matching on-chain transfer |
| `confirming` | EVM only — matching tx seen, waiting for `confirmations_required` blocks |
| `confirmed` | Payment confirmed; webhook delivery attempted |
| `expired` | TTL exceeded while still in `pending` or `confirming` |
| `webhook_failed` | All webhook delivery retries exhausted; manual retry or periodic auto-retry needed |

---

## Indexes

```sql
CREATE INDEX idx_intents_status       ON intents(status);
CREATE INDEX idx_intents_chain_status ON intents(chain_id, status);
CREATE INDEX idx_intents_payment_ref  ON intents(payment_reference);
CREATE INDEX idx_intents_topic_ref    ON intents(topic_ref);
CREATE UNIQUE INDEX idx_intents_tx_log ON intents(tx_hash, log_index)
    WHERE tx_hash IS NOT NULL;
```

`idx_intents_topic_ref` is the performance-critical index — the EVM scanner's inner loop does a single indexed lookup per log entry.

The unique index on `(tx_hash, log_index)` prevents two intents being confirmed from the same on-chain event (double-spend protection).

---

## Migrations

Three additive migrations run at startup (idempotent):

1. `ADD COLUMN topic_ref TEXT` — added after initial schema
2. `ADD COLUMN chain_type TEXT NOT NULL DEFAULT 'evm'` — added for Tron/TON support
3. `ADD COLUMN webhook_delivered_at TEXT` — added for crash recovery

A backfill pass recomputes `topic_ref` for existing EVM intents that had it as NULL.

---

## 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)
- [ScannerBalanceWatch](ScannerBalanceWatch.md) — direct-address balance-watch model for non-smart-contract payment rails
- [Payment](Payment.md) — the backend MongoDB model that triggers intent creation
