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

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

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 for [HyperSync](/docs/v2/HyperIndex/hypersync) & [HyperFuel](/docs/v2/HyperIndex/fuel) data sources starting from `v2.3.0`.
For the [RPC](/docs/v2/HyperIndex/rpc-sync) data source support added in the `v2.12.0` release.
:::

## Index all ERC20 transfers

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

```yaml
name: transefer-indexer
networks:
  - id: 1
    start_block: 0
    contracts:
      - name: ERC20
        handler: ./src/EventHandlers.ts
        events:
          - event: Transfer(address indexed from, address indexed to, uint256 value)
```

Let's also define some entities in [`schema.graphql`](/docs/v2/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/EventHandlers.ts`. Note how we pass the `wildcard: true` option to enable wildcard indexing:

<Tabs groupId="lang" defaultValue="ts">
  <TabItem value="ts" label="TypeScript">

```ts
import { ERC20 } from "generated";

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

  </TabItem>
  <TabItem value="js" label="JavaScript">

```js
const { ERC20 } = require("generated");

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

  </TabItem>
  <TabItem value="res" label="ReScript">

```rescript
Handlers.ERC20.Transfer.handler(
  async ({ event, context }) => {
    context.Transfer.set({
      id: `${event.chainId}_${event.block.number}_${event.logIndex}`,
      from: event.params.from,
      to: event.params.to,
    })
  },
  ~eventConfig={wildcard: true},
)
```

  </TabItem>
</Tabs>

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 is a lot of events, so ideally to reduce it only to the ones you trully need with the Topic Filtering feature.

When you register an event handler or a contract register you can provide the `eventFilters` option. You can filter by each `indexed` parameter on the given event.

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

<Tabs groupId="lang" defaultValue="ts">
  <TabItem value="ts" label="TypeScript">

```ts
import { ERC20 } from "generated";

const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

ERC20.Transfer.handler(
  async ({ event, context }) => {
    //... your handler logic
  },
  { wildcard: true, eventFilters: { from: ZERO_ADDRESS } }
);
```

  </TabItem>
  <TabItem value="js" label="JavaScript">

```js
const { ERC20 } = require("generated");

const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

ERC20.Transfer.handler(
  async ({ event, context }) => {
    //... your handler logic
  },
  { wildcard: true, eventFilters: { from: ZERO_ADDRESS } }
);
```

  </TabItem>
  <TabItem value="res" label="ReScript">

```rescript
open Types.SingleOrMultiple

let zeroAddress = Address.unsafeFromString("0x0000000000000000000000000000000000000000")

Handlers.ERC20.Transfer.handler(
  async ({ event, context }) => {
    //... your handler logic
  },
  ~eventConfig={
    wildcard: true,
    eventFilters: Single({from: single(zeroAddress)}),
  },
)
```

  </TabItem>
</Tabs>

## Multiple Filters

If you want to index both `Mint` and `Burn` events you can provide multiple filters as an array. 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:

<Tabs groupId="lang" defaultValue="ts">
  <TabItem value="ts" label="TypeScript">

```ts
import { ERC20 } from "generated";

const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

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

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

  </TabItem>
  <TabItem value="js" label="JavaScript">

```js
const { ERC20 } = require("generated");

const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

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

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

  </TabItem>
  <TabItem value="res" label="ReScript">

```rescript
open Types.SingleOrMultiple

let zeroAddress = Address.unsafeFromString("0x0000000000000000000000000000000000000000")

let whitelistedAddresses = [
  Address.unsafeFromString("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"),
  Address.unsafeFromString("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"),
  Address.unsafeFromString("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC")
]

Handlers.ERC20.Transfer.handler(
  async ({ event, context }) => {
    //... your handler logic
  },
  ~eventConfig={
    wildcard: true,
    eventFilters: Multiple([
      { from: single(zeroAddress), to: multiple(whitelistedAddresses) },
      { from: multiple(whitelistedAddresses), to: single(zeroAddress) }
    ]),
  },
)
```

  </TabItem>
</Tabs>

## Different Filters per Network

For [Multichain Indexers](/docs/v2/HyperIndex/multichain-indexing) you can pass a function to `eventFilters` and use `chainId` to filter by different values per network:

<Tabs groupId="lang" defaultValue="ts">
  <TabItem value="ts" label="TypeScript">

```ts
import { ERC20 } from "generated";

const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

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

ERC20.Transfer.handler(
  async ({ event, context }) => {
    //... your handler logic
  },
  {
    wildcard: true,
    eventFilters: ({ chainId }) => [
      { from: ZERO_ADDRESS, to: WHITELISTED_ADDRESSES[chainId] },
      { from: WHITELISTED_ADDRESSES[chainId], to: ZERO_ADDRESS },
    ],
  }
);
```

  </TabItem>
  <TabItem value="js" label="JavaScript">

```js
const { ERC20 } = require("generated");

const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

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

ERC20.Transfer.handler(
  async ({ event, context }) => {
    //... your handler logic
  },
  {
    wildcard: true,
    eventFilters: ({ chainId }) => [
      { from: ZERO_ADDRESS, to: WHITELISTED_ADDRESSES[chainId] },
      { from: WHITELISTED_ADDRESSES[chainId], to: ZERO_ADDRESS },
    ],
  }
);
```

  </TabItem>
