sandermuller/laravel-solana-sdk

Laravel wrapper for sandermuller/solana-php-sdk — service provider, facades, env-driven config, and artisan commands for Solana RPC.

Maintainers

Package info

github.com/SanderMuller/laravel-solana-sdk

pkg:composer/sandermuller/laravel-solana-sdk

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

0.1.0 2026-05-13 12:28 UTC

This package is auto-updated.

Last update: 2026-05-13 13:07:29 UTC


README

Latest Version on Packagist Tests PHPStan Total Downloads License

Laravel wrapper around sandermuller/solana-php-sdk — service provider, facades, env-driven config, and artisan commands so you can call Solana RPC from a Laravel app without wiring containers yourself.

use SanderMuller\LaravelSolanaSdk\Facades\Solana;

$balance   = Solana::getBalance('SomeWalletAddressBase58'); // lamports as float
$blockhash = Solana::latestBlockhash();                     // typed BlockhashInfo
$status    = Solana::sendAndConfirmTransaction($tx, [$payer]);

Contents

Requirements

  • PHP ^8.3
  • Laravel ^11.0 || ^12.0 || ^13.0
  • ext-sodium (transitively required by the core SDK)

Install

composer require sandermuller/laravel-solana-sdk

The service provider auto-discovers. Publish the config if you want to customise defaults:

php artisan vendor:publish --tag=solana-sdk-config

Configuration

config/solana-sdk.php exposes four knobs:

