# Stream USDC transfers

Source: https://docs.graphops.xyz/substreams/guides/stream-usdc-transfers

Stream the activity of one Ethereum contract, USDC, as the input to a transfer index or activity
feed. This guide passes the contract address to a package module as a parameter, then shows how to
pick `Transfer` events out of the output.

The module returns raw Ethereum logs with transaction hashes. Decode the Transfer topics and data to obtain sender, recipient, and amount.
Your consumer filters and stores the transfer records, then commits them with the block's cursor.

## Prerequisites

* An API key that includes Substreams.
* A consumer that can decode the module's `sf.substreams.ethereum.v1.Events` output type.
* A durable store for transfer rows and cursors.

## Stream the contract's events

USDC on Ethereum mainnet:

```text
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

```

`--params` passes an address and signature query to `filtered_events`, which returns matching USDC Transfer logs. The CLI tab is a complete command over six blocks. The other tabs are complete consumers. Each requests the same package, module, filter, and finite range.

CLI · Substreams CLI

Prerequisites

1. Install the Substreams CLI.

2. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

Tested with ethereum-common v0.4.0, substreams 1.25.0.

[Example project](/examples/substreams.api.sh-2.tar.gz)

```sh
# The CLI sends SUBSTREAMS_API_KEY to GraphOps in the x-api-key header.
export SUBSTREAMS_API_KEY="${GRAPHOPS_API_KEY}"

# Stream USDC Transfer events (--params) for six blocks from 18000002 as JSON lines.
substreams run \
  -e "https://1.evm.substreams.graphops.xyz" \
  "https://spkg.io/v1/packages/ethereum-common/v0.4.0" \
  "filtered_events" \
  --params "filtered_events=evt_addr:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 && evt_sig:0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" \
  --start-block 18000002 \
  --stop-block +6 \
  -o jsonl

```

Output

```json
{
  "@module": "filtered_events",
  "@block": 18000002,
  "@type": "sf.substreams.ethereum.v1.Events",
  "@data": {
    "clock": {
      "id": "5b94c39ad7eecbf043f62a4a76c5abee87195207df98ae16eb3f289b2267f141",
      "number": "18000002",
      "timestamp": "2023-08-26T16:21:59Z"
    },
    "events": [
      {
        "log": {
          "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
          "topics": [
            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
            "0x000000000000000000000000e673bda19a529d9ff13aeddae0e08c0fda8b5488",
            "0x00000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f5640"
          ],
          "data": "0x00000000000000000000000000000000000000000000000000000000401332c0",
          "index": 1,
          "blockIndex": 172,
          "ordinal": "2582"
        },
        "txHash": "29ec08dc5eed0e1cd411b29c52607661155087ffa107bae7f5e4a79f5306be1d"
      },
      {
        "log": {
          "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
          "topics": [
            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
            "0x000000000000000000000000a40bb1c47f6dd27142a2bd7c93bfa98db9d1f5c5",
            "0x000000000000000000000000b4e16d0168e52d35cacd2c6185b44281ec28c9dc"
          ],
          "data": "0x0000000000000000000000000000000000000000000000000000000011348e5d",
          "index": 2,
          "blockIndex": 315,
          "ordinal": "5139"
        },
        "txHash": "a8b07b56f7bb4ea52eecf5a125c75f143955a8e57e1ea17d2d8a48830e7d67db"
      }
    ]
  }
}
{
  "@module": "filtered_events",
  "@block": 18000003,
  "@type": "sf.substreams.ethereum.v1.Events",
  "@data": {
    "clock": {
      "id": "90c06a9fad66de217ce5862a92a2d45a8f42f9abf8ec17d5bfb85ffdc7836ca4",
      "number": "18000003",
      "timestamp": "2023-08-26T16:22:11Z"
    },
    "events": [
      {
        "log": {
          "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
          "topics": [
            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
            "0x000000000000000000000000180cd4303582d348a54fcb0436d5665f082a23d1",
            "0x000000000000000000000000a9d1e08c7793af67e9d92fe308d5697fb81d3e43"
          ],
          "data": "0x00000000000000000000000000000000000000000000000000000000aad0a340",
          "blockIndex": 222,
          "ordinal": "4219"
        },
        "txHash": "05c9c59145387faa91370e56d717d42856bd20f8f19306814d5b7afbe837efe3"
      },
      {
        "log": {
          "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
          "topics": [
            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
            "0x0000000000000000000000001c727a55ea3c11b0ab7d3a361fe0f3c47ce6de5d",
            "0x000000000000000000000000c3dcc088aef4373e665b00fc8be45a4b6a4b24a6"
          ],
          "data": "0x000000000000000000000000000000000000000000000000000000000686bcda",
          "blockIndex": 230,
          "ordinal": "4543"
        },
        "txHash": "eb8505dd3dc2964c1b43ed40371fb9c907b751ccb9294b99db032270889edc42"
      }
    ]
  }
}
{
  "@module": "filtered_events",
  "@block": 18000004,
  "@type": "sf.substreams.ethereum.v1.Events",
  "@data": {
    "clock": {
      "id": "7f7889c3686d7f560dd690aeb2a10b49e3a76844543cf78cc0dfe4687e2985d2",
      "number": "18000004",
      "timestamp": "2023-08-26T16:22:23Z"
    },
    "events": [
      {
        "log": {
          "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
          "topics": [
            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x00000000000000000000000043954785980c50768373dc1dafd7ba3a2052036f"
          ],
          "data": "0x000000000000000000000000000000000000000000000000000000e8d433aa6e",
          "index": 1,
          "blockIndex": 191,
          "ordinal": "3060"
        },
        "txHash": "b2c1d8c8e7989adab51dafdc9be24fd05243342b8d08828c4325662de629c690"
      }
    ]
  }
}
{
  "@module": "filtered_events",
  "@block": 18000005,
  "@type": "sf.substreams.ethereum.v1.Events",
  "@data": {
    "clock": {
      "id": "127c6b9794874d7011770b3aa2a0a640e1db201c2d677d01b2b3b19e7ad427c7",
      "number": "18000005",
      "timestamp": "2023-08-26T16:22:35Z"
    },
    "events": [
      {
        "log": {
          "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
          "topics": [
            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
            "0x000000000000000000000000c880d8be4e94f644f1daa7d3558b50ff5a1c2db7",
            "0x000000000000000000000000ae2d4617c862309a3d75a0ffb358c7a5009c673f"
          ],
          "data": "0x0000000000000000000000000000000000000000000000000000002e90edd000",
          "blockIndex": 64,
          "ordinal": "1110"
        },
        "txHash": "1691b8e89e13f483c549d9b61efbc54ecc54015b8186726911c8458f0993e13d"
      },
      {
        "log": {
          "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
          "topics": [
            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
            "0x000000000000000000000000b86fc5e985facdcae6034beefc6cbdcd8bbd8cb4",
            "0x00000000000000000000000027f713adefc708469d826ab8f5b1e4672f1a070e"
          ],
          "data": "0x0000000000000000000000000000000000000000000000000000000059682f00",
          "blockIndex": 203,
          "ordinal": "3669"
        },
        "txHash": "440106b494b7eb657d19e5f82a164ce4cc62e5252058fab5fb4ee6b92600543c"
      }
    ]
  }
}
{
  "@module": "filtered_events",
  "@block": 18000006,
  "@type": "sf.substreams.ethereum.v1.Events",
  "@data": {
    "clock": {
      "id": "ed05b59286d4f2db801e78a23ce4584d869d857b70b2758822844427caa55a85",
      "number": "18000006",
      "timestamp": "2023-08-26T16:22:47Z"
    }
  }
}
{
  "@module": "filtered_events",
  "@block": 18000007,
  "@type": "sf.substreams.ethereum.v1.Events",
  "@data": {
    "clock": {
      "id": "db63e26b655ad31d60b2173c02127180ed46b28506f785a2682e058ca61afdc1",
      "number": "18000007",
      "timestamp": "2023-08-26T16:22:59Z"
    },
    "events": [
      {
        "log": {
          "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
          "topics": [
            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
            "0x000000000000000000000000b4eae88d1643e172518dde0ef213c20628798c65",
            "0x00000000000000000000000038c5a4f22e2e290ba9bbb9e3d9aaf8582f79f4d1"
          ],
          "data": "0x0000000000000000000000000000000000000000000000000000000006b78840",
          "blockIndex": 94,
          "ordinal": "1448"
        },
        "txHash": "21d8e836f79606db39fcd51f7e563ea9c12cfb7a2bb3e36f23f4eb89b93045ea"
      },
      {
        "log": {
          "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
          "topics": [
            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
            "0x000000000000000000000000579752cff8fee7af09446b2133ee2f9ff10c4fbf",
            "0x0000000000000000000000001715a3e4a142d8b698131108995174f37aeba10d"
          ],
          "data": "0x000000000000000000000000000000000000000000000000000000001d0221cd",
          "blockIndex": 179,
          "ordinal": "3398"
        },
        "txHash": "d78a180e669534a650d842f13d032610e29bf4a7731fb90a6dff73d752ac049c"
      }
    ]
  }
}
```

