2026-11-12

How to Check if an Address Is Blacklisted on USDT or USDC

Tether and Circle can both freeze an address, and the token contract itself keeps the list. So the check is short: you ask the contract. You don't need an API key or a Klap account for that part. You need the right contract address and the right function name, and the name isn't the same on every token.

Below is the manual check, the same check in code, and what Klap does with those reads. If you want the background on why issuers freeze addresses and what it does to a payout, can USDC be frozen? covers that.


Which function to call

Each contract exposes a read-only function that takes an address and returns true if it's restricted. The name changes between tokens, and on some networks there's no function at all.

Token Networks Function
USDC Ethereum, Base, Optimism, Arbitrum, Polygon, Avalanche, Arc isBlacklisted(address)
USDT Ethereum isBlackListed(address) (capital L)
USDT0 Polygon, Arbitrum, Avalanche isBlocked(address)
USDT Base, Optimism no public function
USDC and USDT BNB Chain no public function

The capital L is a real trap. isBlacklisted on Tether's Ethereum contract doesn't exist, so a script that reuses the USDC name either throws or, with a loose ABI, quietly returns nothing useful.

Where there's no function, the issuer can still freeze addresses. You just have no way to see it before a transfer fails.

USDT on TRON has a blacklist too. Klap isn't creating TRON charges at the moment, so we've left it out of this post.


By hand, on a block explorer

For a one-off check, open the token contract on the network's explorer, go to Contract, then Read Contract (or Read as Proxy for USDC, which sits behind a proxy), find the function from the table, paste the address and query it.

On Ethereum, that's USDC at 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 and USDT at 0xdAC17F958D2ee523a2206206994597C13D831ec7. On Base, USDC is 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913. Get the contract address from the issuer's own docs or your provider's token list. An address copied from a search result could belong to a lookalike token, and that contract will happily tell you nothing is frozen.

false means the contract won't refuse that address today. true means it will, in the direction the token enforces, and the next section is about that direction.


What "blacklisted" blocks on each token

USDC and USDT don't freeze the same way.

A blacklisted address on USDC can't send and can't receive. The contract checks both sides of every transfer and reverts if either one is on the list.

A blacklisted address on USDT can't send, but it can still receive. Tether's contracts only check the sender. A payment to a frozen USDT address goes through, and the money then stays there until Tether lifts the restriction. For a merchant that's arguably worse than a revert: nothing fails, and the funds are stuck anyway.

So "is this address blacklisted?" has two useful answers per pair: can it receive, and can it send out what it receives.


From your own code

If you pay out to addresses your users give you, check when they give it to you, not when the payout runs. One call per token and network is enough. With viem:

import { createPublicClient, http, parseAbi } from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({ chain: mainnet, transport: http() })
const abi = parseAbi([
'function isBlacklisted(address) view returns (bool)',
'function isBlackListed(address) view returns (bool)',
])
const usdcFrozen = await client.readContract({
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
abi,
functionName: 'isBlacklisted',
args: [address],
})
const usdtFrozen = await client.readContract({
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
abi,
functionName: 'isBlackListed',
args: [address],
})

Put a timeout on the read, because a slow RPC shouldn't block a signup. If the read errors or times out, record that you don't know, and don't treat the address as frozen. Cache the answer for a few minutes at most, since an issuer can add or lift a restriction at any time.


Through Klap

If the address is already a Klap recipient, the API does those reads for you on every enabled pair:

const report = await klap.recipients.getTransferability(recipient.id)
{
"recipientId": "rc_abc123",
"address": "0x1111111111111111111111111111111111111111",
"pairs": [
{ "token": "USDC", "network": "ethereum", "receive": "restricted", "send": "restricted" },
{ "token": "USDC", "network": "base", "receive": "allowed", "send": "allowed" },
{ "token": "USDT", "network": "ethereum", "receive": "allowed", "send": "restricted" },
{ "token": "USDT", "network": "base", "receive": "allowed", "send": "unknown" }
]
}

That's the example from the API reference, and it shows all the cases at once. The address is frozen on USDC on Ethereum, so both directions are restricted. It's frozen on USDT on Ethereum too, but USDT doesn't check receivers, so receive stays allowed while send is restricted. On USDT on Base there's no function to ask, so send is unknown.

The same check is in the CLI as klap recipients transferability <id>, and in Klap's MCP server as the recipients_transferability tool, if you'd rather ask an AI assistant. The endpoint is limited to 20 requests a minute per account, which is plenty for checking at registration and not meant for scanning a list.

Klap reads only what the token contract says. There's no Klap blocklist behind it and no third-party sanctions data. Each read has a one-second limit, a definite answer is kept for up to five minutes and an unknown for thirty seconds.


What Klap does with the answer

You don't have to call the endpoint for Klap to use it. When you create a charge, Klap checks that every address the charge pays can receive on every accepted pair, and answers 422 recipient_transfer_restricted naming the pairs that can't. Drop those from acceptedPayments and the charge goes through.

When the payout runs, Klap checks again. If a recipient was frozen after the charge was created, the payout waits instead of failing, and Klap tries again every hour. unknown never blocks either step.

Klap doesn't screen the payer. If the customer's own address is frozen, their wallet's transfer fails on-chain and the charge stays unpaid.


If an address comes back restricted

Only the issuer can lift a freeze. Klap can't, and neither can the owner of the address. What you can do is ask the person for a different address before money moves, or pay them in the other token if that one is open. A USDC-frozen address can usually still receive USDT, since USDT never checks who receives, and USDC vs USDT for merchants covers what else changes between the two.

Under the GENIUS Act, every permitted US issuer has to be able to freeze addresses, so this check will apply to any regulated dollar token you add later.