How Address Risk Scores Are Built (and What's Public)
We pulled today's OFAC file: 19,484 records, 109 with crypto addresses, 1,075 rows — one is not an address, seven sit on the wrong chain. What a score can show.
On 30 October 2026 I downloaded the US Treasury's OFAC sanctions file: 29,271,441 bytes, published 28 October, 19,484 records. Then I counted how many of those records carry a cryptocurrency address. The answer is 109, or 0.56% of the list. Those 109 records hold 1,075 address rows, 1,058 distinct strings, spread over 20 tickers from Bitcoin to Monero to a single Arbitrum address. Every "address risk score" you have ever been shown starts with a file like this one. What almost nobody tells you is how quickly the checkable part ends.
The public half is a list, and it is smaller than you think
Here is the entire public inventory, measured this morning. Bitcoin: 534 rows across 65 designations, of which 209 are legacy 1… addresses, 178 are P2SH 3…, 146 are bech32 bc1…, and one row fails its own checksum. Tron: 266 rows, 40 designations. Ethereum: 124 rows, 48 designations. USDT: 94 rows, 20 designations. Then Litecoin 14, Monero 11, Bitcoin Cash 7, Dash 5, Zcash 4, Solana 4, USDC 2, Dogecoin 2, and one each for BTG, ETC, BSV, XVG, XRP, ARB, BSC and BNB. The programmes are just as lopsided: 332 of the 534 Bitcoin rows sit under CYBER2, Tron splits between SDGT at 167 and FTO at 131, and the eleven Monero rows belong to four designations only. PARSARAD accounts for five of them, ISIL KHORASAN three, SOUTHFRONT two, KARASAVIDI one. That is the whole public layer. Not a score, a roster. (BCH to XMR guide)
Why Monero has eleven rows and Bitcoin has 534
It is not bureaucratic preference, it is graph arithmetic. A Bitcoin address is worth putting on a list because it can be clustered: tie one address to a person and the common-input heuristic pulls in every address that ever co-spent with it, so one designation can cover dozens of rows. The file averages 8.2 rows per Bitcoin designation against 2.75 per Monero designation. On the Monero side, a listed address is only useful if you already know it from outside the chain: an exchange withdrawal log, a seized laptop, a forum post, a vendor's own report. Ring signatures mean the inputs of a spend cannot be attributed, and hidden amounts mean the value flow cannot be read at all. So the roster is thin not because Monero is cleaner, but because a name on a list does not turn into a cluster.
The list is not clean, and two of its errors matter
One of the eleven Monero rows is not a Monero address. It is 64 hexadecimal characters, 5be5543ff734…e456c320, the shape of a transaction hash, not the 95-character base58 string a wallet produces. Ten of eleven are real addresses, and that tenth character class matters: a screener that validates format before comparing against the roster will drop that row and never tell you it did. Second error: of the 94 rows tagged USDT, 79 are Tron addresses (T plus 33 characters), 8 are Ethereum (0x plus 40 hex), and 7 are Bitcoin addresses — 1KUUJPky…, 1CF46Rfb…, 3LtcaPbC… — which is Tether as it existed on Omni Layer before Tron and Ethereum took over. Labelled by ticker, not by chain, those seven rows point at the wrong network. There are also 14 addresses that appear in more than one row. None of this is scandalous; it is what any hand-maintained list of a moving target looks like after a few years. (XMR to USDT exit)
The other two layers are where the word score actually lives
Commercial risk scoring is usually described as three inputs: attribution, exposure and behaviour. Attribution is the clustering above — which addresses belong to the same actor, and which actor that is. Exposure is distance: how many hops between the coins you are depositing and something already labelled, weighted by value and by time. Behaviour is pattern: a peel chain that moves 90% forward each hop, consolidation of many small inputs right before a deposit, round-number amounts that never appear in ordinary commerce. Only the first input has a public, downloadable ground truth, and it is a binary — listed or not. The other two produce a number between 0 and 100 whose thresholds are vendor configuration, not law. That is the part people misread: a high score is not an accusation, and a low score is not a clearance. Both are somebody's model output, and you are not shown the model.
What our own desk checks, measured
I compiled our address checker to plain JavaScript and ran all 1,075 sanctioned rows through it, each against the chain the list claims. Ten of the eleven Monero rows pass, the eleventh is the 64-character string that is not an address. 533 of 534 Bitcoin rows pass. All 266 Tron rows pass, all 124 Ethereum rows, all 79 Tron-USDT rows and all 8 Ethereum-USDT rows. All four Zcash rows and all four Solana rows pass the shape check. The 14 Litecoin rows pass zero times as Bitcoin and, worse, are not validated at all on the Litecoin corridor: the checker has no Litecoin branch and returns ok rather than guessing. So the arithmetic is simple: a sanctioned address walks through our format check in almost every case, because a checksum is not a list. The lookup happens one layer up, at the pool, and its visible symptom is not a pop-up — it is an order that stays in confirming while a human reads a report. (SOL to XMR guide)
What the screening layer costs you in the spread
None of this is free, and the invoice is the quote. At 14:03-14:04 UTC on 30 October 2026, on 513 assets across 74 networks, USDT-TRC20 into Monero priced as follows: 100 units gave 0.14949272 XMR, which is 668.93 dollars per XMR against a spot of 586.41, so 14.1% over. 1,000 gave 1.65779448, 603.21 per XMR, 2.9% over. 10,000 gave 16.73603189, 597.51 per XMR, 1.9% over. 168,000, near the ceiling, gave 281.16413042, still 597.51 per XMR. The floor was 19.316279 and the ceiling 167,896.433875 — and nineteen seconds later the same endpoint reported 168,360.583786, because the ceiling is derived from live pool limits, not stored as a constant. Note what the curve says: past roughly 10,000 units, adding size buys no better rate, so the part of the spread you are paying for is fixed per order. Screening sits inside that fixed part, along with the deposit network fee and the pool's own margin. Treat every figure here as a writing-time snapshot; the live page is the only current one. (XMR to USDT exit)
Six checks you can run yourself, in this order
1) Pull the list, do not read about it. The file lives at sanctionslistservice.ofac.treas.gov, endpoint /api/PublicationPreview/exports/SDN.XML, and today it is 29 MB. Grep the exact address, including case, and remember that a prefix match will match nothing useful. 2) Match the chain, not the ticker. Seven USDT rows in that file are Bitcoin addresses; a Tron-only matcher silently skips them. 3) Validate format before you compare, and log that you dropped a row — one Monero row in eleven is not an address at all, and a silent drop looks identical to a clean result. 4) Read the programme, not just the hit: CYBER2 on a Bitcoin row and SDGT on a Tron row are different conversations, and a Tron row under FTO is a sanctions matter rather than a darknet-market one. 5) Do not translate "not listed" into "low risk". The list is binary and covers 0.56% of its own records; exposure scoring is a different product with different inputs, and neither of them sees a Monero balance. 6) Keep your own receipt: order id, deposit hash, amount, timestamp. If a score ever blocks you, that receipt is the only thing that can move the case, and no support desk can reconstruct it after the fact. (XMR to USDT exit)
If a score does stop you
There is no appeals window in a score, because there is no counterparty to appeal to: the number is produced by a vendor you have no contract with, consumed by a pool you also have no contract with. What you can control is the shape of the failure. Keep the deposit hash, keep the order id, and make sure the refund address is a wallet you hold keys for, on the same network as the asset you sent — a refund goes back on the original asset and network only, and a second deposit to the same address after expiry does not repair anything, it just adds a second unexplained inflow. And if your business depends on this, check the list yourself before you send, on a schedule, and record the file's publish date next to the result. "We screened it" without a version is not an audit, it is a memory. (recovering misdirected funds)
The honest summary is shorter than the marketing: the only part of an address risk score you can verify is a 29 MB XML file with 1,075 address rows in it, one of which is not an address and seven of which point at the wrong network; the rest is a private model whose outputs you receive and never inspect. That is not a reason to ignore scores. It is a reason to stop treating "clean" as a fact and start treating it as a claim someone else made.