tigusigalpa/tokenterminal-php

Production-ready PHP SDK for the Token Terminal API v2

Maintainers

Package info

github.com/tigusigalpa/tokenterminal-php

pkg:composer/tigusigalpa/tokenterminal-php

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.2 2026-08-09 05:55 UTC

This package is auto-updated.

Last update: 2026-08-09 06:51:52 UTC


README

Tokenterminal PHP Laravel SDK

PHP Version License: MIT Tests

A PHP client for the Token Terminal API v2.

It covers every endpoint the API exposes today, keeps the awkward parts (retries, redirects after a project gets renamed, responses that are half data and half errors) out of your application code, and drops into Laravel without any wiring.

What you get

  • All 24 endpoints: Assets, Projects, Market Sectors, Metrics and Datasets
  • Typed request objects and an immutable TokenTerminalResult for responses
  • Partial responses kept intact, so the errors array survives next to valid data
  • Retries with exponential backoff and jitter, and Retry-After is respected
  • Any PSR-18 HTTP client works; Guzzle is just the default
  • One exception class per HTTP status, so you can catch exactly what you care about
  • 308 redirects followed automatically when a project or asset is renamed
  • Laravel service provider, facade, publishable config, auto-discovery
  • TokenTerminalConfig is immutable; change it with with*() methods

Requirements

  • PHP 8.1+
  • ext-json
  • Guzzle (pulled in by default, swap it for any PSR-18 client)

Installation

composer require tigusigalpa/tokenterminal-php

Quick start

use Tigusigalpa\TokenTerminal\TokenTerminalClient;

$client = TokenTerminalClient::make('your-api-key');

$result = $client->projects()->all();
foreach ($result->data() as $project) {
    echo $project['name'] . ' (id: ' . $project['project_id'] . ")\n";
}

Configuration

Environment variables

Variable Required Default Description
TOKEN_TERMINAL_API_KEY Yes Your Token Terminal API key
TOKEN_TERMINAL_BASE_URL No https://api.tokenterminal.com Custom base URL
TOKEN_TERMINAL_TIMEOUT No 30 HTTP timeout in seconds
TOKEN_TERMINAL_RETRY_ATTEMPTS No 3 Maximum retry attempts
TOKEN_TERMINAL_RETRY_DELAY No 1 Initial retry delay in seconds

Once those are set, this is all you need:

$client = TokenTerminalClient::fromEnv();

Building the config by hand

$config = new TokenTerminalConfig(
    apiKey: 'your-key',
    baseUrl: 'https://api.tokenterminal.com',
    timeout: 30,
    retryAttempts: 3,
    retryDelay: 1,
);

// Nothing is mutated; you always get a new instance back
$config = $config->withRetry(5, 2);
$config = $config->withRetryPOST(true);

$client = new TokenTerminalClient($config);

Endpoints

Domain Method HTTP Route
Assets $client->assets()->all() GET /v2/assets
Assets $client->assets()->get($id) GET /v2/assets/{asset_id}
Assets $client->assets()->historicalMetrics($id, $req) GET /v2/assets/{asset_id}/metrics
Assets $client->assets()->metricsBreakdown($id, $req) POST /v2/assets/{asset_id}/metrics-breakdown
Projects $client->projects()->all() GET /v2/projects
Projects $client->projects()->get($id) GET /v2/projects/{project_id}
Projects $client->projects()->product($pid, $prodId) GET /v2/projects/{project_id}/products/{product_id}
Projects $client->projects()->financialStatement($id, $req) GET /v2/projects/{project_id}/financial-statement
Projects $client->projects()->historicalMetrics($id, $req) GET /v2/projects/{project_id}/metrics
Projects $client->projects()->productHistoricalMetrics($pid, $prodId, $req) GET /v2/projects/{project_id}/products/{product_id}/metrics
Projects $client->projects()->metricAggregations($id) GET /v2/projects/{project_id}/metric-aggregations
Market Sectors $client->marketSectors()->all() GET /v2/market-sectors
Market Sectors $client->marketSectors()->get($id) GET /v2/market-sectors/{market_sector_id}
Metrics $client->metrics()->all() GET /v2/metrics
Metrics $client->metrics()->data($id, $req) GET /v2/metrics/{metric_id}
Metrics $client->metrics()->aggregations($req) GET /v2/metric-aggregations
Metrics $client->metrics()->breakdown($req) POST /v2/metric-breakdown
Datasets $client->datasets()->all() GET /v2/datasets
Datasets $client->datasets()->blockchainComparison($req) GET /v2/datasets/blockchain_comparison
Datasets $client->datasets()->cohortAnalysis($req) GET /v2/datasets/cohort_analysis
Datasets $client->datasets()->cryptoScreener($req) GET /v2/datasets/crypto_screener
Datasets $client->datasets()->insiderTransactions($req) GET /v2/datasets/insider_transactions
Datasets $client->datasets()->projectContracts($req) GET /v2/datasets/project_contracts
Datasets $client->datasets()->trendingContracts($req) GET /v2/datasets/trending_contracts

