tigusigalpa/glassnode-php

Framework-agnostic PHP SDK for the Glassnode Basic API with a first-class Laravel bridge.

Maintainers

Package info

github.com/tigusigalpa/glassnode-php

pkg:composer/tigusigalpa/glassnode-php

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-07-30 14:18 UTC

This package is auto-updated.

Last update: 2026-07-30 14:23:24 UTC


README

GlassNode PHP/Laravel SDK

PHP Version License: MIT Tests Laravel

Unofficial PHP SDK for the Glassnode Basic API. Not affiliated with or endorsed by Glassnode.

A framework-agnostic, production-oriented PHP library for the Glassnode Basic API with a first-class Laravel bridge.

If you've ever tried to pull on-chain metrics from Glassnode in a PHP project, you know the drill: hand-rolling cURL requests, manually constructing query strings, decoding JSON, and wrapping everything in try-catch blocks just to get a clean error message. This SDK takes care of all that — and goes further by making the entire documented metric surface discoverable and safely consumable through three layers of API:

  1. Transport/configGlassnodeClient with cURL or PSR-18 transport, immutable config, automatic retries, and rate-limit awareness
  2. Metadata/genericMetadataResource for runtime discovery, GlassnodeClient::get() for any metric path (present or future)
  3. Ergonomic category resources — 25 typed resource classes with convenience methods that map 1:1 to Glassnode's endpoint categories

The result? You write less boilerplate, catch errors earlier, and spend more time analyzing data instead of wrestling with HTTP plumbing.

Why This SDK?

  • Stop hand-writing HTTP calls — every endpoint is a method call away with proper parameter handling
  • Discover metrics at runtime — not sure which parameters a metric accepts? Ask the metadata API
  • Survive rate limits gracefully — automatic retry on 429 with server-aware backoff
  • Stay framework-agnostic — works in plain PHP, Laravel, Symfony, or anything else
  • Keep your API key safe — header-based auth by default, never leaked in URLs or logs
  • Only pay for what you use — bulk endpoints with explicit asset lists so you control credit consumption

Features

  • Framework-agnostic — works in any PHP 8.1+ project, no framework required
  • First-class Laravel bridge — service provider, facade, config publishing, auto-discovery
  • 25 category resources 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
  • 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
  • Header authentication (X-Api-Key) by default; query-string opt-in for environments that need it
  • Automatic retry on HTTP 429 with x-rate-limit-reset support and exponential backoff
  • Rich exception hierarchyBadRequestException, UnauthorizedException, NotFoundException, RateLimitException, and more, each carrying context about what went wrong
  • PSR-18 compatible — use any PSR-18 HTTP client (Guzzle, Symfony HTTP Client, etc.), or the built-in zero-dependency cURL transport
  • Immutable configuration — once created, config objects can't be accidentally mutated mid-request
  • No network requests at boot time — Laravel-safe, won't slow down your app startup

Installation

composer require tigusigalpa/glassnode-php

That's it. The package has no hard dependencies beyond ext-curl and ext-json (both enabled by default in virtually every PHP installation). If you're using Laravel, the service provider and facade are auto-discovered — no manual registration needed.

Laravel Setup

The package auto-discovers the service provider and facade via Laravel's package discovery. Publish the config file to customize defaults:

php artisan vendor:publish --tag=glassnode-config

This creates config/glassnode.php in your project. Then set your API key in .env:

GLASSNODE_API_KEY=your-api-key-here

The published config file lets you override the base URL, timeout, retry settings, auth mode, and more — all in one place.

Plain PHP Setup

No framework? No problem. Just instantiate the client directly:

use Tigusigalpa\Glassnode\GlassnodeClient;
use Tigusigalpa\Glassnode\GlassnodeConfig;

$config = new GlassnodeConfig(apiKey: 'your-api-key');
$client = new GlassnodeClient($config);

Or load from environment variables (GLASSNODE_API_KEY, GLASSNODE_TIMEOUT, etc.):

$config = GlassnodeConfig::fromEnv();
$client = new GlassnodeClient($config);

Quick Start

Standalone PHP

Here's a complete example — fetch BTC price data and print it:

