DEV Community

Cover image for Goldsky from Go, Without the Glue Code: Introducing goldsky-go
Igor
Igor

Posted on

Goldsky from Go, Without the Glue Code: Introducing goldsky-go

If your Go service talks to blockchain data through Goldsky, the hard part should be the product logic—not repeatedly rebuilding HTTP requests, deciding which failures are safe to retry, or chasing pagination tokens across several APIs. goldsky-go is a small, community-maintained Go SDK that packages those integration details into an idiomatic client while deliberately keeping application-defined data flexible.

The library targets the Goldsky REST API v1.2.0 and maps all 40 documented REST operations. It also provides first-class access to Subgraph GraphQL endpoints and Goldsky Edge HTTPS JSON-RPC. That means one Go package can cover the control-plane work of managing pipelines, subgraphs, webhooks, and Edge endpoints, as well as the data-plane work of querying indexed data and calling an RPC endpoint. 1

goldsky-go is a community SDK, not an official Goldsky package. Its purpose is to make the supported APIs pleasant and predictable for Go applications while staying explicit about the boundaries of the underlying platform. 1

Why a Go SDK is useful here

Direct HTTP is always an option. It is also where subtle operational concerns begin to accumulate: authorization headers, escaped path parameters, bounded response bodies, request deadlines, error formats, pagination state, JSON-RPC envelope validation, multipart upload behavior, and retry policy. A thin SDK earns its place when it removes this repeated plumbing without hiding the decisions that matter.

That is the design direction of goldsky-go. Its public methods accept context.Context, its stable API shapes use typed request and response models, and its GraphQL data remains raw JSON because a subgraph schema belongs to the consuming application. The result is Go code that can be concise without pretending that all Web3 data has one static schema. 1

The project also has no third-party runtime dependencies. For services where dependency surface area and deployment simplicity matter, that is a practical advantage: the package is built around the Go standard library rather than a large HTTP abstraction stack. 1

Start with a reusable client

The minimum installation requirement is Go 1.22 or later:

go get github.com/tigusigalpa/goldsky-go
Enter fullscreen mode Exit fullscreen mode

Create the client once, configure an application-appropriate timeout, and reuse it. A project API token authenticates REST calls and private GraphQL queries.

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "time"

    goldsky "github.com/tigusigalpa/goldsky-go"
)

func main() {
    client, err := goldsky.NewClient(
        os.Getenv("GOLDSKY_API_KEY"),
        goldsky.WithTimeout(30*time.Second),
    )
    if err != nil {
        log.Fatal(err)
    }

    ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
    defer cancel()

    page, err := client.Pipelines.List(ctx, goldsky.ListPipelinesOptions{
        PageSize: 25,
    })
    if err != nil {
        log.Fatal(err)
    }

    for _, pipeline := range page.Data {
        fmt.Printf("%-30s %s\n", pipeline.Name, pipeline.Status)
    }
}
Enter fullscreen mode Exit fullscreen mode

This small example illustrates two conventions that scale well in production. The client carries reusable configuration, while each operation receives its own deadline through context.Context. The former sets a safety net; the latter lets a caller budget time according to the job at hand. The constructor validates options but does not make a network call, which also makes application startup and tests more predictable. 1

One package across the Goldsky surface area

goldsky-go organizes its functionality around services rather than requiring callers to assemble endpoint URLs manually. The following table is a useful high-level map.

Service Typical work
Pipelines Validate, create, inspect, pause, resume, restart, delete, and observe Turbo Pipelines.
Subgraphs Deploy subgraph bundles, manage versions and tags, and read indexing logs or webhook entities.
Webhooks Create, list, and delete entity webhooks.
Edge Manage Edge endpoints, keys, lifecycle actions, and metrics.
Catalogs Discover available subgraph chains, Edge networks, and Edge Data sources.
GraphQL Send public or private Subgraph GraphQL queries.
RPC Make individual or batch JSON-RPC 2.0 calls through Goldsky Edge.

