Skip to main content

Address Labels API: Identify Crypto Wallets

The Address Labels API tells you who is behind a blockchain address: which exchange owns a hot wallet, whether an address is a per-user deposit address, whether a contract is a token (or a clone imitating one), and whether an address has been frozen by a stablecoin issuer. One GraphQL query, Metadata { Labels }, answers this across EVM chains, Solana, Bitcoin, and Tron.

CubeMetadata.Labels
Endpointshttps://streaming.bitquery.io/graphql
AuthOAuth token as Authorization: Bearer <token>
RequiredAn Address filter on every query
Batch sizeUp to 100 addresses per query
StreamingQuery-only, no subscription. Poll RecordedAt for newly added labels

Most people use it as the second step of a two-query pattern: pull addresses from an activity API such as DEXTrades or Transfers, then resolve those addresses to entities here. Use the result to enrich analytics, screen flows for exchange or gambling exposure, or strip exchange and contract addresses out of "real user" metrics.

Rules that matter​

  1. The Address filter is mandatory. Every query must pin Address with is or in. There is no way to list all addresses carrying a label. Without it you get: "Labels query requires a Address filter in the where clause".
  2. Up to 100 addresses per query. Split larger lists into batches of 100.
  3. Matching is exact and case-sensitive. Pass EVM addresses in lowercase. A checksummed 0xAbC… returns zero rows. Bitcoin, Solana, and Tron addresses are case-sensitive by nature, so pass them exactly as they appear on-chain.
  4. Labels can be corrected or removed, and old versions are not kept. An address returns one row per chain, per label, per recording pass, so the same label can recur with different RecordedAt values. Fold that into a current view with limitBy, shown below. When we find a wrong label we fix or delete it in place, so re-run a full lookup to pick up corrections.

How to look up labels for one address​

{
Metadata {
Labels(
where: {Address: {in: ["0x18e296053cbdf986196903e889b7dca7a73882f6"]}}
) {
Address
Chain
Label {
Type
Value
}
RecordedAt
}
}
}

The response identifies the address as a Bybit hot wallet on every chain where it is labeled (abridged; the full response also repeats labels recorded on earlier passes):

{
"Metadata": {
"Labels": [
{
"Address": "0x18e296053cbdf986196903e889b7dca7a73882f6",
"Chain": "ethereum",
"Label": { "Type": "cex-hot-wallet", "Value": "bybit-hot-1" },
"RecordedAt": "2026-07-31T13:11:13Z"
},
{
"Address": "0x18e296053cbdf986196903e889b7dca7a73882f6",
"Chain": "bsc",
"Label": { "Type": "cex-hot-wallet", "Value": "bybit-hot" },
"RecordedAt": "2026-07-22T07:53:03Z"
}
]
}
}

How to get only the current labels​

Because the same label can come back once per recording pass, most applications want the latest record per address, chain, and label type. limitBy plus a RecordedAt sort does exactly that, and this is the shape you should reach for by default:

{
Metadata {
Labels(
where: {Address: {is: "0x18e296053cbdf986196903e889b7dca7a73882f6"}}
limitBy: {by: [Address, Chain, Label_Type], count: 1}
orderBy: {descending: RecordedAt}
) {
Address
Chain
Label {
Type
Value
}
RecordedAt
}
}
}

This returns one clean row per chain instead of the wallet's full recording history. Note the nested field is addressed as Label_Type in limitBy and orderBy. The groupable and sortable names are Address, Chain, Label_Type, Label_Value, and RecordedAt.

Keep Label_Type in the limitBy key unless you deliberately want one row per address: an address can legitimately carry several different labels, since a token contract is often tagged both token-contract and contract.

How to label up to 100 addresses at once​

Pass the list as a variable. This is the shape to use when enriching the output of another query, such as top traders, transfer counterparties, or token holders:

query ($addresses: [String!]) {
Metadata {
Labels(
where: {Address: {in: $addresses}}
limitBy: {by: [Address, Chain, Label_Type], count: 1}
orderBy: {descending: RecordedAt}
) {
Address
Chain
Label {
Type
Value
}
RecordedAt
}
}
}
{
"addresses": [
"0x18e296053cbdf986196903e889b7dca7a73882f6",
"0x28c6c06298d514db089934071355e5743bf21d60"
]
}

Addresses with no labels are simply absent from the response, with no error and no placeholder row. Anything missing is "unlabeled so far", which in wallet analytics usually means an ordinary user wallet.

How to check if an address is an exchange wallet​

Combine a batch with a Label.Type filter to keep only exchange-owned addresses:

{
Metadata {
Labels(
where: {
Address: {in: [
"0x28c6c06298d514db089934071355e5743bf21d60",
"0x18e296053cbdf986196903e889b7dca7a73882f6",
"0x7a250d5630b4cf539739df2c5dacb4c659f2488d"
]}
Label: {Type: {in: ["cex-hot-wallet", "cex-cold-wallet", "cex-deposit-address"]}}
Chain: {is: "ethereum"}
}
limitBy: {by: [Address, Label_Type], count: 1}
orderBy: {descending: RecordedAt}
) {
Address
Label {
Type
Value
}
RecordedAt
}
}
}

Only the Binance and Bybit hot wallets come back. The Uniswap router drops out because its labels (contract: uniswap) don't match the requested types, which is exactly how you separate exchange addresses from protocol contracts in a mixed list.

How to screen for issuer-blocked (frozen) addresses​

Stablecoin issuers freeze addresses on their own contracts. issuer-blocked-usdt and issuer-blocked-usdc capture those, so a startsWith filter screens for both at once:

{
Metadata {
Labels(
where: {
Address: {in: [
"0x098b716b8aaf21512996dc57eb0615e2383e2f96",
"0x28c6c06298d514db089934071355e5743bf21d60"
]}
Label: {Type: {startsWith: "issuer-blocked"}}
}
limitBy: {by: [Address, Label_Type], count: 1}
orderBy: {descending: RecordedAt}
) {
Address
Chain
Label {
Type
Value
}
}
}
}

How to label Bitcoin, Tron, and Solana addresses​

Address formats mix freely in one batch. Each row's Chain tells you where the label applies:

{
Metadata {
Labels(
where: {Address: {in: [
"34xp4vRoCGJym3xR7yCVPFHoCNxv4Twseo",
"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"
]}}
limitBy: {by: [Address, Chain, Label_Type], count: 1}
orderBy: {descending: RecordedAt}
) {
Address
Chain
Label {
Type
Value
}
}
}
}

This resolves a Binance Bitcoin cold wallet (Chain: "bitcoin"), the USDT contract on Tron (Chain: "tron"), and a Binance cold wallet on Solana (Chain: "solana").

How to watch for newly added labels​

The cube has no subscription, so poll the addresses you track with a RecordedAt window and keep the high-water mark on your side:

{
Metadata {
Labels(
where: {
Address: {is: "0x18e296053cbdf986196903e889b7dca7a73882f6"}
RecordedAt: {since: "2026-07-15T00:00:00Z"}
}
) {
Chain
Label {
Type
Value
}
RecordedAt
}
}
}

This window catches newly added labels only. Labels can be corrected or removed when we find errors, and earlier versions are not kept. A label we fix can keep its old RecordedAt, so it never enters the window, and a label we remove simply disappears. To catch corrections, re-run a full lookup on the addresses you track from time to time and compare it with what you stored.

How to aggregate labels​

The cube supports the standard metrics count and uniq, and the dimensions you select become the grouping key. This counts label records and distinct labeled addresses per chain:

{
Metadata {
Labels(
where: {Address: {in: [
"0x18e296053cbdf986196903e889b7dca7a73882f6",
"0x28c6c06298d514db089934071355e5743bf21d60",
"0xdac17f958d2ee523a2206206994597c13d831ec7"
]}}
) {
Chain
count
uniq(of: Address)
}
}
}

Filters​

Full filter support applies on top of the mandatory Address:

FilterOperatorsNotes
Addressis, in onlyMandatory, max 100 in in. No negation, so you cannot exclude addresses server-side.
Chainis, in, not, notIn, like, includes, startsWith, …Exact chain slugs, listed in the table below.
Label: {Type: …}full string sete.g. {is: "cex-hot-wallet"}
Label: {Value: …}full string sete.g. {startsWith: "binance"}
RecordedAtsince, till, after, before, is, plus _relative variantsStandard DateTime filters.
anylist of sub-filtersOR-combinator across conditions.

Response fields​

FieldTypeMeaning
AddressStringThe queried address, exactly as stored (EVM addresses lowercase).
ChainStringChain this label applies to. One address can map to many chains.
Label.TypeStringLabel category, listed below.
Label.ValueStringEntity slug within the category, e.g. binance-hot-1, bybit-hot, wavax, banned-by-usdt. Numbered suffixes distinguish instances of the same entity.
RecordedAtDateTimeWhen the row was added to Bitquery's label table. Most Bitcoin labels carry 28 May 2026, the date of the first bulk load, so it does not tell you when an address was first identified. A corrected label can keep its old RecordedAt.

Supported chains​

Chain values are plain slugs and the set grows as coverage expands. Verified live:

