Metadata-Version: 2.4
Name: ofac-sdn-parser
Version: 1.0.0
Summary: Fetch and parse the U.S. Treasury OFAC SDN list into crypto addresses. No API key.
Author: OmniTrace
License: MIT
Project-URL: Homepage, https://omnitraceapp.com
Project-URL: Repository, https://github.com/omnitraceapp/ofac-sdn-parser
Keywords: ofac,sdn,sanctions,compliance,aml,crypto,blockchain
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Security
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aiohttp>=3.9
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# ofac-sdn-parser

Fetch and parse the **U.S. Treasury OFAC SDN list** into crypto addresses. No API key, no account —
the export is public.

```python
import asyncio
from ofac_sdn_parser import fetch_sanctioned_addresses

rows = asyncio.run(fetch_sanctioned_addresses())
print(len(rows))                       # ~990
print(rows[0].address, rows[0].network, rows[0].entity_name, rows[0].program_label)
```

```bash
pip install git+https://github.com/omnitraceapp/ofac-sdn-parser
python -m ofac_sdn_parser.verify              # counts + per-chain breakdown + digest
python -m ofac_sdn_parser.verify --json       # machine-readable
python -m ofac_sdn_parser.verify --file SDN_ENHANCED.XML   # diff against a downloaded export
```

## Why this exists

Every guide older than mid-2026 points at an endpoint that no longer works, and the two failures
that follow are easy to make and hard to notice — both of them look like *"OFAC has no crypto
addresses today"*.

**1. The old endpoints are gone.** Verified 2026-09-06:

| URL | status |
|---|---|
| `sanctionslist.ofac.treas.gov/api/sdn` (old JSON API) | **403 Forbidden** |
| `www.treasury.gov/ofac/downloads/sdn.xml` (legacy) | **302** to the new service |
| `sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/SDN.XML` | **200** ✅ |

The working URL redirects once more to a signed S3 object. Any HTTP client that follows redirects
is fine.

**2. `Element.iter()` silently returns nothing.** The export uses a default XML namespace. Python's
`iter()` does plain string matching and does **not** honour the `{*}` wildcard; only the ElementPath
methods do.

```python
root.iter("{*}sdnEntry")      # -> nothing, no error
root.iterfind("{*}sdnEntry")  # -> works
```

That one costs an afternoon if you hit it, and it fails quietly — you get an empty list, not an
exception.

## Proving a parse is complete

Non-empty is not the same as complete. `address_set_digest()` is an MD5 over the sorted,
de-duplicated address set: compute it over your output and over Treasury's own export, and if the
two agree you are reading the same list.

```python
from ofac_sdn_parser import address_set_digest
print(address_set_digest(rows))
```

The bundled verifier does this for you and prints the per-chain breakdown:

```
addresses:        991
  bitcoin        536
  tron           282
  ethereum       124
  unknown         27
  litecoin        12
  dash             5
  zcash            4
  xrp              1
sdn entries read: 19,329
duplicates:       16
skipped (blank):  0
digest (md5):     3c29b9cfef715f206bcb8c8ec8dd8356
```

That is a real run against the live feed on 6 September 2026. **The digest is not a constant to
match** - OFAC designates and delists continually, so it changes. It is a fingerprint for comparing
two parses of the *same* download.

## Notes worth knowing

- **Do not switch to `SDN_ENHANCED.XML`.** It is 103 MB against 28 MB and contains exactly the same
  addresses — the extra content is entity metadata and ownership relationships, not more wallets.
- **OFAC publishes deltas.** `/changes/latest` is ~15 KB against the 28 MB full export, and
  `/changes/history/{year}[/{month}[/{day}]]` lists publication ids. Crypto designations are rare —
  roughly one publication in eight carries any. Deltas use the namespace
  `https://www.treasury.gov/ofac/DeltaFile/1.0` and mark each entity `action="add"` / `"remove"`.
- **`skipped_blank` should be zero.** A crypto row with a valid `idType` and no `idNumber` means the
  schema moved and addresses are being dropped. The parser reports the count instead of swallowing it.
- **`network` is `unknown` rather than a guess.** A wrong chain label sends a lookup to the wrong
  explorer, which is worse than no label.
- **An absence of match is not a clean result.** OFAC covers US-designated entities and nothing else.

## Install

```bash
pip install git+https://github.com/omnitraceapp/ofac-sdn-parser
```

Not on PyPI yet. Requires Python 3.10+ and `aiohttp` — though `parse_sdn_xml` itself is pure and
needs neither, so you can vendor the one file if you already have the XML.

## Tests

```bash
pip install -e ".[dev]"
pytest
```

The suite runs against a bundled fixture and makes no network calls.

## Licence

MIT.

---

Maintained by [OmniTrace](https://omnitraceapp.com), which tracks USDT and USDC blacklist events on
TRON and Ethereum and screens wallets against this list. The same parser runs in production there;
[measured statistics are published here](https://omnitraceapp.com/stats).