The full method-to-operation mapping is maintained in the repository’s API coverage document. 2 The broader point is convenience with a clear boundary: REST management, GraphQL querying, and HTTPS RPC have different protocols and failure modes, but a Go service can work with each through one familiar client model.

Treat pagination as a protocol, not as a slice

A common integration bug is assuming that a page with fewer records than the requested size must be the last page. Goldsky pagination instead uses a continuation token. goldsky-go exposes pagers for pipelines, subgraphs, and Edge endpoints, and finishes only when there is no next-page token.

pager := client.Subgraphs.NewSubgraphPager(
    goldsky.ListSubgraphsOptions{PageSize: 100},
)

for {
    page, err := pager.NextPage(ctx)
    if err != nil {
        return err
    }

    for _, subgraph := range page.Data {
        fmt.Println(subgraph.Name, subgraph.Version, subgraph.Health)
    }

    if !page.HasMore() {
        break
    }
}
Enter fullscreen mode Exit fullscreen mode

This keeps continuation-token management out of the application loop. The pager also rejects invalid sizes locally, and after it reaches the final page, subsequent calls return an empty page instead of causing needless HTTP requests. Pagers themselves are not intended for concurrent use; the reusable client and its services are safe to share. 1

Deploy subgraphs without first loading the whole bundle

Deployment is one place where the mechanics of an HTTP client matter. Subgraphs.Deploy streams a zip bundle as multipart/form-data, rather than buffering the entire archive in application memory.

bundle, err := os.Open("build.zip")
if err != nil {
    return err
}
defer bundle.Close()

subgraph, err := client.Subgraphs.Deploy(ctx, "my-subgraph", "v1",
    goldsky.DeploySubgraphOptions{
        Bundle:         bundle,
        BundleFilename: "build.zip",
    },
)
if err != nil {
    return err
}

fmt.Println("deployed", subgraph.Name, subgraph.Version)
Enter fullscreen mode Exit fullscreen mode

That streaming behavior is paired with an important safety choice: deployments are not automatically retried. An arbitrary io.Reader cannot necessarily be replayed, and an ambiguous timeout on a mutation can otherwise create duplicate or confusing outcomes. The practical pattern is to reopen the file and retry only after checking whether the first request took effect. This is a good example of the library choosing an explicit operational rule over a superficially convenient one. 1

Query GraphQL, then decode the schema you own

Goldsky’s Subgraph GraphQL endpoints are useful precisely because different subgraphs can expose different entities and fields. Instead of generating brittle universal types, goldsky-go returns GraphQL data as json.RawMessage, alongside errors, headers, and HTTP status. Your application then decodes the query result into the structure it expects.

resp, err := client.GraphQL.QueryPrivate(ctx, projectID, "my-subgraph", "v1",
    goldsky.GraphQLRequest{
        Query: "{ _meta { block { number } } }",
    },
)
if err != nil {
    return err
}
if resp.HasErrors() {
    for _, graphQLError := range resp.Errors {
        fmt.Println(graphQLError.Message)
    }
    return nil
}

var data struct {
    Meta struct {
        Block struct {
            Number int `json:"number"`
        } `json:"block"`
    } `json:"_meta"`
}
if err := json.Unmarshal(resp.Data, &data); err != nil {
    return err
}
fmt.Println("indexed through block", data.Meta.Block.Number)
Enter fullscreen mode Exit fullscreen mode

There are two query paths: QueryPrivate uses the project token, while QueryPublic can be used with a tokenless data client. A tokenless client will reject REST and private GraphQL access locally before any outbound request. This separation makes it easier to issue services the least privilege they need: a public data consumer does not need control-plane credentials. 1

Call Edge JSON-RPC without putting a secret in the URL

For Goldsky Edge, the SDK supports HTTPS JSON-RPC 2.0, including batch calls. The Edge key is sent through X-ERPC-Secret-Token, so it is not placed in a query string or endpoint URL.

rpcClient, err := goldsky.NewDataClient(
    goldsky.WithEdgeAPIKey(os.Getenv("GOLDSKY_EDGE_API_KEY")),
)
if err != nil {
    return err
}

