Search by

amashukov / toncenter-client-php

Andrey Mashukov

Typed toncenter v2 HTTP client for The Open Network — {ok, result} envelope unwrapping, retry-aware via any PSR-18 client, typed Value Objects (masterchain info, account state, run-get-method stack reader, transactions). Pluggable wallet RPC adapter.

Package info

github.com/AndreyMashukov/toncenter-client-php

pkg:composer/amashukov/toncenter-client-php

Statistics

Installs: 434

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v0.2.5 2026-10-15 17:35 UTC

This package is auto-updated.

Last update: 2026-10-16 06:04:09 UTC


README

Typed PHP toncenter v2 client for The Open Network — PSR-18 transport, {ok, result} envelope unwrapping, retry-aware, typed Value Objects.

CI PHPStan L9 Latest Version Downloads PHP License Stars

A typed PHP client for the toncenter v2 HTTP API on The Open Network (TON). It speaks any PSR-18 HTTP client, unwraps the {ok, result} envelope into typed exceptions in one place, and returns Value Objects instead of loose arrays — masterchain info, account state, a run-get-method stack reader, and typed transactions. TON balances exceed PHP_INT_MAX past ~9,223 TON, so every nano-TON value comes back as a decimal string, never a lossy float. Includes ToncenterWalletRpc, a drop-in WalletRpcInterface adapter for ton-wallet-php.

Features

  • {ok, result} envelope unwrapping in one place — a non-200 status, ok: false, or a missing result becomes a TonRpcException; business code never touches the envelope. Toncenter sends its errors with a matching HTTP status and a {"ok": false, "error": …, "code": …} body, so a non-200 response is read for that body: the exception carries toncenter's code and error (code 0 when the body is not toncenter JSON, e.g. a proxy error page). That is how tryLocateResultTx turns toncenter's 404 "transaction was not found" into null.
  • Typed Value Objects — masterchain info, account state, run-get-method stack reader, and a five-state transaction status (Pending, Success, ComputePhaseFailed, ActionPhaseFailed, Aborted) derived from the TVM compute / action phases.
  • Transaction phases from the transaction BOC — toncenter v2 answers getTransactions without a description, so a naive reader sees every transaction as successful. When the row has no description, the client parses the TL-B Transaction in the row's data field: credit, compute and action phases, aborted, whether a bounce message was sent, and destroyed land in TonTransaction::$description (TonTransactionDescription) and drive the status. getTypedTransactions, getTypedTransaction and tryLocateResultTx refuse a row without data with a TonRpcException, so a transaction read through the client never comes back without its phases: a caller that sees $description === null on such a transaction can treat it as a bug, not as "no bounce".
  • Decimal-string big numbers — balances and gas values are returned as decimal strings, safe past PHP_INT_MAX.
  • Transport-agnostic — bring your own PSR-18 client and PSR-17 factories. The retry policy and the X-Api-Key rate-limit header are middleware concerns, not baked into the client.
  • 542 / 429 retry decider — toncenter emits transient 542 ("no workers available") and 429; the recommended retry status set is documented so a retry middleware can target exactly those.
  • Wallet RPC adapter — ToncenterWalletRpc implements Amashukov\TonWallet\WalletRpcInterface (getSeqno + sendBoc) so a ton-wallet-php wallet broadcasts through toncenter with no glue code.

Why amashukov/toncenter-client-php

The common alternative is hand-rolled Guzzle calls scattered across a service layer, each re-implementing envelope checks, bigint parsing, and 542/429 retry logic. This package centralizes all of that behind a typed surface with PHPStan level 9, returns Value Objects instead of arrays, and stays transport-agnostic so retry / API-key concerns live in your PSR-18 pipeline.

Installation

composer require amashukov/toncenter-client-php

Usage

Any PSR-18 client and PSR-17 factory pair works. The example below wires amashukov/http-client-php (cURL transport + retry / header-injection middleware) with nyholm/psr7 for the PSR-7 messages:

use Amashukov\HttpClient\CurlClient;
use Amashukov\HttpClient\Middleware\HeaderInjectionMiddleware;
use Amashukov\HttpClient\Middleware\RetryMiddleware;
use Amashukov\HttpClient\Pipeline;
use Amashukov\Toncenter\ToncenterClient;
use Nyholm\Psr7\Factory\Psr17Factory;

$psr17 = new Psr17Factory();

$http = new Pipeline(
    new CurlClient($psr17, $psr17, timeoutSeconds: 60),
    [
        // X-Api-Key lifts the rate limit from 1 RPS to 10 RPS (optional).
        new HeaderInjectionMiddleware(['X-Api-Key' => $apiKey]),
        // Toncenter emits transient 542 ("no workers available") + 429; retry those.
        new RetryMiddleware(maxAttempts: 3, retryStatusCodes: [429, 500, 502, 503, 504, 542]),
    ],
);

$client = new ToncenterClient($http, $psr17, $psr17);

$info    = $client->getMasterchainInfo();        // TonMasterchainInfo
$balance = $client->getBalance('EQ...');          // decimal-string nano-TON
$account = $client->getAddressInformation('EQ...');
if ($account->isActive()) {
    // ...
}