EcosystemChain values
EVMethereum, bsc, polygon, arbitrum, base, avalanche-c, fantom, ethpow
Non-EVMbitcoin, tron, solana

If you're unsure what a chain is called, query a known address from it without a Chain filter and read the slug off the response.

Label types​

Label.Type is an open taxonomy. New categories appear as the pipeline learns new entity classes, so handle unknown types gracefully. The common ones:

Label.TypeMeaningExample Value
cex-hot-walletExchange-operated hot walletbinance-hot-1, bybit-hot
cex-cold-walletExchange cold storagebinance-cold
cex-deposit-addressPer-user deposit address swept to an exchangecoinex-deposit
token-contractA token's contract or mint addressusdt, wavax, wbnb
token-cloneContract imitating a well-known tokenclone-wmatic-2
contractGeneral smart-contract taguniswap, bridge, stablecoin
gamblingGambling platform walletstake-com-hot
issuer-blocked-usdtFrozen or blacklisted by Tetherbanned-by-usdt
issuer-blocked-usdcFrozen or blacklisted by Circlebanned-by-usdc

Limits and common errors​

  • Missing Address filter. The query fails with "Labels query requires a Address filter in the where clause". A Chain or Label filter alone does not satisfy it.
  • Wrong casing. A checksummed EVM address silently returns zero rows. Lowercase before querying.
  • No negation on Address. not/notIn aren't part of the Address filter. Exclusion belongs client-side, or in Chain/Label filters, which do support it.
  • Subscriptions. subscription { Metadata { … } } is rejected; the cube exists only under query.
  • Empty result is not an error. Unlabeled addresses, and an empty in: [], return an empty list.
  • limit / limitBy and orderBy behave as on every other cube.

Pick the right query​

You needUse
Who is behind one addressAddress: {is: …}
Enrich a list of ≤100 addressesAddress: {in: …} with limitBy: {by: [Address, Chain, Label_Type], count: 1}
Exchange-wallet screeningLabel: {Type: {in: ["cex-hot-wallet", "cex-cold-wallet", "cex-deposit-address"]}}
Fake-token checksLabel: {Type: {is: "token-clone"}}
Sanctions and issuer-freeze screeningLabel: {Type: {startsWith: "issuer-blocked"}}
Newly added labelsPoll with RecordedAt: {since: …}, plus a periodic full lookup for corrections

Frequently Asked Questions

What is the Bitquery Address Labels API?

It is the Metadata.Labels cube in Bitquery's GraphQL API. It maps a blockchain address to the real-world entity behind it across EVM chains, Solana, Bitcoin and Tron. Entities include exchange hot and cold wallets, per-user deposit addresses, token contracts and clones, gambling platforms, and addresses frozen by stablecoin issuers.

Is the Address filter required?

Yes. Every Labels query must pin the Address field with is or in. Without it the API returns the error 'Labels query requires a Address filter in the where clause'. A Chain or Label filter alone does not satisfy the requirement.

How many addresses can I look up in one query?

Up to 100 addresses per query in the Address in list. To enrich a larger set, split it into batches of 100 and issue one query per batch.

Can I list every address that has a given label?

No. Because the Address filter is mandatory, the API answers 'what labels does this address have' rather than 'which addresses have this label'. You supply the candidate addresses, then filter them by label type or value.

Why does my address return no labels?

Either the address genuinely has no label yet, or the casing is wrong. Matching is exact and case-sensitive, so EVM addresses must be lowercase. A checksummed address returns zero rows. Unlabeled addresses are simply absent from the response rather than returning an error.

Why does one address return multiple rows with the same label?

Labels are recorded in passes, and the same label can come back with a new RecordedAt value on a later pass. An address can also be labeled on several chains. Use limitBy with the key Address, Chain, Label_Type and count 1, ordered by descending RecordedAt, to get one current row per label. Labels can also be corrected or removed when we find errors, and earlier versions are not kept.

Can I subscribe to label updates in real time?

No. The Metadata cube is query-only and does not exist under the GraphQL subscription root, so labels cannot be streamed. To pick up newly added labels, poll the addresses you track with a RecordedAt since filter and keep the high-water mark on your side. That filter does not flag corrections. A label we fix can keep its old RecordedAt, and a label we remove simply disappears, so re-run a full lookup on those addresses from time to time.

Which blockchains have address labels?

Verified chains include Ethereum, BSC, Polygon, Arbitrum, Base, Avalanche C-Chain, Fantom and EthereumPoW on the EVM side, plus Bitcoin, Tron and Solana. The set grows as label coverage expands, and a single address can carry different labels on different chains.

Build with Bitquery

Ready to run this in production?

Get an API key and run these queries in minutes, or talk to us about plans and enterprise delivery.