Historical block: 18,000,002

Ethereum mainnet USDC contract; hexadecimal in CLI output, base64 in Protobuf JSON

ERC-20 Transfer signature

Sender address, left-padded to 32 bytes

Recipient address, left-padded to 32 bytes

Transfer amount: 1,075,000,000 base units, 1,075 USDC

Transaction containing this log

TypeScript · @substreams/core

Prerequisites

1. Install Node.js.

2. Install @connectrpc/connect, @connectrpc/connect-node, @bufbuild/protobuf, @substreams/core, and tsx. `npm install @connectrpc/connect @connectrpc/connect-node @bufbuild/protobuf @substreams/core tsx`

3. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

Tested with Node.js 24.22.1, @connectrpc/connect 1.7.0, @connectrpc/connect-node 1.7.0, ethereum-common v0.4.0, @bufbuild/protobuf 1.10.1, @substreams/core 0.17.0, tsx 4.23.15.

[Example project](/examples/substreams.api.ts-5.tar.gz)

```typescript
import { createHash } from "node:crypto";
import { createGrpcTransport, Http2SessionManager } from "@connectrpc/connect-node";
import {
  applyParams,
  createRegistry,
  createRequest,
  createSubstream,
  streamBlocks,
  unpackMapOutput,
} from "@substreams/core";

const endpoint = "https://1.evm.substreams.graphops.xyz";
const apiKey = process.env.GRAPHOPS_API_KEY;
if (!apiKey) throw new Error("Set GRAPHOPS_API_KEY");

// Check the package's SHA-256, so the program only runs the exact module code you reviewed.
const download = await fetch("https://spkg.io/v1/packages/ethereum-common/v0.4.0");
if (!download.ok) throw new Error(`Package download failed: ${download.status}`);
const bytes = new Uint8Array(await download.arrayBuffer());
const checksum = createHash("sha256").update(bytes).digest("hex");
if (checksum !== "beda75cef8a655941816a7c2be222ed590f618fe389e7c08c3ac5f0826161345") {
  throw new Error("Package checksum mismatch");
}

const pkg = createSubstream(bytes);
if (!pkg.modules) throw new Error("Package has no modules");
// Keep Transfer events (evt_sig) emitted by the USDC contract (evt_addr).
applyParams(
  [
    "filtered_events=evt_addr:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 && evt_sig:0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
  ],
  pkg.modules.modules,
);
// The package embeds its protobuf schemas, so the output decodes without generated code.
const registry = createRegistry(pkg);

const request = createRequest({
  substreamPackage: pkg,
  outputModule: "filtered_events",
  startBlockNum: 18000002,
  stopBlockNum: 18000008, // exclusive
  // Unset starts at startBlockNum; a committed cursor resumes right after its block.
  startCursor: process.env.GRAPHOPS_CURSOR || undefined,
  // Production mode processes the range in parallel and sends only the output module.
  productionMode: true,
  // Only irreversible blocks, so the server never sends a blockUndoSignal (reorg rewind).
  finalBlocksOnly: true,
});

const sessionManager = new Http2SessionManager(endpoint);
const transport = createGrpcTransport({ baseUrl: endpoint, httpVersion: "2", sessionManager });
// GraphOps authenticates every gRPC call with the x-api-key metadata header.
const headers = { "x-api-key": apiKey };
try {
  for await (const response of streamBlocks(transport, request, { headers })) {
    // Skip session, progress and other non-data messages.
    if (response.message.case !== "blockScopedData") continue;
    const { clock, cursor } = response.message.value;
    const output = unpackMapOutput(response, registry);
    const data = output?.toJson() as { events?: unknown[] } | undefined;
    if (!data?.events) continue; // no matching transfers in this block

    // Store the events and this cursor together; after a restart, pass the last stored
    // cursor as startCursor to continue from the next block without gaps or duplicates.
    console.log(JSON.stringify({ block: String(clock?.number), cursor, data }));
  }
} finally {
  sessionManager.abort(); // Close the HTTP/2 connection so the process can exit.
}

```

Output

```json
{
  "block": "18000002",
  "cursor": "89c_0WDIRTi08Zu_7v-ibqWwLpc_DFtnVQvlKhFDj46k-XaW3pT3VGYgPU6Cxqn03kPtSQuoj9jLR3sqoJUFtNbryr1m7SZsEHIkwtzv_eG9e6XwMQNJc7lkWLyPZtnRWjTXYgr7erMK5YfuPqDabhNmZZJzfzTljztT9INWIadAvnVmxm2vJpra1_qf9IBB-LZzTezwwCqlUmd-ek1SNJ6AZfSY6T0kZA==",
  "data": {
    "clock": {
      "id": "5b94c39ad7eecbf043f62a4a76c5abee87195207df98ae16eb3f289b2267f141",
      "number": "18000002",
      "timestamp": "2023-08-26T16:21:59Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAA5nO9oZpSnZ/xOu3a4OCMD9qLVIg=",
            "AAAAAAAAAAAAAAAAiOagwt3Sb+62TwOaLEEpb8s/VkA="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEATMsA=",
          "index": 1,
          "blockIndex": 172,
          "ordinal": "2582"
        },
        "txHash": "29ec08dc5eed0e1cd411b29c52607661155087ffa107bae7f5e4a79f5306be1d"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAApAuxxH9t0nFCor18k7+pjbnR9cU=",
            "AAAAAAAAAAAAAAAAtOFtAWjlLTXKzSxhhbRCgewoydw="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABE0jl0=",
          "index": 2,
          "blockIndex": 315,
          "ordinal": "5139"
        },
        "txHash": "a8b07b56f7bb4ea52eecf5a125c75f143955a8e57e1ea17d2d8a48830e7d67db"
      }
    ]
  }
}
{
  "block": "18000003",
  "cursor": "n-jOoTkc7lESaOaRPLPCC6WwLpc_DFtnVQvlKhFCj4L2o3LDjJTwUTVzbkmFkqj3jkDuQ1yuj9bPRXwv9sUBtIfuwe5tvHNuTi5_ko66_ua5Kvv3b1xPIrxqXOzdM9zRWjTXYgr7erIK6dW0OvWIbhRjNsEgeDOxjjgD94BcdqFAsHFkwWj5dp7ahv-Up4sQrbQtEbegkn-mVWMvJEoMa5jRYPqcuW9xYQ==",
  "data": {
    "clock": {
      "id": "90c06a9fad66de217ce5862a92a2d45a8f42f9abf8ec17d5bfb85ffdc7836ca4",
      "number": "18000003",
      "timestamp": "2023-08-26T16:22:11Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAGAzUMDWC00ilT8sENtVmXwgqI9E=",
            "AAAAAAAAAAAAAAAAqdHgjHeTr2fp2S/jCNVpf7gdPkM="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKrQo0A=",
          "blockIndex": 222,
          "ordinal": "4219"
        },
        "txHash": "05c9c59145387faa91370e56d717d42856bd20f8f19306814d5b7afbe837efe3"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAHHJ6Veo8EbCrfTo2H+DzxHzm3l0=",
            "AAAAAAAAAAAAAAAAw9zAiK70Nz5mWwD8i+RaS2pLJKY="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaGvNo=",
          "blockIndex": 230,
          "ordinal": "4543"
        },
        "txHash": "eb8505dd3dc2964c1b43ed40371fb9c907b751ccb9294b99db032270889edc42"
      }
    ]
  }
}
{
  "block": "18000004",
  "cursor": "egpIfwVCY1AAna0iQWwoTaWwLpc_DFtnVQvlKhFFj4yg93rN1M6lBmlzPBqGla_wiUHtQlr9i43PRX97oMRZ6dK7xL5s6SU9Qnh_xY7mqOfvLKWnPQwTdu5gVuKLNtrRWjTXYgr7erUK54PgMvvQNEE0asFyKzC2iT8E9oNdcPJE63Fkwjyvd8aH06qR94pF--chR7f3kiPwVDUpehpeO8SFMvCWtzl0Zw==",
  "data": {
    "clock": {
      "id": "7f7889c3686d7f560dd690aeb2a10b49e3a76844543cf78cc0dfe4687e2985d2",
      "number": "18000004",
      "timestamp": "2023-08-26T16:22:23Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
            "AAAAAAAAAAAAAAAAQ5VHhZgMUHaDc9wdr9e6OiBSA28="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6NQzqm4=",
          "index": 1,
          "blockIndex": 191,
          "ordinal": "3060"
        },
        "txHash": "b2c1d8c8e7989adab51dafdc9be24fd05243342b8d08828c4325662de629c690"
      }
    ]
  }
}
{
  "block": "18000005",
  "cursor": "ZLBOgvAzsxIgZXZxiKsc4aWwLpc_DFtnVQvlKhFEj4r09yHDj5ShCWV9bxmEl6nx3BLsSwivj47PRX4q9MRQ6dC-kbpk7HI6En0rlN3u-ubtKvCgOANOduo2W-iJMd_RWjTXYgr7erQK4dfgafWLbkU7Zs8hKDK0jz5RpYJUIqBA6HFkw237d8-H0a_E84JArOBxQuOmwSuiVTcvLx1bNJmFNqabvTtzYg==",
  "data": {
    "clock": {
      "id": "127c6b9794874d7011770b3aa2a0a640e1db201c2d677d01b2b3b19e7ad427c7",
      "number": "18000005",
      "timestamp": "2023-08-26T16:22:35Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAyIDYvk6U9kTx2qfTVYtQ/1ocLbc=",
            "AAAAAAAAAAAAAAAAri1GF8hiMJo9daD/s1jHpQCcZz8="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALpDt0AA=",
          "blockIndex": 64,
          "ordinal": "1110"
        },
        "txHash": "1691b8e89e13f483c549d9b61efbc54ecc54015b8186726911c8458f0993e13d"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAuG/F6YX6zcrmA0vu/Gy9zYu9jLQ=",
            "AAAAAAAAAAAAAAAAJ/cTre/HCEadgmq49bHkZy8aBw4="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFloLwA=",
          "blockIndex": 203,
          "ordinal": "3669"
        },
        "txHash": "440106b494b7eb657d19e5f82a164ce4cc62e5252058fab5fb4ee6b92600543c"
      }
    ]
  }
}
{
  "block": "18000007",
  "cursor": "JtpB3QUXuAcCSUDww-4QDqWwLpc_DFtnVQvlKhFGj9-k9nGQ35v0BmRwOUnTkf323UfpSl2vjd_PFXx888hQ6YXuxepm5SQ4QC0rm4y_-bLneqbyPAJIIL1jDrzaMdnRWjTXYgr7erYKtIfhOabbYRA0Z8J3eGWy2zlQ8IdVd6BCuXE0wTv8e8-HhP-Qo4BJ-uIjEuOpkHqhAT1_eU9fNZ_TYfPO6WhzZA==",
  "data": {
    "clock": {
      "id": "db63e26b655ad31d60b2173c02127180ed46b28506f785a2682e058ca61afdc1",
      "number": "18000007",
      "timestamp": "2023-08-26T16:22:59Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAtOrojRZD4XJRjd4O8hPCBih5jGU=",
            "AAAAAAAAAAAAAAAAOMWk8i4uKQupu7nj2ar4WC959NE="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa3iEA=",
          "blockIndex": 94,
          "ordinal": "1448"
        },
        "txHash": "21d8e836f79606db39fcd51f7e563ea9c12cfb7a2bb3e36f23f4eb89b93045ea"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAV5dSz/j+568JRGshM+4vn/EMT78=",
            "AAAAAAAAAAAAAAAAFxWj5KFC2LaYExEImVF083rroQ0="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0CIc0=",
          "blockIndex": 179,
          "ordinal": "3398"
        },
        "txHash": "d78a180e669534a650d842f13d032610e29bf4a7731fb90a6dff73d752ac049c"
      }
    ]
  }
}
```

Historical block: 18,000,002

Commit this cursor with the block’s events; resume from it after a disconnect

Ethereum mainnet USDC contract; hexadecimal in CLI output, base64 in Protobuf JSON

ERC-20 Transfer signature

Sender address, left-padded to 32 bytes

Recipient address, left-padded to 32 bytes

Transfer amount: 1,075,000,000 base units, 1,075 USDC

Transaction containing this log

Go · substreams

Prerequisites

1. Install Go.

2. Install github.com/streamingfast/substreams. `go get github.com/streamingfast/substreams`

3. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

Tested with Go 1.26.0, ethereum-common v0.4.0, github.com/streamingfast/substreams v1.25.0.

[Example project](/examples/substreams.api.go-3.tar.gz)