Errors

Catch the specific case you can actually handle, and let ApiException cover the rest:

use Tigusigalpa\TokenTerminal\Exceptions\UnauthorizedException;
use Tigusigalpa\TokenTerminal\Exceptions\RateLimitException;
use Tigusigalpa\TokenTerminal\Exceptions\NotFoundException;
use Tigusigalpa\TokenTerminal\Exceptions\ApiException;

try {
    $result = $client->projects()->all();
} catch (UnauthorizedException $e) {
    echo "Invalid API key\n";
} catch (RateLimitException $e) {
    printf("Rate limited. Retry-After: %s\n", $e->getRetryAfter());
} catch (NotFoundException $e) {
    echo "Not found\n";
} catch (ApiException $e) {
    printf("API error %d: %s\n", $e->getStatusCode(), $e->getMessage());
}

The hierarchy:

TokenTerminalException
├── ConfigurationException
├── TransportException
└── ApiException
    ├── BadRequestException (400)
    ├── UnauthorizedException (401)
    ├── SubscriptionException (402)
    ├── ForbiddenException (403)
    ├── NotFoundException (404)
    └── RateLimitException (429)

Partial success

Some endpoints happily return usable data together with an errors array, for example when one metric id out of five is wrong. Instead of throwing everything away, the SDK hands you both:

$result = $client->projects()->historicalMetrics('uniswap', new HistoricalMetricsRequest(
    metricIds: ['fees', 'invalid-metric'],
));

foreach ($result->errors() as $error) {
    printf("Partial error: %s %s=%s\n", $error->getCode(), $error->getField(), $error->getValue());
}

How retries behave

  • GET requests are retried on 429, 5xx and transport failures.
  • POST requests are not retried, unless you opt in with withRetryPOST(true).
  • Delays grow exponentially, are capped, and get a bit of jitter so parallel workers don't sync up.
  • If the API sends Retry-After, that value wins.

Laravel

Auto-discovery takes care of the service provider. Publish the config if you want to tweak it:

php artisan vendor:publish --tag=tokenterminal-config
use Tigusigalpa\TokenTerminal\Laravel\Facades\TokenTerminal;

$projects = TokenTerminal::projects()->all();

There's more detail in examples/laravel_usage.md.

Examples

Example What it shows Run it
Basic Listing projects and metrics php examples/basic.php
Project Metrics Historical metrics with filters php examples/project_metrics.php
Metric Breakdown POST breakdown with stats php examples/metric_breakdown.php
Datasets Querying the crypto screener php examples/datasets.php
Error Handling Dealing with API errors php examples/error_handling.php
Laravel Usage Laravel integration guide See examples/laravel_usage.md

Testing

composer install
vendor/bin/phpunit
vendor/bin/phpstan analyse src --level=5
composer validate --strict

Everything runs against Guzzle's MockHandler, so the test suite never touches the real API and needs no API key.

API documentation

License

MIT © Igor Sazonov