Skip to content

About

Production-ready Go SDK for Bitquery GraphQL APIs. Query V1 historical and V2 streaming blockchain data with OAuth2 or static tokens, regional or custom endpoints, retries, rate limiting, strict GraphQL errors, credential-safe logging, and resilient WebSocket subscriptions with reconnection support. Designed for production use.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

1 watching

Forks

Latest commit

 

History

21 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bitquery Golang Client/SDK/Library

Bitquery Golang SDK Client

CI Tests Go Version License CodeQL Codecov GitHub Release GoDoc

bitquery-go is a production-oriented Go SDK for Bitquery GraphQL. It keeps the two Bitquery contracts deliberately separate:

  • V1 is the historical HTTPS GraphQL API.
  • V2 is the streaming GraphQL API: HTTPS queries plus opt-in WebSocket subscriptions.

The library never rewrites a document, changes an endpoint, or falls back from one version to the other. That matters because V1 and V2 have different schemas and coverage.

  • Module: github.com/tigusigalpa/bitquery-go/v2
  • Go: 1.21 or newer (CI covers Go 1.21–1.26)
  • License: MIT — © Igor Sazonov

Pick the right client first

If you need… Use Important detail
An existing historical V1 document v1.Client HTTPS only; no subscription API
A new supported EVM or Solana integration v2.Client Start with the current V2 schema in the Bitquery IDE
Live V2 updates subscription.Client A separate, explicit WebSocket worker

V1 still has legacy coverage, but Bitquery marks Ethereum, BSC, Matic/Polygon and Tron V1 usage as deprecated. The SDK reports this as a typed notice; it never blocks a deliberate V1 call. V2 is not a drop-in replacement for every V1 dataset. Check the live schema and the V1/V2 coverage guide before migrating.

Install

go get github.com/tigusigalpa/bitquery-go/v2

Creating a client makes no network request. Requests happen only when you call Execute; a socket is opened only when you call Subscribe.

Migrating to v2.0.0

Version 2 adopts Go semantic import versioning. Change the root import from github.com/tigusigalpa/bitquery-go to github.com/tigusigalpa/bitquery-go/v2. The client packages keep their existing directories, so their new import paths are:

  • V1 client: github.com/tigusigalpa/bitquery-go/v2/v1
  • V2 client: github.com/tigusigalpa/bitquery-go/v2/v2
  • Subscriptions: github.com/tigusigalpa/bitquery-go/v2/subscription

The release also makes the already documented dynamic GraphQL error values precision-safe: values in GraphQLError.Path, Locations, and Extensions are json.Number, not float64. Update code that type-asserts those values.

Authentication: choose one safe source of tokens

Keep credentials in your process environment or your own secret store — never in source code, examples, or logs.

// A pre-minted Bitquery access token.
provider := bitquery.NewStaticTokenProvider(os.Getenv("BITQUERY_TOKEN"))

// Or client credentials. The provider coalesces concurrent refreshes
// and caches the token until shortly before expiry.
provider := bitquery.NewClientCredentialsProvider(
    os.Getenv("BITQUERY_CLIENT_ID"),
    os.Getenv("BITQUERY_CLIENT_SECRET"),
)

HTTP requests use Authorization: Bearer <token>. WebSocket authentication is different: Bitquery requires the OAuth token in the ?token= URL query parameter, which this SDK adds internally. Do not put that parameter in a custom endpoint. The SDK redacts it from its errors and logger output.

For a proxy or a test OAuth server, use bitquery.WithOAuthTokenEndpoint("https://…") while creating the credentials provider. bitquery.WithTokenEndpoint also configures a default ClientCredentialsProvider supplied to a client; an explicit OAuth-provider endpoint takes precedence.

Make an HTTP query

V2 EVM: generic GraphQL is the main API

Use a raw Operation whenever you need a field or cube the helpers do not cover. Values belong in Variables, never in string-concatenated GraphQL.

client, err := v2.New(provider, bitquery.WithRegion(bitquery.RegionUS))
if err != nil { return err }

