Screen a wallet against OFAC (free, anonymous)

2026-05-27 · Docs ofac free-tier trust-check

POST /v1/trust-check/ofac returns an allow or block verdict for any wallet address, screened against the U.S. Treasury OFAC SDN list. Anonymous, free, rate-limited at 1 r/s + burst 3, refreshed daily from the Treasury XML feed.

Live status: see /health for current version, list refresh timestamp, and recent error count.

Why this vs alternatives

cURL

curl -X POST https://swap.paladinfi.com/v1/trust-check/ofac \
  -H "content-type: application/json" \
  -d '{"address":"0x0000000000000000000000000000000000000000"}'

Allow response (full live shape):

{
  "address": "0x0000000000000000000000000000000000000000",
  "chainId": 8453,
  "trust": {
    "recommendation": "allow",
    "factors": [
      {"source":"ofac","signal":"not_listed","weight":0,"details":"","real":true}
    ],
    "version": "1.1",
    "_real": true,
    "_scope": "ofac-only (wallet-address screen; use /v1/trust-check for full composition: GoPlus + Etherscan + anomaly heuristics)",
    "_ofac_list_updated_at": "2026-05-27T04:07:49Z",
    "_ofac_sdn_count": 93
  },
  "_paid_endpoint_info": {
    "url": "https://swap.paladinfi.com/v1/trust-check",
    "method": "POST",
    "auth": "x402 (USDC EIP-3009 transferWithAuthorization on Base)",
    "price_usdc": "0.001",
    "plugins": {
      "elizaos": "https://www.npmjs.com/package/@paladinfi/eliza-plugin-trust",
      "agentkit": "https://www.npmjs.com/package/@paladinfi/agentkit-actions"
    },
    "docs": "https://paladinfi.com/trust-check/"
  },
  "_signature": "y8Uq/g0AmP+RC6yG…KJDg==",
  "_signature_alg": "ed25519",
  "_signature_pubkey_hex": "272b6b62230d9da810f3ed64b5e5147f1ae062cb46ad7f25044b0aab1d18fb6f"
}

The three top-level _signature* fields are an Ed25519 signature over the rest of the response — you can verify a verdict wasn't tampered with in transit before acting on it. See Verify a PaladinFi response for the canonical-JSON contract and copy-paste verifiers in Python and JavaScript. Existing consumers can safely ignore these fields.

Block response (sanctioned wallet — abbreviated):

{
  "trust": {
    "recommendation": "block",
    "factors": [
      {"source":"ofac","signal":"listed","weight":100,"details":"OFAC SDN listed","real":true}
    ],
    "version": "1.1",
    "_real": true
  }
}

Branch on trust.recommendation"allow", "block", or "warn" (returned with HTTP 200 when the SDN screen can't complete; treat it like "block" in compliance flows — fail-closed, never a silent allow). The _real: true field is your machine-verifiable signal that this is live screening (not a fixture). The chainId: 8453 field is informational only — OFAC SDN entries are EOA addresses screened identically across chains.

Drop-in React hook

import { useEffect, useState } from "react";

export function useOfacScreen(address?: `0x${string}`) {
  const [verdict, setVerdict] = useState<"allow" | "block" | "warn" | "idle" | "loading" | "error">(
    address ? "loading" : "idle"
  );
  useEffect(() => {
    if (!address) { setVerdict("idle"); return; }
    setVerdict("loading");
    const ctrl = new AbortController();
    fetch("https://swap.paladinfi.com/v1/trust-check/ofac", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ address }),
      signal: ctrl.signal,
    })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => {
        // Map the three real verdicts explicitly; anything unexpected (incl. a
        // missing body) becomes "error" so it fail-closes below — never a silent allow.
        const rec = d?.trust?.recommendation;
        setVerdict(rec === "allow" || rec === "block" || rec === "warn" ? rec : "error");
      })
      .catch(() => { if (!ctrl.signal.aborted) setVerdict("error"); });
    return () => ctrl.abort();
  }, [address]);
  return verdict;
}

Use in a wallet-gate (fail-closed default for compliance use):

const verdict = useOfacScreen(connectedAddress);
// Fail-closed: block on warn/error/loading too. "warn" = the SDN screen
// couldn't complete (source briefly unreachable) — treat it like a block.
if (verdict === "block" || verdict === "warn" || verdict === "error" || verdict === "loading") {
  return <SanctionsBlockedMessage />; // replace with your component
}
// Fail-open is appropriate only for non-compliance UX gates where false-blocks
// cost more than false-allows.

When you need more

For token-contract risk (proxy detection, holder concentration, mint authority, transfer hooks, recently-deployed flag, multi-source composition with fail-closed contract), use the paid /v1/trust-check endpoint:

Caching + rate limits

Scope of coverage

Scope is OFAC SDN cryptocurrency-tagged wallet addresses only (~93 entries as of 2026-05-27). Not the EU consolidated list, not UK OFSI, not UN sanctions, not token contracts, not entity names, not country-level sanctions. For broader coverage, layer additional providers.

This endpoint surfaces the signal. Your dApp decides policy.

Plugins + MCP

Last Updated: 2026-06-12 · API version: 1.1 · Service version: 0.11.79