> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bloklake.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sets

> Upload a large list of addresses or topics once and refer to it by id.

If a filter has thousands of addresses (every pool of a DEX, every wallet you track) and you
reuse it across many requests, upload it once as a **set** and refer to it by id. Requests
stay small, and the server can cache which parts of the chain contain your addresses.

## Create a set

```bash theme={null}
curl https://api.bloklake.com/v1/chains/eth/v1/sets \
  -H "Authorization: Bearer $BLOKLAKE_API_KEY" -H 'content-type: application/json' \
  -d '{"kind": "address", "values": ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "0xdac17f958d2ee523a2206206994597c13d831ec7"]}'
```

```json theme={null}
{"set_id": "0x2cab7d92e0aa08ea29ffe92cf95896a794a369e5e180e174299c1c958b4a1178", "size": 2, "created": true}
```

* `kind` is `"address"` (20-byte values) or `"topic"` (32-byte values).
* Up to 100,000 values per set (about 4.6 MB of JSON for addresses).
* The id is a hash of the sorted, deduplicated values. Posting the same values again returns the
  same `set_id` with `"created": false`. Creating a set is **idempotent and cheap**, so you
  don't have to remember whether you already did.

With a [client library](https://docs.bloklake.com/clients.md):

```typescript TypeScript theme={null}
import { Bloklake } from "@bloklake/client";

const bl = new Bloklake({ chain: "eth" });
const tokens = ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "0xdac17f958d2ee523a2206206994597c13d831ec7"];

const { setId } = await bl.createSet({ kind: "address", values: tokens }); // same values, same id
const page = await bl.query({
  fromBlock: 19426000,
  toBlock: 19426099,
  logs: [{ addressSet: setId, topic0: ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"] }],
  fields: { log: ["address", "data"] },
});
console.log(setId, page.logs.length);
await bl.deleteSet(setId); // when you no longer need it
```

```python Python theme={null}
from bloklake import Bloklake

bl = Bloklake(chain="eth")
tokens = ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "0xdac17f958d2ee523a2206206994597c13d831ec7"]

s = bl.create_set("address", tokens)  # same values, same id
page = bl.query({
    "from_block": 19426000,
    "to_block": 19426099,
    "logs": [{"address_set": s.set_id, "topic0": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}],
    "fields": {"log": ["address", "data"]},
})
print(s.set_id, len(page.logs))
bl.delete_set(s.set_id)  # when you no longer need it
```

## Use it in a filter

| Set key | Works like |
| - | - |
| `addressSet` (logs) | `address` |
| `topic0Set` … `topic3Set` (logs) | `topic0` … `topic3` |
| `fromSet`, `toSet` (transactions) | `from`, `to` |

```json theme={null}
{
  "fromBlock": 19000000,
  "logs": [{
    "addressSet": "0x2cab7d92e0aa08ea29ffe92cf95896a794a369e5e180e174299c1c958b4a1178",
    "topic0": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]
  }],
  "fields": {"log": {"blockNumber": true, "address": true, "topic1": true, "topic2": true, "data": true}}
}
```

A literal list and a set in the same object are combined: `"address": [...]` plus
`"addressSet": "…"` matches either.

To track wallets in topics (transfers to any of 50,000 wallets), create a `topic` set of the
wallets **padded to 32 bytes** and use it as `topic2Set`.

## Growing a set

A common pattern: you discover new addresses as you go (child contracts of a factory, new
wallets) and want to query future ranges with the bigger list. Create the new set with
`basedOn` pointing at the previous one:

```json theme={null}
{"kind": "address", "values": ["0xabc…", "0xdef…", "0x123…"], "basedOn": "0x2cab7d92…"}
```

`basedOn` is only a hint: the server can work out the new set's matches incrementally instead
of from scratch. The new set is a normal set with its own id. Always pass the previous id when
you grow a set. See [Factory contracts](https://docs.bloklake.com/guides/factory-contracts.md).

## Lifetime and limits

* **Scope**: sets belong to your **project**. Every key of the project can use them; nobody
  else can.
* **Per chain**: a set lives on the chain where you created it. Create it on each chain you use
  it on.
* **Expiry**: a set nobody has used for 7 days is deleted automatically. Don't rely on an
  unused set surviving; just create it again (same values, same id).
* **Quota**: 50 sets and 50 MiB of set data per project. A create past it makes room by
  deleting the project's least recently used sets that have been idle for at least 10 minutes,
  so a set you grow step by step never needs cleaning up by hand. Only when every set was used
  more recently is the create refused with `403 quota_exceeded`;
  [delete](https://docs.bloklake.com/api-reference/delete-set.md) sets you no longer need.
* **Create rate**: up to 1,000,000 values per minute per project, counted across creates. Past
  it, `429 set_rate_limited` with `Retry-After`.
* **Unknown set**: a filter referring to a set that doesn't exist (expired, made room for, or
  created on another chain) gets `404 unknown_set`. Create the set again and retry.

> [!TIP]
>
> The safe pattern is to create the set at the start of every job, even if it exists. It costs one
> small request and makes `unknown_set` impossible for the rest of the job.

Set management calls (`POST`/`GET`/`DELETE /v1/sets`) count toward your rate limit but return no
row data, so they're billed at the [10 KB minimum](https://docs.bloklake.com/billing/how-billing-works.md#minimum-per-request).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.