resp, err := client.Execute(ctx, bitquery.Operation{
    OperationName: "LatestBlocks",
    Query: `query LatestBlocks($network: evm_network!) {
        EVM(network: $network) {
            Blocks(limit: {count: 3}) { Block { Number Time } }
        }
    }`,
    Variables: map[string]any{"network": "eth"},
})
if err != nil { return err }

There are deliberately thin helpers for a few documented common cases; they still produce an ordinary Operation that you can inspect or modify:

resp, err := client.Execute(ctx, v2.EVMDexTrades(bitquery.NetworkBSC, 20, false))

Pinned EVM projections are explicit about source choices

For audit-oriented integrations, optional helpers build narrow BQ1–BQ4 EVM projections for transactions, DEX trades, balances, holders, transaction balances, and decoded events. They remain plain Operation values; they do not provide a universal response model or make a network request.

EVMSource deliberately makes dataset and block-branch selection caller owned. Its optional fields distinguish an omitted GraphQL variable from an explicit null; neither choice is silently replaced with an SDK default.

source := v2.EVMSource{
    Network: bitquery.NetworkEthereum,
    Dataset: bitquery.Present("archive"),
    // SelectBlocks is omitted here. Use bitquery.Null[string]() only when
    // the selected cube intentionally requires an explicit GraphQL null.
}
op := v2.EVMTransactionByHash(v2.EVMTransactionByHashRequest{
    Source: source,
    Hash:   "0x…",
})
resp, err := client.Execute(ctx, op)

Use a raw Operation for any other cube, filter, status, trigger, cursor, or coverage decision. Dataset availability and ordering are provider properties, not guarantees made by these helpers.

V2 Solana: use the Solana cube

client, err := v2.New(provider)
if err != nil { return err }

op := v2.SolanaTransfers(25, os.Getenv("SOLANA_MINT")) // a non-empty mint address
resp, err := client.Execute(ctx, op)
if err != nil { return err }

The helper set includes EVM DEX trades, transfers and transactions, and Solana DEX trades, transfers, balance updates, instructions and transactions. It intentionally does not generate a brittle model of every evolving Bitquery cube.

V1 historical query: explicit and inspectable

client, err := v1.New(provider)
if err != nil { return err }

op := v1.Blocks(bitquery.NetworkEthereum, 10, "", "")
for _, notice := range client.DeprecationNotices(op) {
    log.Printf("%s: %s", notice.Network, notice.Message)
}

resp, err := client.Execute(ctx, op)
if err != nil { return err }

V1 helpers cover blocks, transactions, transfers, DEX trades and smart contract calls. They are conveniences, not a replacement for Execute.

Read responses without losing numeric precision

Bitquery amounts, decimals, block heights and IDs can exceed the safe range of float64. The SDK leaves Data as json.RawMessage and uses json.Number when decoding into interface{} values.

if resp.HasErrors() {
    // HTTP 200 can still contain GraphQL errors.
    for _, graphQLError := range resp.Errors {
        log.Printf("GraphQL: %s", graphQLError.Message)
    }
}
if resp.HasPartialData() {
    // Some data is still usable; decide case by case.
}

var data map[string]any
if err := resp.DecodeData(&data); err != nil { return err }
// Convert an amount explicitly with math/big or a decimal package.

The default is tolerant: GraphQL errors[] live on Response. Use ExecuteStrict (or bitquery.WithStrict()) if your application wants a KindGraphQL error instead. Even then, the error retains the complete response so partial data is not discarded.

Every HTTP response observed during an execution, including retry attempts, is available through Response.Receipts (and through Error.Receipts for an HTTP failure). A receipt holds a redacted target, capture time, response status, operation SHA-256, and defensive copies of the exact serialized operation and raw body. The SDK keeps receipts only in memory; persist them yourself when you need durable lineage.

For query HTTP receipts, receipt.HTTPBody records the body lifecycle. Its zero value is unknown/not applicable for WebSocket receipts and for receipts built with NewReceipt. Complete means only that the SDK read and closed that HTTP body without an error; it does not prove dataset coverage, finality, or a complete GraphQL result. ReadComplete, ReadError, LimitExceeded, and CloseError distinguish the observed phases.