use Tigusigalpa\Glassnode\GlassnodeClient;
use Tigusigalpa\Glassnode\GlassnodeConfig;

$client = new GlassnodeClient(new GlassnodeConfig(
    apiKey: 'YOUR_API_KEY',
    timeout: 15.0,
    retryAttempts: 3,
));

// Fetch BTC price with 24h resolution
$price = $client->market->price(['a' => 'BTC', 'i' => '24h']);
foreach ($price as $point) {
    printf("BTC price at %d: $%.2f\n", $point['t'], $point['v']);
}

Want to explore what's available before pulling data? Use the metadata API:

// List all supported assets
$assets = $client->metadata->assets();
foreach ($assets as $asset) {
    printf("%s (%s)\n", $asset['name'], $asset['symbol']);
}

// Inspect a specific metric's parameters
$metricInfo = $client->metadata->metric('/market/price_usd');
print_r($metricInfo);

Laravel

In Laravel, you can use the facade for quick access:

use Tigusigalpa\Glassnode\Laravel\Facades\Glassnode;

// Fetch metrics with a clean, expressive syntax
$price = Glassnode::market()->price(['a' => 'BTC', 'i' => '24h']);
$sopr = Glassnode::indicators()->sopr(['a' => 'BTC']);
$assets = Glassnode::metadata()->assets();

Or via dependency injection (recommended for controllers and services):

use Tigusigalpa\Glassnode\GlassnodeClient;

class DashboardController extends Controller
{
    public function index(GlassnodeClient $client)
    {
        $price = $client->market->price(['a' => 'BTC']);
        $activeAddresses = $client->addresses->activeCount(['a' => 'BTC']);

        return view('dashboard', [
            'price' => $price,
            'activeAddresses' => $activeAddresses,
        ]);
    }
}

Common Use Cases

Building a market dashboard:

// Get OHLC candles for charting
$ohlc = $client->market->priceOhlc(['a' => 'BTC', 'i' => '1h']);

// Market cap and realized cap for valuation analysis
$mcap = $client->market->marketCap(['a' => 'BTC']);
$rcap = $client->market->realizedCap(['a' => 'BTC']);

// MVRV ratio — a classic cycle indicator
$mvrv = $client->indicators->mvrv(['a' => 'BTC']);

Monitoring network health:

// Active addresses — measures network usage
$active = $client->addresses->activeCount(['a' => 'BTC', 'i' => '24h']);

// Hash rate — mining security
$hashrate = $client->mining->hashRateMean(['a' => 'BTC']);

// Total supply — track inflation
$supply = $client->supply->current(['a' => 'BTC']);

API Key Security

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

  • Never hardcode your API key in source code, commit it to version control, or expose it in client-side code
  • Use GlassnodeConfig::fromEnv() in plain PHP or Laravel's env('GLASSNODE_API_KEY') to load from environment variables
  • The SDK uses header mode (X-Api-Key) by default — your key is sent in an HTTP header and never appears in URLs, query strings, or server access logs
  • Query-string mode (api_key parameter) is available as an opt-in for environments where headers aren't supported, but it's less secure — the key may be visible in proxy logs, browser history, or referrer headers
  • If you accidentally expose a key, rotate it immediately in Glassnode Studio settings

Authentication Modes

The SDK supports two authentication modes. Header mode is the default and recommended for all production usage:

// Header mode (default, recommended)
// Sends the key as an X-Api-Key header — invisible in URLs and logs
new GlassnodeConfig(apiKey: 'key')

// Query-string mode (opt-in, less secure)
// Appends ?api_key=... to every request URL
new GlassnodeConfig(apiKey: 'key', authMode: GlassnodeConfig::AUTH_MODE_QUERY)

Configuration

The GlassnodeConfig object is immutable — once created, its values can't change. This prevents accidental mutation mid-request in long-running processes.

All configuration options with their defaults:

