Install the package:
composer require amashukov/eth-rpc-client-php
Basic HTTP client setup (using amashukov/http-client-php for simplicity):
use Amashukov\HttpClient\CurlClient;
use Nyholm\Psr7\Factory\Psr17Factory;
use Symfony\Component\Clock\NativeClock;
$psr17 = new Psr17Factory();
$http = new CurlClient($psr17, $psr17, timeoutSeconds: 30);
Initialize the client and provider:
use Amashukov\EthRpc\EthRpcClient;
use Amashukov\EthRpc\JsonRpcProvider;
$client = new EthRpcClient($http, $psr17, $psr17, 'https://your-rpc-endpoint');
$provider = new JsonRpcProvider($client, new NativeClock());
$balanceWei = $provider->getBalance('0x123...'); // Returns a decimal string (safe for large values)
EthRpcClient: Use for raw eth_* RPC calls (e.g., eth_call, eth_getTransactionByHash) when you need direct parity with the JSON-RPC wire format.
$blockNumber = $client->eth_blockNumber();
JsonRpcProvider: Prefer for new code—provides typed Value Objects (e.g., EthereumTransaction, EthereumFeeData) and ethers.js-style methods.
$txBundle = $provider->getTypedTransaction('0xTxHash');
$feeData = $provider->getFeeData(); // Includes EIP-1559 fields
Fetching Transaction Data:
$txBundle = $provider->getTypedTransaction('0xTxHash');
if ($txBundle->isStatusSuccess()) {
$gasUsed = $txBundle->receipt->gasUsed; // Decimal string
}
Handling EIP-1559 Fees:
$feeData = $provider->getFeeData();
$maxFeePerGas = $feeData->maxFeePerGas; // Automatically calculated as `2 × baseFee + tip`
Polling for Transaction Confirmations:
$txReceipt = $provider->waitForTransaction('0xTxHash', 12); // Waits for 12 blocks
ERC-20 Token Balances:
$usdtBalance = $provider->getErc20Balance('0xdAC17F958D2ee523a2206206994597C13D831ec7', '0xUserAddress');
Leverage amashukov/http-client-php to add retries, API key rotation, or load balancing:
use Amashukov\HttpClient\Middleware\RetryMiddleware;
use Amashukov\HttpClient\Middleware\HeaderMiddleware;
$pipeline = new Pipeline([
new RetryMiddleware(3),
new HeaderMiddleware(['Authorization' => 'Bearer $API_KEY']),
]);
$http = new CurlClient($pipeline, $psr17, $psr17);
Node Quirks:
'0x' for empty values (e.g., eth_getLogs). The package handles this gracefully, but log for unexpected responses.baseFeePerGas. The provider defaults to 0 and logs a warning.BigInt Handling:
Wei or HexBig for large values (e.g., balances > PHP_INT_MAX). Avoid floats to prevent precision loss.$balance = $provider->getBalance('0xAddress'); // Returns string, e.g., "12345678901234567890"
$wei = new \Amashukov\EthRpc\Numeric\Wei($balance);
Clock Dependency:
waitForTransaction uses a Psr\Clock\ClockInterface. Mock this in tests:
$clock = new \Symfony\Component\Clock\MockClock('2023-01-01');
$provider = new JsonRpcProvider($client, $clock);
Enable RPC Logging:
Set the ETH_RPC_DEBUG environment variable to log raw requests/responses:
putenv('ETH_RPC_DEBUG=1');
PHPStan Compatibility:
The package is PHPStan L9 compliant. Use --level=9 for strict type checking:
vendor/bin/phpstan analyse --level=9
Custom PSR-18 Clients:
Replace CurlClient with Guzzle, Symfony’s HttpClient, or any PSR-18-compliant client.
Extending Value Objects:
Extend classes like EthereumTransaction to add chain-specific logic:
class CustomTransaction extends \Amashukov\EthRpc\EthereumTransaction {
public function isMinted(): bool {
return $this->to === '0x0000000000000000000000000000000000000000';
}
}
Overriding Fee Calculation:
Inject a custom FeeCalculator to modify EIP-1559 logic:
$provider = new JsonRpcProvider($client, $clock, new CustomFeeCalculator());
Timeouts:
Set timeouts at the PSR-18 client level (e.g., CurlClient’s timeoutSeconds). The provider does not enforce global timeouts.
Gas Limits:
The package does not validate gas limits. Always sanity-check gasLimit in transactions:
if ($txBundle->transaction->gasLimit > 10_000_000) {
throw new \RuntimeException('Gas limit too high!');
}
Batch Requests:
Use eth_call with multiple from/to pairs to reduce RPC calls:
$results = $client->eth_call([
['from' => '0xA', 'to' => '0xB', 'data' => '0x...'],
['from' => '0xC', 'to' => '0xD', 'data' => '0x...'],
]);
Cache Frequently Accessed Data:
Cache getBlockNumber(), getFeeData(), or getBalance() results if your use case allows stale data:
$cache = new \Symfony\Component\Cache\Adapter\FilesystemAdapter();
$blockNumber = $cache->get('block_number', function() use ($client) {
return $client->eth_blockNumber();
});
How can I help you explore Laravel packages today?