---
title: "Wallet API"
description: "Token holder rankings, address portfolios and trades over REST, with the same key, quota and pricing as the On-chain DEX API"
url: "https://blockreq.com/docs/build/wallet-api/"
---

# Wallet API

The **Wallet API** answers wallet questions over REST: who holds a token, what an address holds and what it is worth in USD, and which addresses traded in a pool. Per-position PnL and average cost are [coming soon](#coming-soon-positions-and-pnl).

The Wallet API and the [On-chain DEX API](https://blockreq.com/docs/build/onchain-dex-api/index.md) are the two products of the **Crypto Data API**. They share everything except the product page:

- **One key.** The key of your On-chain DEX API endpoint calls both. There is no separate Wallet API endpoint or key.
- **One quota and rate limit.** Wallet API calls count against the same Crypto Data API quota, separate from RPC Requests.
- **One price list.** There is no Wallet API price list; [Crypto Data API pricing](https://blockreq.com/crypto-data-api/pricing) covers both.

```
https://api.blockreq.com/v1/dex
```

Supported chains: **Ethereum** (`ethereum`), **Base** (`base`) and **Arbitrum** (`arbitrum`). Holder rankings on Base are still being backfilled (November 2026), so every Base token answers `404` on the holders route for now.

## Authentication

Create an **On-chain DEX API** endpoint in the Dashboard (**Endpoints → + Add Endpoint**) and send its key in the `X-API-Key` header. `Authorization: Bearer <key>` works too. Key scope, the RPC-key transition and anonymous access are the same as for the On-chain DEX API: see [Get a key](https://blockreq.com/docs/build/onchain-dex-api/index.md).

```bash
export BLOCKREQ_DATA_API_KEY="your On-chain DEX API key"
```

Every request in this page counts **1 call**. Quota, rate limits, `429` handling and the usage header are described once for both products in [Plans and quota](https://blockreq.com/docs/build/onchain-dex-api/index.md) and [Limits and 429](https://blockreq.com/docs/build/onchain-dex-api/index.md).

You can also try the holders and portfolio routes without a key in the tester on the [Wallet API product page](https://blockreq.com/wallet-api).

## Routes

| Method and path                                    | What it returns                                  | Status      |
| -------------------------------------------------- | ------------------------------------------------ | ----------- |
| `GET /v1/dex/{chain}/tokens/{address}/holders`     | The largest holders of a tracked token           | Live        |
| `GET /v1/dex/{chain}/accounts/{address}/portfolio` | The tokens an address holds, with USD value      | Live        |
| `GET /v1/dex/{chain}/pools/{id}/trades`            | A pool's trades, each with the trader's address  | Live        |
| `GET /v1/dex/{chain}/traders/{address}/positions`  | A trader's positions, average cost, realized and unrealized PnL | Coming soon |

Amounts are strings: `balance` and other raw amounts are integers in base units (no decimals applied); `amount`, prices and USD values are decimals. Addresses in responses are lowercase.

## Token holders

```
GET /v1/dex/{chain}/tokens/{address}/holders
```

The top `limit` holders of a token, counted from its ERC-20 Transfers. Burn addresses, Uniswap v4 PoolManagers and the pools holding the token are left out of the ranking and of the total that `pct` is a share of.

Only tracked tokens have a ranking. An anchor token (stablecoins and majors such as WETH and USDC), a token in no indexed pool, a blocked token, or one that has not caught up with the chain yet answers `404`. A newly tracked token is backfilled from the chain's genesis and answers `404` until that is done.

| Parameter | In    | Description                                    |
| --------- | ----- | ---------------------------------------------- |
| `chain`   | path  | `ethereum`, `base` or `arbitrum`               |
| `address` | path  | The token, `0x` and 40 hex digits, in any case |
| `limit`   | query | Most holders returned, 1–5000 (default 5000)   |

**Response fields** (`data`):

| Field                | Type                   | Description                                                                                                                   |
| -------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `chain`, `address`   | string                 | The chain and the token                                                                                                       |
| `symbol`, `decimals` | string, integer / null | Token metadata                                                                                                                |
| `block`              | integer                | Transfers up to this block are counted                                                                                        |
| `known_total`        | raw amount             | Sum of every balance besides the excluded addresses'; the denominator of `pct`                                                |
| `known_total_amount` | decimal / null         | `known_total` in whole tokens; null when the decimals are unknown                                                             |
| `total_supply`       | raw amount / null      | `totalSupply()` read from BlockReq's own node; null when it could not be read                                                 |
| `consistent`         | boolean / null         | Whether the Transfers add up to within 1% of `total_supply`. When false (rebasing tokens and the like), every `pct` is null |
| `excluded`           | string[]               | The burn addresses, PoolManagers and pools left out                                                                           |
| `top[]`              | object[]               | Largest balance first: `rank` (1 for the largest), `address`, `balance` (raw), `amount` (whole tokens), `pct` (to 6 decimals) |

**Examples:** the 20 largest ARB holders on Arbitrum.

```bash
curl -s "https://api.blockreq.com/v1/dex/arbitrum/tokens/0x912ce59144191c1204e64559fe8253a0e49e6548/holders?limit=20" \
  -H "X-API-Key: $BLOCKREQ_DATA_API_KEY"
```

```js
const res = await fetch(
  "https://api.blockreq.com/v1/dex/arbitrum/tokens/0x912ce59144191c1204e64559fe8253a0e49e6548/holders?limit=20",
  { headers: { "X-API-Key": process.env.BLOCKREQ_DATA_API_KEY } },
);
const { data } = await res.json();
for (const holder of data.top) console.log(holder.rank, holder.address, holder.pct);
```

```python
import os, requests

res = requests.get(
    "https://api.blockreq.com/v1/dex/arbitrum/tokens/0x912ce59144191c1204e64559fe8253a0e49e6548/holders",
    params={"limit": 20},
    headers={"X-API-Key": os.environ["BLOCKREQ_DATA_API_KEY"]},
    timeout=30,
)
for holder in res.json()["data"]["top"]:
    print(holder["rank"], holder["address"], holder["pct"])
```

A response with `limit=2` (the `excluded` list is shortened here; for ARB it lists every pool holding the token):

```json
{
  "data": {
    "chain": "arbitrum",
    "address": "0x912ce59144191c1204e64559fe8253a0e49e6548",
    "symbol": "ARB",
    "decimals": 18,
    "block": 522352929,
    "known_total": "9982381908048402768748531744",
    "known_total_amount": "9982381908.048402768748531744",
    "excluded": [
      "0x0000000000000000000000000000000000000000",
      "0x000000000000000000000000000000000000dead",
      "0xdead000000000000000042069420694206942069"
    ],
    "total_supply": "9999998977610261816650915825",
    "consistent": true,
    "top": [
      {
        "rank": 1,
        "address": "0xf3fc178157fb3c87548baa86f9d24ba38e649b58",
        "balance": "2556141991509656323975221470",
        "amount": "2556141991.50965632397522147",
        "pct": "25.606534"
      },
      {
        "rank": 2,
        "address": "0xf7f468b184a48f6ca37eeffe12733ee1c16b6e26",
        "balance": "449339761507879669910372383",
        "amount": "449339761.507879669910372383",
        "pct": "4.501328"
      }
    ]
  }
}
```

## Address portfolio

```
GET /v1/dex/{chain}/accounts/{address}/portfolio
```

The tokens an address holds on the chain, from two sources only:

1. the balances the holder rankings store: tracked tokens whose top 5,000 holders include the address (`source: "holders"`);
2. `balanceOf` read from BlockReq's own node for the tokens you list in `tokens` (`source: "node"`), which replaces the stored balance of those tokens; a zero balance drops the token.

There is no full wallet index: a token that is neither tracked with the address in its top 5,000 nor listed in `tokens` does not appear. Holdings are sorted by largest USD value, holdings without one last. An address with nothing found answers `200` with an empty `holdings` list.

| Parameter | In    | Description                                                                                                                                         |
| --------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `chain`   | path  | `ethereum`, `base` or `arbitrum`                                                                                                                    |
| `address` | path  | The holder, `0x` and 40 hex digits, in any case                                                                                                     |
| `tokens`  | query | Up to 50 comma-separated ERC-20 addresses (duplicates count once) to read with `balanceOf`. The native coin's address is refused |

**Response fields** (`data`):

| Field                       | Type              | Description                                                                         |
| --------------------------- | ----------------- | ----------------------------------------------------------------------------------- |
| `chain`, `address`          | string            | The chain and the holder                                                            |
| `total_value_usd`           | decimal           | Sum of every `value_usd`, to 6 decimals; holdings without one are not counted       |
| `holdings[]`                | object[]          | See below                                                                           |
| `node_block`                | integer / null    | Block of the node's `balanceOf` reads; null when none was made                      |
| `failed`                    | string[]          | Listed tokens whose `balanceOf` failed; a stored balance is kept for them           |
| `holdings[].token`          | string            | The token                                                                           |
| `holdings[].symbol`, `name` | string / null     | Token metadata                                                                      |
| `holdings[].decimals`       | integer / null    |                                                                                     |
| `holdings[].balance`        | raw amount        |                                                                                     |
| `holdings[].amount`         | decimal / null    | The balance in whole tokens; null when the decimals are unknown                     |
| `holdings[].price_usd`      | decimal / null    |                                                                                     |
| `holdings[].value_usd`      | decimal / null    | `amount` × `price_usd`, to 6 decimals; null when either is missing                  |
| `holdings[].source`         | `holders`, `node` | Stored ranking balance, or `balanceOf` on BlockReq's node                           |
| `holdings[].block`          | integer           | Block the balance is as of                                                          |

**Examples:** an exchange wallet on Ethereum, plus a live USDC balance.

```bash
curl -s "https://api.blockreq.com/v1/dex/ethereum/accounts/0x28c6c06298d514db089934071355e5743bf21d60/portfolio?tokens=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" \
  -H "X-API-Key: $BLOCKREQ_DATA_API_KEY"
```

```js
const url = new URL("https://api.blockreq.com/v1/dex/ethereum/accounts/0x28c6c06298d514db089934071355e5743bf21d60/portfolio");
url.searchParams.set("tokens", "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48");
const res = await fetch(url, { headers: { "X-API-Key": process.env.BLOCKREQ_DATA_API_KEY } });
const { data } = await res.json();
console.log(data.total_value_usd, data.holdings.length);
```

```python
import os, requests

res = requests.get(
    "https://api.blockreq.com/v1/dex/ethereum/accounts/0x28c6c06298d514db089934071355e5743bf21d60/portfolio",
    params={"tokens": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"},
    headers={"X-API-Key": os.environ["BLOCKREQ_DATA_API_KEY"]},
    timeout=30,
)
data = res.json()["data"]
print(data["total_value_usd"], len(data["holdings"]))
```

A response without `tokens` (the first 2 of 12 holdings shown):

```json
{
  "data": {
    "chain": "ethereum",
    "address": "0x28c6c06298d514db089934071355e5743bf21d60",
    "total_value_usd": "133198.620099",
    "holdings": [
      {
        "token": "0x15d4c048f83bd7e37d49ea4c83a07267ec4203da",
        "symbol": "GALA",
        "name": "Gala",
        "decimals": 8,
        "balance": "496270440907221534",
        "amount": "4962704409.07221534",
        "price_usd": "0.0000268169615577973807",
        "value_usd": "133084.653361",
        "source": "holders",
        "block": 26369822
      },
      {
        "token": "0x03ab458634910aad20ef5f1c8ee96f1d6ac54919",
        "symbol": "RAI",
        "name": "Rai Reflex Index",
        "decimals": 18,
        "balance": "33000000000000000000",
        "amount": "33",
        "price_usd": "3.01662215219692683",
        "value_usd": "99.548531",
        "source": "holders",
        "block": 26369822
      }
    ],
    "node_block": null,
    "failed": []
  }
}
```

Listing `tokens` on a chain without a node configured, or when the node does not answer, gives `503 unavailable`; the message says which. Repeat the request without `tokens` to get the stored balances.

## Pool trades

```
GET /v1/dex/{chain}/pools/{id}/trades
```

The same route the [On-chain DEX API](https://blockreq.com/docs/build/onchain-dex-api/index.md) documents; for wallet use, each trade carries `trader`, the transaction's sender. Newest first. A full page comes with `next_cursor`; pass it as `cursor` for the next page. Raw trades are kept for 90 days for a listed pool and 30 days for any other pool; older history is in the candles.

| Parameter | In    | Description                                                                         |
| --------- | ----- | ----------------------------------------------------------------------------------- |
| `chain`   | path  | `ethereum`, `base` or `arbitrum`                                                    |
| `id`      | path  | The pool: `0x` and 40 (pool contract) or 64 (Uniswap v4 pool key) hex digits        |
| `limit`   | query | Most trades per page, 1–100 (default 50)                                            |
| `cursor`  | query | The `next_cursor` of the previous page; any other value answers `400`               |

**Response fields:** `data[]` holds trades with `block_number`, `log_index`, `time` (ISO 8601), `tx_hash`, `trader` (address or null), `amount0` and `amount1` (raw, in the pool's view: positive means the pool received it), `price` (whole token1 per whole token0), `amount_usd`, `price0_usd` and `price1_usd` (decimal or null). `next_cursor` is null on the last page. The response also carries the `X-Data-Tier` header.

The OpenAPI document also defines `earliest_available` (Unix seconds where the kept window starts). As of 2026-11-08 the production API does not return it yet; do not depend on it until it appears.

```bash
curl -s "https://api.blockreq.com/v1/dex/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/trades?limit=20" \
  -H "X-API-Key: $BLOCKREQ_DATA_API_KEY"
```

```js
let cursor = null;
do {
  const url = new URL("https://api.blockreq.com/v1/dex/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/trades");
  url.searchParams.set("limit", "100");
  if (cursor) url.searchParams.set("cursor", cursor);
  const res = await fetch(url, { headers: { "X-API-Key": process.env.BLOCKREQ_DATA_API_KEY } });
  const page = await res.json();
  for (const trade of page.data) console.log(trade.time, trade.trader, trade.amount_usd);
  cursor = page.next_cursor;
} while (cursor);
```

```python
import os, requests

res = requests.get(
    "https://api.blockreq.com/v1/dex/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/trades",
    params={"limit": 20},
    headers={"X-API-Key": os.environ["BLOCKREQ_DATA_API_KEY"]},
    timeout=30,
)
for trade in res.json()["data"]:
    print(trade["time"], trade["trader"], trade["amount_usd"])
```

A response with `limit=1`:

```json
{
  "data": [
    {
      "block_number": 26369898,
      "log_index": 55,
      "time": "2026-11-08T11:37:11.000Z",
      "tx_hash": "0x4a2c1e87ccb499d442452a2ef17abafc4c36c7019768c9940fe748b46768d23a",
      "trader": "0x8dbee8f3917049bc30fef01924a7ac79d16cf2b9",
      "amount0": "6839954413",
      "amount1": "-3056128806869109546",
      "price": "0.000446805434998315031",
      "amount_usd": "6839.954413",
      "price0_usd": null,
      "price1_usd": null
    }
  ],
  "next_cursor": "1794137831000.26369898.55"
}
```

## Coming soon: positions and PnL

```
GET /v1/dex/{chain}/traders/{address}/positions
```

**Not open yet.** As of 2026-11-08 this path answers `404`. It is documented ahead of launch so you can plan for it; the field list below is a draft from the OpenAPI document and may still change before it opens. There are no sample numbers until it is live.

The route will return the trader's position in each token traded in listed pools, most recently traded first, at weighted average cost: buys add their USD to the cost; a sell realizes its USD minus the average cost of what it sold. Tokens that came in by transfer have no cost: selling them goes to `unmatched_sell_qty` and earns no realized PnL. Unrealized PnL prices the held amount at the token's current USD price. Positions are rebuilt hourly.

| Parameter | In    | Description                                     |
| --------- | ----- | ----------------------------------------------- |
| `address` | path  | The trader (transaction sender), `0x`-prefixed  |
| `limit`   | query | Most positions, 1–1000 (default 100)            |

| Field (draft)        | Type           | Description                                                                                      |
| -------------------- | -------------- | ------------------------------------------------------------------------------------------------ |
| `token`, `symbol`, `decimals` | string, string / null, integer / null | The token                                                         |
| `qty`                | raw amount     | Held amount in base units; negative after selling tokens that came in by transfer                |
| `unmatched_sell_qty` | raw amount     | Base units sold beyond what was bought in indexed trades; they earn no realized PnL              |
| `cost_usd`           | decimal        | USD cost of the held amount, at weighted average cost                                            |
| `avg_cost_usd`       | decimal / null | Average buy price: USD per whole token; null without a positive `qty` or known decimals          |
| `realized_pnl_usd`   | decimal        | Realized PnL in USD                                                                              |
| `price_usd`          | decimal / null | The token's current USD price                                                                    |
| `unrealized_pnl_usd` | decimal / null | `qty` in whole tokens × `price_usd` − `cost_usd`; null without a price or decimals, 0 without a positive `qty` |
| `buys`, `sells`      | integer        | Number of buys and sells                                                                         |
| `first_day`, `last_day` | date        | UTC days of the first and the latest indexed trade, `YYYY-MM-DD`                                 |

## Errors

Errors are JSON `{"error": {"code": "...", "message": "..."}}`. Branch on `code`, not on `message`.

| Status | `error.code`                            | When                                                                                                         |
| ------ | --------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| 400    | `invalid_request`                       | A parameter is invalid (a malformed address, `limit` out of range, a bad `cursor`) |
| 401    | `invalid_key`                           | The key is unknown or revoked, or was sent somewhere other than `X-API-Key` / `Authorization: Bearer`         |
| 403    | `forbidden`                             | The account is suspended, or the key's endpoints are expired or do not allow your IP                          |
| 404    | `not_found`                             | The chain is not served, the token has no holder ranking yet, or the pool is not indexed                     |
| 429    | `rate_limited`, `quota_exceeded`        | Over the rate, or the period's quota is used up: see [Limits and 429](https://blockreq.com/docs/build/onchain-dex-api/index.md) |
| 500    | `internal`                              | An unexpected failure                                                                                        |
| 502    | `upstream_failed`, `response_too_large` | The data service failed or answered too much; try again with a growing delay                                         |
| 503    | `unavailable`                           | Briefly unavailable, or `tokens` could not be read from the node; try again shortly                              |
| 504    | `timeout`                               | Ran out of time; try again                                                                                   |

## See also

- [On-chain DEX API](https://blockreq.com/docs/build/onchain-dex-api/index.md): prices, pools, candles and WebSocket subscriptions, with the same key
- [Interactive reference (Swagger UI)](https://blockreq.com/docs/build/onchain-dex-api/reference/) and [OpenAPI JSON](https://blockreq.com/docs/build/onchain-dex-api/openapi.json)
- [Crypto Data API pricing](https://blockreq.com/crypto-data-api/pricing), shared by the On-chain DEX API and the Wallet API