$config = new GlassnodeConfig(
    apiKey: 'YOUR_API_KEY',        // Required — your Glassnode API key
    baseUrl: 'https://api.glassnode.com',  // API base URL (rarely needs changing)
    timeout: 15.0,                 // HTTP timeout in seconds
    retryAttempts: 3,             // Max retries on HTTP 429
    retryDelay: 1.0,              // Base delay in seconds for exponential backoff
    authMode: 'header',           // 'header' (default) or 'query'
    userAgent: 'glassnode-php/1.0', // User-Agent header sent with each request
    appId: null,                  // Optional app identifier for Glassnode analytics
);

Creating Config from Different Sources

From an array (great for loading from a config file):

$config = GlassnodeConfig::fromArray([
    'api_key' => 'YOUR_API_KEY',
    'timeout' => 15.0,
    'retry_attempts' => 3,
]);

From environment variables (reads GLASSNODE_API_KEY, GLASSNODE_TIMEOUT, etc.):

$config = GlassnodeConfig::fromEnv();

In Laravel, the published config/glassnode.php file handles all of this for you — just set the values in .env and the service provider wires everything up automatically.

Category Resources

Resource Client Property Laravel Facade Documentation
Addresses $client->addresses Glassnode::addresses() Addresses
Bridges $client->bridges Glassnode::bridges() Bridges
Blockchain $client->blockchain Glassnode::blockchain() Blockchain
Breakdowns $client->breakdowns Glassnode::breakdowns() Breakdowns
DeFi $client->defi Glassnode::defi() DeFi
Derivatives $client->derivatives Glassnode::derivatives() Derivatives
Distribution $client->distribution Glassnode::distribution() Distribution
Entities $client->entities Glassnode::entities() Entities
ETH 2.0 $client->eth2 Glassnode::eth2() ETH 2.0
Fees $client->fees Glassnode::fees() Fees
Global $client->global Glassnode::global() Global
Indicators $client->indicators Glassnode::indicators() Indicators
Institutions $client->institutions Glassnode::institutions() Institutions
Lightning $client->lightning Glassnode::lightning() Lightning
Macro $client->macro Glassnode::macro() Macro
Market $client->market Glassnode::market() Market
Mempool $client->mempool Glassnode::mempool() Mempool
Mining $client->mining Glassnode::mining() Mining
Options $client->options Glassnode::options() Options
Point-In-Time $client->pointInTime Glassnode::pointInTime() PIT
Protocols $client->protocols Glassnode::protocols() Protocols
Signals $client->signals Glassnode::signals() Signals
Supply $client->supply Glassnode::supply() Supply
Transactions $client->transactions Glassnode::transactions() Transactions
Treasuries $client->treasuries Glassnode::treasuries() Treasuries

Full endpoint coverage: docs/endpoint-coverage.md

Generic Metric API

The 25 category resources cover every documented endpoint category, but Glassnode occasionally adds new metrics before a typed wrapper exists. The generic API ensures you're never blocked — it works with any valid metric path, present or future:

// Raw decoded JSON — works with any metric path
$data = $client->get('/v1/metrics/addresses/sending_count', ['a' => 'BTC']);

// Full parameter set: asset, resolution, time range
$data = $client->get('/v1/metrics/market/price_ohlc', [
    'a' => 'BTC',      // Asset
    'i' => '24h',      // Resolution: 10m, 1h, 24h, 1w, 1month
    's' => 1609459200, // Since (Unix timestamp)
    'u' => 1609545600, // Until (Unix timestamp)
    'c' => 'USD',      // Currency (for market metrics)
    'f' => 'Json',     // Format: Json, CSV
]);

Common Query Parameters

Parameter Description Example
a Asset symbol 'BTC', 'ETH'
i Resolution interval '10m', '1h', '24h', '1w', '1month'
s Since (Unix timestamp) 1609459200
u Until (Unix timestamp) 1609545600
c Currency (market metrics) 'USD', 'EUR'
f Response format 'Json', 'CSV'
timestamp_format Timestamp format 'unix', 'humanized', 'humanized-30d'

Use metadata/metric to discover which parameters a specific metric accepts.

Bulk Metrics

Bulk endpoints let you fetch data for multiple assets in a single request, saving on rate limits. However, credits are still consumed per asset — so a bulk call for 5 assets costs the same as 5 individual calls.

