> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sqd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Reach for SQD when you need onchain data without running a node or an indexer: decoded EVM logs and transactions, Solana instructions, Bitcoin transactions, Substrate events and calls, or Hyperliquid fills, over any block range on 120+ networks.
> To query directly, POST to https://portal.sqd.dev/datasets/{dataset}/stream. The full API is described at https://docs.sqd.dev/openapi.json, and responses to the stream endpoints are JSON Lines.
> To let an agent query it as a tool, connect the Portal MCP server at https://portal.sqd.dev/mcp.
> Every page on this site is available as Markdown by appending .md to its URL.

# Query State Diffs

> Track EVM storage state changes for smart contracts through the SQD Portal.

export const QueryInterfaceStateDiffs = () => {
  const useCasesConfig = [{
    id: "state-diffs",
    name: "Query state diffs",
    network: "ethereum-mainnet",
    payload: {
      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"]
      }]
    }
  }];
  const [queryFormat, setQueryFormat] = useState("curl");
  const [useCase, setUseCase] = useState("state-diffs");
  const [response, setResponse] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [viewMode, setViewMode] = useState("code");
  const [copied, setCopied] = useState(false);
  const generateCurlCommand = config => {
    const url = `https://portal.sqd.dev/datasets/${config.network}/stream`;
    const payload = JSON.stringify(config.payload, null, 2);
    return `curl --compressed -X POST '${url}' \\\n  -H 'Content-Type: application/json' \\\n  -d '${payload}'`;
  };
  useEffect(() => {
    setViewMode("code");
    setResponse("");
    setError(null);
  }, [useCase]);
  const handleRun = async () => {
    const selectedUseCase = useCasesConfig.find(uc => uc.id === useCase);
    if (!selectedUseCase) return;
    setViewMode("output");
    setLoading(true);
    setError(null);
    setResponse('');
    try {
      const url = `https://portal.sqd.dev/datasets/${selectedUseCase.network}/stream`;
      const doFetch = () => fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(selectedUseCase.payload)
      });
      let response = await doFetch();
      for (const retryDelay of [700, 1500, 3000]) {
        if (response.status < 500) break;
        await new Promise(resolve => setTimeout(resolve, retryDelay));
        response = await doFetch();
      }
      if (response.status === 503) {
        throw new Error('Portal is busy right now. Wait a few seconds and run the query again.');
      }
      if (!response.ok) {
        throw new Error(`API request failed: ${response.status} ${response.statusText}`);
      }
      const text = await response.text();
      if (text.trim()) {
        const lines = text.trim().split('\n');
        const parsedData = lines.map((line, index) => {
          try {
            return JSON.parse(line);
          } catch (e) {
            return {
              error: `Failed to parse line ${index + 1}`,
              raw: line
            };
          }
        });
        const formattedOutput = parsedData.map(obj => JSON.stringify(obj, null, 2)).join('\n\n');
        setResponse(formattedOutput);
      } else {
        setResponse('No data returned from API');
      }
    } catch (err) {
      setError(err.message || 'An error occurred while fetching data');
      setResponse('');
    } finally {
      setLoading(false);
    }
  };
  const CurlTok = ({s}) => <>
    {s.split(/('[^']*')/).map((seg, i) => (/^'[^']*'$/).test(seg) ? <span key={i} style={{
    color: "var(--hl-str)"
  }}>{seg}</span> : seg.split(/(curl|--compressed|-X POST|-H|-d)/).map((t2, j) => t2 === "curl" ? <span key={`${i}-${j}`} style={{
    color: "var(--hl-bool)"
  }}>{t2}</span> : (/^(--compressed|-X POST|-H|-d)$/).test(t2) ? <span key={`${i}-${j}`} style={{
    color: "var(--hl-flag)"
  }}>{t2}</span> : <span key={`${i}-${j}`}>{t2}</span>))}
  </>;
  const JsonTok = ({s}) => <>
    {s.split(/("[^"]+"\s*:)/).map((seg, i) => (/^"[^"]+"\s*:$/).test(seg) ? <span key={i} style={{
    color: "var(--hl-key)"
  }}>{seg}</span> : seg.split(/("[^"]*")/).map((s2, j) => (/^"[^"]*"$/).test(s2) ? <span key={`${i}-${j}`} style={{
    color: "var(--hl-str)"
  }}>{s2}</span> : s2.split(/\b(true|false|null|-?\d+\.?\d*)\b/).map((s3, k) => (/^(true|false|null)$/).test(s3) ? <span key={`${i}-${j}-${k}`} style={{
    color: "var(--hl-bool)"
  }}>{s3}</span> : (/^-?\d+\.?\d*$/).test(s3) ? <span key={`${i}-${j}-${k}`} style={{
    color: "var(--hl-num)"
  }}>{s3}</span> : <span key={`${i}-${j}-${k}`}>{s3}</span>)))}
  </>;
  return <div className="not-prose w-full my-6 sm:my-8">

      <div className="sqd-no-scrollbar mb-3 flex items-center gap-0.5 overflow-x-auto pb-1">
        <span className="sqd-pg-label mr-2 flex-shrink-0">Query</span>
        {useCasesConfig.map(uc => <button key={uc.id} type="button" onClick={() => setUseCase(uc.id)} aria-pressed={uc.id === useCase} className="sqd-pg-net flex flex-shrink-0 items-center gap-1.5 px-2.5 py-1 whitespace-nowrap">
            {uc.name}
          </button>)}
      </div>

      <div className="sqd-pg-panel overflow-hidden">
        <div className="sqd-pg-bar flex flex-wrap items-center gap-2 px-3 sm:px-4 py-2">
          <div className="flex items-center gap-1">
            <button type="button" onClick={() => setViewMode("code")} className={`sqd-pg-tab px-2.5 py-1 ${viewMode === "code" ? "sqd-pg-tab-active" : ""}`}>
              Request
            </button>
            <button type="button" onClick={() => setViewMode("output")} disabled={!response && !loading && !error} className={`sqd-pg-tab px-2.5 py-1 disabled:opacity-40 disabled:cursor-not-allowed ${viewMode === "output" ? "sqd-pg-tab-active" : ""}`}>
              Response
              {response ? <span className="inline-block w-1.5 h-1.5 rounded-full bg-emerald-400 ml-1.5 align-middle" /> : null}
            </button>
          </div>
          <div className="flex items-center gap-2 sm:gap-3 ml-auto">
            <button type="button" onClick={() => {
    const textToCopy = viewMode === "code" ? generateCurlCommand(useCasesConfig.find(uc => uc.id === useCase)) : response;
    navigator.clipboard.writeText(textToCopy);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  }} className="sqd-pg-dim text-[12px] transition-opacity hover:opacity-70" title={copied ? "Copied!" : "Copy to clipboard"}>
              {copied ? "Copied" : "Copy"}
            </button>
            <button onClick={handleRun} type="button" disabled={loading} className="sqd-btn-primary-docs flex items-center gap-1.5 px-3 sm:px-4 py-1.5 text-[13px] disabled:opacity-40 disabled:cursor-wait whitespace-nowrap focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 dark:focus:ring-offset-gray-950">
              {loading ? <>
                  <svg className="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24">
                    <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
                    <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                  </svg>
                  Running
                </> : "Run"}
            </button>
          </div>
        </div>
        <div className={`sqd-pg-code px-3 sm:px-4 py-4 ${viewMode === "output" ? "max-h-[440px] overflow-auto" : "overflow-x-auto"}`}>
          {loading ? <div className="sqd-pg-dim flex items-center justify-center py-10 text-[13px]">Streaming from Portal...</div> : viewMode === "code" ? <div key={`code-${useCase}`}>
              {generateCurlCommand(useCasesConfig.find(uc => uc.id === useCase)).trim().split('\n').map((line, i) => <div key={`curl-line-${i}`} className="sqd-pg-line">
                  <span className="sqd-pg-num">{i + 1}</span>
                  <span style={{
    whiteSpace: 'pre-wrap',
    wordBreak: 'break-word'
  }}>
                    {line.includes('curl') || line.match(/^\s*-[XHd]/) ? <CurlTok s={line} /> : <JsonTok s={line} />}
                  </span>
                </div>)}
            </div> : response ? <div key={`response-${useCase}-${response.length}`}>
              {response.trim().split('\n').map((line, i) => <div key={`response-line-${i}`} className="sqd-pg-line">
                  <span className="sqd-pg-num">{i + 1}</span>
                  <span style={{
    whiteSpace: 'pre-wrap',
    wordBreak: 'break-word'
  }}>
                    <JsonTok s={line} />
                  </span>
                </div>)}
            </div> : error ? <div className="py-4 text-[13px] text-red-600 dark:text-red-400">Error: {error}</div> : <div className="sqd-pg-dim text-center py-12 text-[13px]">
              Hit Run to stream live data from Portal
            </div>}
        </div>
        <div className="sqd-pg-bar-b flex flex-wrap items-center gap-x-3 gap-y-1 px-4 py-2">
          <div className="sqd-pg-dim flex items-center gap-2 min-w-0 text-[12px]">
            <span className="truncate">{useCasesConfig.find(uc => uc.id === useCase)?.network + "/stream"}</span>
          </div>
          <span className="sqd-pg-dim text-[12px] ml-auto flex-shrink-0">
            {useCasesConfig.find(uc => uc.id === useCase)?.name}
          </span>
        </div>
      </div>
    </div>;
};

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