When a query body exceeds the response limit, the receipt preserves at most the configured limit plus one control byte that proves overflow. A read or close failure preserves every byte made available before the failure. These attempts return an error rather than parsing a JSON-looking prefix as a successful response; inspect Error.Receipts and use errors.Is(err, bitquery.ErrResponseTooLarge) or the original reader/closer error as needed.

GraphQLError.Raw now retains the exact JSON for each errors[] entry. Numbers inside dynamic GraphQLError.Path, Locations, and Extensions are json.Number, not float64. If an application previously asserted float64, update it to handle json.Number and perform an explicit conversion suitable for the value.

Regions and endpoint overrides

The default is Europe. Choose the closest region for your deployment:

client, err := v2.New(provider,
    bitquery.WithRegion(bitquery.RegionAsia),
    bitquery.WithTimeout(20*time.Second),
    bitquery.WithUserAgent("my-indexer/1.0"),
)
Region V1 HTTPS V2 HTTPS V2 WebSocket
Europe (default) https://graphql.bitquery.io https://streaming.bitquery.io/graphql wss://streaming.bitquery.io/graphql
Asia https://asia.graphql.bitquery.io https://asia.streaming.bitquery.io/graphql wss://asia.streaming.bitquery.io/graphql
US https://us.graphql.bitquery.io https://us.streaming.bitquery.io/graphql wss://us.streaming.bitquery.io/graphql

WithBaseURL overrides the selected HTTP endpoint. For a private proxy or a local test socket, use WithWebSocketURL; it overrides the derived WSS endpoint. Only absolute HTTP(S) and WS(S) URLs are accepted, and credential-bearing URL parameters are rejected.

Network is an open string type, not a closed list. Bitquery coverage varies by region, plan, dataset and cube, so verify unfamiliar networks in the Bitquery schema instead of waiting for an SDK release.

Retries, limits, and cancellation

The default retry policy is intentionally conservative: up to four attempts with an approximately 5-second exponential backoff, capped at 60 seconds and jittered. Retry-After wins when Bitquery supplies it. It handles transient network errors, 429, temporary 5xx responses and documented shared-compute blocks.

Only read operations are retried. Mutations and HTTP subscriptions are never replayed automatically — whether they are safe to repeat is your decision. Pass bitquery.WithRetryPolicy(bitquery.NoRetry()) to turn off SDK retries.

client, err := v2.New(provider,
    bitquery.WithRateLimiter(bitquery.NewRateLimiter(30)), // 30 req/min burst + pace
    bitquery.WithTimeout(20*time.Second),
)

NewRateLimiter(0) is a convenient no-op. The SDK does not fan out or parallelize heavy queries; use your own worker limits and respect your Bitquery plan's concurrency allowance. Every request and reconnect obeys the supplied context.Context.

Subscribe to V2 updates

Run subscriptions in a long-lived worker, not a short HTTP handler. Cancellation closes the WebSocket, which is the way Bitquery ends a stream. Delivery is at-least-once and can be unordered across block portions, so persist a stable event key and deduplicate downstream.

workerCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()

client, err := subscription.New(provider,
    bitquery.WithSubProtocol(bitquery.SubProtocolGraphQLTransportWS),
    bitquery.WithSubscriptionQueue(500, bitquery.OverflowDropOldest),
)
if err != nil { return err }

stream, err := client.Subscribe(workerCtx, bitquery.Operation{
    Query: `subscription {
        EVM(network: eth) { Blocks { Block { Number Time } } }
    }`,
})
if err != nil { return err }
defer stream.Close()

for event := range stream.Events {
    switch event.Type {
    case subscription.EventData:
        // Deduplicate event.Payload before writing it anywhere.
    case subscription.EventKeepalive:
        // The connection is healthy.
    case subscription.EventComplete:
        // The server completed this operation.
    }
}
if err := stream.Err(); err != nil { return err }

