Awesome Go

glassnode-go

CategoryFinancial
SubcategoryFinancial
Stars19

Go client for Glassnode Basic API with 25 metric categories, typed structs, bulk endpoints, Point-in-Time data, and zero dependencies

About glassnode-go

This large README is shown as a plain-text preview. Read the full README at the source

# GlassNode Golang SDK

![GlassNode Golang SDK](https://i.postimg.cc/853CkbFB/glassnode-golang-github.jpg)

[![CI](https://github.com/tigusigalpa/glassnode-go/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/tigusigalpa/glassnode-go/actions/workflows/ci.yml)
[![Tests](https://github.com/tigusigalpa/glassnode-go/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/tigusigalpa/glassnode-go/actions/workflows/test.yml)
[![Go Version](https://img.shields.io/badge/Go-1.21+-00ADD8?style=flat-square&logo=go)](https://golang.org/)
[![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE)
[![CodeQL](https://github.com/tigusigalpa/glassnode-go/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/tigusigalpa/glassnode-go/actions/workflows/codeql.yml)
[![Codecov](https://codecov.io/gh/tigusigalpa/glassnode-go/graph/badge.svg)](https://codecov.io/gh/tigusigalpa/glassnode-go)
[![GitHub Release](https://img.shields.io/github/v/release/tigusigalpa/glassnode-go?style=flat-square)](https://github.com/tigusigalpa/glassnode-go/releases)
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue?style=flat-square&logo=go)](https://pkg.go.dev/github.com/tigusigalpa/glassnode-go)

> **Unofficial** Go SDK for the [Glassnode Basic API](https://docs.glassnode.com/basic-api/). Not affiliated with or
> endorsed by Glassnode — just a community-built tool I wish had existed when I started working with on-chain data.

A dependency-light, production-oriented Go module for the Glassnode Basic API, built with the kind of care you'd want
from a library you depend on every day.

If you've ever found yourself hand-crafting `net/http` calls to Glassnode, parsing JSON into `map[string]interface{}`,
and string-building metric paths at 2 AM to ship a dashboard — you already know it gets tedious fast. This SDK wraps all
of that into a clean, idiomatic Go package with strongly-typed structs, `context.Context` propagation, and sensible
defaults, so you can spend your time on application logic instead of HTTP plumbing.

The SDK exposes the complete documented metric surface through three layers of API, each one building on the last:

1. **Typed transport/config** — a `Client` with functional options, secure defaults, `context.Context` support, and zero
   external dependencies
2. **Metadata & generic metrics** — a `MetadataService` for runtime discovery and a `MetricsService` that works with any
   present or future metric path
3. **Ergonomic category services** — 25 typed service structs with convenience methods that map 1:1 to Glassnode's
   endpoint categories

## Who Is This For?

This SDK was written for the kind of developer who:

- Builds trading dashboards, research tools, or backtesting pipelines that need on-chain data
- Cares about dependency hygiene — no surprise transitive packages, no `go.sum` bloat
- Wants errors they can actually branch on with `errors.Is` / `errors.As` instead of grepping strings
- Runs services in production and needs concurrency-safe clients that won't fall over under load
- Likes their libraries to do the boring stuff (retries, redaction, context propagation) so they can focus on the
  interesting stuff

If that sounds like you, welcome — you're in the right place.

## Why This SDK?

- **Zero external dependencies** — uses only the Go standard library, so it won't bloat your `go.sum` or create
  transitive dependency conflicts that surface at the worst possible moment
- **Idiomatic Go** — functional options, `context.Context` propagation, strongly-typed structs, and `errors.Is` /
  `errors.As` support throughout
- **Concurrency-safe** — the `Client` is safe for concurrent use across goroutines, perfect for high-throughput services
  and fan-out workloads
- **Discover metrics at runtime** — not sure which parameters a metric accepts? Ask the metadata API before you make
  data calls and avoid wasting credits on bad requests
- **Survive rate limits gracefully** — automatic retry on 429 with server-aware backoff, so you don't have to wrap every
  call in your own retry loop
- **Keep your API key safe** — header-based auth by default, and the SDK redacts the key in request URLs and response metadata
  so it never leaks into your logs
- **Only pay for what you use** — bulk endpoints with explicit asset lists put you in control of credit consumption, no
  accidental wildcard blow-ups

## Features

- **Zero external dependencies** — uses only the Go standard library, nothing else. Your `go.sum` stays as short as the
  day you started the project
- **25 category services** with typed convenience methods covering every documented endpoint category
- **Generic metric API** for every valid metric path — present or future, even ones not yet wrapped by a typed method
- **Metadata-first** — discover assets, metric paths, and parameter capabilities at runtime before making data calls
- **Bulk endpoint support** with repeated query parameters for multi-asset requests
- **Point-in-Time metrics** with `computed_at` timestamp preservation for historically accurate analysis and backtesting
- **Header authentication** (`X-Api-Key`) by default; query-string opt-in for the rare environments that genuinely need
  it
- **Automatic retry** on HTTP 429 with `x-rate-limit-reset` support and exponential backoff as a fallback
- **Exported, inspectable error types** with `errors.Is` / `errors.As` support — no string matching required
- **API key redaction** in request URLs and response metadata, including query-string authentication
- **Concurrency-safe** `Client` suitable for goroutine use without additional locking
- **Configurable HTTP transport** for testing and custom `http.RoundTripper` implementations (proxies, middleware,
  recording)
- **Functional options** — configure only what you need, sensible defaults for everything else

## Installation

```bash
go get github.com/tigusigalpa/glassnode-go
```

That's it. The module has zero external dependencies, so your `go.sum` stays clean. Just import it and start using it —
no `go mod tidy` surprises, no version conflicts to untangle.

## Quick Start

Here's a complete, runnable example — create a client, fetch BTC price data, and print it. Drop it into a `main.go` and
you're off:

```go
package main

import (
    "context"
    "fmt"
    "log"

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

func main() {
    // From environment variable GLASSNODE_API_KEY
    client, err := glassnode.NewClientFromEnv()
    if err != nil {
        log.Fatal(err)
    }

    // Or pass an explicit key if you prefer
    // client := glassnode.NewClient("YOUR_API_KEY")

    ctx := context.Background()

    // Fetch BTC price with 24h resolution
    price, err := client.Market.Price(ctx, &glassnode.MetricQuery{
        Asset:      "BTC",
        Resolution: glassnode.Resolution24h,
    })
    if err != nil {
        log.Fatal(err)
    }
    for _, p := range price {
        fmt.Printf("BTC price at %d: $%.2f\n", p.T, p.V)
    }
}
```

Want to explore what's available before pulling data? The metadata API is your friend — it lets you window-shop metrics
without spending credits on guesses:

```go
// List all supported assets
assets, err := client.Metadata.Assets(ctx, "")
if err != nil {
    log.Fatal(err)
}
for _, a := range assets {
    fmt.Printf("%s (%s)\n", a.Name, a.Symbol)
}

// Inspect a specific metric's parameters
metricInfo, err := client.Metadata.Metric(ctx, "/market/price_usd")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Metric: %s\n", metricInfo.Path)
fmt.Printf("Parameters: %v\n", metricInfo.Parameters)
```

### Common Use Cases

A few real-world patterns to get you oriented. These are intentionally small — copy, paste, and adapt.

**Building a market dashboard:**

```go
ctx := context.Background()

// Get OHLC candles for charting
ohlc, err := client.Market.PriceOHLC(ctx, &glassnode.MetricQuery{
    Asset:      "BTC",
    Resolution: glassnode.Resolution1h,
})

// Market cap and realized cap for valuation analysis
mcap, err := client.Market.MarketCap(ctx, &glassnode.MetricQuery{Asset: "BTC"})
rcap, err := client.Market.RealizedCap(ctx, &glassnode.MetricQuery{Asset: "BTC"})

// MVRV ratio — a classic cycle indicator
mvrv, err := client.Indicators.MVRV(ctx, &glassnode.MetricQuery{Asset: "BTC"})
```

**Monitoring network health:**

```go
// Active addresses — measures network usage
active, err := client.Addresses.ActiveCount(ctx, &glassnode.MetricQuery{
    Asset:      "BTC",
    Resolution: glassnode.Resolution24h,
})

// Hash rate — mining security
hashrate, err := client.Mining.HashRate(ctx, &glassnode.MetricQuery{Asset: "BTC"})

// Total supply — track inflation
supply, err := client.Supply.CirculatingSupply(ctx, &glassnode.MetricQuery{Asset: "BTC"})
```

**Concurrent fetches with goroutines:**

The `Client` is concurrency-safe, so fan-out patterns just work. Here's the textbook pattern for pulling several metrics
in parallel:

```go
var (
    price   []glassnode.TimePoint
    sopr    []glassnode.TimePoint
    active  []glassnode.TimePoint
)

var wg sync.WaitGroup
wg.Add(3)

query := &glassnode.MetricQuery{Asset: "BTC", Resolution: glassnode.Resolution24h}

go func() {
    defer wg.Done()
    price, _ = client.Market.Price(ctx, query)
}()
go func() {
    defer wg.Done()
    sopr, _ = client.Indicators.SOPR(ctx, query)
}()
go func() {
    defer wg.Done()
    active, _ = client.Addresses.ActiveCount(ctx, query)
}()

wg.Wait()
// The Client is concurrency-safe — no additional locking needed
```

> **Tip:** In production code, propagate errors back from each goroutine through a channel or shared `error` slice
> instead of discarding them with `_`. The example above keeps things short; real services should never swallow errors
> silently.

**Tracking credit usage over time:**

```go
usage, err := client.User.APIUsage(ctx)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Credits used this month: %d\n", usage.CreditsUsed)
```

## API Key Security

Your Glassnode API key is the gateway to your account and its data credits. Treat it like a password — because it
effectively is one.

- **Never hardcode** your API key in source code or commit it to version control. Secrets in git history are forever
- Use `NewClientFromEnv()` to read from the `GLASSNODE_API_KEY` environment variable — this is the recommended path for
  almost every deployment
- Or pass the key at runtime: `glassnode.NewClient(os.Getenv("GLASSNODE_API_KEY"))`
- The SDK **redacts** the API key in request URLs and response metadata, including when query-string authentication is
  enabled
- **Header mode** (`X-Api-Key`) is the default and recommended; the key is sent in an HTTP header and never appears in
  URLs, access logs, or proxy traces
- **Query-string mode** (`api_key`) is opt-in via `WithAuthMode(AuthModeQuery)` for environments where headers aren't
  supported, but it's less secure — the key ends up in URLs that get logged everywhere they pass through
- If you accidentally expose a key, rotate it immediately
  in [Glassnode Studio settings](https://studio.glassnode.com/settings/api). Don't wait, don't hope nobody noticed

## Authentication Modes

The SDK supports two authentication modes. Header mode is the default and recommended for all production usage — there's
rarely a good reason to switch:

```go
// Header mode (default, recommended)
// Sends the key as an X-Api-Key header — invisible in URLs and logs
client := glassnode.NewClient("key")

// Query-string mode (opt-in, less secure)
// Appends ?api_key=... to every request URL
client := glassnode.NewClient("key", glassnode.WithAuthMode(glassnode.AuthModeQuery))
```

## Configuration

The SDK uses the functional options pattern — configure only what you need, and everything else gets sensible defaults.
This keeps the common case a one-liner while still letting you tune behavior when you need to:

```go
client := glassnode.NewClient("key",
    glassnode.WithBaseURL("https://api.glassnode.com"),  // API base URL (rarely needs changing)
    glassnode.WithTimeout(15*time.Second),               // HTTP timeout
    glassnode.WithRetry(3, time.Second),                 // Max retries + base delay for 429 backoff
    glassnode.WithHTTPClient(customHTTPClient),          // Custom *http.Client for testing or proxies
    glassnode.WithUserAgent("my-app/1.0"),               // User-Agent header
    glassnode.WithAppID("trading-bot"),                  // Optional app identifier for Glassnode analytics
)
```

### Available Options

| Option                | Default                     | Description                                                  |
|-----------------------|-----------------------------|--------------------------------------------------------------|
| `WithBaseURL(url)`    | `https://api.glassnode.com` | API base URL                                                 |
| `WithTimeout(d)`      | `30s`                       | HTTP request timeout                                         |
| `WithRetry(n, delay)` | `1, 500ms`                  | Max retry attempts on 429 + base backoff delay               |
| `WithHTTPClient(c)`   | `&http.Client{Timeout: 30s}` | Custom HTTP client (for testing, proxies, custom transports) |
| `WithUserAgent(ua)`   | `glassnode-go/1.0`          | User-Agent header                                            |
| `WithAppID(id)`       | `""`                        | Optional app identifier                                      |
| `WithAuthMode(mode)`  | `AuthModeHeader`            | `AuthModeHeader` or `AuthModeQuery`                          |

### A Note on Timeouts

The default 30s timeout is generous on purpose — some historical metric pulls return large payloads and take a moment.
If you're building a latency-sensitive service (say, a live trading dashboard), dial it down with `WithTimeout`. If
you're pulling years of daily data in a batch job, you may want to dial it up. The right value is the one that matches
your workload, not a magic number.

## Category Services

The 25 category services map 1:1 to Glassnode's documented endpoint categories. Each one exposes typed convenience
methods so you get autocomplete, compile-time checks, and self-documenting code:

| Service       | Client Field          | Documentation                                                               |
|---------------|-----------------------|-----------------------------------------------------------------------------|
| Addresses     | `client.Addresses`    | [Addresses](https://docs.glassnode.com/basic-api/endpoints/addresses)       |
| Bridges       | `client.Bridges`      | [Bridges](https://docs.glassnode.com/basic-api/endpoints/bridges)           |
| Blockchain    | `client.Blockchain`   | [Blockchain](https://docs.glassnode.com/basic-api/endpoints/blockchain)     |
| Breakdowns    | `client.Breakdowns`   | [Breakdowns](https://docs.glassnode.com/basic-api/endpoints/breakdowns)     |
| DeFi          | `client.DeFi`         | [DeFi](https://docs.glassnode.com/basic-api/endpoints/defi)                 |
| Derivatives   | `client.Derivatives`  | [Derivatives](https://docs.glassnode.com/basic-api/endpoints/derivatives)   |
| Distribution  | `client.Distribution` | [Distribution](https://docs.glassnode.com/basic-api/endpoints/distribution) |
| Entities      | `client.Entities`     | [Entities](https://docs.glassnode.com/basic-api/endpoints/entities)         |
| ETH 2.0       | `client.ETH2`         | [ETH 2.0](https://docs.glassnode.com/basic-api/endpoints/eth2)              |
| Fees          | `client.Fees`         | [Fees](https://docs.glassnode.com/basic-api/endpoints/fees)                 |
| Global        | `client.Global`       | [Global](https://docs.glassnode.com/basic-api/endpoints/global)             |
| Indicators    | `client.Indicators`   | [Indicators](https://docs.glassnode.com/basic-api/endpoints/indicators)     |
| Institutions  | `client.Institutions` | [Institutions](https://docs.glassnode.com/basic-api/endpoints/institutions) |
| Lightning     | `client.Lightning`    | [Lightning](https://docs.glassnode.com/basic-api/endpoints/lightning)       |
| Macro         | `client.Macro`        | [Macro](https://docs.glassnode.com/basic-api/endpoints/macro)               |
| Market        | `client.Market`       | [Market](https://docs.glassnode.com/basic-api/endpoints/market)             |
| Mempool       | `client.Mempool`      | [Mempool](https://docs.glassnode.com/basic-api/endpoints/mempool)           |
| Mining        | `client.Mining`       | [Mining](https://docs.glassnode.com/basic-api/endpoints/mining)             |
| Options       | `client.Options`      | [Options](https://docs.glassnode.com/basic-api/endpoints/options)           |
| Point-In-Time | `client.PointInTime`  | [PIT](https://docs.glassnode.com/basic-api/endpoints/pit)                   |
| Protocols     | `client.Protocols`    | [Protocols](https://docs.glassnode.com/basic-api/endpoints/protocols)       |
| Signals       | `client.Signals`      | [Signals](https://docs.glassnode.com/basic-api/endpoints/signals)           |
| Supply        | `client.Supply`       | [Supply](https://docs.glassnode.com/basic-api/endpoints/supply)             |
| Transactions  | `client.Transactions` | [Transactions](https://docs.glassnode.com/basic-api/endpoints/transactions) |
| Treasuries    | `client.Treasuries`   | [Treasuries](https://docs.glassnode.com/basic-api/endpoints/treasuries)     |

**Full endpoint coverage:** [docs/endpoint-coverage.md](docs/endpoint-coverage.md)

## Generic Metric API

The 25 category services cover every documented endpoint category, but Glassnode occasionally adds new metrics before a
typed wrapper exists. The generic `MetricsService` ensures you're never blocked waiting for an SDK release — it works
with **any** valid metric path, present or future:

```go
// Raw JSON — works with any metric path, returns []byte
// Use this when you want full control over decoding, or when the response shape is unusual
raw, err := client.Metrics.Get(ctx, "/addresses/sending_count", &glassnode.MetricQuery{
    Asset: "BTC",
})

// Typed scalar time-series — parses into []TimePoint
// Use this when the metric returns {t, v} pairs
data, err := client.Metrics.GetTimePoints(ctx, "/indicators/sopr", &glassnode.MetricQuery{
    Asset:      "BTC",
    Resolution: glassnode.Resolution24h,
})

// Typed object time-series — parses into []ObjectPoint
// Use this when the metric returns {t, o: {...}} pairs (e.g. OHLC)
ohlc, err := client.Metrics.GetObjectPoints(ctx, "/market/price_ohlc", &glassnode.MetricQuery{
    Asset:      "BTC",
    Resolution: glassnode.Resolution1h,
})
```

### MetricQuery Parameters

The `MetricQuery` struct covers all common query parameters. Only `Asset` is required — the rest are optional and only
sent when set:

```go
type MetricQuery struct {
    Asset           string          // Required — e.g. "BTC", "ETH"
    Resolution      Resolution      // Optional — Resolution10m, Resolution1h, etc.
    Since           *int64   // Optional — Unix timestamp
    Until           *int64   // Optional — Unix timestamp
    Currency        Currency        // Optional — CurrencyNative or CurrencyUSD
    Format          Format          // Optional — FormatJSON or FormatCSV
    TimestampFormat TimestampFormat // Optional — TimestampUnix or TimestampHumanized
}
```

Use `metadata/metric` to discover which parameters a specific metric accepts — not every metric honors every field, and
sending unsupported parameters can cause 400s.

### Convenience Resolution Constants

Instead of remembering string literals, use the exported constants for the common resolutions:

```go
glassnode.Resolution10m     // "10m"
glassnode.Resolution1h      // "1h"
glassnode.Resolution24h     // "24h"
glassnode.Resolution1w      // "1w"
glassnode.Resolution1month  // "1month"
```

## Bulk Metrics

Frequently Asked Questions

What is glassnode-go?

glassnode-go is a Financial library for the Go programming language. Go client for Glassnode Basic API with 25 metric categories, typed structs, bulk endpoints, Point-in-Time data, and zero dependencies

How do I install glassnode-go?

Install glassnode-go with the Go module system using `go get tigusigalpa/glassnode-go`. Check the repository for the current installation instructions.

What category does glassnode-go belong to?

glassnode-go is listed under Financial, specifically Financial.

← Back to Financial