> For the complete documentation index, see [llms.txt](https://docs.envio.dev/llms.txt).

Wildcard indexing is a feature that allows you to index all events matching a specified event signature without requiring the contract address from which the event was emitted. This is useful in cases such as indexing contracts deployed through factories, where the factory contract does not emit any events upon contract creation. It also enables indexing events from all contracts implementing a standard (e.g. all ERC20 transfers).

:::note
Wildcard Indexing is supported on [HyperSync](/docs/HyperIndex/hypersync), [HyperFuel](/docs/HyperIndex/fuel), and [RPC](/docs/HyperIndex/rpc-sync) data sources. Since v3.3, an RPC-backed indexer can register **multiple** wildcard events - earlier versions allowed only one.
:::

## Index all ERC20 transfers

As an example, let's say we want to index all ERC20 `Transfer` events. Start with a [`config.yaml`](/docs/HyperIndex/configuration-file) file:

```yaml
name: transefer-indexer
chains:
  - id: 1
    start_block: 0
    contracts:
      - name: ERC20
        events:
          - event: Transfer(address indexed from, address indexed to, uint256 value)
```

Let's also define some entities in [`schema.graphql`](/docs/HyperIndex/schema) file, so our handlers can store the processed data:

```graphql
type Transfer {
  id: ID!
  from: String!
  to: String!
}
```

And the last bit is to register an event handler in the `src/handlers`. Note how we pass the `wildcard: true` option to enable wildcard indexing:

```typescript
import { indexer } from "envio";

indexer.onEvent(
  { contract: "ERC20", event: "Transfer", wildcard: true },
  async ({ event, context }) => {
    context.Transfer.set({
      id: `${event.chainId}_${event.block.number}_${event.logIndex}`,
      from: event.params.from,
      to: event.params.to,
    });
  },
);
```

After running your indexer with `pnpm dev` you will have all ERC20 `Transfer` events indexed, regardless of the contract address from which the event was emitted.

## Topic Filtering

Indexing all ERC20 `Transfer` events can be noisy. Use Topic Filtering to keep only the events you need.

When registering an event handler or a contract registration handler, provide the `where` option (formerly `eventFilters` in V2). Filter by each `indexed` event parameter by returning `{ params: [...] }`.

Let's say you only want to index `Mint` events where the `from` address is equal to `ZERO_ADDRESS`:

```typescript
import { indexer } from "envio";

const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

indexer.onEvent(
  {
    contract: "ERC20",
    event: "Transfer",
    wildcard: true,
    where: () => ({ params: [{ from: ZERO_ADDRESS }] }),
  },
  async ({ event, context }) => {
    //... your handler logic
  },
);
```

## Multiple Filters

If you want to index both `Mint` and `Burn` events you can provide multiple filters as an array, and an event is indexed if it matches any one of them. Within a single filter, all parameters must match. Also, every parameter can accept an array to filter by multiple possible values. We'll use it to filter by a group of whitelisted addresses in the example below:

```typescript
import { indexer } from "envio";

const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

const WHITELISTED_ADDRESSES = [
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
];

indexer.onEvent(
  {
    contract: "ERC20",
    event: "Transfer",
    wildcard: true,
    where: () => ({
      params: [
        { from: ZERO_ADDRESS, to: WHITELISTED_ADDRESSES },
        { from: WHITELISTED_ADDRESSES, to: ZERO_ADDRESS },
      ],
    }),
  },
  async ({ event, context }) => {
    //... your handler logic
  },
);
```

:::note
On [RPC data sources](/docs/HyperIndex/rpc-sync), passing an array requires v3.3 or later. HyperSync supports it on all V3 versions.
:::

## Different Filters per Chain

For [Multichain Indexers](/docs/HyperIndex/multichain-indexing) the `where` callback receives `{ chain }` and you can read `chain.id` to filter by different values per chain:

```typescript
import { indexer } from "envio";

const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

const WHITELISTED_ADDRESSES = {
1: ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"],
137: [
  "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
  "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
],
};

indexer.onEvent(
  {
    contract: "ERC20",
    event: "Transfer",
    wildcard: true,
    where: ({ chain }) => ({
      params: [
        { from: ZERO_ADDRESS, to: WHITELISTED_ADDRESSES[chain.id] },
        { from: WHITELISTED_ADDRESSES[chain.id], to: ZERO_ADDRESS },
      ],
    }),
  },
  async ({ event, context }) => {
    //... your handler logic
  },
);
```

## Index all ERC20 transfers to your Contract

Besides `chain.id` you can also read the contract's configured (and dynamically registered) addresses from `chain.<ContractName>.addresses`.

For example, if you have a `Safe` contract, you can index all ERC20 transfers sent specifically to/from your `Safe` contracts. The `where` callback can read `chain.Safe.addresses`, so we need to define the `Transfer` event on the `Safe` contract:

```yaml
name: locker
chains:
  - id: 1
    start_block: 0
    contracts:
      - name: Safe
        events:
          - event: Transfer(address indexed from, address indexed to, uint256 value)
        address:
          - "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
          - "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
          - "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"
```

```typescript
import { indexer } from "envio";

indexer.onEvent(
  {
    contract: "Safe",
    event: "Transfer",
    wildcard: true,
    where: ({ chain }) => ({
      params: [
        { from: chain.Safe.addresses },
        { to: chain.Safe.addresses },
      ],
    }),
  },
  async ({ event, context }) => {},
);
```

This example is not much different from using a `WHITELISTED_ADDRESSES` constant, but this becomes much more powerful when the `Safe` contract addresses are [registered dynamically](/docs/HyperIndex/dynamic-contracts) by a factory contract:

```yaml
name: locker
chains:
  - id: 1
    start_block: 0
    contracts:
      - name: SafeRegistry
        events:
          - event: NewSafe(address safe)
        address:
          - "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
      - name: Safe
        events:
          - event: Transfer(address indexed from, address indexed to, uint256 value)
```

```typescript
import { indexer } from "envio";

indexer.contractRegister(
  { contract: "SafeRegistry", event: "NewSafe" },
  async ({ event, context }) => {
    context.chain.Safe.add(event.params.safe);
  },
);

indexer.onEvent(
  {
    contract: "Safe",
    event: "Transfer",
    wildcard: true,
    where: ({ chain }) => ({
      params: [
        { from: chain.Safe.addresses },
        { to: chain.Safe.addresses },
      ],
    }),
  },
  async ({ event, context }) => {},
);
```

### Assert ERC20 Transfers in Handler

After you got all ERC20 Transfers relevant to your contracts, you can additionally filter them in the handler. For example, to get only `USDC` transfers:

```typescript
import { indexer } from "envio";

const USDC_ADDRESS = {
84532: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
11155111: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
};

indexer.onEvent(
  {
    contract: "Safe",
    event: "Transfer",
    wildcard: true,
    where: ({ chain }) => ({
      params: [
        { from: chain.Safe.addresses },
        { to: chain.Safe.addresses },
      ],
    }),
  },
  async ({ event, context }) => {
    // Filter and store only the USDC transfers that involve a Safe address
    if (event.srcAddress === USDC_ADDRESS[event.chainId]) {
      context.Transfer.set({
        id: `${event.chainId}_${event.block.number}_${event.logIndex}`,
        from: event.params.from,
        to: event.params.to,
      });
    }
  },
);
```

## Contract Register Example

The same `where` option can be applied to `indexer.contractRegister`. Here is an example where we only register Uniswap pools that contain DAI token:

```typescript
import { indexer } from "envio";

const DAI_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F";

indexer.contractRegister(
  {
    contract: "UniV3Factory",
    event: "PoolCreated",
    where: () => ({
      params: [{ token0: DAI_ADDRESS }, { token1: DAI_ADDRESS }],
    }),
  },
  async ({ event, context }) => {
    const poolAddress = event.params.pool;
    context.chain.UniV3Pool.add(poolAddress);
  },
);
```

:::note
Wildcard registrations are matched by event signature. If the same signature is declared on more than one contract in your `config.yaml`, set `wildcard: true` on just one of them.
:::

For an end-to-end example on a live chain, [How to Build a HyperEVM Indexer on Hyperliquid](/blog/index-hyperevm-data) uses a wildcard `Transfer` handler with a `where` filter on `to` to keep only transfers into Hyperliquid's system addresses, with the full config, schema and handler.