Both graphql-transport-ws and graphql-ws are supported. The stream handles connection_init/acknowledgement, next/data, ping/pong and ka, bounded reconnects, and clean shutdown. Its bounded event queue protects memory: drop_oldest keeps the newest events and reports the count via Dropped(); fail stops the stream rather than losing an event silently.

Event.Delivery records its connection epoch, receive sequence and receive time. The first data event after a reconnect has ReconnectGap: true, which means a gap is possible — it does not mean the SDK replayed or recovered one.

Stream.Receipts() is a bounded in-memory observer buffer, not an unbounded history. Its default capacity is 1,024 and its default overflow policy is fail, so raw evidence cannot be discarded silently. Long-lived consumers should either periodically call DrainReceipts() or install a synchronous observer with WithSubscriptionReceiptObserver. The callback runs on the connection worker, must honour its context and return quickly, and must not call Stream.Close or Stream.Wait synchronously.

client, err := subscription.New(provider,
    bitquery.WithSubscriptionQueue(500, bitquery.OverflowFail),
    bitquery.WithSubscriptionReceiptBuffer(0, bitquery.OverflowFail),
    bitquery.WithSubscriptionReceiptObserver(func(ctx context.Context, receipt bitquery.Receipt) error {
        // Persist or hand off receipt here. Returning an error stops the stream.
        return storeReceipt(ctx, receipt)
    }),
)

With a zero receipt-buffer capacity, the observer receives every frame without retaining a second in-memory copy. Stream.Gaps() reports reconnects, event queue failures and receipt-observer failures; DroppedReceipts() and DroppedGaps() make drop_oldest retention loss explicit. None of these signals is automatic gap recovery or a completeness guarantee. See the lifecycle guide for the state machine and recovery checklist.

Errors you can act on

All SDK failures use *bitquery.Error; errors.Is and errors.As work as expected.

var apiErr *bitquery.Error
if errors.As(err, &apiErr) {
    switch apiErr.Kind {
    case bitquery.KindAuthentication:  // 401 or OAuth rejection
    case bitquery.KindAuthorization:   // 403
    case bitquery.KindPlanEntitlement: // 402; do not retry
    case bitquery.KindRateLimited:     // 429; inspect RetryAfter
    case bitquery.KindServer:          // temporary for 500/502/503/504
    case bitquery.KindGraphQL:         // strict mode; Response is retained
    case bitquery.KindSubscription:
    }
}

Diagnostic messages and the built-in structured logger redact Bearer tokens, OAuth secrets and URL token parameters. The logger is a no-op unless you supply one with WithLogger.

Runnable examples

Every example compiles without a credential and only contacts Bitquery when you run it with the required environment variables.

Example Run Shows
examples/http BITQUERY_TOKEN=… go run ./examples/http V2 EVM HTTP query and partial GraphQL errors
examples/oauth BITQUERY_CLIENT_ID=… BITQUERY_CLIENT_SECRET=… go run ./examples/oauth client-credentials token provider
examples/subscription BITQUERY_TOKEN=… go run ./examples/subscription graceful V2 subscription worker
examples/v1-historical BITQUERY_TOKEN=… go run ./examples/v1-historical V1 helper and deprecation signal
examples/v2-solana BITQUERY_TOKEN=… go run ./examples/v2-solana V2 Solana transfers helper
examples/custom-endpoint BITQUERY_TOKEN=… go run ./examples/custom-endpoint region, endpoint, timeout and user-agent options

Reference and project docs

Verify a checkout

go build ./...
gofmt -l .            # no output means formatted
go vet ./...
go test ./...         # no network
go test -race ./...   # concurrency suite
go mod verify

Live smoke tests are opt-in only: set both BITQUERY_LIVE=1 and BITQUERY_TOKEN. They never run in public CI.

About

Production-ready Go SDK for Bitquery GraphQL APIs. Query V1 historical and V2 streaming blockchain data with OAuth2 or static tokens, regional or custom endpoints, retries, rate limiting, strict GraphQL errors, credential-safe logging, and resilient WebSocket subscriptions with reconnection support. Designed for production use.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

1 watching

Forks

Releases

Contributors

Languages