</Tabs>

## Index all ERC20 transfers to your Contract

Besides `chainId` you can also access the `addresses` value to filter by.

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

```yaml
name: locker
networks:
  - id: 1
    start_block: 0
    contracts:
      - name: Safe
        handler: ./src/EventHandlers.ts
        events:
          - event: Transfer(address indexed from, address indexed to, uint256 value)
        address:
          - 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
          - 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
          - 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
```

<Tabs groupId="lang" defaultValue="ts">
  <TabItem value="ts" label="TypeScript">

```ts
import { Safe } from "generated";

Safe.Transfer.handler(async ({ event, context }) => {}, {
  wildcard: true,
  eventFilters: ({ addresses }) => [{ from: addresses }, { to: addresses }],
});
```

  </TabItem>
  <TabItem value="js" label="JavaScript">

```js
const { Safe } = require("generated");

Safe.Transfer.handler(async ({ event, context }) => {}, {
  wildcard: true,
  eventFilters: ({ addresses }) => [{ from: addresses }, { to: addresses }],
});
```

  </TabItem>
</Tabs>

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/v2/HyperIndex/dynamic-contracts) by a factory contract:

```yaml
name: locker
networks:
  - id: 1
    start_block: 0
    contracts:
      - name: SafeRegistry
        handler: ./src/EventHandlers.ts
        events:
          - event: NewSafe(address safe)
        address:
          - 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
      - name: Safe
        handler: ./src/EventHandlers.ts
        events:
          - event: Transfer(address indexed from, address indexed to, uint256 value)
```

<Tabs groupId="lang" defaultValue="ts">
  <TabItem value="ts" label="TypeScript">

```ts
import { SafeRegistry, Safe } from "generated";

SafeRegistry.NewSafe.contractRegister(async ({ event, context }) => {
  context.addSafe(event.params.safe);
});

Safe.Transfer.handler(async ({ event, context }) => {}, {
  wildcard: true,
  eventFilters: ({ addresses }) => [{ from: addresses }, { to: addresses }],
});
```

  </TabItem>
  <TabItem value="js" label="JavaScript">

```js
const { SafeRegistry, Safe } = require("generated");

SafeRegistry.NewSafe.contractRegister(async ({ event, context }) => {
  context.addSafe(event.params.safe);
});

Safe.Transfer.handler(async ({ event, context }) => {}, {
  wildcard: true,
  eventFilters: ({ addresses }) => [{ from: addresses }, { to: addresses }],
});
```

  </TabItem>
</Tabs>

### 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:

<Tabs groupId="lang" defaultValue="ts">
  <TabItem value="ts" label="TypeScript">

```ts
import { Safe } from "generated";

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

Safe.Transfer.handler(
  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,
      });
    }
  },
  {
    wildcard: true,
    eventFilters: ({ addresses }) => [{ from: addresses }, { to: addresses }],
  }
);
```

  </TabItem>
  <TabItem value="js" label="JavaScript">

```js
const { Safe } = require("generated");

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

Safe.Transfer.handler(
  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,
      });
    }
  },
  {
    wildcard: true,
    eventFilters: ({ addresses }) => [{ from: addresses }, { to: addresses }],
  }
);
```

  </TabItem>
</Tabs>

## Contract Register Example

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

<Tabs groupId="lang" defaultValue="ts">
  <TabItem value="ts" label="TypeScript">

```ts
import { UniV3Factory } from "generated";

const DAI_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F";

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

  </TabItem>
  <TabItem value="js" label="JavaScript">

```js
const { UniV3Factory } = require("generated");

const DAI_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F";

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

  </TabItem>
  <TabItem value="res" label="ReScript">

```rescript
open Types.SingleOrMultiple

let daiAddress = Address.unsafeFromString("0x6B175474E89094C44Da98b954EedeAC495271d0F")

Handlers.UniV3Factory.PoolCreated.contractRegister(
  async ({ event, context }) => {
    let poolAddress = event.params.pool
    context.UniV3Pool.add(poolAddress)
  },
  ~eventConfig={
    eventFilters: Multiple([
      { token0: single(daiAddress) },
      { token1: single(daiAddress) }
    ])
  },
)
```

  </TabItem>
</Tabs>

## Handler With Loader Example

For `handlerWithLoader` API simply add `wildcard` or `eventFilters` options to the single argument object:

```ts
ERC20.Transfer.handlerWithLoader({
  loader: async ({ event, context }) => {},
  handler: async ({ event, context }) => {},
  wildcard: ...,
  eventFilters: ...,
});
```

## Limitations

- For any given network, only one event of a given signature can be indexed using wildcard indexing. This means that if you have multiple contract definitions in your config that contain the same event signature. Only one of them is allowed to be set to `wildcard: true`

- Either the `contractRegister` or the `handler` function can take an event config object (with wildcard/eventFilters fields) but not both.

- The RPC data source currently supports Topic Filtering only applied to a single wildcard event.
