Address screening
Every crypto address the platform touches is screened against sanctions and risk lists, and the result is kept. This page describes what the control actually does, which recognised practices it maps to, and — just as importantly — what it does not cover.
Scope
This is an engineering description of a control, not a compliance opinion and not legal advice. Whether it satisfies any particular regulator's expectations is a question for counsel. The gaps at the bottom are stated plainly so that nobody reads this page as a claim of compliance.
What gets screened
Three populations, defined once in marketplace.platform_addresses:
| Population | Where it comes from | Why it matters |
|---|---|---|
| Operator — registered | wallets connected through the consent flow | funds campaigns |
| Operator — funding | wallets observed funding a campaign on chain | funds campaigns, never registered |
| Streamer — payout | payout destinations on streamer profiles | money leaves the platform here |
| Invoice payers | senders of USDC to invoice addresses | money enters the platform here |
The second row exists because the first was nearly empty. The registration table holds two wallets; twenty-seven more have actually funded campaigns without ever registering. Screening only the registered set meant screening two internal test wallets while every wallet that moves money went unexamined — so the estate is defined by observed behaviour, not by who filled in a form.
How a verdict is produced
Two independent sources, each weighted, both currently enabled at weight 1.0:
chainalysis_oracle— Chainalysis's public sanctions oracle, queried on-chain viaisSanctioned(address). Address-based and chain-agnostic.publicaml— risk score 0–100 plus a sanctioned flag and a hop-based exposure graph (OFAC/EU lists, mixers, scam databases, stablecoin issuer freezes).
The composite is a weighted mean over the sources that answered. Weights shape that advisory number only:
A
sanctioned = truefrom any enabled source setsblocked = trueregardless of weight.
Sanctions are strict liability, so no tunable knob is allowed to dilute a hit. Disabling a source is possible, but it is an explicit, logged administrative act — which is the point: the escape hatch leaves a trace.
Mapping to AML practice
Risk-based approach
The flag threshold is set per context, because the cost of a false positive differs by flow:
| Context | Threshold | Quorum | Reasoning |
|---|---|---|---|
invoice_payer | 70 | 1.0 | money we are about to sweep to treasury — strictest, and requires a source to have answered |
sweep / manual | 70 | 0 | periodic and ad-hoc re-checks |
wallet_save / deal_fund | 100 | 0 | only a sanctions hit blocks — a risk score must not stop a streamer being paid |
Thresholds are editable from the admin Screening panel, so the risk appetite is a setting with an audit trail rather than a constant in code.
Failure handling is chosen per flow, not globally
- Wallet save and deal funding fail OPEN. Free-tier providers with no SLA must not gate a streamer's payout. A degraded check records
degraded = trueand does not block. - Invoice payer screening fails CLOSED. If no source answered, the money stays un-sweepable. Nobody is inconvenienced by holding funds; releasing them on an unverified payer is the irreversible direction.
This asymmetry is deliberate: fail toward the reversible outcome.
Ongoing monitoring, not one-time
Gates check an address once, at first use. Lists move — an address that cleared in July can be listed in September — and a gate that has already fired never looks again.
An hourly sweep re-screens the estate: never-screened addresses first, then the oldest verdict. Anything older than 90 days is treated as stale and comes back round. Coverage and staleness are both visible in Admin → Screener and in the Grafana AML screening dashboard, because a screening system with a silent backlog reports exactly the same green as one that is working.
Record-keeping
Every screen appends to marketplace.aml_screenings with the verbatim per-source result, not just the verdict. Lists change; the ability to reconstruct why an address passed on a given date does not survive unless the raw answer is stored at the time.
The context column records which control produced the verdict — a write-time gate, the scheduled sweep, an admin clicking a button, or the payer gate — so a control that ran on schedule is distinguishable from a human decision to look at one address.
Escalation
A flag raised by the sweep logs at ERROR and increments vf_aml_sweep_flagged_total, which pages the on-call Telegram channel at critical severity on a single occurrence. A payout destination that has become sanctioned is an incident, not a statistic: money may already have gone there, and is about to again.
A second alert fires if the sweep itself stops for three hours, because a stalled control and a clean estate look identical from the outside.
What this does NOT cover
Read this section before citing the one above.
- No KYB on operators. Wallets are screened; the businesses behind them are not verified. Streamer identity is verified through Sumsub; operator identity is not.
- No PEP or adverse-media screening. Sanctions and on-chain risk exposure only.
- Two sources, both free-tier, with no service-level agreement and no contractual accuracy guarantee. Coverage of newly-added listings is unknown and unmeasured.
- Screening does not block payouts. It observes and records. The sweep has no power to freeze anything; acting on a flag is a manual decision.
- Chain handling is approximate. Sanctions lists are address-based and chain-agnostic, so this is sound for sanctions; a chain-specific risk source would need per-chain queries the sweep does not currently make.
- No transaction monitoring. Addresses are screened; payment patterns (structuring, velocity, unusual counterparties) are not.
Where to look
| Admin → Screener | Payers / Operator / Streamers, with coverage and a re-screen button |
| Grafana → Verifluence — AML screening | currently-flagged addresses, coverage, sweep health |
marketplace.aml_screenings | the append-only record, with per-source raw results |
marketplace.platform_addresses | the estate definition |