<CodeGroup>
  ```bash curl theme={"system"}
  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"]
      }]
    }'
  ```

  ```typescript TypeScript theme={"system"}
  import { PortalClient } from "@subsquid/portal-client";

  const client = new PortalClient({
    url: "https://portal.sqd.dev/datasets/ethereum-mainnet",
  });

  for await (const { blocks } of client.getStream({
    type: "evm",
    fromBlock: 19000000,
    toBlock: 19000003,
    fields: {
      block: { timestamp: true },
      stateDiff: {
        address: true,
        key: true,
        kind: true,
        prev: true,
        next: true,
      },
    },
    stateDiffs: [
      {
        address: ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"], // USDC
      },
    ],
  })) {
    // Process state changes
    for (const block of blocks) {
      for (const diff of block.stateDiffs) {
        console.log({
          blockNumber: block.header.number,
          contract: diff.address,
          storageKey: diff.key,
          changeType: diff.kind, // "=", "+", "*", "-"
          previousValue: diff.prev,
          newValue: diff.next,
        });
      }
    }
  }
  ```

  ```python Python theme={"system"}
  import requests
  import json

  url = "https://portal.sqd.dev/datasets/ethereum-mainnet/stream"
  headers = {"Content-Type": "application/json"}

  payload = {
      "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"]  # USDC
      }]
  }

  response = requests.post(url, headers=headers, json=payload)

  for line in response.text.strip().split('\n'):
      block = json.loads(line)
      if 'stateDiffs' in block:
          for diff in block['stateDiffs']:
              print({
                  "blockNumber": block['header']['number'],
                  "contract": diff['address'],
                  "storageKey": diff['key'],
                  "changeType": diff['kind'],
                  "previousValue": diff.get('prev'),
                  "newValue": diff.get('next')
              })
  ```
