Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Bitcoind Php Laravel Package

nbobtc/bitcoind-php

A PHP client for the Bitcoin Core daemon’s JSON-RPC API, offering a clean object-oriented wrapper around bitcoind commands so applications can query blockchain data, manage wallets, and broadcast transactions with minimal setup.

Deep Wiki
Context7

Getting Started

1. Verify it even installs on your stack. The last tagged release is from May 2015 (PHP 5 / Guzzle 4–5 era). On Laravel 8+ you'll likely hit a Guzzle conflict (guzzlehttp/guzzle ^7 is required by the framework). Options, in order of preference:

  • Use a maintained fork or path repo: "repositories": [{"type": "vcs", "url": "..."}] pointing at a Guzzle 7-compatible fork.
  • Keep the package but replace the transport (see Gotchas) by implementing its ClientInterface on top of Laravel's Http facade.
  • If a fork isn't an option, prefer denpa/php-bitcoind or a hand-rolled JSON-RPC client — the RPC surface is small and stable.

2. Point bitcoind at your app. In bitcoin.conf:

server=1
rpcuser=...          # or rpcauth=
rpcpassword=...
rpcbind=127.0.0.1
rpcallowip=127.0.0.1
# testnet: rpcport=18332, regtest: rpcport=18443

3. Configure and bind it in Laravel.

// config/bitcoind.php
return [
    'user'     => env('BITCOIND_USER'),
    'password' => env('BITCOIND_PASSWORD'),
    'host'     => env('BITCOIND_HOST', '127.0.0.1'),
    'port'     => env('BITCOIND_PORT', 8332),
    'protocol' => env('BITCOIND_PROTOCOL', 'http'),
];
// app/Providers/BitcoinServiceProvider.php
public function register(): void
{
    $this->app->singleton(Bitcoind::class, function ($app) {
        $c = $app['config']->get('bitcoind');

        $config = new Config($c['user'], $c['password'], $c['host'], $c['port']);
        // $config->setProtocol($c['protocol']); // check the vendored Config for exact setters

        return new Bitcoind(new Client($config));
    });
}

4. First call — smoke test from Tinker.

$bitcoind = app(Nbobtc\Bitcoind\Bitcoind::class);

$bitcoind->getblockcount();          // int
$bitcoind->getblockhash(800000);     // string
$bitcoind->getnewaddress('deposit'); // string

Every public method is a magic __call that resolves an RPC command, so method names match bitcoind's RPC names exactly, all lowercase, and arguments are passed positionally as JSON-RPC params. If getblockcount() returns, credentials, ports, and transport are correct.

Implementation Patterns

Wrap it in a domain service. Don't let Bitcoind leak into controllers or jobs — the magic __call gives you zero type safety and no IDE help.

final class Bitcoin
{
    public function __construct(private Bitcoind $bitcoind) {}

    public function height(): int
    {
        return (int) $this->bitcoind->getblockcount();
    }

    public function newDepositAddress(string $label): string
    {
        return $this->bitcoind->getnewaddress($label);
    }

    /** @return string txid */
    public function send(string $address, string $amountBtc): string
    {
        // Pass the amount as a string to avoid float drift before it hits JSON.
        return $this->bitcoind->sendtoaddress($address, $amountBtc);
    }

    public function confirmations(string $txid): int
    {
        $tx = $this->bitcoind->gettransaction($txid); // wallet tx
        return (int) ($tx['confirmations'] ?? 0);
    }
}

Bind both: $this->app->singleton(Bitcoin::class, fn ($app) => new Bitcoin($app->make(Bitcoind::class)));

Reads inline, writes on the queue. getblockcount, getnewaddress, getbalance, listtransactions are cheap and fine in a request or a Blade render. sendtoaddress should always be a queued job so a web timeout doesn't leave you unsure whether the send happened.

final class SendPayout implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $backoff = 30;

    public function __construct(
        public string $address,
        public string $amount,
        public int $payoutId,
    ) {}

    public function handle(Bitcoin $bitcoin): void
    {
        // Guard against retrying an ambiguous prior attempt.
        $payout = Payout::findOrFail($this->payoutId);
        if ($payout->txid) {
            return;
        }

        $txid = $bitcoin->send($this->address, $this->amount);
        $payout->update(['txid' => $txid, 'sent_at' => now()]);
    }
}

Set public bool $failOnTimeout = true and a short $timeout; a hung RPC ties up the worker.

Poll for deposits on a schedule, with a cursor. listtransactions takes (account, count, skip); persist skip so you don't rescan history every minute.

