JavaScript Quickstart

In this guide we will show you how to create a basic setup so you can benefit from the dfuse GraphQL API under one of the supported JavaScript environments:

  • Browser through a bundler like Webpack (includes Create React App projects) (see Bundler code tab below)
  • Browser standalone (see Browser code tab below)
  • Node.js server (see Node.js code tab below)

Note

While not in the supported list (yet), you should in theory be able to use the example under a React Native environment.

All examples uses ES6 syntax with await/async keywords, using import keywords on Bundler and Browser environments while using the require syntax on Node.js environment. However,

Note

The library compiles itself to down to ES5 features so we support older ES5 compliant browsers that are not compatible with ES6 features (IE 11 for example).

We assume the commands below are performed in an empty project folder. To quickly start an empty project:


mkdir -p example-dfuse-javascript
cd example-dfuse-javascript
npm init -y

mkdir -p example-dfuse-javascript
cd example-dfuse-javascript
yarn init -y

1. Get a dfuse API Key

Get an API key

  1. Create your account on https://app.dfuse.io
  2. Click “Create New Key” and give it a name, a category. In the case of a web key give it an “Origin” value.
See Authentication for further details

2. Adding the Client Library

The simplest way to get started with dfuse and JavaScript/TypeScript development is to use the dfuse JS client library .

Here are a few of its key features:

  • Handles API token issuance
  • Refreshes your API token upon expiration
  • Automatically reconnects if the connection closes
  • Supports Browsers and Node.js environments

You can add it to your project using Yarn or NPM.


npm install @dfuse/client

yarn add @dfuse/client

Node.js Extra Steps

If you are targeting the Node.js environment, a few extra steps are required to be able to use the @dfuse/client library. Indeed, the library relies on node-fetch package for HTTP requests and on the ws package for WebSocket connection.


npm install node-fetch ws

yarn add node-fetch ws

Once installed, prior calling anything else, ensure that global.fetch and global.WebSocket are set in the global scope.

Important

This is required only in a Node.js environment. When targeting a Browser environment (in standalone HTML or through a bundler, @dfuse/client library automatically uses fetch and WebSocket objects provided by the browser).

global.fetch = require('node-fetch')
global.WebSocket = require('ws')

Note

You prefer to not pollute the global scope? Check Node.js Configuration Example to see how you can pass the options directly when instantiating the client instead of polluting the global scope.

3. Create the client

With the initial setup completed, you can start coding. The first thing we will do is initialize the dfuse client using the API key you created in the first step and the network you want to connect to.

Valid networks can be found at Ethereum API Endpoints


const { createDfuseClient } = require("@dfuse/client")

const client = createDfuseClient({
  apiKey: process.env.DFUSE_API_KEY,
  network: "mainnet.eth.dfuse.io",
})

const { createDfuseClient } = require("@dfuse/client")

const client = createDfuseClient({
  apiKey: process.env.DFUSE_API_KEY,
  network: "mainnet.eth.dfuse.io",
})

<head>
    <style> li { font-family: monospace; margin: 0.15; }</style>
    <script src="https://unpkg.com/@dfuse/client"></script>
    <script>
        const client = dfuseClient.createDfuseClient({
          // Replace 'web_abcdef12345678900000000000' with your own API key!
          apiKey: 'web_abcdef12345678900000000000',
          network: 'mainnet.eth.dfuse.io'
        })
    </script>
</head>

4. Stream your first results

Let’s first define the GraphQL operation, as a string, that we will use to perform GraphQL subscription. This element tells the backend server what fields to return to you, you get to choose and pick only what you are interested in.

Note

Want to inspect the full set of available fields you can retrieve?


// You must use a `$cursor` variable so stream starts back at last marked cursor on reconnect!
const operation = `subscription($cursor: String!) {
  searchTransactions(indexName:CALLS, query:"-value:0 type:call", lowBlockNum: -1, cursor: $cursor) {
    undo cursor
    node { hash matchingCalls { from to value(encoding:ETHER) } }
  }
}`

// You must use a `$cursor` variable so stream starts back at last marked cursor on reconnect!
const operation = `subscription($cursor: String!) {
  searchTransactions(indexName:CALLS query:"-value:0 type:call", lowBlockNum: -1, cursor: $cursor) {
    undo cursor
    node { hash matchingCalls { from to value(encoding:ETHER) } }
  }
}`

<script>
// You must use a `$cursor` variable so stream starts back at last marked cursor on reconnect!
const operation = `subscription($cursor: String!) {
  searchTransactions(indexName:CALLS, query:"-value:0 type:call", lowBlockNum: -1, cursor: $cursor) {
    undo cursor
    node { hash matchingCalls { from to value(encoding:ETHER) } }
  }
}`
</script>

Next, you create the GraphQL subscription to stream transfers as they come. You will use the searchTransactions operation, with the "-value:0 type:call" query (See the Search Query Language reference here). This basically means, give me all transactions containing one or more EVM calls for which the value field (amount of Ether the call is transferring) is not 0.

You can combine the dfuse client instance we created in step 3 with the GraphQL document we defined above in a main function:


async function main() {
  const stream = await client.graphql(operation, (message) => {
    if (message.type === "data") {
      const { undo, cursor, node: { hash, value, matchingCalls }} = message.data.searchTransactions
      matchingCalls.forEach(({ from, to, value }) => {
        console.log(`Transfer ${from} -> ${to} [${value} Ether]${undo ? " REVERTED" : ""}`)
      })

      // Mark stream at cursor location, on re-connect, we will start back at cursor
      stream.mark({ cursor })
    }

    if (message.type === "error") {
      console.log("An error occurred", message.errors, message.terminal)
    }

    if (message.type === "complete") {
      console.log("Completed")
    }
  })

  // Waits until the stream completes, or forever
  await stream.join()
  await client.release()
}

// You would normally use your framework entry point and render using components,
// we are using pure HTML manipulation for sake of example simplicity.
async function main() {
  const stream = await client.graphql(operation, (message) => {
    if (message.type === "data") {
      const { undo, cursor, node: { hash, value, matchingCalls }} = message.data.searchTransactions
      matchingCalls.forEach(({ from, to, value }) => {
        const paragraphNode = document.createElement("li")
        paragraphNode.innerText = `Transfer ${from} -> ${to} [${value} Ether]${undo ? " REVERTED" : ""}`

        document.body.prepend(paragraphNode)
      })

      // Mark stream at cursor location, on re-connect, we will start back at cursor
      stream.mark({ cursor })
    }

    if (message.type === "error") {
      const { errors, terminal } = message
      const paragraphNode = document.createElement("li")
      paragraphNode.innerText = `An error occurred ${JSON.stringify({ errors, terminal })}`

      document.body.prepend(paragraphNode)
    }

    if (message.type === "complete") {
        const paragraphNode = document.createElement("li")
        paragraphNode.innerText = "Completed"

        document.body.prepend(paragraphNode)
    }
  })

  // Waits until the stream completes, or forever
  await stream.join()
  await client.release()
}

<script>
async function main() {
  const stream = await client.graphql(operation, (message) => {
    if (message.type === "data") {
      const { undo, cursor, node: { hash, value, matchingCalls }} = message.data.searchTransactions
      matchingCalls.forEach(({ from, to, value }) => {
        const paragraphNode = document.createElement("li")
        paragraphNode.innerText = `Transfer ${from} -> ${to} [${value} Ether]${undo ? " REVERTED" : ""}`

        document.body.prepend(paragraphNode)
      })

      // Mark stream at cursor location, on re-connect, we will start back at cursor
      stream.mark({ cursor })
    }

    if (message.type === "error") {
      const { errors, terminal } = message
      const paragraphNode = document.createElement("li")
      paragraphNode.innerText = `An error occurred ${JSON.stringify({ errors, terminal })}`

      document.body.prepend(paragraphNode)
    }

    if (message.type === "complete") {
        const paragraphNode = document.createElement("li")
        paragraphNode.innerText = "Completed"

        document.body.prepend(paragraphNode)
    }
  })

  // Waits until the stream completes, or forever
  await stream.join()
  await client.release()
}
</script>

The function passed as the 2nd parameter to client.graphql() will be called every time a new result is returned by the API. And here a sample of the prints you can see from as a result of execution the streaming operation above:

Transfer 0xd7afbf5141a7f1d6b0473175f7a6b0a7954ed3d2 -> 0x43d2b8827218752ffe5a35cefc3bbe50ca79af47 [0.000497522732 Ether]
Transfer 0x43d2b8827218752ffe5a35cefc3bbe50ca79af47 -> 0xd7e2cfd68a66b0f085d6b011df17ce03230278b7 [0.001180743062 Ether]
Transfer 0x81c5cc877b61fa836bd3ffe83ab4659868183492 -> 0xb3199b592b4e6841839d1c83a0719d2f2a5db2a8 [0.19705971 Ether]
Transfer 0x3fee97826b2630d1fed97a35d4559937a5d183c3 -> 0xbea4e9f3a7752a5b44b13aaee4aaba2505cc60a6 [0.061404268 Ether]
Transfer 0x1c22fa9495d1d65df8e48d61d217732eb5b06b23 -> 0x298aca39f7bc65f9c7537c790b81968220bc1fc7 [0.00335537974 Ether]
...

5. Full Working Examples

Here the small glue code containing the main function, imports and other helper functions to run the example:


main().catch((error) => console.log("Unexpected error", error))

main().catch((error) => document.body.innerHTML = `<p>${error}</p>`)

<script>
main().catch((error) => document.body.innerHTML = `<p>${error}</p>`)
</script>

git clone https://github.com/dfuse-io/docs
cd docs/quickstarts/javascript/node.js
npm install

# Replace 'server_abcdef12345678900000000000' with your own API key!
DFUSE_API_KEY=server_abcdef12345678900000000000 node index.ethereum.js

git clone https://github.com/dfuse-io/docs
cd docs/quickstarts/javascript/bundler
npm install

# Replace 'web_abcdef12345678900000000000' with your own API key!
DFUSE_API_KEY=web_abcdef12345678900000000000 npm run build:ethereum

# Open `index.ethereum.html` directly in your favorite Browser
open index.ethereum.html       # Mac
xdg-open index.ethereum.html   # Ubuntu
start index.ethereum.thml      # Windows

git clone https://github.com/dfuse-io/docs
cd docs/quickstarts/javascript/browser
# Manually edit index.ethereum.html changing `web_abcdef12345678900000000000` with your own API key

# Open `index.ethereum.html` directly in your favorite Browser
open index.ethereum.html       # Mac
xdg-open index.ethereum.html   # Ubuntu
start index.ethereum.thml      # Windows

7. What’s next ?