# eth_getFilterChanges - Bittensor RPC Method

Polls a filter on Bittensor for changes since the last poll. Log filters return log objects. Block filters return block hashes. Pending transaction filters return transaction hashes.

> **Why Bittensor?** Build on the decentralized machine intelligence network built around subnets, TAO staking, and validator-miner coordination with Yuma Consensus, subnet-based specialization, dual Substrate and EVM surfaces, and onchain incentive coordination.

## When to Use This Method

`eth_getFilterChanges` is essential for AI/ML developers, subnet operators, and teams building decentralized machine learning applications:

- **Event Streaming** - Incrementally consume new contract events without re-fetching the entire log history on Bittensor
- **Real-Time Monitoring** - Track contract activity, token transfers, or governance votes for decentralized AI inference, subnet-specific AI models, TAO staking, and cross-subnet AI collaboration
- **Efficient Log Indexing** - Process only new events since your last poll, minimizing bandwidth and compute overhead
- **Block & Transaction Tracking** - When used with block or pending-transaction filters, detect new blocks or mempool activity in real time

## Best Practices

- Poll filters at most every 1-5 seconds; excessive polling wastes bandwidth and may trigger rate limiting
- Always call `eth_uninstallFilter` when monitoring is complete to release server-side resources
- Handle null or empty array returns gracefully as they indicate no new results since the last poll
- Use WebSocket subscriptions (`eth_subscribe`) instead of polling for real-time production applications

## Request Parameters

- `filterId` (`QUANTITY, required`): The filter ID returned by eth_newFilter, eth_newBlockFilter, or eth_newPendingTransactionFilter

## Request Example

```json
{
  "jsonrpc": "2.0",
  "method": "eth_getFilterChanges",
  "params": ["0x1a"],
  "id": 1
}
```

## Response Fields

- `address` (`DATA, required`): Address from which the log originated
- `topics` (`Array<DATA>, required`): Array of 0-4 indexed log arguments (32 bytes each)
- `data` (`DATA, required`): Non-indexed log arguments (ABI-encoded)
- `blockNumber` (`QUANTITY, required`): Block number where the log was emitted
- `transactionHash` (`DATA, required`): Hash of the transaction that generated the log
- `transactionIndex` (`QUANTITY, required`): Index position of the transaction in the block
- `blockHash` (`DATA, required`): Hash of the block containing the log
- `logIndex` (`QUANTITY, required`): Log index position in the block
- `removed` (`Boolean, required`): true if the log was removed due to a chain reorganization
- `result` (`Array<DATA>, required`): Array of 32-byte block hashes of new blocks
- `result` (`Array<DATA>, required`): Array of 32-byte transaction hashes of pending transactions

## Successful Response

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
      "topics": [
        "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
        "0x000000000000000000000000a7a93fd0a276fc1c0197a5b5623ed117786bac38",
        "0x0000000000000000000000002f3e5f5c0b5e9b0c7e1a0e8b2f6c3d4e5a6b7c8d"
      ],
      "data": "0x00000000000000000000000000000000000000000000000000000000001e8480",
      "blockNumber": "0x1234567",
      "transactionHash": "0xabc123...",
      "transactionIndex": "0x0",
      "blockHash": "0xdef456...",
      "logIndex": "0x0",
      "removed": false
    }
  ]
}
```

## Error Responses

### Error Response

- Code: `-32000`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32000,
    "message": "filter not found"
  }
}
```

## Code Examples

cURL
JavaScript
Python
Go

```bash
curl -X POST https://api-bittensor-mainnet.n.dwellir.com/YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "eth_getFilterChanges",
    "params": ["0x1a"],
    "id": 1
  }'
```

```javascript
import { JsonRpcProvider } from 'ethers';

const provider = new JsonRpcProvider('https://api-bittensor-mainnet.n.dwellir.com/YOUR_API_KEY');

// Create a log filter first
const filterId = await provider.send('eth_newFilter', [{
  fromBlock: 'latest',
  address: '0x156e431cc96e0e3b70c97214d869c9bc4b5bbd21',
  topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef']
}]);

// Poll for new events
async function pollFilter(filterId, interval = 2000) {
  while (true) {
    const changes = await provider.send('eth_getFilterChanges', [filterId]);
    if (changes.length > 0) {
      for (const log of changes) {
        console.log('New event in block:', parseInt(log.blockNumber, 16));
        console.log('  Contract:', log.address);
        console.log('  Data:', log.data);
      }
    }
    await new Promise(r => setTimeout(r, interval));
  }
}

pollFilter(filterId);
```