// app/Console/Commands/SyncWallet.php
$skip = Cache::get('btc.skip', 0);
$txs  = $this->bitcoin->bitcoind->listtransactions('*', 100, $skip);

foreach ($txs as $tx) {
    if (($tx['category'] ?? null) === 'receive' && ($tx['confirmations'] ?? 0) >= 3) {
        Deposit::updateOrCreate(['txid' => $tx['txid']], ['amount' => $tx['amount'], 'address' => $tx['address']]);
    }
}
Cache::put('btc.skip', $skip + count($txs), now()->addDay());

Register it in Kernel::schedule() with ->everyMinute()->withoutOverlapping().

Use a decorator around the transport for logging. Wrap the Client/ClientInterface you register in the container and log the command name, params, duration, and outcome (never the credentials). This is what you'll use to debug the "why is production slow" question.

Test with a fake client, not a node. Bitcoind only depends on ClientInterface, so bind a fake in TestCase that returns canned arrays. That's far faster than regtest and keeps CI node-free. Keep one regtest-only integration test for the send → confirm path; on modern Core use generatetoaddress 1 <addr> (the old generate RPC is gone).

Multi-wallet / multi-node. Don't overload the singleton. Build a small factory keyed by node name that constructs Config + Client + Bitcoind per endpoint; this is also the cleanest way to run testnet and regtest side by side.

Gotchas and Tips

  • Unmaintained + Guzzle coupling. This is the single biggest risk. If Composer refuses, don't fight it — implement ClientInterface yourself with Http::withBasicAuth(...)->post($url, $payload) and keep Bitcoind + the command classes. That preserves the API while eliminating the dead dependency. Alternatively fork and bump the Guzzle constraint.
  • PHP 8. Old code can trip on removed behaviors and missing return types. Run a full smoke test (getblockcount, getblock, getrawtransaction, getnewaddress) before assuming it works, and prefer require-pinning if you vendor a fork.
  • The bundled command classes target ~0.9–0.11. Modern Bitcoin Core removed getinfo, getaccount*, listaccounts, sendfrom, move, getaddressesbyaccount, getreceivedbyaccount, setaccount. Expect Method not found on those. Replace with getblockchaininfo, getwalletinfo, getbalances, and label-based getnewaddress. If the package lacks a command class for a new RPC, write one extending the package's base Command (it's a short class exposing the RPC method name and params) — that's the intended extension point.
  • Signature drift on verbose params. getblock's verbosity argument is now an int/bool with different semantics, and getrawtransaction needs txindex=1 (or a block hash) to return arbitrary txids. If you get null or a parse error, log the exact params before blaming the wrapper.
  • Floats are a bug waiting to happen. sendtoaddress takes a BTC amount; do all arithmetic in satoshis with bcmath/integers and convert to a fixed-8-decimal string at the last moment. Never compare $tx['amount'] === 0.01.
  • Ambiguous failures. If the RPC call times out after bitcoind accepted the send, a retry can double-pay. Always record a payout intent before dispatch and reconcile with gettransaction($txid) / listtransactions rather than blindly retrying. failOnTimeout plus an idempotency check is the minimum bar.
  • Error codes matter. bitcoind returns -28 while warming up, -5 for invalid addresses, -6 for insufficient funds, -8 for bad params. Catch the package exception, inspect its code, retry only -28 and connection refused; treat the rest as permanent failures. Add a circuit breaker so a down node doesn't burn every queue worker.
  • Security posture. Bind RPC to 127.0.0.1 or a private network only. Use long random credentials or rpcauth. Never log the RPC URL with embedded creds, and never expose the port. For testnet/regtest this matters less, but keep the same habit.
  • HTTPS / self-signed. If you front bitcoind with a TLS proxy, the underlying HTTP client needs its verify option pointed at the CA bundle (Config has a CA setter — check the vendored source). Don't disable verification.
  • Config cache. Since credentials flow through config/bitcoind.php and .env, remember php artisan config:cache in deploy; a stale cached config pointing at mainnet instead of testnet is an expensive mistake.
  • No batching. The wrapper is one RPC per call. Backfills (scanning 100k blocks) will be slow; for those, hit bitcoind with a raw batched JSON-RPC payload via Http instead of looping the wrapper.
  • Confirmations, not zero-conf. Never credit a balance on a 0-conf receive. Store confirmations on every deposit row and re-check against a configurable threshold, plus a reorg-window (a tx can go from 3 confs back to 0).
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource