Getting Started with Other Languages

dfuse exposes its data through a GraphQL over gRPC interface. The protobuf files are in this github repository .

The code from the examples on this page can be found in the quickstarts folder of this docs GitHub repository .

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. Generate a JWT from your API key

The JWT is a token with a short expiration period, used to communicate with dfuse services. You will have to implement token caching and manage renewal upon expiration. See Authentication for more details.


func getToken(apiKey string) (token string, expiration time.Time, err error) {
    reqBody := bytes.NewBuffer([]byte(fmt.Sprintf(`{"api_key":"%s"}`, apiKey)))
    resp, err := http.Post("https://auth.dfuse.io/v1/auth/issue", "application/json", reqBody)
    if err != nil {
        err = fmt.Errorf("unable to obtain token: %s", err)
        return
    }

    if resp.StatusCode != 200 {
        err = fmt.Errorf("unable to obtain token, status not 200, got %d: %s", resp.StatusCode, reqBody.String())
        return
    }

    if body, err := ioutil.ReadAll(resp.Body); err == nil {
        token = gjson.GetBytes(body, "token").String()
        expiration = time.Unix(gjson.GetBytes(body, "expires_at").Int(), 0)
    }
    return
}

def get_token(api_key):
    connection = HTTPSConnection("auth.dfuse.io")
    connection.request('POST', '/v1/auth/issue', json.dumps({"api_key": api_key}), {'Content-type': 'application/json'})
    response = connection.getresponse()

    if response.status != 200:
        raise Exception(" Status: %s reason: %s" % (response.status, response.reason))

    token = json.loads(response.read().decode())['token']
    connection.close()

    return token

curl https://auth.dfuse.io/v1/auth/issue -s \
  --data-binary '{"api_key":"server_abcdef12345678900000000000"}'

3. Get the client stub and dependencies for your language

The protobuf files defining our graphql-over-grpc interface are available in this GitHub repository .

A lot of languages provide tools to generate client stubs from protobuf files, as you can find in the official gRPC documentation .

For your convenience, we also provide pre-generated client stubs for some languages. The code from the examples on this page can be found in the quickstarts folder of this docs GitHub repository .


git clone https://github.com/dfuse-io/docs
cd docs/quickstarts/go

git clone https://github.com/dfuse-io/docs
cd docs/quickstarts/python
python -m pip install grpcio-tools --ignore-installed

# On MacOS
brew install grpcurl

# On Linux/Windows
go get github.com/fullstorydev/grpcurl
go install github.com/fullstorydev/grpcurl/cmd/grpcurl

# Download from Git
git clone https://github.com/dfuse-io/graphql-over-grpc

# Download from a Zip archive
curl -sLO https://github.com/dfuse-io/graphql-over-grpc/archive/master.zip
unzip -q master.zip

# Generate your code
cd graphql-over-grpc
protoc graphql/graphql.proto # add your language-specific flags here

4. Create the client

Now that you have generated the client stub (or picked the generated one), we can define the client creation code. The client can be re-used across all the requests and streams you need to do, it should be properly cached at the appropriate level for your use case.


func createClient(endpoint string) pb.GraphQLClient {
    dfuseAPIKey := os.Getenv("DFUSE_API_KEY")
    if dfuseAPIKey == "" {
        panic("you must specify a DFUSE_API_KEY environment variable")
    }

    token, _, err := getToken(dfuseAPIKey)
    panicIfError(err)

    credential := oauth.NewOauthAccess(&oauth2.Token{AccessToken: token, TokenType: "Bearer"})
    transportCreds := credentials.NewClientTLSFromCert(nil, "")
    conn, err := grpc.Dial(endpoint,
        grpc.WithPerRPCCredentials(credential),
        grpc.WithTransportCredentials(transportCreds),
    )
    panicIfError(err)

    return pb.NewGraphQLClient(conn)
}

def create_client(endpoint):
    dfuse_api_key = os.environ.get("DFUSE_API_KEY")
    if dfuse_api_key == None:
        raise Exception("you must specify a DFUSE_API_KEY environment variable")

    channel = grpc.secure_channel(endpoint,
        credentials = grpc.composite_channel_credentials(
            grpc.ssl_channel_credentials(),
            grpc.access_token_call_credentials(get_token(dfuse_api_key))
    ))

    return graphql_pb2_grpc.GraphQLStub(channel)

5. 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 pick and choose only what you are interested in.

Note

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


const operationEOS = `subscription {
  searchTransactionsForward(query:"receiver:eosio.token action:transfer -data.quantity:'0.0001 EOS'") {
    undo cursor
    trace { id matchingActions { json } }
  }
}`

