Docs
Supported ChainsZetachainJSON-RPC APISmart Contract Methods

eth_newFilter - Zetachain RPC Method

Create an event log filter on Zetachain. Essential for event monitoring, contract activity tracking, and DeFi event streaming for omnichain DeFi, native Bitcoin smart contracts, cross-chain asset management, and unified liquidity aggregation.

Creates a filter object on Zetachain based on the given filter options, to notify when the state changes (new logs). The filter monitors for log entries that match the specified criteria — contract addresses, topics, and block ranges. Use eth_getFilterChanges to poll for new matching logs or eth_getFilterLogs to retrieve all matching logs at once.

Why Zetachain? Build on the universal omnichain blockchain enabling cross-chain smart contracts across 50+ chains including Bitcoin with native Bitcoin support, 50+ chain interoperability via UNISON, no bridging required, and partnerships with Curve and SushiSwap.

When to Use This Method

eth_newFilter is essential for cross-chain dApp developers, Bitcoin DeFi builders, and teams requiring native multi-chain interoperability:

  • Event Monitoring — Subscribe to specific contract events on Zetachain such as token transfers, approvals, or governance votes
  • Contract Activity Tracking — Watch one or multiple contracts for any emitted events relevant to omnichain DeFi, native Bitcoin smart contracts, cross-chain asset management, and unified liquidity aggregation
  • DeFi Event Streaming — Monitor swap events, liquidity changes, or oracle price updates in real time
  • Incremental Indexing — Build event indexes by creating a filter and polling with eth_getFilterChanges for only new logs

Request Parameters

Request
fromBlockQUANTITY|TAG

Starting block number (hex) or tag ("latest", "earliest", "pending"). Defaults to "latest"

toBlockQUANTITY|TAG

Ending block number (hex) or tag. Defaults to "latest"

addressDATA

No

topicsArray<DATA

null>

Response Body

Response
resultQUANTITY

A hex-encoded filter ID used to poll for matching logs via eth_getFilterChanges or eth_getFilterLogs

Error Responses

Errors
Error Response-32000

Code Examples

Bash
# Create a filter for Transfer events on a specific contract
curl -X POST https://api-zetachain-mainnet.n.dwellir.com/YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_newFilter",
    "params": [{
      "fromBlock": "latest",
      "address": "0x5F0b1a82749cb4E2278EC87F8BF6B618dC71a8bf",
      "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]
    }],
    "id": 1
  }'

Common Use Cases

1. ERC-20 Token Transfer Monitor

Watch for all transfers of a specific token on Zetachain:

JavaScript
async function monitorTokenTransfers(provider, tokenAddress) {
  // keccak256('Transfer(address,address,uint256)')
  const transferTopic = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';

  const filterId = await provider.send('eth_newFilter', [{
    fromBlock: 'latest',
    address: tokenAddress,
    topics: [transferTopic]
  }]);

  console.log(`Monitoring ${tokenAddress} transfers...`);

  setInterval(async () => {
    try {
      const logs = await provider.send('eth_getFilterChanges', [filterId]);

      for (const log of logs) {
        const from = '0x' + log.topics[1].slice(26);
        const to = '0x' + log.topics[2].slice(26);
        const value = BigInt(log.data);

        console.log(`Transfer: ${from} -> ${to} (${value})`);
      }
    } catch (error) {
      if (error.message.includes('filter not found')) {
        console.log('Filter expired — application should recreate');
      }
    }
  }, 3000);

  return filterId;
}

2. Multi-Event DeFi Monitor

Track multiple event types across DEX contracts:

JavaScript
async function monitorDeFiEvents(provider, dexRouterAddress) {
  // Watch for Swap and LiquidityAdded events
  const swapTopic = '0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822';
  const syncTopic = '0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1';

  const filterId = await provider.send('eth_newFilter', [{
    fromBlock: 'latest',
    address: dexRouterAddress,
    topics: [[swapTopic, syncTopic]]  // OR matching — either event type
  }]);

  setInterval(async () => {
    const logs = await provider.send('eth_getFilterChanges', [filterId]);

    for (const log of logs) {
      const eventType = log.topics[0] === swapTopic ? 'SWAP' : 'SYNC';
      console.log(`${eventType} at block ${parseInt(log.blockNumber, 16)}`);
    }
  }, 2000);

  return filterId;
}

3. Filter Lifecycle Manager

Manage filter creation, polling, and cleanup with automatic renewal:

JavaScript
class FilterManager {
  constructor(provider) {
    this.provider = provider;
    this.filters = new Map();
  }

  async createFilter(name, filterParams, callback) {
    const filterId = await this.provider.send('eth_newFilter', [filterParams]);

    const filter = {
      id: filterId,
      params: filterParams,
      callback,
      interval: setInterval(() => this.poll(name), 3000),
      lastPoll: Date.now()
    };

    this.filters.set(name, filter);
    console.log(`Filter "${name}" created: ${filterId}`);
    return filterId;
  }

  async poll(name) {
    const filter = this.filters.get(name);
    if (!filter) return;

    try {
      const logs = await this.provider.send('eth_getFilterChanges', [filter.id]);
      filter.lastPoll = Date.now();

      if (logs.length > 0) {
        await filter.callback(logs);
      }
    } catch (error) {
      if (error.message.includes('filter not found')) {
        console.log(`Filter "${name}" expired — recreating...`);
        const newId = await this.provider.send('eth_newFilter', [filter.params]);
        filter.id = newId;
      }
    }
  }

  async removeFilter(name) {
    const filter = this.filters.get(name);
    if (!filter) return;

    clearInterval(filter.interval);
    await this.provider.send('eth_uninstallFilter', [filter.id]);
    this.filters.delete(name);
    console.log(`Filter "${name}" removed`);
  }

  async removeAll() {
    for (const name of this.filters.keys()) {
      await this.removeFilter(name);
    }
  }
}

// Usage
const manager = new FilterManager(provider);

await manager.createFilter('transfers', {
  fromBlock: 'latest',
  address: '0x5F0b1a82749cb4E2278EC87F8BF6B618dC71a8bf',
  topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef']
}, (logs) => {
  console.log(`${logs.length} new transfer events`);
});

Error Handling

Common errors and solutions:

Error CodeDescriptionSolution
-32000Filter block range too largeReduce the range between fromBlock and toBlock
-32000Too many topicsLimit topics array to 4 entries maximum
-32602Invalid paramsVerify address format (0x-prefixed, 20 bytes) and topic format (0x-prefixed, 32 bytes)
-32603Internal errorNode may be overloaded — retry with exponential backoff
-32005Rate limit exceededReduce filter creation frequency
JavaScript
async function createFilterSafely(provider, params, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const filterId = await provider.send('eth_newFilter', [params]);
      return filterId;
    } catch (error) {
      if (error.message.includes('block range too large')) {
        // Narrow the range — split in half
        const from = parseInt(params.fromBlock, 16);
        const to = parseInt(params.toBlock, 16);
        const mid = Math.floor((from + to) / 2);
        console.warn(`Block range too large — splitting at ${mid}`);
        throw new Error(`Split needed: ${from}-${mid} and ${mid + 1}-${to}`);
      }
      if (error.code === -32005 && i < maxRetries - 1) {
        const delay = Math.pow(2, i) * 1000;
        await new Promise(r => setTimeout(r, delay));
      } else {
        throw error;
      }
    }
  }
}