```go
package main

import (
	"context"
	"crypto/sha256"
	"crypto/tls"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"

	rpc "github.com/streamingfast/substreams/pb/sf/substreams/rpc/v2"
	pb "github.com/streamingfast/substreams/pb/sf/substreams/v1"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protodesc"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/types/descriptorpb"
	"google.golang.org/protobuf/types/dynamicpb"
)

const (
	endpoint      = "https://1.evm.substreams.graphops.xyz"
	packageURL    = "https://spkg.io/v1/packages/ethereum-common/v0.4.0"
	packageSHA256 = "beda75cef8a655941816a7c2be222ed590f618fe389e7c08c3ac5f0826161345"
	module        = "filtered_events"
	outputType    = "sf.substreams.ethereum.v1.Events"
	// Keep Transfer events (evt_sig) emitted by the USDC contract (evt_addr).
	params = "evt_addr:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 && evt_sig:0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
)

func main() {
	if err := run(); err != nil {
		log.Fatal(err)
	}
}

func run() error {
	apiKey := os.Getenv("GRAPHOPS_API_KEY")
	if apiKey == "" {
		return errors.New("set GRAPHOPS_API_KEY")
	}

	pkg, err := downloadPackage()
	if err != nil {
		return err
	}
	// Set the module's params input, like `substreams run --params`.
	for _, m := range pkg.Modules.Modules {
		if m.Name != module {
			continue
		}
		for _, input := range m.Inputs {
			if p := input.GetParams(); p != nil {
				p.Value = params
			}
		}
	}

	// The package embeds its protobuf schemas, so the output decodes without generated code.
	files, err := protodesc.NewFiles(&descriptorpb.FileDescriptorSet{File: pkg.ProtoFiles})
	if err != nil {
		return err
	}
	descriptor, err := files.FindDescriptorByName(outputType)
	if err != nil {
		return err
	}
	eventsType := descriptor.(protoreflect.MessageDescriptor)

	// Trust the system roots, or the CA bundle in SSL_CERT_FILE when set (Go ignores it on macOS).
	creds := credentials.NewTLS(&tls.Config{})
	if caFile := os.Getenv("SSL_CERT_FILE"); caFile != "" {
		if creds, err = credentials.NewClientTLSFromFile(caFile, ""); err != nil {
			return err
		}
	}
	conn, err := grpc.NewClient(strings.TrimPrefix(endpoint, "https://"), grpc.WithTransportCredentials(creds))
	if err != nil {
		return err
	}
	defer conn.Close()

	// GraphOps authenticates every gRPC call with the x-api-key metadata header.
	ctx := metadata.AppendToOutgoingContext(context.Background(), "x-api-key", apiKey)
	stream, err := rpc.NewStreamClient(conn).Blocks(ctx, &rpc.Request{
		Modules:       pkg.Modules,
		OutputModule:  module,
		StartBlockNum: 18000002,
		StopBlockNum:  18000008, // exclusive
		// Empty starts at StartBlockNum; a committed cursor resumes right after its block.
		StartCursor: os.Getenv("GRAPHOPS_CURSOR"),
		// Production mode processes the range in parallel and sends only the output module.
		ProductionMode: true,
		// Only irreversible blocks, so the server never sends a BlockUndoSignal (reorg rewind).
		FinalBlocksOnly: true,
	})
	if err != nil {
		return err
	}

	for {
		response, err := stream.Recv()
		if errors.Is(err, io.EOF) {
			return nil // reached StopBlockNum
		}
		if err != nil {
			return err
		}
		data := response.GetBlockScopedData()
		if data == nil {
			continue // session, progress and other non-data messages
		}

		output := dynamicpb.NewMessage(eventsType)
		if err := proto.Unmarshal(data.GetOutput().GetMapOutput().GetValue(), output); err != nil {
			return err
		}
		outputJSON, err := protojson.Marshal(output)
		if err != nil {
			return err
		}
		var payload map[string]any
		if err := json.Unmarshal(outputJSON, &payload); err != nil {
			return err
		}
		if payload["events"] == nil {
			continue // no matching transfers in this block
		}

		// Store the events and this cursor together; after a restart, pass the last stored
		// cursor as StartCursor to continue from the next block without gaps or duplicates.
		record := map[string]any{"block": fmt.Sprint(data.Clock.Number), "cursor": data.Cursor, "data": payload}
		if err := json.NewEncoder(os.Stdout).Encode(record); err != nil {
			return err
		}
	}
}

// downloadPackage fetches the Substreams package and checks its SHA-256, so the program only
// ever runs the exact module code that was reviewed.
func downloadPackage() (*pb.Package, error) {
	response, err := http.Get(packageURL)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()
	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("package download: %s", response.Status)
	}
	content, err := io.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}
	if sum := sha256.Sum256(content); hex.EncodeToString(sum[:]) != packageSHA256 {
		return nil, errors.New("package checksum mismatch")
	}
	pkg := &pb.Package{}
	return pkg, proto.Unmarshal(content, pkg)
}

```

Output

```json
{
  "block": "18000002",
  "cursor": "89c_0WDIRTi08Zu_7v-ibqWwLpc_DFtnVQvlKhFDj46k-XaW3pT3VGYgPU6Cxqn03kPtSQuoj9jLR3sqoJUFtNbryr1m7SZsEHIkwtzv_eG9e6XwMQNJc7lkWLyPZtnRWjTXYgr7erMK5YfuPqDabhNmZZJzfzTljztT9INWIadAvnVmxm2vJpra1_qf9IBB-LZzTezwwCqlUmd-ek1SNJ6AZfSY6T0kZA==",
  "data": {
    "clock": {
      "id": "5b94c39ad7eecbf043f62a4a76c5abee87195207df98ae16eb3f289b2267f141",
      "number": "18000002",
      "timestamp": "2023-08-26T16:21:59Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 172,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEATMsA=",
          "index": 1,
          "ordinal": "2582",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAA5nO9oZpSnZ/xOu3a4OCMD9qLVIg=",
            "AAAAAAAAAAAAAAAAiOagwt3Sb+62TwOaLEEpb8s/VkA="
          ]
        },
        "txHash": "29ec08dc5eed0e1cd411b29c52607661155087ffa107bae7f5e4a79f5306be1d"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 315,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABE0jl0=",
          "index": 2,
          "ordinal": "5139",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAApAuxxH9t0nFCor18k7+pjbnR9cU=",
            "AAAAAAAAAAAAAAAAtOFtAWjlLTXKzSxhhbRCgewoydw="
          ]
        },
        "txHash": "a8b07b56f7bb4ea52eecf5a125c75f143955a8e57e1ea17d2d8a48830e7d67db"
      }
    ]
  }
}
{
  "block": "18000003",
  "cursor": "n-jOoTkc7lESaOaRPLPCC6WwLpc_DFtnVQvlKhFCj4L2o3LDjJTwUTVzbkmFkqj3jkDuQ1yuj9bPRXwv9sUBtIfuwe5tvHNuTi5_ko66_ua5Kvv3b1xPIrxqXOzdM9zRWjTXYgr7erIK6dW0OvWIbhRjNsEgeDOxjjgD94BcdqFAsHFkwWj5dp7ahv-Up4sQrbQtEbegkn-mVWMvJEoMa5jRYPqcuW9xYQ==",
  "data": {
    "clock": {
      "id": "90c06a9fad66de217ce5862a92a2d45a8f42f9abf8ec17d5bfb85ffdc7836ca4",
      "number": "18000003",
      "timestamp": "2023-08-26T16:22:11Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 222,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKrQo0A=",
          "ordinal": "4219",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAGAzUMDWC00ilT8sENtVmXwgqI9E=",
            "AAAAAAAAAAAAAAAAqdHgjHeTr2fp2S/jCNVpf7gdPkM="
          ]
        },
        "txHash": "05c9c59145387faa91370e56d717d42856bd20f8f19306814d5b7afbe837efe3"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 230,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaGvNo=",
          "ordinal": "4543",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAHHJ6Veo8EbCrfTo2H+DzxHzm3l0=",
            "AAAAAAAAAAAAAAAAw9zAiK70Nz5mWwD8i+RaS2pLJKY="
          ]
        },
        "txHash": "eb8505dd3dc2964c1b43ed40371fb9c907b751ccb9294b99db032270889edc42"
      }
    ]
  }
}
{
  "block": "18000004",
  "cursor": "egpIfwVCY1AAna0iQWwoTaWwLpc_DFtnVQvlKhFFj4yg93rN1M6lBmlzPBqGla_wiUHtQlr9i43PRX97oMRZ6dK7xL5s6SU9Qnh_xY7mqOfvLKWnPQwTdu5gVuKLNtrRWjTXYgr7erUK54PgMvvQNEE0asFyKzC2iT8E9oNdcPJE63Fkwjyvd8aH06qR94pF--chR7f3kiPwVDUpehpeO8SFMvCWtzl0Zw==",
  "data": {
    "clock": {
      "id": "7f7889c3686d7f560dd690aeb2a10b49e3a76844543cf78cc0dfe4687e2985d2",
      "number": "18000004",
      "timestamp": "2023-08-26T16:22:23Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 191,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6NQzqm4=",
          "index": 1,
          "ordinal": "3060",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
            "AAAAAAAAAAAAAAAAQ5VHhZgMUHaDc9wdr9e6OiBSA28="
          ]
        },
        "txHash": "b2c1d8c8e7989adab51dafdc9be24fd05243342b8d08828c4325662de629c690"
      }
    ]
  }
}
{
  "block": "18000005",
  "cursor": "ZLBOgvAzsxIgZXZxiKsc4aWwLpc_DFtnVQvlKhFEj4r09yHDj5ShCWV9bxmEl6nx3BLsSwivj47PRX4q9MRQ6dC-kbpk7HI6En0rlN3u-ubtKvCgOANOduo2W-iJMd_RWjTXYgr7erQK4dfgafWLbkU7Zs8hKDK0jz5RpYJUIqBA6HFkw237d8-H0a_E84JArOBxQuOmwSuiVTcvLx1bNJmFNqabvTtzYg==",
  "data": {
    "clock": {
      "id": "127c6b9794874d7011770b3aa2a0a640e1db201c2d677d01b2b3b19e7ad427c7",
      "number": "18000005",
      "timestamp": "2023-08-26T16:22:35Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 64,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALpDt0AA=",
          "ordinal": "1110",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAyIDYvk6U9kTx2qfTVYtQ/1ocLbc=",
            "AAAAAAAAAAAAAAAAri1GF8hiMJo9daD/s1jHpQCcZz8="
          ]
        },
        "txHash": "1691b8e89e13f483c549d9b61efbc54ecc54015b8186726911c8458f0993e13d"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 203,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFloLwA=",
          "ordinal": "3669",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAuG/F6YX6zcrmA0vu/Gy9zYu9jLQ=",
            "AAAAAAAAAAAAAAAAJ/cTre/HCEadgmq49bHkZy8aBw4="
          ]
        },
        "txHash": "440106b494b7eb657d19e5f82a164ce4cc62e5252058fab5fb4ee6b92600543c"
      }
    ]
  }
}
{
  "block": "18000007",
  "cursor": "JtpB3QUXuAcCSUDww-4QDqWwLpc_DFtnVQvlKhFGj9-k9nGQ35v0BmRwOUnTkf323UfpSl2vjd_PFXx888hQ6YXuxepm5SQ4QC0rm4y_-bLneqbyPAJIIL1jDrzaMdnRWjTXYgr7erYKtIfhOabbYRA0Z8J3eGWy2zlQ8IdVd6BCuXE0wTv8e8-HhP-Qo4BJ-uIjEuOpkHqhAT1_eU9fNZ_TYfPO6WhzZA==",
  "data": {
    "clock": {
      "id": "db63e26b655ad31d60b2173c02127180ed46b28506f785a2682e058ca61afdc1",
      "number": "18000007",
      "timestamp": "2023-08-26T16:22:59Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 94,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa3iEA=",
          "ordinal": "1448",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAtOrojRZD4XJRjd4O8hPCBih5jGU=",
            "AAAAAAAAAAAAAAAAOMWk8i4uKQupu7nj2ar4WC959NE="
          ]
        },
        "txHash": "21d8e836f79606db39fcd51f7e563ea9c12cfb7a2bb3e36f23f4eb89b93045ea"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 179,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0CIc0=",
          "ordinal": "3398",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAV5dSz/j+568JRGshM+4vn/EMT78=",
            "AAAAAAAAAAAAAAAAFxWj5KFC2LaYExEImVF083rroQ0="
          ]
        },
        "txHash": "d78a180e669534a650d842f13d032610e29bf4a7731fb90a6dff73d752ac049c"
      }
    ]
  }
}
```

Historical block: 18,000,002

Commit this cursor with the block’s events; resume from it after a disconnect

Ethereum mainnet USDC contract; hexadecimal in CLI output, base64 in Protobuf JSON

Transfer amount: 1,075,000,000 base units, 1,075 USDC

ERC-20 Transfer signature

Sender address, left-padded to 32 bytes

Recipient address, left-padded to 32 bytes

Transaction containing this log

Rust · Buffa

Prerequisites

1. Install Rust.

2. Install buffa, buffa-descriptor, buffa-types, connectrpc, reqwest, ring, rustls-native-certs, serde, serde\_json, and tokio. `cargo add buffa buffa-descriptor buffa-types connectrpc reqwest ring rustls-native-certs serde serde_json tokio -F buffa/json -F buffa-descriptor/json -F buffa-descriptor/reflect -F buffa-descriptor/views -F buffa-types/json -F connectrpc/client-tls -F reqwest/json -F tokio/macros -F tokio/rt-multi-thread`

3. Install connectrpc-build. `cargo add --build connectrpc-build`

4. Use the Substreams protocol bindings included in the example project, or generate them from https\://github.com/streamingfast/substreams/tree/v1.25.0/proto.

5. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

Tested with Rust 1.98.1, buffa 0.9.2, buffa-descriptor 0.9.2, buffa-types 0.9.2, connectrpc 0.9.1, connectrpc-build 0.9.0, ethereum-common v0.4.0, reqwest 0.13.5, ring 0.17.14, rustls-native-certs 0.8.5, serde 1.0.228, serde\_json 1.0.145, Substreams protocol bindings v1.25.0, tokio 1.48.0.

[Example project](/examples/substreams.api.rust-4.tar.gz)

```rust
// build.rs generates the Substreams protocol types and client with Buffa and connectrpc.
mod pb {
    connectrpc::include_generated!();
}

use buffa::Message;
use buffa_descriptor::{generated::descriptor::FileDescriptorSet, DescriptorPool, DynamicMessage};
use connectrpc::client::{ClientConfig, HttpClient};
use connectrpc::{rustls, Protocol};
use pb::sf::substreams::rpc::v2::{response, Request, StreamClient};
use pb::sf::substreams::v1::{module::input::Input, Package};
use ring::digest::{digest, SHA256};
use serde_json::json;
use std::{env, error::Error, sync::Arc};

const ENDPOINT: &str = "https://1.evm.substreams.graphops.xyz";
const PACKAGE_URL: &str = "https://spkg.io/v1/packages/ethereum-common/v0.4.0";
const PACKAGE_SHA256: &str = "beda75cef8a655941816a7c2be222ed590f618fe389e7c08c3ac5f0826161345";
const MODULE: &str = "filtered_events";
const OUTPUT_TYPE: &str = "sf.substreams.ethereum.v1.Events";
// Keep Transfer events (evt_sig) emitted by the USDC contract (evt_addr).
const PARAMS: &str = "evt_addr:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 && evt_sig:0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let api_key = env::var("GRAPHOPS_API_KEY")?;

    // Check the package's SHA-256, so the program only runs the exact module code you reviewed.
    let bytes = reqwest::get(PACKAGE_URL)
        .await?
        .error_for_status()?
        .bytes()
        .await?;
    let checksum: String = digest(&SHA256, &bytes)
        .as_ref()
        .iter()
        .map(|byte| format!("{byte:02x}"))
        .collect();
    if checksum != PACKAGE_SHA256 {
        return Err("package checksum mismatch".into());
    }
    let mut package = Package::decode_from_slice(&bytes)?;

    // Set the module's params input, like `substreams run --params`.
    let module = package
        .modules
        .as_option_mut()
        .ok_or("package has no modules")?
        .modules
        .iter_mut()
        .find(|module| module.name == MODULE)
        .ok_or("module not found")?;
    for input in &mut module.inputs {
        if let Some(Input::Params(params)) = &mut input.input {
            params.value = PARAMS.into();
        }
    }

    // The package embeds its protobuf schemas, so the output decodes without generated code.
    let pool = Arc::new(DescriptorPool::new(FileDescriptorSet {
        file: std::mem::take(&mut package.proto_files),
        ..Default::default()
    })?);
    let events_type = pool
        .message_index(OUTPUT_TYPE)
        .ok_or("output type not found")?;

    // Trust the system roots, or the CA bundle in SSL_CERT_FILE when set.
    let mut roots = rustls::RootCertStore::empty();
    roots.add_parsable_certificates(rustls_native_certs::load_native_certs().certs);
    let tls = rustls::ClientConfig::builder()
        .with_root_certificates(roots)
        .with_no_client_auth();
    // GraphOps authenticates every gRPC call with the x-api-key metadata header.
    let config = ClientConfig::new(ENDPOINT.parse()?)
        .with_protocol(Protocol::Grpc)
        .with_default_header("x-api-key", api_key);
    let client = StreamClient::new(HttpClient::with_tls(Arc::new(tls)), config);

    let mut stream = client
        .blocks(Request {
            modules: package.modules,
            output_module: MODULE.into(),
            start_block_num: 18000002,
            stop_block_num: 18000008, // exclusive
            // Empty starts at start_block_num; a committed cursor resumes right after its block.
            start_cursor: env::var("GRAPHOPS_CURSOR").unwrap_or_default(),
            // Production mode processes the range in parallel and sends only the output module.
            production_mode: true,
            // Only irreversible blocks, so the server never sends a BlockUndoSignal (reorg rewind).
            final_blocks_only: true,
            ..Default::default()
        })
        .await?;
    while let Some(message) = stream.message().await? {
        // Skip session, progress and other non-data messages.
        let Some(response::Message::BlockScopedData(data)) = message.to_owned_message().message
        else {
            continue;
        };
        let output = &data.output.map_output;
        let events = DynamicMessage::decode(pool.clone(), events_type, &output.value)?;
        let events = serde_json::to_value(&events)?;
        if events.get("events").is_none() {
            continue; // no matching transfers in this block
        }

        // Store the events and this cursor together; after a restart, pass the last stored
        // cursor as start_cursor to continue from the next block without gaps or duplicates.
        let record = json!({
            "block": data.clock.number.to_string(),
            "cursor": data.cursor,
            "data": events,
        });
        println!("{record}");
    }
    Ok(())
}

```