Logging

Pass any PSR-3 logger (Psr\Log\LoggerInterface, e.g. Monolog or a framework's logger) as the last constructor argument; without one the client logs nothing.

$client = new ToncenterClient($http, $psr17, $psr17, ToncenterClient::DEFAULT_BASE_URL, $logger);
  • debug — every request (method, path) and every response (status).
  • error — every failure that reaches the caller as a TonRpcException: transport error, non-200 status, ok: false, invalid JSON, missing result. The context carries exception, method, path and toncenter's code.
  • An answer the client handles itself is not an error: toncenter's 404 "transaction was not found" from tryLocateResultTx is logged at debug, and the method returns null.

Reading get-method results

$result = $client->runMethod('EQ...', 'get_wallet_data');
if ($result->isOk()) {
    $balance    = $result->stack->readBigInt();    // numeric-string
    $ownerSlice = $result->stack->readSliceBoc();   // base64 BOC
}

The stack reader is a cursor over a TON TupleItem sum-type (TonTupleItemInt / Cell / Slice / Builder / Null / Tuple) and decodes both decimal and 0x-prefixed bigints via GMP.

Following a message to its result

$result = $client->tryLocateResultTx($sourceAddress, $destinationAddress, $createdLt);
if (null !== $result && null !== $result->description) {
    $credited = $result->description->credited;   // nano-TON credited, null without a credit phase
    $bounced  = $result->description->bounced;    // a bounce message was sent back
    $executed = $result->isStatusSuccess();       // compute and action phases succeeded
}

tryLocateResultTx asks toncenter for the transaction in which the destination processed one internal message, so it does not page through the destination's history and answers the same for a quiet wallet and for an exchange address with thousands of transactions a minute. It returns null while the transaction is not known yet.

Broadcasting a wallet transfer

use Amashukov\Toncenter\ToncenterWalletRpc;

$rpc = new ToncenterWalletRpc($client);

$seqno = $rpc->getSeqno($walletRawAddress); // 0 if the wallet is not yet deployed
$rpc->sendBoc($signedTransferBocBase64);

API

Method Returns
getMasterchainInfo() TonMasterchainInfo
getBalance(string $address) string (decimal nano-TON)
getAddressInformation(string $address) TonAccountInfo
isContractDeployed(string $address) bool
getTypedTransactions(string $address, array $opts = []) list<TonTransaction>
getTypedTransaction(string $address, string $lt, string $hash) TonTransaction
tryLocateResultTx(string $source, string $destination, string $createdLt) ?TonTransaction
runMethod(string $address, string $method, array $stack = []) TonRunMethodResult
sendBoc(string $bocBase64) TonSendBocResult

Requirements

  • PHP 8.3+
  • ext-gmp (bigint decoding of get-method stack values)
  • A PSR-18 client + PSR-17 request / stream factories
  • Optional: a PSR-3 logger

Related packages

Package Role
amashukov/ton-wallet-php Wallet v4r2 + WalletRpcInterface this client adapts (dependency)
amashukov/http-client-php PSR-18 cURL client + retry / header-injection middleware
amashukov/ton-cell-php TLB Cell / Builder / BOC layer
amashukov/ton-php TON meta-package
amashukov/blockchain-context-bundle Symfony bundle wiring the whole family

Quality

  • PHPStan level 9, clean.
  • php-cs-fixer @PER-CS ruleset.
  • GitHub Actions CI on every push.

License

MIT — see LICENSE.

Strict evidence for undeployed accounts

TonAccountInfo::isProvenUninitialized() is suitable for deciding that a contract has no deployed token state. It requires an explicit uninitialized, uninit or nonexist wire state, an unsigned decimal-string native balance, and absent/empty code and data. Missing, unknown or malformed fields never qualify. Native TON may still be present at an undeployed address; this predicate does not mean its native balance is zero. The legacy state normalization and isUninitialized() remain compatible, including their defaults; use the strict predicate for absence decisions. Direct constructor calls have no strict wire evidence by default.

Estimate message fees

$estimate = $client->estimateFee($walletAddress, $unsignedBodyBoc);
$sourceGasNanotons = $estimate->sourceFees->gasFee;

estimateFee calls the v2 /estimateFee endpoint without sending a transaction. Pass the base64 message body, not the complete external-message BOC. Optional initCodeBoc and initDataBoc support undeployed accounts. ignoreSignature defaults to true; pass false to require a valid signature during estimation.

TonFeeEstimate preserves sourceFees and the ordered destinationFees list. Each TonFeeBreakdown exposes inForwardFee, storageFee, gasFee and forwardFee as nonnegative decimal strings in nanotons, including explicit zero. Missing, negative, fractional or malformed components raise TonRpcException; large JSON integers are preserved without floating-point conversion. Existing transport/envelope errors and logging apply to estimation as to other methods.

This is an estimate, not a receipt or a guaranteed final charge. The provider may return no destination fees; that does not establish zero downstream cost or full trace coverage. Applications must establish which message executions are covered before using the estimate to price a multi-contract operation.