Skip to main content
Track storage state changes for smart contracts to monitor specific storage slots, analyze contract state evolution, or track ownership changes.

Use Case

State diff tracking enables you to:
  • Monitor specific storage slots (e.g., owner, balance mappings)
  • Track contract state evolution over time
  • Analyze state changes without replaying transactions
  • Build state-based analytics

Code Example

curl --compressed -X POST 'https://portal.sqd.dev/datasets/ethereum-mainnet/stream' \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "evm",
    "fromBlock": 19000000,
    "toBlock": 19000003,
    "fields": {
      "block": {
        "number": true,
        "timestamp": true
      },
      "stateDiff": {
        "address": true,
        "key": true,
        "kind": true,
        "prev": true,
        "next": true
      }
    },
    "stateDiffs": [{
      "address": ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"]
    }]
  }'
Try it yourself with the interactive query interface below:

Key Parameters

ParameterDescription
addressContract address to track state changes
keyStorage slot key (keccak256 hash for mappings)
kindChange type: = (unchanged), + (added), * (modified), - (removed)
prevPrevious value (null for new slots)
nextNew value (null for removed slots)
State diffs capture all storage changes that occurred in a block, providing a complete picture of contract state evolution.

Expected Output

{
  "header": {
    "number": 18000123
  },
  "stateDiffs": [
    {
      "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
      "key": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
      "kind": "*",
      "prev": "0x0000000000000000000000000000000000000000000000000000000000000064",
      "next": "0x00000000000000000000000000000000000000000000000000000000000000c8"
    }
  ]
}

Filter by Contract

Track state changes for a specific contract:
curl --compressed -X POST 'https://portal.sqd.dev/datasets/ethereum-mainnet/stream' \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "evm",
    "fromBlock": 19000000,
    "toBlock": 19000003,
    "fields": {
      "stateDiff": {
        "address": true,
        "key": true,
        "kind": true,
        "prev": true,
        "next": true
      }
    },
    "stateDiffs": [{
      "address": ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"]
    }]
  }'

Understanding Change Types

The kind field indicates the type of state change:
KindMeaningDescription
=UnchangedStorage slot was accessed but not modified
+AddedNew storage slot created
*ModifiedExisting storage slot value changed
-RemovedStorage slot deleted (SSTORE with zero value)
// Filter only modifications
for (const block of blocks) {
  if (block.stateDiffs) {
    for (const diff of block.stateDiffs) {
      if (diff.kind === "*") {
        console.log(`Modified at block ${block.header.number}`);
        console.log(`  ${diff.prev}${diff.next}`);
      }
    }
  }
}

Track Specific Storage Slots

Monitor a specific storage slot (e.g., owner variable):
// Example: Track owner changes
// Owner is typically at slot 0 for many contracts
const OWNER_SLOT = "0x0000000000000000000000000000000000000000000000000000000000000000";

const blocks = await dataSource.getBlocks({
  from: 19000000,
  to: 19000003,
  fields: {
    block: { number: true, timestamp: true },
    stateDiff: {
      address: true,
      key: true,
      prev: true,
      next: true,
    },
  },
  stateDiffs: [
    {
      address: ["0xYourContractAddress"],
    },
  ],
});

for (const block of blocks) {
  if (block.stateDiffs) {
    for (const diff of block.stateDiffs) {
      if (diff.key === OWNER_SLOT) {
        console.log({
          block: block.header.number,
          timestamp: block.header.timestamp,
          previousOwner: diff.prev,
          newOwner: diff.next,
        });
      }
    }
  }
}
For mappings, the storage key is computed as keccak256(abi.encode(mappingKey, slot)). You’ll need to compute this off-chain to track specific mapping entries.

Monitor Balance Changes

Track balance mapping changes for ERC-20 tokens:
import { ethers } from "ethers";

// Compute storage key for balance mapping
function getBalanceSlot(address: string, balanceMappingSlot: number): string {
  // For Solidity: mapping(address => uint256) balances;
  // Key = keccak256(abi.encode(address, slot))
  return ethers.keccak256(
    ethers.AbiCoder.defaultAbiCoder().encode(
      ["address", "uint256"],
      [address, balanceMappingSlot]
    )
  );
}

const WALLET = "0x28C6c06298d514Db089934071355E5743bf21d60";
const BALANCE_SLOT = 1; // Balance mapping slot for USDC

const balanceKey = getBalanceSlot(WALLET, BALANCE_SLOT);

// Query state diffs and filter for this specific key
const blocks = await dataSource.getBlocks({
  from: 19000000,
  to: 19000003,
  fields: {
    stateDiff: { address: true, key: true, prev: true, next: true },
  },
  stateDiffs: [
    {
      address: ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"], // USDC
    },
  ],
});

for (const block of blocks) {
  if (block.stateDiffs) {
    for (const diff of block.stateDiffs) {
      if (diff.key === balanceKey) {
        const prevBalance = BigInt(diff.prev || "0");
        const nextBalance = BigInt(diff.next || "0");
        console.log(`Balance change: ${prevBalance}${nextBalance}`);
      }
    }
  }
}

Performance Tips

  1. Filter by contract: Always specify contract addresses to reduce data volume
  2. Post-filter keys: Filter specific storage keys in your application
  3. Understand data volume: State diffs can be very large for active contracts
  4. Combine with logs: Use event logs to identify interesting blocks, then query state diffs
State diffs can generate massive amounts of data, especially for busy contracts. Always filter by specific contract addresses.

Common Use Cases

Track Governance Changes

Monitor governance parameter changes:
// Filter state changes for governance contracts
// Look for proposal state changes, quorum updates, etc.

Monitor Oracle Updates

Track price oracle state updates:
// Track specific storage slots for price data
// Useful for MEV analysis or price feed monitoring

Audit Storage Access

Analyze which storage slots are frequently accessed:
const slotAccessCounts = new Map<string, number>();

for (const block of blocks) {
  if (block.stateDiffs) {
    for (const diff of block.stateDiffs) {
      const key = `${diff.address}:${diff.key}`;
      slotAccessCounts.set(key, (slotAccessCounts.get(key) || 0) + 1);
    }
  }
}
Last modified on November 18, 2025