Output

```json
{
  "block": "18000002",
  "cursor": "89c_0WDIRTi08Zu_7v-ibqWwLpc_DFtnVQvlKhFDj46k-XaW3pT3VGYgPU6Cxqn03kPtSQuoj9jLR3sqoJUFtNbryr1m7SZsEHIkwtzv_eG9e6XwMQNJc7lkWLyPZtnRWjTXYgr7erMK5YfuPqDabhNmZZJzfzTljztT9INWIadAvnVmxm2vJpra1_qf9IBB-LZzTezwwCqlUmd-ek1SNJ6AZfSY6T0kZA==",
  "data": {
    "clock": {
      "id": "5b94c39ad7eecbf043f62a4a76c5abee87195207df98ae16eb3f289b2267f141",
      "number": "18000002",
      "timestamp": "2023-08-26T16:21:59Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 172,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEATMsA=",
          "index": 1,
          "ordinal": "2582",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAA5nO9oZpSnZ/xOu3a4OCMD9qLVIg=",
            "AAAAAAAAAAAAAAAAiOagwt3Sb+62TwOaLEEpb8s/VkA="
          ]
        },
        "txHash": "29ec08dc5eed0e1cd411b29c52607661155087ffa107bae7f5e4a79f5306be1d"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 315,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABE0jl0=",
          "index": 2,
          "ordinal": "5139",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAApAuxxH9t0nFCor18k7+pjbnR9cU=",
            "AAAAAAAAAAAAAAAAtOFtAWjlLTXKzSxhhbRCgewoydw="
          ]
        },
        "txHash": "a8b07b56f7bb4ea52eecf5a125c75f143955a8e57e1ea17d2d8a48830e7d67db"
      }
    ]
  }
}
{
  "block": "18000003",
  "cursor": "n-jOoTkc7lESaOaRPLPCC6WwLpc_DFtnVQvlKhFCj4L2o3LDjJTwUTVzbkmFkqj3jkDuQ1yuj9bPRXwv9sUBtIfuwe5tvHNuTi5_ko66_ua5Kvv3b1xPIrxqXOzdM9zRWjTXYgr7erIK6dW0OvWIbhRjNsEgeDOxjjgD94BcdqFAsHFkwWj5dp7ahv-Up4sQrbQtEbegkn-mVWMvJEoMa5jRYPqcuW9xYQ==",
  "data": {
    "clock": {
      "id": "90c06a9fad66de217ce5862a92a2d45a8f42f9abf8ec17d5bfb85ffdc7836ca4",
      "number": "18000003",
      "timestamp": "2023-08-26T16:22:11Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 222,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKrQo0A=",
          "ordinal": "4219",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAGAzUMDWC00ilT8sENtVmXwgqI9E=",
            "AAAAAAAAAAAAAAAAqdHgjHeTr2fp2S/jCNVpf7gdPkM="
          ]
        },
        "txHash": "05c9c59145387faa91370e56d717d42856bd20f8f19306814d5b7afbe837efe3"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 230,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaGvNo=",
          "ordinal": "4543",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAHHJ6Veo8EbCrfTo2H+DzxHzm3l0=",
            "AAAAAAAAAAAAAAAAw9zAiK70Nz5mWwD8i+RaS2pLJKY="
          ]
        },
        "txHash": "eb8505dd3dc2964c1b43ed40371fb9c907b751ccb9294b99db032270889edc42"
      }
    ]
  }
}
{
  "block": "18000004",
  "cursor": "egpIfwVCY1AAna0iQWwoTaWwLpc_DFtnVQvlKhFFj4yg93rN1M6lBmlzPBqGla_wiUHtQlr9i43PRX97oMRZ6dK7xL5s6SU9Qnh_xY7mqOfvLKWnPQwTdu5gVuKLNtrRWjTXYgr7erUK54PgMvvQNEE0asFyKzC2iT8E9oNdcPJE63Fkwjyvd8aH06qR94pF--chR7f3kiPwVDUpehpeO8SFMvCWtzl0Zw==",
  "data": {
    "clock": {
      "id": "7f7889c3686d7f560dd690aeb2a10b49e3a76844543cf78cc0dfe4687e2985d2",
      "number": "18000004",
      "timestamp": "2023-08-26T16:22:23Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 191,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6NQzqm4=",
          "index": 1,
          "ordinal": "3060",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
            "AAAAAAAAAAAAAAAAQ5VHhZgMUHaDc9wdr9e6OiBSA28="
          ]
        },
        "txHash": "b2c1d8c8e7989adab51dafdc9be24fd05243342b8d08828c4325662de629c690"
      }
    ]
  }
}
{
  "block": "18000005",
  "cursor": "ZLBOgvAzsxIgZXZxiKsc4aWwLpc_DFtnVQvlKhFEj4r09yHDj5ShCWV9bxmEl6nx3BLsSwivj47PRX4q9MRQ6dC-kbpk7HI6En0rlN3u-ubtKvCgOANOduo2W-iJMd_RWjTXYgr7erQK4dfgafWLbkU7Zs8hKDK0jz5RpYJUIqBA6HFkw237d8-H0a_E84JArOBxQuOmwSuiVTcvLx1bNJmFNqabvTtzYg==",
  "data": {
    "clock": {
      "id": "127c6b9794874d7011770b3aa2a0a640e1db201c2d677d01b2b3b19e7ad427c7",
      "number": "18000005",
      "timestamp": "2023-08-26T16:22:35Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 64,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALpDt0AA=",
          "ordinal": "1110",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAyIDYvk6U9kTx2qfTVYtQ/1ocLbc=",
            "AAAAAAAAAAAAAAAAri1GF8hiMJo9daD/s1jHpQCcZz8="
          ]
        },
        "txHash": "1691b8e89e13f483c549d9b61efbc54ecc54015b8186726911c8458f0993e13d"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 203,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFloLwA=",
          "ordinal": "3669",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAuG/F6YX6zcrmA0vu/Gy9zYu9jLQ=",
            "AAAAAAAAAAAAAAAAJ/cTre/HCEadgmq49bHkZy8aBw4="
          ]
        },
        "txHash": "440106b494b7eb657d19e5f82a164ce4cc62e5252058fab5fb4ee6b92600543c"
      }
    ]
  }
}
{
  "block": "18000007",
  "cursor": "JtpB3QUXuAcCSUDww-4QDqWwLpc_DFtnVQvlKhFGj9-k9nGQ35v0BmRwOUnTkf323UfpSl2vjd_PFXx888hQ6YXuxepm5SQ4QC0rm4y_-bLneqbyPAJIIL1jDrzaMdnRWjTXYgr7erYKtIfhOabbYRA0Z8J3eGWy2zlQ8IdVd6BCuXE0wTv8e8-HhP-Qo4BJ-uIjEuOpkHqhAT1_eU9fNZ_TYfPO6WhzZA==",
  "data": {
    "clock": {
      "id": "db63e26b655ad31d60b2173c02127180ed46b28506f785a2682e058ca61afdc1",
      "number": "18000007",
      "timestamp": "2023-08-26T16:22:59Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 94,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa3iEA=",
          "ordinal": "1448",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAtOrojRZD4XJRjd4O8hPCBih5jGU=",
            "AAAAAAAAAAAAAAAAOMWk8i4uKQupu7nj2ar4WC959NE="
          ]
        },
        "txHash": "21d8e836f79606db39fcd51f7e563ea9c12cfb7a2bb3e36f23f4eb89b93045ea"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "blockIndex": 179,
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0CIc0=",
          "ordinal": "3398",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAV5dSz/j+568JRGshM+4vn/EMT78=",
            "AAAAAAAAAAAAAAAAFxWj5KFC2LaYExEImVF083rroQ0="
          ]
        },
        "txHash": "d78a180e669534a650d842f13d032610e29bf4a7731fb90a6dff73d752ac049c"
      }
    ]
  }
}
```