return [
    'network'          => env('SOLANA_NETWORK', 'mainnet'),
    'token_program_id' => env('SOLANA_TOKEN_PROGRAM_ID', 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'),
    'transport' => [
        'mode'    => env('SOLANA_TRANSPORT_MODE', 'fallback'),
        'urls'    => array_values(array_filter([
            env('SOLANA_RPC_URL'),
            env('SOLANA_RPC_URL_FALLBACK'),
        ])),
        'headers' => [],
        'timeout' => (float) env('SOLANA_RPC_TIMEOUT', 30.0),
        'retry'   => [
            'max_attempts'  => (int) env('SOLANA_RPC_RETRY_ATTEMPTS', 3),
            'base_delay_ms' => (int) env('SOLANA_RPC_RETRY_BASE_MS', 100),
            'max_delay_ms'  => (int) env('SOLANA_RPC_RETRY_MAX_MS', 2_000),
        ],
    ],
    'commands' => [
        'enabled' => env('SOLANA_COMMANDS_ENABLED', true),
    ],
];

SOLANA_NETWORK accepts mainnet, mainnet-beta, testnet, devnet (also main, test, dev). The RPC endpoint is derived from the Network enum in the core SDK.

Facades

use SanderMuller\LaravelSolanaSdk\Facades\Solana;
use SanderMuller\LaravelSolanaSdk\Facades\SolanaRpc;

$balance   = Solana::getBalance('SomeWalletAddressBase58');
$info      = Solana::accountInfo('SomeWalletAddressBase58'); // typed AccountInfo
$blockhash = Solana::latestBlockhash();                      // typed BlockhashInfo

// Send + poll until confirmed in a single call:
$status = Solana::sendAndConfirmTransaction($tx, [$payer]);

// Low-level JSON-RPC escape hatch
$result = SolanaRpc::call('getSlot');

Solana proxies the typed Connection API (~60 RPC methods covering ~80 % of the Solana JSON-RPC spec — accounts, blocks, slots, signatures, tokens, supply, stake, vote, inflation). SolanaRpc proxies the raw SolanaRpcClient for calls the typed facade does not yet cover.

Both bindings are also reachable via plain DI:

use SanderMuller\SolanaPhpSdk\Connection;

class WalletController
{
    public function show(Connection $solana, string $address): array
    {
        return ['balance' => $solana->getBalance($address)];
    }
}

Programs, builders, signers

The core SDK ships first-class program builders (SystemProgram, SplTokenProgram, Token2022Program, MemoProgram, StakeProgram, VoteProgram, AddressLookupTableProgram, ComputeBudgetProgram, MetaplexProgram, AnchorIdl, …), a sanitize-safe TransactionBuilder, a Util\PriorityFee helper, and a Contracts\MessageSigner interface (with Signing\InMemoryMessageSigner as the local adapter). Use them directly — no wrapping required:

use SanderMuller\SolanaPhpSdk\TransactionBuilder;
use SanderMuller\SolanaPhpSdk\Programs\SystemProgram;
use SanderMuller\LaravelSolanaSdk\Facades\Solana;

$blockhash = Solana::latestBlockhash();

$tx = TransactionBuilder::make()
    ->feePayer($payer->publicKey)
    ->recentBlockhash($blockhash)
    ->addInstruction(SystemProgram::transfer($payer->publicKey, $to, $lamports))
    ->addSigner($payer)
    ->build();

$status = Solana::sendAndConfirmTransaction($tx, [$payer]);

Multi-endpoint transport

Set SOLANA_RPC_URL (and optionally SOLANA_RPC_URL_FALLBACK) to route through your own RPC provider with automatic retry + fallback. Leave both unset to keep the public-endpoint default.

SOLANA_RPC_URL=https://mainnet.helius-rpc.com/?api-key=…
SOLANA_RPC_URL_FALLBACK=https://api.mainnet-beta.solana.com
SOLANA_TRANSPORT_MODE=fallback        # or round_robin
SOLANA_RPC_TIMEOUT=30
SOLANA_RPC_RETRY_ATTEMPTS=3

For more elaborate setups (custom auth headers, multiple fallbacks) publish the config and edit transport directly — the wrapper hands the array straight to SanderMuller\SolanaPhpSdk\Rpc\TransportFactory.

Confirming transactions on the queue

The core SDK ships SanderMuller\SolanaPhpSdk\Queue\ConfirmTransactionJob out of the box. Dispatch it after sendTransaction so the long-tail confirmation phase becomes a background job that fires TransactionConfirmed / TransactionExpired events:

use SanderMuller\LaravelSolanaSdk\Facades\Solana;
use SanderMuller\SolanaPhpSdk\Queue\ConfirmTransactionJob;

$blockhash = Solana::latestBlockhash();
$signature = Solana::sendTransaction($tx, [$payer]);

ConfirmTransactionJob::dispatch(
    signature: $signature,
    lastValidBlockHeight: $blockhash->lastValidBlockHeight,
    context: ['order_id' => $order->id],
);

Listen for SanderMuller\SolanaPhpSdk\Events\TransactionConfirmed and TransactionExpired in EventServiceProvider.

PubSub / WebSocket

SolanaPubSubClient is bound transient against the configured network, so you can typehint it directly:

use SanderMuller\SolanaPhpSdk\Services\SolanaPubSubClient;

class WatchSignatures
{
    public function handle(SolanaPubSubClient $pubsub, string $signature): void
    {
        $pubsub->enableAutoReconnect();
        $pubsub->signatureSubscribe($signature);

        foreach ($pubsub->listen() as $event) {
            // …
        }
    }
}

Artisan commands

Command Purpose
solana:balance {address} Print SOL balance + lamports
solana:airdrop {address} {sol=1} Request devnet/testnet airdrop
solana:account {address} Dump raw account info JSON
solana:tx {signature} Look up a transaction by signature
solana:health RPC health + version
solana:tokens {owner} List SPL token accounts owned by an address
solana:fees {addresses?*} Recent prioritization fee samples

Disable all bundled commands with SOLANA_COMMANDS_ENABLED=false (they hit live RPC; production may want them off).

Testing

composer test

Stubbing RPC in tests

Call Solana::fake() to swap the bound SolanaRpcClient for the core SDK's InMemoryRpcStub so the SDK never hits the network:

use SanderMuller\LaravelSolanaSdk\Facades\Solana;

it('reads the balance', function (): void {
    Solana::fake()->script([
        'getBalance' => ['value' => 5_000_000],
    ]);

    expect(Solana::getBalance($address))->toBe(5_000_000.0);
    expect(Solana::fakedStub()?->methodCalls())->toContain('getBalance');
});

Wire the core Pest expectations (toBeConfirmed, toHaveCustomCode, toBeInstructionError) once from tests/Pest.php:

use SanderMuller\SolanaPhpSdk\Testing\PestExpectations;

PestExpectations::register();

The wrapper's own test suite runs against Orchestra Testbench with the package provider auto-registered. Network-dependent tests are not shipped — facade unit tests just verify resolution + container shape.

Upgrading

See UPGRADING.md.

Changelog

See CHANGELOG.md. Updated automatically on release publish.

Contributing

PRs welcome. Run the local gauntlet before opening one:

vendor/bin/pest          # tests
vendor/bin/pint --test   # style
vendor/bin/phpstan       # static analysis
vendor/bin/rector --dry-run

The package is intentionally a thin wrapper — net-new RPC methods, program builders, and DTOs belong in sandermuller/solana-php-sdk. The wrapper only adds Laravel glue (@method lines on the Solana facade, env-driven config keys, container bindings, solana:* commands). See CLAUDE.md for the full scope rules.

Security

See SECURITY.md.

Credits

License

The MIT License (MIT). See LICENSE.