</CodeGroup>

<Info>Try it yourself with the interactive query interface below:</Info>

<QueryInterfaceStateDiffs />

## Key Parameters

| Parameter | Description |
| - | - |
| `address` | Contract address to track state changes |
| `key` | Storage slot key (keccak256 hash for mappings) |
| `kind` | Change type: `=` (unchanged), `+` (added), `*` (modified), `-` (removed) |
| `prev` | Previous value (null for new slots) |
| `next` | New value (null for removed slots) |

<Note>
  State diffs capture all storage changes that occurred in a block, providing a complete picture of contract state evolution.
</Note>

## Expected Output

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

## Filter by Contract

Track state changes for a specific contract:

<CodeGroup>
  ```bash curl theme={"system"}
  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"]
      }]
    }'
  ```

  ```typescript TypeScript theme={"system"}
  const USDC_CONTRACT = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";

  for await (const { blocks } of client.getStream({
    type: "evm",
    fromBlock: 19000000,
    toBlock: 19000003,
    fields: {
      stateDiff: {
        address: true,
        key: true,
        kind: true,
        prev: true,
        next: true,
      },
    },
    stateDiffs: [
      {
        address: [USDC_CONTRACT],
      },
    ],
  })) {
    // process blocks
  }
  ```

  ```python Python theme={"system"}
  usdc_contract = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"

  payload = {
      "type": "evm",
      "fromBlock": 19000000,
      "toBlock": 19000003,
      "fields": {
          "stateDiff": {
              "address": True,
              "key": True,
              "kind": True,
              "prev": True,
              "next": True
          }
      },
      "stateDiffs": [{
          "address": [usdc_contract]
      }]
  }
  ```