Historical block: 18,000,002

Commit this cursor with the block’s events; resume from it after a disconnect

Ethereum mainnet USDC contract; hexadecimal in CLI output, base64 in Protobuf JSON

Transfer amount: 1,075,000,000 base units, 1,075 USDC

ERC-20 Transfer signature

Sender address, left-padded to 32 bytes

Recipient address, left-padded to 32 bytes

Transaction containing this log

Python · grpcio

Prerequisites

1. Install Python.

2. Install grpcio, protobuf, and requests. `python -m pip install grpcio protobuf requests`

3. Use the Substreams protocol bindings included in the example project, or generate them from https\://github.com/streamingfast/substreams/tree/v1.25.0/proto.

4. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

Tested with Python 3.12.12, ethereum-common v0.4.0, grpcio 1.84.0, protobuf 7.36.2, requests 2.34.2, Substreams protocol bindings v1.25.0.

[Example project](/examples/substreams.api.python-6.tar.gz)

```python
import hashlib
import json
import os

import grpc
import requests
from google.protobuf import descriptor_pool, json_format, message_factory

# Generated Substreams protocol bindings, included in the example project.
from sf.substreams.rpc.v2 import service_pb2, service_pb2_grpc
from sf.substreams.v1 import package_pb2

ENDPOINT = "https://1.evm.substreams.graphops.xyz"
PACKAGE_URL = "https://spkg.io/v1/packages/ethereum-common/v0.4.0"
PACKAGE_SHA256 = "beda75cef8a655941816a7c2be222ed590f618fe389e7c08c3ac5f0826161345"
MODULE = "filtered_events"
OUTPUT_TYPE = "sf.substreams.ethereum.v1.Events"
# Keep Transfer events (evt_sig) emitted by the USDC contract (evt_addr).
PARAMS = "evt_addr:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 && evt_sig:0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"

api_key = os.environ["GRAPHOPS_API_KEY"]

# Check the package's SHA-256, so the program only runs the exact module code you reviewed.
download = requests.get(PACKAGE_URL, timeout=30)
download.raise_for_status()
if hashlib.sha256(download.content).hexdigest() != PACKAGE_SHA256:
    raise RuntimeError("Package checksum mismatch")
package = package_pb2.Package.FromString(download.content)

# Set the module's params input, like `substreams run --params`.
for module in package.modules.modules:
    if module.name == MODULE:
        for module_input in module.inputs:
            if module_input.HasField("params"):
                module_input.params.value = PARAMS

# The package embeds its protobuf schemas, so the output decodes without generated code.
pool = descriptor_pool.DescriptorPool()
for proto_file in package.proto_files:
    pool.Add(proto_file)
Events = message_factory.GetMessageClass(pool.FindMessageTypeByName(OUTPUT_TYPE))

# Trust gRPC's default roots, or the CA bundle in SSL_CERT_FILE when set.
roots = None
if os.environ.get("SSL_CERT_FILE"):
    with open(os.environ["SSL_CERT_FILE"], "rb") as bundle:
        roots = bundle.read()
credentials = grpc.ssl_channel_credentials(root_certificates=roots)

with grpc.secure_channel(ENDPOINT.removeprefix("https://"), credentials) as channel:
    request = service_pb2.Request(
        modules=package.modules,
        output_module=MODULE,
        start_block_num=18000002,
        stop_block_num=18000008,  # exclusive
        # Empty starts at start_block_num; a committed cursor resumes right after its block.
        start_cursor=os.environ.get("GRAPHOPS_CURSOR", ""),
        # Production mode processes the range in parallel and sends only the output module.
        production_mode=True,
        # Only irreversible blocks, so the server never sends a BlockUndoSignal (reorg rewind).
        final_blocks_only=True,
    )
    # GraphOps authenticates every gRPC call with the x-api-key metadata header.
    stream = service_pb2_grpc.StreamStub(channel).Blocks(
        request, metadata=[("x-api-key", api_key)]
    )
    for response in stream:
        # Skip session, progress and other non-data messages.
        if not response.HasField("block_scoped_data"):
            continue
        data = response.block_scoped_data
        events = json_format.MessageToDict(
            Events.FromString(data.output.map_output.value)
        )
        if "events" not in events:
            continue  # no matching transfers in this block

        # Store the events and this cursor together; after a restart, pass the last stored
        # cursor as start_cursor to continue from the next block without gaps or duplicates.
        record = {"block": str(data.clock.number), "cursor": data.cursor, "data": events}
        print(json.dumps(record))

```

Output

