arkecosystem/client
PHP client for the Ark blockchain ecosystem. Query wallets, blocks, transactions, and delegates; build and broadcast signed transactions via Ark Core APIs, with a simple interface for Laravel and plain PHP projects.
Install
composer require arkecosystem/client
Requires PHP 7.x+ and pulls in Guzzle as its HTTP transport. There is no framework coupling — it is a plain library.
Where to look first
src/Connection.php — the entry point; how the host/base URL and Guzzle client are configured.src/API/ — one class per endpoint group (Blocks, Transactions, Wallets, Delegates, Peers, Node, Blockchain, Votes). This directory is the documentation; the method names track the REST paths./api/node/status, /api/blocks, /api/transactions, …) for parameter semantics — the client is a thin passthrough, so the REST docs apply directly.First use case: confirm you can reach a node
require 'vendor/autoload.php';
use ArkEcosystem\Client\Connection;
$connection = new Connection([
'host' => 'https://api.ark.io/api', // scheme + host + /api, no trailing slash
]);
// Node status is the cheapest liveness + height probe.
$status = $connection->node()->status(); // ['data' => ['height' => ..., 'synced' => true, ...]]
echo $status['data']['height'];
// Then a first read: most recent blocks, one page.
$blocks = $connection->blocks()->all(['limit' => 5]);
foreach ($blocks['data'] as $block) {
echo $block['height'] . ' ' . $block['id'] . PHP_EOL;
}
If an accessor doesn't exist in the version you installed, drop to the resource you need (blocks(), transactions(), wallets(), …) or use the connection's generic request method against a path — the underlying call is just <host>/<resource>.
Start with node()->status() and blocks()->all() before building anything else: they validate host formatting, TLS, and the {data, meta} response envelope in one shot.
Laravel service binding + config
Keep the Connection a singleton and the host in config so you can swap nodes per environment.
// config/ark.php
return ['host' => env('ARK_HOST', 'https://api.ark.io/api')];
// app/Providers/AppServiceProvider.php
$this->app->singleton(\ArkEcosystem\Client\Connection::class, fn () => new \ArkEcosystem\Client\Connection([
'host' => config('ark.host'),
]));
Wrap it in a domain gateway so controllers/jobs never touch the vendor class directly — this is your seam for fakes in tests and for failover logic later.
final class ArkGateway
{
public function __construct(private Connection $connection) {}
public function height(): int
{
return (int) $this->connection->node()->status()['data']['height'];
}
public function recentTransactions(int $limit = 50): array
{
return $this->connection->transactions()->all(['limit' => $limit])['data'] ?? [];
}
}
Pagination loop
Every list endpoint returns {data: [...], meta: {page, pageCount, totalCount, ...}}. Write one helper and reuse it; limit caps at 100 per page.
$page = 1;
do {
$res = $connection->transactions()->all(['page' => $page, 'limit' => 100, 'orderBy' => 'timestamp:desc']);
foreach ($res['data'] as $tx) { $this->persist($tx); }
$page++;
} while ($page <= ($res['meta']['pageCount'] ?? 1));
For ongoing ingestion, don't deep-paginate — persist the last seen height/timestamp and poll forward with small pages.
Host pool / failover
Public ARK endpoints throttle and occasionally go down. Round-robin a list of hosts behind your gateway, catch RequestException, and move to the next. Treat node status (synced === true) as a health check before trusting reads.
Polling for confirmation
After broadcasting a transaction (signed elsewhere — see gotchas), poll transactions()->all(['id' => $id]) on a schedule (queued job, backoff every 8s up to ~30 blocks) rather than blocking a web request.
Caching
Chain reads are cheap but rate-limited. Cache node()->status() for 5–10s, delegates()->all() for minutes, and static lookups (fees, transaction types) for the process lifetime. Cache the envelope, not just data, so you keep meta for pagination.
Testing
Bind a fake Connection (or a Guzzle handler stack with MockHandler) into the container and assert your gateway's mapping. Don't mock the vendor resource classes — they're thin and will drift.
/api suffix. Trailing slashes produce doubled paths and 404s. Some versions expect a base_uri-style key; check Connection::__construct in the installed tag.arkecosystem/crypto (or an external signer) using the network's nonce/network byte, then POST the signed payload — either through the client's transaction resource or plain Guzzle. Never put a passphrase in a request body.bcmath or string math, and never float for balances. On 32-bit PHP this matters immediately.{data, meta}; errors from the node may be {error, message, statusCode} or a bare HTML 502 from a proxy. Always guard with $res['data'] ?? null and check meta for emptiness rather than assuming an array.Retry-After, add jitter, and never fan out concurrent requests across a whole wallet list without a pool.ConnectException, RequestException, and TooManyRedirectsException all propagate. Catch them at the gateway boundary and translate to domain errors; otherwise a node hiccup becomes a 500.timeout/connect_timeout explicitly, and inject your own Guzzle client (with logging middleware) into Connection if the constructor supports it. That's the cleanest extension point.composer.json, read src/API/* for the methods you actually call, and be prepared to vendor a fork. For anything beyond a handful of endpoints, a 60-line Guzzle wrapper against the documented REST API is a legitimate alternative — you'd lose almost nothing.transactions lookup returning a confirmations field.How can I help you explore Laravel packages today?