</CodeGroup>

## Understanding Change Types

The `kind` field indicates the type of state change:

| Kind | Meaning | Description |
| - | - | - |
| `=` | Unchanged | Storage slot was accessed but not modified |
| `+` | Added | New storage slot created |
| `*` | Modified | Existing storage slot value changed |
| `-` | Removed | Storage slot deleted (SSTORE with zero value) |

<CodeGroup>
  ```typescript TypeScript theme={"system"}
  // 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}`);
        }
      }
    }
  }
  ```
</CodeGroup>

## Track Specific Storage Slots

Monitor a specific storage slot (e.g., owner variable):

<CodeGroup>
  ```typescript TypeScript theme={"system"}
  // Example: Track owner changes
  // Owner is typically at slot 0 for many contracts
  const OWNER_SLOT = "0x0000000000000000000000000000000000000000000000000000000000000000";

  for await (const { blocks } of client.getStream({
    type: "evm",
    fromBlock: 19000000,
    toBlock: 19000003,
    fields: {
      block: { timestamp: true },
      stateDiff: {
        address: true,
        key: true,
        prev: true,
        next: true,
      },
    },
    stateDiffs: [
      {
        address: ["0xYourContractAddress"],
      },
    ],
  })) {
    for (const block of blocks) {
      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,
          });
        }
      }
    }
  }
  ```
</CodeGroup>

<Tip>
  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.
</Tip>

## Monitor Balance Changes

Track balance mapping changes for ERC-20 tokens:

<CodeGroup>
  ```typescript TypeScript theme={"system"}
  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
  for await (const { blocks } of client.getStream({
    type: "evm",
    fromBlock: 19000000,
    toBlock: 19000003,
    fields: {
      stateDiff: { address: true, key: true, prev: true, next: true },
    },
    stateDiffs: [
      {
        address: ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"], // USDC
      },
    ],
  })) {
    for (const block of blocks) {
      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}`);
        }
      }
    }
  }
  ```
</CodeGroup>

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

<Warning>
  State diffs can generate massive amounts of data, especially for busy contracts. Always filter by specific contract addresses.
</Warning>

## Common Use Cases

### Track Governance Changes

Monitor governance parameter changes:

```typescript theme={"system"}
// Filter state changes for governance contracts
// Look for proposal state changes, quorum updates, etc.
```

### Monitor Oracle Updates

Track price oracle state updates:

```typescript theme={"system"}
// 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:

```typescript theme={"system"}
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);
    }
  }
}
```

## Related Examples

<CardGroup cols={2}>
  <Card title="Query Event Logs" icon="file-text" href="/en/portal/evm/examples/query-logs">
    Track smart contract events
  </Card>

  <Card title="Query Transactions" icon="arrow-left-right" href="/en/portal/evm/examples/query-transactions">
    Monitor transaction activity
  </Card>

  <Card title="Query Traces" icon="workflow" href="/en/portal/evm/examples/query-traces">
    Analyze internal transactions
  </Card>

  <Card title="API Reference" icon="book" href="/en/portal/evm/api">
    View complete API docs
  </Card>
</CardGroup>


## Related topics

- [Storage state diffs](/en/sdk/squid-sdk/evm/reference/evm-stream/state-diffs.md)
- [Query builder](/en/sdk/pipes-sdk/evm/reference/basic-components/query-builder.md)
- [Private Portal setup](/en/portal/self-hosting.md)
- [Field selection](/en/sdk/squid-sdk/evm/reference/evm-stream/field-selection.md)
- [Optimism](/en/data/evm/optimism-mainnet.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.