type eosioDocument struct {
    SearchTransactionsForward struct {
        Cursor string
        Undo   bool
        Trace  struct {
            ID              string
            MatchingActions []struct {
                JSON map[string]interface{}
            }
        }
    }
}

OPERATION_EOS = """subscription {
  searchTransactionsForward(query:"receiver:eosio.token action:transfer") {
    undo cursor
    trace { id matchingActions { json } }
  }
}"""

And we can finally define the code needed to perform your first stream using dfuse Search. The snippet initiates the connection with dfuse servers and starts streaming transfers forever.


func streamEOSIO(ctx context.Context) {
    /* The client can be re-used for all requests, cache it at the appropriate level */
    client := createClient("mainnet.eos.dfuse.io:443")
    executor, err := client.Execute(ctx, &pb.Request{Query: operationEOS})
    panicIfError(err)

    for {
        resp, err := executor.Recv()
        panicIfError(err)

        if len(resp.Errors) > 0 {
            for _, err := range resp.Errors {
                fmt.Printf("Request failed: %s\n", err)
            }

            /* We continue here, but you could take another decision here, like exiting the process */
            continue
        }

        document := &eosioDocument{}
        err = json.Unmarshal([]byte(resp.Data), document)
        panicIfError(err)

        result := document.SearchTransactionsForward
        reverted := ""
        if result.Undo {
            reverted = " REVERTED"
        }

        for _, action := range result.Trace.MatchingActions {
            data := action.JSON
            fmt.Printf("Transfer %s -> %s [%s]%s\n", data["from"], data["to"], data["quantity"], reverted)
        }
    }
}

def stream_eosio():
    # The client can be re-used for all requests, cache it at the appropriate level
    client = create_client('mainnet.eos.dfuse.io:443')
    stream = client.Execute(Request(query = OPERATION_EOS))

    for rawResult in stream:
        if rawResult.errors:
            print("An error occurred")
            print(rawResult.errors)
        else:
            result = json.loads(rawResult.data)
            for action in result['searchTransactionsForward']['trace']['matchingActions']:
                undo = result['searchTransactionsForward']['undo']
                data = action['json']
                print("Transfer %s -> %s [%s]%s" % (data['from'], data['to'], data['quantity'], " REVERTED" if undo else ""))

And here is a sample of the prints you will receive from the standard output after running the example above:

Transfer eosbetdice11 -> eosbetbank11 [0.0500 EOS]
Transfer newdexpublic -> gq4tcnrwhege [2.8604 EOS]
Transfer wpwpwp222222 -> eosioeosios3 [20.0000 EOS]
Transfer wallet.bg -> bulls.bg [0.9000 EOS]
Transfer bluebetproxy -> bluebetbulls [0.6000 EOS]
...

6. Full working examples

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


package main

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
    "time"

    pb "github.com/dfuse-io/docs/quickstarts/pb"
    "github.com/tidwall/gjson"
    "golang.org/x/oauth2"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/credentials/oauth"
)

/* DFUSE_API_KEY="server_abcdef12345678900000000000" go run main.go eosio|ethereum */
func main() {
    proto := ""
    if len(os.Args) >= 2 {
        proto = os.Args[1]
    }

    switch proto {
    case "ethereum", "ETH":
        streamEthereum(context.Background())
    default:
        streamEOSIO(context.Background())
    }
}

func panicIfError(err error) {
    if err != nil {
        panic(err)
    }
}

try:
    # python3
    from http.client import HTTPSConnection
except ImportError:
    # python2
    from httplib import HTTPSConnection

import grpc
import json
import os
import ssl
import sys

from graphql import graphql_pb2_grpc
from graphql.graphql_pb2 import Request

# DFUSE_API_KEY="server_abcdef12345678900000000000" python main.py eosio|ethereum
proto = ""
if len(sys.argv) > 1:
    proto = sys.argv[1].lower()

if proto == "ethereum" or proto == "ETH":
    stream_ethereum()
else:
    stream_eosio()

If you prefer, you can directly clone our ready-made repository with all the quick start examples:


git clone https://github.com/dfuse-io/docs
cd docs/quickstarts/go

# Replace 'server_abcdef12345678900000000000' with your own API key!
DFUSE_API_KEY="server_abcdef12345678900000000000" go run main.go eosio

git clone https://github.com/dfuse-io/docs
cd docs/quickstarts/python
python -m pip install grpcio-tools --ignore-installed

# Replace 'server_abcdef12345678900000000000' with your own API key!
DFUSE_API_KEY="server_abcdef12345678900000000000" python main.py eosio

7. What’s next?