```json
{
  "block": "18000002",
  "cursor": "89c_0WDIRTi08Zu_7v-ibqWwLpc_DFtnVQvlKhFDj46k-XaW3pT3VGYgPU6Cxqn03kPtSQuoj9jLR3sqoJUFtNbryr1m7SZsEHIkwtzv_eG9e6XwMQNJc7lkWLyPZtnRWjTXYgr7erMK5YfuPqDabhNmZZJzfzTljztT9INWIadAvnVmxm2vJpra1_qf9IBB-LZzTezwwCqlUmd-ek1SNJ6AZfSY6T0kZA==",
  "data": {
    "clock": {
      "id": "5b94c39ad7eecbf043f62a4a76c5abee87195207df98ae16eb3f289b2267f141",
      "number": "18000002",
      "timestamp": "2023-08-26T16:21:59Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAA5nO9oZpSnZ/xOu3a4OCMD9qLVIg=",
            "AAAAAAAAAAAAAAAAiOagwt3Sb+62TwOaLEEpb8s/VkA="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEATMsA=",
          "index": 1,
          "blockIndex": 172,
          "ordinal": "2582"
        },
        "txHash": "29ec08dc5eed0e1cd411b29c52607661155087ffa107bae7f5e4a79f5306be1d"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAApAuxxH9t0nFCor18k7+pjbnR9cU=",
            "AAAAAAAAAAAAAAAAtOFtAWjlLTXKzSxhhbRCgewoydw="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABE0jl0=",
          "index": 2,
          "blockIndex": 315,
          "ordinal": "5139"
        },
        "txHash": "a8b07b56f7bb4ea52eecf5a125c75f143955a8e57e1ea17d2d8a48830e7d67db"
      }
    ]
  }
}
{
  "block": "18000003",
  "cursor": "n-jOoTkc7lESaOaRPLPCC6WwLpc_DFtnVQvlKhFCj4L2o3LDjJTwUTVzbkmFkqj3jkDuQ1yuj9bPRXwv9sUBtIfuwe5tvHNuTi5_ko66_ua5Kvv3b1xPIrxqXOzdM9zRWjTXYgr7erIK6dW0OvWIbhRjNsEgeDOxjjgD94BcdqFAsHFkwWj5dp7ahv-Up4sQrbQtEbegkn-mVWMvJEoMa5jRYPqcuW9xYQ==",
  "data": {
    "clock": {
      "id": "90c06a9fad66de217ce5862a92a2d45a8f42f9abf8ec17d5bfb85ffdc7836ca4",
      "number": "18000003",
      "timestamp": "2023-08-26T16:22:11Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAGAzUMDWC00ilT8sENtVmXwgqI9E=",
            "AAAAAAAAAAAAAAAAqdHgjHeTr2fp2S/jCNVpf7gdPkM="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKrQo0A=",
          "blockIndex": 222,
          "ordinal": "4219"
        },
        "txHash": "05c9c59145387faa91370e56d717d42856bd20f8f19306814d5b7afbe837efe3"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAHHJ6Veo8EbCrfTo2H+DzxHzm3l0=",
            "AAAAAAAAAAAAAAAAw9zAiK70Nz5mWwD8i+RaS2pLJKY="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaGvNo=",
          "blockIndex": 230,
          "ordinal": "4543"
        },
        "txHash": "eb8505dd3dc2964c1b43ed40371fb9c907b751ccb9294b99db032270889edc42"
      }
    ]
  }
}
{
  "block": "18000004",
  "cursor": "egpIfwVCY1AAna0iQWwoTaWwLpc_DFtnVQvlKhFFj4yg93rN1M6lBmlzPBqGla_wiUHtQlr9i43PRX97oMRZ6dK7xL5s6SU9Qnh_xY7mqOfvLKWnPQwTdu5gVuKLNtrRWjTXYgr7erUK54PgMvvQNEE0asFyKzC2iT8E9oNdcPJE63Fkwjyvd8aH06qR94pF--chR7f3kiPwVDUpehpeO8SFMvCWtzl0Zw==",
  "data": {
    "clock": {
      "id": "7f7889c3686d7f560dd690aeb2a10b49e3a76844543cf78cc0dfe4687e2985d2",
      "number": "18000004",
      "timestamp": "2023-08-26T16:22:23Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
            "AAAAAAAAAAAAAAAAQ5VHhZgMUHaDc9wdr9e6OiBSA28="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6NQzqm4=",
          "index": 1,
          "blockIndex": 191,
          "ordinal": "3060"
        },
        "txHash": "b2c1d8c8e7989adab51dafdc9be24fd05243342b8d08828c4325662de629c690"
      }
    ]
  }
}
{
  "block": "18000005",
  "cursor": "ZLBOgvAzsxIgZXZxiKsc4aWwLpc_DFtnVQvlKhFEj4r09yHDj5ShCWV9bxmEl6nx3BLsSwivj47PRX4q9MRQ6dC-kbpk7HI6En0rlN3u-ubtKvCgOANOduo2W-iJMd_RWjTXYgr7erQK4dfgafWLbkU7Zs8hKDK0jz5RpYJUIqBA6HFkw237d8-H0a_E84JArOBxQuOmwSuiVTcvLx1bNJmFNqabvTtzYg==",
  "data": {
    "clock": {
      "id": "127c6b9794874d7011770b3aa2a0a640e1db201c2d677d01b2b3b19e7ad427c7",
      "number": "18000005",
      "timestamp": "2023-08-26T16:22:35Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAyIDYvk6U9kTx2qfTVYtQ/1ocLbc=",
            "AAAAAAAAAAAAAAAAri1GF8hiMJo9daD/s1jHpQCcZz8="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALpDt0AA=",
          "blockIndex": 64,
          "ordinal": "1110"
        },
        "txHash": "1691b8e89e13f483c549d9b61efbc54ecc54015b8186726911c8458f0993e13d"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAuG/F6YX6zcrmA0vu/Gy9zYu9jLQ=",
            "AAAAAAAAAAAAAAAAJ/cTre/HCEadgmq49bHkZy8aBw4="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFloLwA=",
          "blockIndex": 203,
          "ordinal": "3669"
        },
        "txHash": "440106b494b7eb657d19e5f82a164ce4cc62e5252058fab5fb4ee6b92600543c"
      }
    ]
  }
}
{
  "block": "18000007",
  "cursor": "JtpB3QUXuAcCSUDww-4QDqWwLpc_DFtnVQvlKhFGj9-k9nGQ35v0BmRwOUnTkf323UfpSl2vjd_PFXx888hQ6YXuxepm5SQ4QC0rm4y_-bLneqbyPAJIIL1jDrzaMdnRWjTXYgr7erYKtIfhOabbYRA0Z8J3eGWy2zlQ8IdVd6BCuXE0wTv8e8-HhP-Qo4BJ-uIjEuOpkHqhAT1_eU9fNZ_TYfPO6WhzZA==",
  "data": {
    "clock": {
      "id": "db63e26b655ad31d60b2173c02127180ed46b28506f785a2682e058ca61afdc1",
      "number": "18000007",
      "timestamp": "2023-08-26T16:22:59Z"
    },
    "events": [
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAtOrojRZD4XJRjd4O8hPCBih5jGU=",
            "AAAAAAAAAAAAAAAAOMWk8i4uKQupu7nj2ar4WC959NE="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa3iEA=",
          "blockIndex": 94,
          "ordinal": "1448"
        },
        "txHash": "21d8e836f79606db39fcd51f7e563ea9c12cfb7a2bb3e36f23f4eb89b93045ea"
      },
      {
        "log": {
          "address": "oLhpkcYhizbB0Z1KLp6wzjYG60g=",
          "topics": [
            "3fJSrRviyJtpwrBo/DeNqpUrp/FjxKEWKPVaTfUjs+8=",
            "AAAAAAAAAAAAAAAAV5dSz/j+568JRGshM+4vn/EMT78=",
            "AAAAAAAAAAAAAAAAFxWj5KFC2LaYExEImVF083rroQ0="
          ],
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0CIc0=",
          "blockIndex": 179,
          "ordinal": "3398"
        },
        "txHash": "d78a180e669534a650d842f13d032610e29bf4a7731fb90a6dff73d752ac049c"
      }
    ]
  }
}
```

Historical block: 18,000,002

Commit this cursor with the block’s events; resume from it after a disconnect

Ethereum mainnet USDC contract; hexadecimal in CLI output, base64 in Protobuf JSON

ERC-20 Transfer signature

Sender address, left-padded to 32 bytes

Recipient address, left-padded to 32 bytes

Transfer amount: 1,075,000,000 base units, 1,075 USDC

Transaction containing this log

## Read the output

CLI JSON places events
inside `@data` and identifies the output type with `@type`. The CLI encodes log bytes as hexadecimal;
the language consumers use standard Protobuf JSON with base64 bytes. Transaction hashes are strings.

* `topics[0]` identifies the event. `ddf252ad…` is the ERC-20 `Transfer` signature; keep events
that match it.
* `topics[1]` and `topics[2]` are the sender and recipient, left-padded to 32 bytes. The last 40
hex characters are the address.
* `txHash` links the event to its transaction.

The `data` field contains the transfer amount as a 32-byte unsigned integer. USDC has six decimals,
so divide its decoded integer by 1,000,000 to obtain the token amount.

## Before production

* Commit a block's transfer rows and its cursor together.
* Handle undo signals if you follow blocks near the chain head; see
[Cursors and reorgs](/substreams/concepts/cursors-and-reorgs).
* Backfill history with finite ranges, then continue live; see
[Backfill a range](/substreams/guides/backfill-a-range).
* Watch bytes out. Event-heavy modules can produce large responses.

## Next steps

* [Backfill a range](/substreams/guides/backfill-a-range): Load the contract's event history before you follow the chain.
* [Packages and modules](/substreams/concepts/packages-and-modules): Choose a package and output module that fit the data you store.
* [Usage and limits](/substreams/usage-and-limits): Estimate the cost of an event-heavy stream.