var blockHex string
if err := rpcClient.RPC.Call(ctx, 1, "eth_blockNumber", nil, &blockHex); err != nil {
    return err
}
fmt.Println("latest block:", blockHex)
Enter fullscreen mode Exit fullscreen mode

Batch responses deserve special attention because JSON-RPC permits responses to arrive in any order. RPC.Batch associates responses back to the input calls by request ID and validates malformed envelopes, duplicate IDs, unknown IDs, and invalid result/error combinations. Still, a transport-level success does not guarantee that every batch item succeeded, so applications should inspect each response’s Error field. 1

The current Edge scope is intentionally HTTPS JSON-RPC only; WebSockets and subscriptions are outside the library’s supported surface. Being clear about this limitation helps teams choose the SDK for the right workloads rather than discovering it after an architecture is committed. 1

Errors and retries designed for production decisions

A client library should make failures easier to handle, not just easier to print. REST errors are represented as RFC 9457 problem details, which allows code to branch on a stable problem type or status helper instead of parsing a human-readable error string.

if problem := goldsky.AsProblem(err); problem != nil {
    switch {
    case problem.IsNotFound():
        fmt.Println("resource does not exist")
    case problem.IsRateLimited():
        if seconds, ok := problem.RetryAfter(); ok {
            fmt.Println("retry after", seconds, "seconds")
        }
    default:
        fmt.Println(problem.Type)
    }
}
Enter fullscreen mode Exit fullscreen mode

The retry defaults are conservative. Safe read methods (GET, HEAD, and OPTIONS) may be retried for transport errors and selected transient HTTP statuses, with capped exponential backoff, jitter, and Retry-After support. Mutating requests are not retried unless the application explicitly opts in with WithRetryMutations(). Since Goldsky does not document idempotency keys for those mutations, that default protects callers from accidentally duplicating creates or updates after uncertain network failures. Streaming deployments remain single-attempt even when mutation retries are enabled. 1

Responses are also bounded in memory by default. REST, GraphQL, and RPC bodies are limited to 16 MiB unless the application deliberately chooses a larger maximum. This kind of default is not glamorous, but it is useful protection when a service is exposed to unexpected upstream behavior. 1

Webhooks: verify before you process

Goldsky webhook deliveries include a shared secret in the literal goldsky-webhook-secret header. The package provides constant-time comparison through VerifyWebhookRequest.

func handleWebhook(w http.ResponseWriter, r *http.Request ) {
    if !goldsky.VerifyWebhookRequest(r, storedSecret) {
        http.Error(w, "invalid webhook secret", http.StatusUnauthorized )
        return
    }

    // Cap and read the body, enqueue idempotent work, then acknowledge.
    w.WriteHeader(http.StatusNoContent )
}
Enter fullscreen mode Exit fullscreen mode

The documented header is a shared secret, not an HMAC signature. Therefore, authenticate the request before reading or processing its body, keep the secret protected like any credential, and make downstream handling idempotent because deliveries may be retried. The SDK provides the comparison primitive; reliable event processing remains an application responsibility. 1

A practical fit for Go teams building on Goldsky

goldsky-go will be most useful to Go teams that want a typed, testable client for Goldsky’s supported APIs but do not want a large framework or a generated client dictating every application data shape. It is especially compelling when one service needs to manage infrastructure through REST, deploy or observe subgraphs, query GraphQL, and call Edge RPC from the same codebase.

Its value is not only the number of endpoints covered. The library codifies the unglamorous details that shape reliable integrations: context propagation, escaping, pagination termination, streaming uploads, error categorization, bounded bodies, explicit credentials, redacted diagnostics, and cautious retry behavior. Those decisions leave more room for the part of the service that is actually unique.

To get started, install the package, work through the runnable examples, and consult the API coverage and security notes before enabling mutations in production. The repository is released under the permissive MIT license, and contributions are welcome. 1

Repository: github.com/tigusigalpa/goldsky-goPackage reference: pkg.go.dev/github.com/tigusigalpa/goldsky-goGoldsky API documentation: docs.goldsky.com/api-reference/overview

References

Top comments (0)