Skip to content

streamingfast/streamingfast-client

Repository files navigation

查看中文版

Streaming Fast

Streaming Fast mutli-blockchain allows to you to stream Ethereum or Solana data like there's no tomorrow

https://streamingfast.io/

StreamingFast Demo

Impressed? Star the repo!

Getting started

  1. Get an API key from https://streamingfast.io
  2. Download a release from the releases
  3. Get streaming!

Install

Build from source:

go get -v github.com/streamingfast/streamingfast-client/cmd/sf

or download a statically linked binary for Windows, macOS or Linux.

Usage

Ethereum

$ export STREAMINGFAST_API_KEY="server_......................"

# Watch all calls for a single block and close
$ sf eth 11700000 11700001


# Continue where you left off, start from the last known cursor, get all fork notifications (UNDO, IRREVERSIBLE), stream forever
$ sf eth --handle-forks --start-cursor "10928019832019283019283"

# Look at ALL blocks in a given range on Binance Smart Chain (BSC)
$ sf eth --bsc 100000 100002

# Look at ALL blocks in a given range on Polygon Chain
$ sf eth --polygon 100000 100002

# Look at ALL blocks in a given range on Huobi ECO Chain
$ sf eth --heco 100000 100002

# Look at recent blocks and stream forever on Fantom Opera Mainnet
$ sf eth --fantom -- -5

# Look at recent blocks and stream forever on xDai Chain
$ sf eth --xdai --  -5

Programmatic access

Access is done through gRPC, supporting more than a dozen popular languages.

You will need these protobuf definition files:

Refer to the Authentication section of our docs for details.

Take inspiration from the main.go file in this repository.

Sample output

Here is a short peek into some output. Read a full sample output here.

{
  // Indicator that this is a new block, could be IRREVERSIBLE or UNDO
  "step": "STEP_NEW",

  // Use this to continue *exactly* where you left off, guaranteeing linearity of your streaming
  // processes.
  "cursor": "PWxQqpUKpA64sLwiUK9I7aWwLpcyB1toUQvhKRJLhY2goSHD1JryAGZ8YE-DmKukiRToGFOljdvOFix7-8ZWuIPrkr426CMxTy95woDt-73mefKhPFsfc-9hVuqJatLbUQ=="

  // Obtain block-level information, filtered to keep only the transactions
  // that matched your filtering criterias.
  "block": {
    "hash": "06fac697d0798bc82dd13c99c432b09664b1a6b5299dea9309911c5a42d39078",
    "number": "11740433",
    "header": {
      "parentHash": "d9e47617a4b60c6466c82f979b80046f765dc3ac341c0db113ba7428d6b192f8",
      "uncleHash": "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
      "coinbase": "ea674fdde714fd979de3edf0f56aa9716b898ec8",
      "stateRoot": "9a35f4e8dd255e63d9fadc21796b7b9eb5e77deec8d3c553156ecd3bf8299ab9",
      "transactionsRoot": "c822a62db0ac0fee7b3ee5c5c774de6541050d0fe62a0ea8cd5389b0156e622a",
      "receiptRoot": "72a07569c369fc073b374a9f930e37fbf2aa52400f7d9b47c17375211a15b1c1",
      "logsBloom": "16ba440e4d1623d1e0ab05a28c019a82d0409011b86e59f04cf9580e12f755816b3c97242ea1122651acdbda300429d89b2964c21b42c8808759180bb07c3850551d2ceaa790987a5a71ca1c1149e1e97f5400362556f455151239489a473215dd4094562ef5c0042888c08813108fcacea8119167891496a6778851d71275ec8906c975df09a0309841316c4c806acbc8312f992172d0ea5122b16f255954e3279926464d1739f70492ffd8b599b0ea0a0100d5619ad89850b34c393c9662459cbb9d43292fc0000e00b60104de054cc31c8f0215cb10981240d876b34068a4901b6e0134e4d5ac7186364e0b97e3a5842d231e01cd32795ae8f85481920c21",
      "difficulty": "10489a91f1e9e1",
      "number": "11740433",
      "gasLimit": "12481378",
      "gasUsed": "12477353",
      "timestamp": "2021-01-27T21:58:38Z",
      "extraData": "65746865726d696e652d6575312d35",
      "mixHash": "744e2e38ed64d84bf393c8535478587eea7957cd12ec68a43b0deae14136c3c1",
      "nonce": "8789940137479893166",
      "hash": "06fac697d0798bc82dd13c99c432b09664b1a6b5299dea9309911c5a42d39078"
    },

    // Execution traces of matching transactions, along withj
    // LOTS of data and details.
    "transactionTraces": [
      {
        "hash": "3ef815c7b531ca2c9da824bc9daa322edbc1ae7ba99548f2498d7ecc4279b934",
        "to": "582b409cbf6c026a639f065641f910e9d2d7f482",
        "from": "ea674fdde714fd979de3edf0f56aa9716b898ec8",
        "nonce": "30654932",
        "gasPrice": "3b9aca00",       // Gas price, hex-encoded.
        "gasLimit": "50000",
        "value": "0164732a08272565",  // ETH value being transfered, hex-encoded.
        "v": "25", "r": "c355c........a515", "s": "4b04e........c314",
        "gasUsed": "21000",
        "receipt": {
          "cumulativeGasUsed": "21000",
          "logsBloom": "00000...."
        },
        "calls": [

          // This is where it gets interesting:

          {
            "index": 1,
            "callType": "CALL",
            "caller": "ac844b604d6c600fbe55c4383a6d87920b46a160",
            "address": "d9e1ce17f2641f24ae83637ab66a2cca9c378b9f",
            "value": "",              // This would be value == 0
            "gasLimit": "476928",
            "gasConsumed": "128974",

            // You get return data between each calls
            "returnData": "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000130feb5cd4e3ad00000000000000000000000000000000000000000000000000001bf019b3165faeed7",

            // You're also privy to the input that was given to each call in the callgraph.
            "input": "18cbafe5000000000000000000000000000000000000000000000130feb5cd4e3ad00000000000000000000000000000000000000000000000000001bec863b0fd5a700000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000ac844b604d6c600fbe55c4383a6d87920b46a160000000000000000000000000000000000000000000000000000000006011e28b00000000000000000000000000000000000000000000000000000000000000020000000000000000000000006b3595068778dd592e39a122f4f5a5cf09c90fe2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
            "executedCode": true,

            // You can observe all the internal ETH transfers, and their
            // previous and next balance.  Can't do that anywhere else!

            "balanceChanges": [
              {
                "address": "ac844b604d6c600fbe55c4383a6d87920b46a160",
                "oldValue": "1a85cdf3216e9f3c14",
                "newValue": "1a850c4f1f2f5abd34",
                "reason": "REASON_GAS_BUY"
              },
              {
                "address": "ac844b604d6c600fbe55c4383a6d87920b46a160",
                "oldValue": "1c440dea509555ac0b",
                "newValue": "1c449dbbd79f460b71",
                "reason": "REASON_GAS_REFUND"
              },

              // [... snip ...]

              {
                "address": "ea674fdde714fd979de3edf0f56aa9716b898ec8",
                "oldValue": "4fc1fd62ca11ec8278",
                "newValue": "4fc22f35454740a1f2",
                "reason": "REASON_REWARD_TRANSACTION_FEE"
              }
            ],

            // Self explanatory I guess, but pretty useful to avoid calling nodes all the time.

            "nonceChanges": [
              {
                "address": "ac844b604d6c600fbe55c4383a6d87920b46a160",
                "oldValue": "25310",
                "newValue": "25311"
              }
            ],
            "gasChanges": [
              {
                "oldValue": "500000",
                "newValue": "476928",
                // You've got all the reasons why gas is charged or refunded
                "reason": "REASON_INTRINSIC_GAS"
              },
              {
                // [... snip ...]
                "reason": "REASON_CALL_DATA_COPY"
              },
              {
                // [... snip ...]
                "reason": "REASON_REFUND_AFTER_EXECUTION"
              },

              // [... snip ...]

            ],

            // These allow you to rebuild a full graph of consumption and trace
            // gas consumption even between EVM calls
            "gasEvents": [
              {
                "id": "ID_BEFORE_CALL",
                "gas": "473893",
                "linkedCallIndex": "2"
              },
              {
                "id": "ID_AFTER_CALL",
                "gas": "471970",
                "linkedCallIndex": "2"
              },

              // [... snip ...]

            ]
          },

          // The following call is an internal transaction, a call initiated by the previous
          // one.

          {
            // 1-based index of the call within the transaction.
            "index": 2,

            // This is a 1-based index, where the value 0 would mean "no parent", or top-level
            "parentIndex": 1,

            // Can easily represent the call tree with depth
            "depth": 1,

            // This is a static call, mostly to get data out of the other contract.
            "callType": "STATIC",
            "caller": "d9e1ce17f2641f24ae83637ab66a2cca9c378b9f",
            "address": "795065dcc9f64b5614c407a6efdc400da6221fb0",
            "value": "",
            "gasLimit": "465794",
            "gasConsumed": "1217",

            // Also useful to debug or understand what's happening
            "returnData": "0000000000000000000000000000000000000000000f2484783460e8ff44c235000000000000000000000000000000000000000000001644567a439aadc9a3ea000000000000000000000000000000000000000000000000000000006011e1b4",
            "input": "0902f1ac",
            "executedCode": true
          },

          // Seems we made a third call here:

          {
            "index": 3,
            "parentIndex": 1,
            "depth": 1,
            "callType": "CALL",

            [... snip ...]

            "logs": [
             {
                "address": "6b3595068778dd592e39a122f4f5a5cf09c90fe2",
                "topics": [

                  // Yes, that's an ERC-20 transfer

                  "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",

                  // From address

                  "000000000000000000000000ac844b604d6c600fbe55c4383a6d87920b46a160",

                  // To address
                  "000000000000000000000000795065dcc9f64b5614c407a6efdc400da6221fb0"
                ],

                // And the amount is in here:
                "data": "000000000000000000000000000000000000000000000130feb5cd4e3ad00000",

                // This is the index of this log event within the whole block.

                "blockIndex": 5
              },
              {
                "address": "6b3595068778dd592e39a122f4f5a5cf09c90fe2",
                "topics": [
                  "8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
                  "000000000000000000000000ac844b604d6c600fbe55c4383a6d87920b46a160",
                  "000000000000000000000000d9e1ce17f2641f24ae83637ab66a2cca9c378b9f"
                ],
                "data": "ffffffffffffffffffffffffffffffffffffffffffbe614b550e4dc2b786ffff",
                "index": 1,
                "blockIndex": 6
              }
            ]

            // This should be helpful: you get the BALANCES (previous and next) for the
            // updated ERC-20 accounts.  This avoids hundreds of `getBalance()` calls afterward.

            "erc20BalanceChanges": [
              {
                "holderAddress": "164e948cb069f2008bda69d89b5bbdc0639f6783",
                "oldBalance": "0000000000000000000000000000000000000000028427eec9781a19c7b19ac0",
                "newBalance": "0000000000000000000000000000000000000000028427ee6162bfd660439ac0"
              },
              {
                "holderAddress": "703052a1ef835dd5842190e53896672b8f9249f1",
                "oldBalance": "00000000000000000000000000000000000000000000000068155a43676e0000",
                "newBalance": "000000000000000000000000000000000000000000000000d02ab486cedc0000"
              }
            ],
            "erc20TransferEvents": [
              {
                "from": "164e948cb069f2008bda69d89b5bbdc0639f6783",
                "to": "703052a1ef835dd5842190e53896672b8f9249f1",
                "amount": "00000000000000000000000000000000000000000000000068155a43676e0000"
              }
            ]

            // You can use this data to reverse the changes to the storage below
            // and understand which Externally Owned Address's balance is being
            // modified.. Avoid costly, and difficult to sync `getBalance()`
            // eth_calls, by using the State directly (see below)

            "keccakPreimages": {
              "0490a33f730091720d4c0d29bd2bf6a18ca8c44a423a64002ff24115dd8b8381": "000000000000000000000000ac844b604d6c600fbe55c4383a6d87920b46a1600000000000000000000000000000000000000000000000000000000000000001",

              // Take note of this one, and read below:

              "a60c07f2aed92cf0e2ca94448542cb8f5cc91bf932d411877ec1850bf66a155f": "000000000000000000000000795065dcc9f64b5614c407a6efdc400da6221fb00000000000000000000000000000000000000000000000000000000000000000",
              "b995e795ef3cc8a80fd42d092cd13c326fe2a42885cee30593e77e4b404db0e3": "000000000000000000000000ac844b604d6c600fbe55c4383a6d87920b46a1600000000000000000000000000000000000000000000000000000000000000000",
              "d7e11f80431dbe8f8fe4aba8c8c50b6b80718ea5764ac29e9b9b6e5b537bc944": "000000000000000000000000d9e1ce17f2641f24ae83637ab66a2cca9c378b9f0490a33f730091720d4c0d29bd2bf6a18ca8c44a423a64002ff24115dd8b8381"
            },

            // These are the *actual* values that are modified by the contract, with their balances.
            // What you usually only found on Etherscan can now be used by your algorithms
            // and apps, to speed up things and make things more consistent.

            "storageChanges": [
              {
                "address": "6b3595068778dd592e39a122f4f5a5cf09c90fe2",
                "key": "b995e795ef3cc8a80fd42d092cd13c326fe2a42885cee30593e77e4b404db0e3",
                "oldValue": "00000000000000000000000000000000000000000000062a350284837151aee9",
                "newValue": "0000000000000000000000000000000000000000000004f9364cb7353681aee9"
              },
              {
                "address": "6b3595068778dd592e39a122f4f5a5cf09c90fe2",

                // Noticed that the `keccakPreimages` above has data associated with the key
                // that follows (a60c07...)?
                // It happens to hold the address of the user's balance we're changing here:
                //   0x795065dcc9f64b5614c407a6efdc400da6221fb
                // Without the keccakPreimages, those state changes are admittedly pretty
                // opaque :)

                "key": "a60c07f2aed92cf0e2ca94448542cb8f5cc91bf932d411877ec1850bf66a155f",
                "oldValue": "0000000000000000000000000000000000000000000f2484783460e8ff44c235",
                "newValue": "0000000000000000000000000000000000000000000f25b576ea2e373a14c235"
              },
              {
                "address": "6b3595068778dd592e39a122f4f5a5cf09c90fe2",
                "key": "d7e11f80431dbe8f8fe4aba8c8c50b6b80718ea5764ac29e9b9b6e5b537bc944",
                "oldValue": "ffffffffffffffffffffffffffffffffffffffffffbe627c53c41b10f256ffff",
                "newValue": "ffffffffffffffffffffffffffffffffffffffffffbe614b550e4dc2b786ffff"
              }
            ]
          }
        ]
      }
    ],

    // These are transaction-level balance changes (not caused by an EVM Call):

    "balanceChanges": [
      {
        "address": "ea674fdde714fd979de3edf0f56aa9716b898ec8",
        "oldValue": "4fcdc5db9057ea8285",
        "newValue": "4fe98748f7a6b28285",
        "reason": "REASON_REWARD_MINE_BLOCK"
      }
    ]
  }
}

License

Apache 2.0