Oracle07
Re-screening & webhooks
A grade is only as good as the day it was issued, so CompliR keeps re-grading. Watched addresses follow the cadence the analyst chose, USDC blocklist events trigger an immediate re-screen, and every tier change reaches your webhook with the full signed verdict.
Cadence windows
The analyst proposes nextRescreenInSeconds; code clamps it into the window for the tier. After a tier change the ceiling halves for the next cycle. The fallback (no model) always picks the ceiling.
| Tier | Min | Max | After a tier change |
|---|---|---|---|
| HIGH | 5 min | 1 h | max 30 min |
| MEDIUM | 15 min | 24 h | max 12 h |
| LOW | 1 h | 7 d | max 3.5 d |
Source: RESCREEN_WINDOW_SECONDS in src/lib/analyst/guards.ts.
Watch an address
POST /api/v1/watch # 0.05 USDC / 30 days via x402 · settled after the handler succeeds
{ "address": "0x…", "webhookUrl": "https://treasury.example/complir", "clientId": "treasury-7", "payeeBook": [ … ] }
→ 200 X-CompliR-Watch-Id: wat_…
{ "id": "wat_…", "address": "0x…", "webhookUrl": "…", "clientId": "treasury-7",
"lastTier": null, "nextRescreenAt": 1760003600, "expiresAt": 1762592000, "createdAt": 1760000000 }- 30 days per purchase. Re-registering the same address and client upserts the watch and extends it.
- Payee book travels with the watch, so lookalike and payee-change checks keep running on re-screens.
- Charged only after success: the handler runs first, then x402 settles; a failed registration is never charged.
- Schedule. The watcher runs every 10 minutes as a Vercel cron (
/api/internal/watcher, bearerCRON_SECRET) or every 60 s asworker/watcher.ts. A due watch is re-screened withkind: RESCREEN; a failed re-screen is retried in 10 minutes and keeps the last tier.
The tier.changed webhook
POST <your webhookUrl>
content-type: application/json
x-complir-event: tier.changed # 8 s timeout; delivery is best-effort, the ledger entry is not
{
"type": "tier.changed",
"address": "0x…",
"from": "LOW", # null on the first grade of a watch
"to": "MEDIUM",
"verdict": { "score": 37, "tier": "MEDIUM", "recommendedLimits": { … }, "reasons": [ … ], "rationale": "…",
"eddRequested": [ … ], "nextRescreenAt": "…", "attestation": { … }, "decisionId": "dec_…", "screenedAt": "…" },
"registryTx": null # tx hash once the registry write lands
}Before the POST, a TIER_CHANGE entry is appended to the ledger with from, to, decisionId, watchId and the trigger. Verify verdict.attestation exactly as you would a screen response; the evidence bundle is omitted from the webhook body and available at GET /api/v1/decision/:decisionId.
Blocklist-event reactor
Arc's USDC is Circle's FiatToken: Blacklisted(address) and UnBlacklisted(address) logs surface at the proxy 0x3600…0000 (the reactor also subscribes to Blocklisted/UnBlocklisted in case a future upgrade emits those). A tick polls new blocks and, for every affected address that is watched, on the Trust Map, or screened by anyone in the last 30 days (EVENTS_SCREENED_DAYS), runs a real re-screen with clientId: "events" and pushes tier.changed to every watch on it. Everything else is logged as ignored.
- Schedule
- Vercel cron every 5 min (/api/internal/events) · worker/events.ts every 20 s (EVENTS_TICK_MS)
- Budget
- EVENTS_MAX_BLOCKS=500 per tick · EVENTS_RPC_BUDGET=12 calls · cursor only advances through contiguously fetched blocks
- Lag
- a lag above EVENTS_MAX_LAG=5000 blocks skips ahead: this is a live reactor, not an indexer; the cadence watcher is the baseline
- Visibility
GET /api/v1/network/eventslists the last events seen, whether a re-screen ran, tierFrom → tierTo, webhooks delivered, latency
Simulate a blocklist event
# Blacklist drill: run the exact reactor handler as if USDC had just emitted Blacklisted(addr).
# The subject is a real sanctioned wallet (Ronin Bridge exploiter, OFAC SDN + JP MOF) that USDC on Arc really blacklists,
# so the re-screen reads live chain facts - no overrides, no synthetic evidence.
curl -s -X POST http://localhost:3000/api/internal/events -H "Authorization: Bearer $CRON_SECRET" \
-H 'content-type: application/json' -d '{ "simulate": "0x098b716b8aaf21512996dc57eb0615e2383e2f96" }'
# The reverse event only exercises the UnBlacklisted path: the re-screen still reads the chain, so the tier
# stays HIGH until Circle really un-blacklists the address.
curl -s -X POST … -d '{ "simulate": "0x098b716b8aaf21512996dc57eb0615e2383e2f96", "kind": "UNBLOCKLISTED" }'The drill runs the identical handler and is marked simulated: true in the event log; only the event is simulated. The re-screen reads the chain and the sanctions lists, so the verdict is the real one for that address. Trust Map re-grades are a third loop: recipients of 25 USDC or more are re-graded every 24 hours by the tailer. See the trust model for what these loops cannot see.