```python
import requests
import time

RPC_URL = 'https://api-bittensor-mainnet.n.dwellir.com/YOUR_API_KEY'

def rpc_call(method, params):
    response = requests.post(RPC_URL, json={
        'jsonrpc': '2.0',
        'method': method,
        'params': params,
        'id': 1
    })
    return response.json()

# Create a log filter
filter_result = rpc_call('eth_newFilter', [{
    'fromBlock': 'latest',
    'address': '0x156e431cc96e0e3b70c97214d869c9bc4b5bbd21'
}])
filter_id = filter_result['result']

# Poll for changes
while True:
    changes = rpc_call('eth_getFilterChanges', [filter_id])
    logs = changes.get('result', [])
    if logs:
        for log in logs:
            block = int(log['blockNumber'], 16)
            print(f'New event in block {block}: {log["address"]}')
    time.sleep(2)
```

```go
package main

import (
    "context"
    "fmt"
    "log"
    "math/big"
    "time"

    "github.com/ethereum/go-ethereum"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"
)

func main() {
    client, err := ethclient.Dial("https://api-bittensor-mainnet.n.dwellir.com/YOUR_API_KEY")
    if err != nil {
        log.Fatal(err)
    }

    // Create a log filter
    contractAddress := common.HexToAddress("0x156e431cc96e0e3b70c97214d869c9bc4b5bbd21")
    query := ethereum.FilterQuery{
        Addresses: []common.Address{contractAddress},
    }

    // Using SubscribeFilterLogs for real-time events
    logs := make(chan types.Log)
    sub, err := client.SubscribeFilterLogs(context.Background(), query, logs)
    if err != nil {
        // Fallback: use polling with FilterLogs
        ticker := time.NewTicker(2 * time.Second)
        currentBlock, _ := client.BlockNumber(context.Background())

        for range ticker.C {
            latestBlock, _ := client.BlockNumber(context.Background())
            if latestBlock > currentBlock {
                query.FromBlock = new(big.Int).SetUint64(currentBlock + 1)
                query.ToBlock = new(big.Int).SetUint64(latestBlock)
                results, err := client.FilterLogs(context.Background(), query)
                if err == nil {
                    for _, l := range results {
                        fmt.Printf("Log in block %d from %s\n", l.BlockNumber, l.Address.Hex())
                    }
                }
                currentBlock = latestBlock
            }
        }
    }

    for {
        select {
        case err := <-sub.Err():
            log.Fatal(err)
        case vLog := <-logs:
            fmt.Printf("Log in block %d from %s\n", vLog.BlockNumber, vLog.Address.Hex())
        }
    }
}
```

## Common Use Cases

### 1. Real-Time Token Transfer Monitor

Stream ERC-20 transfer events on Bittensor:

```javascript
async function monitorTransfers(provider, tokenAddress) {
  // Transfer event topic: keccak256('Transfer(address,address,uint256)')
  const transferTopic = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';

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

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

  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 - recreating...');
        // Filters expire after ~5 minutes of inactivity on most nodes
      }
    }
  }, 3000);
}
```

### 2. Multi-Contract Event Aggregator

Monitor events across multiple contracts simultaneously:

```javascript
async function aggregateEvents(provider, contracts) {
  const filterId = await provider.send('eth_newFilter', [{
    fromBlock: 'latest',
    address: contracts  // Array of contract addresses
  }]);

  const eventBuffer = [];
  let pollCount = 0;

  const interval = setInterval(async () => {
    const changes = await provider.send('eth_getFilterChanges', [filterId]);
    pollCount++;

    if (changes.length > 0) {
      eventBuffer.push(...changes);
      console.log(`Poll #${pollCount}: ${changes.length} new events (total: ${eventBuffer.length})`);

      // Process in batches
      if (eventBuffer.length >= 50) {
        await processBatch(eventBuffer.splice(0, 50));
      }
    }
  }, 2000);

  return { filterId, stop: () => clearInterval(interval) };
}
```

### 3. Block-Aware Event Processor with Reorg Handling

Detect chain reorganizations by checking the `removed` flag:

```javascript
async function safeEventProcessor(provider, filterParams) {
  const filterId = await provider.send('eth_newFilter', [filterParams]);
  const processedEvents = new Map();

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

    for (const log of changes) {
      const eventKey = `${log.transactionHash}-${log.logIndex}`;

      if (log.removed) {
        // Chain reorganization - undo previously processed event
        console.warn(`Reorg detected: removing event ${eventKey}`);
        processedEvents.delete(eventKey);
        await rollbackEvent(log);
      } else {
        processedEvents.set(eventKey, log);
        await processEvent(log);
      }
    }
  }, 2000);
}
```

## Related Methods

- [`eth_newFilter`](https://www.dwellir.com/docs/bittensor/eth_newFilter) - Create a log/event filter to poll with this method
- [`eth_newBlockFilter`](https://www.dwellir.com/docs/bittensor/eth_newBlockFilter) - Create a block filter for new block notifications
- [`eth_getFilterLogs`](https://www.dwellir.com/docs/bittensor/eth_getFilterLogs) - Get all logs matching a filter (full history, not incremental)
- [`eth_uninstallFilter`](https://www.dwellir.com/docs/bittensor/eth_uninstallFilter) - Remove a filter when no longer needed
- [`eth_getLogs`](https://www.dwellir.com/docs/bittensor/eth_getLogs) - Query logs directly without creating a filter
