slvler/ether
A Laravel package for interacting with the Ethereum blockchain via JSON-RPC. It offers a simple client for querying blocks, transactions, accounts, and network data, making Ethereum connectivity easier to integrate into PHP applications.
slvler/ether is a thin Laravel wrapper around the Etherscan.io HTTP API. With 17 stars, zero dependents, and a small surface area, treat the source as the primary documentation: read the README once, then inspect src/, the service provider, the facade, and the package config file before designing around it.
Minimal setup:
composer require slvler/ether
Publish the package config using the tag/provider documented in the README. If no tag is documented, copy the package’s config/*.php file into your app’s config/ directory manually. Then set your Etherscan key:
ETHERSCAN_API_KEY=your-key-from-etherscan.io
Clear cached config after changing env values:
php artisan config:clear
Where to look first:
config/ in the package — determine the exact key name (ether.api_key, etherscan.api_key, or services.etherscan.key).__call, so check docblocks and the underlying client.Http facade or Guzzle directly; this determines how you fake it in tests.composer.json — check Laravel/Illuminate version constraints before adopting it in a Laravel 11/12 app.First realistic use case: show the ETH balance and latest transactions for a watched address in an admin dashboard. Smoke-test in Tinker first:
php artisan tinker
// Inspect the facade/source for the exact method name.
// Typical shape: a balance lookup for a 0x address.
$wei = Ether::balance('0xabc...');
Then move that call into a queued job or application service before wiring it into a controller. Do not call Etherscan directly from a page request in production; the API is external, rate-limited, and occasionally slow.
Wrap the package behind an application-level gateway. Because this is a low-star, low-maintenance dependency, avoid letting its facade leak across controllers and jobs. Create one class that owns all Etherscan calls and returns your own DTOs or arrays:
final class EtherscanGateway
{
public function __construct(private Ether $ether) {}
public function balanceWei(string $address): string
{
// Normalize the package response here.
// Convert wei to decimal outside this method.
}
}
If the package only exposes a facade and not an injectable client, wrap the facade inside this gateway and inject the gateway everywhere else.
Rate-limit at the Laravel layer. Etherscan’s free tier is commonly limited to roughly 5 requests/second. The package may not throttle for you. Use Laravel’s RateLimiter or Redis throttling around every outbound call:
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Cache\RateLimiting\Limit;
RateLimiter::for('etherscan', fn () => Limit::perSecond(4));
Then run calls through RateLimiter::attempt() or a queued job with release()/backoff.
Cache aggressively but briefly. Balances and token metadata are expensive and change frequently. Cache successful responses for short windows:
$balance = Cache::remember(
"ether:balance:{$address}",
now()->addSeconds(30),
fn () => $gateway->balanceWei($address)
);
Never cache error responses; Etherscan returns status: "0" for both errors and empty result sets, so normalize before caching.
Use queued jobs for anything bulk. Address scans, transaction backfills, token transfers, and contract verification checks should run in jobs with retries and backoff. Do not loop over 500 addresses synchronously in a controller.
Test with Http::fake(). If the package uses Laravel’s HTTP client, fake api.etherscan.io/* and assert query parameters such as module, action, and apikey. If it uses Guzzle directly, bind a mocked client into the container or refactor behind your gateway so tests never hit Etherscan.
Http::fake([
'api.etherscan.io/*' => Http::sequence()
->push(['status' => '1', 'result' => '1000000000000000000'])
->push(['status' => '0', 'message' => 'No transactions found', 'result' => []]),
]);
Use scheduler commands for polling. Etherscan has no webhook model for ordinary account activity. For “watched address” features, schedule a command every minute or few minutes, compare the latest block/transaction cursor, and dispatch notifications.
Normalize wei values with bcmath, not floats. Etherscan returns balances and values as strings in wei. A float will lose precision on realistic balances:
$eth = bcdiv($wei, bcpow('10', '18'), 18);
Etherscan returns HTTP 200 for many API errors. Inspect the JSON body: status, message, and result. A status of "0" can mean a real error, a rate limit, or simply “No transactions found.” Handle those cases separately or you will show false failures for empty wallets.
The package may target an older Etherscan API version. The last release was 2024-12-30, and Etherscan has been migrating API versions/endpoints. Verify the exact base URL and endpoint paths in the source. If Etherscan changes or deprecates an endpoint, this package will not automatically adapt. Pin the version and add a live smoke test in staging.
Rate limits are easy to blow through. A page that loops over 20 addresses can exceed the free tier in seconds. Use a queue, a rate limiter, and exponential backoff on "Max rate limit reached" responses. Do not retry immediately on rate-limit errors.
Config key names vary between versions. Check whether the package reads config('ether.api_key'), config('etherscan.api_key'), or Laravel’s services.etherscan.key. If you run php artisan config:cache with the wrong key, calls fail with an authentication error that looks like a bad API key.
Address formatting matters. Validate addresses with a strict 0x[a-fA-F0-9]{40} check. Some endpoints expect checksummed addresses, while others accept lowercase. Normalize consistently at your gateway boundary.
Pagination caps out. Etherscan endpoints typically return at most 10,000 records per query and use page/offset or block ranges. The package may not paginate for you. For wallet history, iterate by block range and persist a cursor.
result is polymorphic. It may be a string, an array, or an empty array depending on the endpoint and outcome. Do not assume result is always an array. Contract ABI responses, for example, are often JSON strings that need json_decode().
Timeout and retry defaults may be too generous or too short. Set an explicit HTTP timeout in your app config and retry only on connection failures, 5xx responses, or rate-limit responses with backoff. Do not blindly retry all failures; Etherscan errors such as invalid address will never succeed.
Maintenance risk is real. Zero dependents and a small star count mean you should be prepared to fork. The MIT license permits it. Keep your integration isolated in one gateway class so a fork or replacement package only requires changing that class.
Extension point: container binding. If the package binds its client to the container, you can swap or decorate it in a service provider. If it only exposes a facade, use your own gateway wrapper rather than calling the facade throughout the codebase.
How can I help you explore Laravel packages today?