# API integration

Source: https://docs.graphops.xyz/substreams/api

Look up what a Substreams client sends and receives. GraphOps Substreams exposes standard
Substreams gRPC endpoints, so standard Substreams tooling works with a GraphOps endpoint URL and
API key. This page lists the stream inputs, the messages a consumer handles, and where the upstream
protocol reference lives.

## Endpoint patterns

```text
https://<CHAIN_ID>.evm.substreams.graphops.xyz

```

Endpoint preview. Replace placeholders with your identifier and API key before making requests.

```text
https://1.evm.substreams.graphops.xyz
x-api-key: <GRAPHOPS_API_KEY>
```

[Get query endpoint](https://platform.graphops.xyz/organizations/current/overview/endpoint?product=substreams&networkId=eip155%3A1)
[Supported networks](/substreams/networks)

Use [Supported networks](/substreams/networks) for supported chains and testnet availability.

## Transport

Substreams uses gRPC streams. A client selects:

| Field          | Meaning                                                                                |
| -------------- | -------------------------------------------------------------------------------------- |
| Endpoint       | GraphOps chain endpoint.                                                               |
| Package        | Public package name, package URL, local `.spkg`, or other standard package reference.  |
| Module         | Output module to stream.                                                               |
| Start block    | First block to process.                                                                |
| Stop block     | Optional end block or relative range.                                                  |
| Cursor         | Optional resume point from a previous stream.                                          |
| Authentication | `SUBSTREAMS_API_KEY` for CLI, or `x-api-key: <GRAPHOPS_API_KEY>` for raw gRPC clients. |

## API versions

Every GraphOps Substreams endpoint serves the v2, v3, and v4 streaming APIs with the same API key.
Use the version your client library implements.

| Version | gRPC method                          | Request                                                                                                                                          | Response                                                                                                                 |
| ------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| v2      | `sf.substreams.rpc.v2.Stream/Blocks` | The client sends the module definitions from the package.                                                                                        | One `BlockScopedData` message per block.                                                                                 |
| v3      | `sf.substreams.rpc.v3.Stream/Blocks` | The client sends the full `.spkg` package. The server applies module parameter overrides (`params`) and selects the package network (`network`). | Same as v2.                                                                                                              |
| v4      | `sf.substreams.rpc.v4.Stream/Blocks` | Same as v3.                                                                                                                                      | `BlockScopedDatas`, a batch of consecutive `BlockScopedData` messages in block order. Other messages are the same as v2. |

The Firehose services that also appear in an endpoint's service description are not available with
a GraphOps API key.

## Request

The CLI tab is a complete command. The TypeScript, Go, Rust, and Python tabs are complete consumers
of the v2 API.

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

## Response

A stream sends these standard Substreams messages. A v4 stream wraps `BlockScopedData` in
`BlockScopedDatas` batches; handle each item in order.

| Message                   | Meaning                                            | Consumer action                               |
| ------------------------- | -------------------------------------------------- | --------------------------------------------- |
| `SessionInit`             | Stream session metadata.                           | Log the session and continue.                 |
| `BlockScopedData`         | Output for one block and a cursor.                 | Commit the output, then persist the cursor.   |
| `BlockUndoSignal`         | Reorg correction with last valid block and cursor. | Revert local state to the last valid point.   |
| `ModulesProgress`         | Progress and processed-byte metadata.              | Use for logs, metrics, and backfill progress. |
| `InitialSnapshotData`     | Snapshot data for supported sink flows.            | Process before live or range output.          |
| `InitialSnapshotComplete` | Snapshot completion marker.                        | Mark snapshot ingestion complete.             |

## Authentication

See [Authentication](/substreams/authentication) for API-key creation, `x-api-key` headers,
and secret handling. Route-token authentication is not supported for Substreams.

## Standard reference

GraphOps supports standard Substreams behavior. Use upstream Substreams documentation for protocol,
package, manifest, module, and sink details:

* [Substreams documentation](https://docs.substreams.dev/)
* [Substreams package registry](https://substreams.dev/packages)
* [Substreams core concepts](https://docs.substreams.dev/reference-material/core-concepts)
* [Substreams manifest and components](https://docs.substreams.dev/reference-material/manifest-and-components)
* [Substreams gRPC protocol definitions](https://github.com/streamingfast/substreams/tree/develop/proto/sf/substreams/rpc)

## Next steps

* [Cursors and reorgs](/substreams/concepts/cursors-and-reorgs): Decide how your consumer commits cursors and handles undo signals.
* [Packages and modules](/substreams/concepts/packages-and-modules): Choose the package and output module a stream runs.
* [Sinks and materialization](/substreams/concepts/sinks-and-materialization): Pick a standard sink for your destination.