// Fetch MVRV for BTC and ETH in one call
$resp = $client->get('/v1/metrics/market/mvrv/bulk', [
    'a' => ['BTC', 'ETH'],   // Multiple assets as an array
    's' => strtotime('-7 days'),
    'i' => '24h',
]);

Bulk Best Practices

  • Always specify assets explicitly — never use wildcards, as this can consume unexpected credits
  • Check bulk_supported via metadata/metric before calling bulk endpoints — not all metrics support it
  • Batch in reasonable sizes — 5–10 assets per call is a good balance between throughput and credit visibility
  • Monitor your credit usage with $client->user->apiUsage() to catch unexpected consumption early

Error Handling

The SDK throws a rich hierarchy of exceptions, so you can catch exactly the errors you care about without parsing error messages or HTTP status codes yourself:

use Tigusigalpa\Glassnode\Exceptions\BadRequestException;
use Tigusigalpa\Glassnode\Exceptions\UnauthorizedException;
use Tigusigalpa\Glassnode\Exceptions\NotFoundException;
use Tigusigalpa\Glassnode\Exceptions\RateLimitException;
use Tigusigalpa\Glassnode\Exceptions\GlassnodeException;

try {
    $data = $client->indicators->sopr(['a' => 'BTC']);
} catch (UnauthorizedException $e) {
    // 401 — API key is missing, invalid, or expired
    echo "Invalid API key — check your configuration\n";
} catch (RateLimitException $e) {
    // 429 — you've hit the rate limit (after all retries are exhausted)
    printf("Rate limited. Reset in %s seconds.\n", $e->rateLimitReset ?? 'unknown');
} catch (BadRequestException $e) {
    // 400 — invalid parameters, unsupported asset, etc.
    printf("Bad request: %s\n", $e->getMessage());
} catch (NotFoundException $e) {
    // 404 — the metric path doesn't exist
    echo "Metric not found — check the path with metadata/metric\n";
} catch (GlassnodeException $e) {
    // Catch-all for any other SDK error (network issues, transport errors, etc.)
    printf("Unexpected error: %s\n", $e->getMessage());
}

Exception Hierarchy

All exceptions extend GlassnodeException, so you can catch everything with a single catch block if you prefer:

GlassnodeException (base)
├── BadRequestException        (HTTP 400)
├── UnauthorizedException      (HTTP 401)
├── NotFoundException          (HTTP 404)
├── RateLimitException         (HTTP 429, after retries)
├── ConfigurationException     (invalid config)
└── TransportException         (network/HTTP errors)

Each exception carries the HTTP status code, response body, and (for RateLimitException) the rateLimitReset value from the x-rate-limit-reset header.

PSR-18 HTTP Client Support

The SDK ships with a zero-dependency cURL transport that works out of the box. But if you're already using Guzzle, Symfony HTTP Client, or any other PSR-18 compatible client in your project, you can plug it in directly — no need for a second HTTP dependency:

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;

$client = new GlassnodeClient(
    config: $config,
    psrClient: new Client(),       // Any PSR-18 ClientInterface
    requestFactory: new HttpFactory(),  // PSR-17 RequestFactoryInterface
    streamFactory: new HttpFactory(),   // PSR-17 StreamFactoryInterface
);

This is particularly useful in Symfony projects (which ship with their own HTTP client) or in projects where you need custom middleware like request logging, circuit breakers, or proxy configuration.

Retry Behavior

Nobody likes getting rate-limited. The SDK handles 429 responses automatically so you don't have to wrap every call in retry logic:

  • Retries only GET requests on HTTP 429 — non-idempotent methods are never retried
  • Honors x-rate-limit-reset header when present — waits exactly as long as the server tells us
  • Falls back to exponential backoff when the header is absent: retryDelay * 2^attempt
  • Never retries 400, 401, or 404 — these are client errors that won't resolve by retrying
  • Configurable via retryAttempts (default: 3) and retryDelay (default: 1.0s)

If all retry attempts are exhausted, a RateLimitException is thrown with the rateLimitReset value (if available) so your application can decide how to handle it — queue the request, alert the user, or back off further.

Rate Limits

Rate limits are governed by Glassnode's servers and depend on your subscription tier. The API returns these headers on every response, so you can monitor your usage proactively:

Header Description
x-rate-limit-limit Total request limit per minute (e.g. 600 for standard tier)
x-rate-limit-remaining Requests remaining in the current window
x-rate-limit-reset Seconds until the limit resets

Metadata endpoints are separately limited to 1200 req/min, so you can discover metrics freely without worrying about impacting your data request budget.

Tips for Staying Within Limits

  • Cache metadata responses — assets and metric definitions rarely change
  • Use appropriate resolutions — don't fetch 10-minute data when you only need daily aggregates
  • Batch with bulk endpoints where supported — one HTTP call instead of many
  • Monitor x-rate-limit-remaining and back off before hitting zero

Data Credits

Glassnode charges data credits per request, not per data point. Understanding the credit model helps you avoid surprises:

  • BTC: 1 credit per request
  • All other assets: 2 credits per request
  • Bulk endpoints: credits = sum of individual calls (e.g., 5 assets = 5× credits)
  • Monitor usage via $client->user->apiUsage() or Studio settings

If you're building a dashboard that pulls many metrics, consider caching results and refreshing on a schedule rather than polling continuously.

Testing

The test suite uses mocked HTTP transports — no API key or live requests are required, so you can run the full suite anywhere:

composer install
vendor/bin/phpunit

For verbose output with test names:

vendor/bin/phpunit --testdox

The suite includes 41 tests (37 unit + 4 feature) covering all resources, error handling, retry logic, configuration, and the Laravel bridge.

Examples

The examples/ directory contains ready-to-run scripts you can adapt for your own projects:

  • Basic usage — price, indicators, assets, API usage
  • Laravel — facade and dependency injection in a controller
  • Error handling — exception types and recovery strategies

Compatibility

  • PHP 8.1+ (uses named arguments, readonly properties, enums)
  • Laravel 10, 11, 12, 13 (auto-discovery, facade, config publishing)
  • ext-curl and ext-json required (both enabled by default in standard PHP)
  • PSR-18 client optional (for custom HTTP transports like Guzzle or Symfony HTTP Client)

FAQ

Do I need a Glassnode account to use this SDK?

Yes. You need a Glassnode account with an API key. Sign up at studio.glassnode.com — there's a free tier with limited credits to get started.

Is this an official Glassnode product?

No. This is an unofficial, community-built SDK. It's not affiliated with or endorsed by Glassnode. The official API documentation is at docs.glassnode.com.

Does the SDK work without Laravel?

Absolutely. The core library is framework-agnostic. The Laravel bridge is an optional layer on top — if you're using plain PHP, Symfony, or any other framework, just instantiate GlassnodeClient directly.

What happens when Glassnode adds new metrics?

The generic API ($client->get()) works with any valid metric path, so you can use new metrics immediately even before a typed wrapper is added. Check metadata/metrics to discover new paths at runtime.

Can I use this in a long-running process (queue worker, daemon)?

Yes. The GlassnodeConfig is immutable, and the GlassnodeClient is safe to reuse across requests. The cURL transport creates fresh handles per request, so there's no state leakage between calls.

How do I get CSV format instead of JSON?

Pass 'f' => 'CSV' in the query parameters. The SDK returns the raw CSV string as-is. JSON (the default) is automatically decoded into PHP arrays.

Contributing

Contributions are welcome! Whether it's a bug fix, a new example, improved documentation, or a feature — here's how to get started:

  1. Fork the repository and create your branch from main
  2. Run tests to make sure everything passes before you start: vendor/bin/phpunit
  3. Make your changes — keep code style consistent with the existing codebase
  4. Add tests for any new functionality — all tests must pass with mocked transports (no live API calls)
  5. Submit a pull request with a clear description of what and why

Reporting Issues

Found a bug or have a feature request? Please open an issue on GitHub with:

  • A clear description of the problem or request
  • Steps to reproduce (for bugs)
  • Expected vs. actual behavior
  • PHP version and framework (if applicable)

Changelog

See CHANGELOG.md for version history and breaking changes.

Author

Igor Sazonovsovletig@gmail.comgithub.com/tigusigalpa

License

MIT — do whatever you want, just don't blame me if something breaks.

Official Links