Errors & Exceptions

All API errors are surfaced as typed PHP exceptions. Every exception carries the HTTP status code and raw response body.

Exception Class Hierarchy

\RuntimeException (PHP built-in)
└── Web3ApiException — base for all API errors
├── AuthException — HTTP 401 (unrecoverable auth failure)
└── InsufficientCreditsException — HTTP 402 (not enough credits)

All three live in the Web3Sdk\Laravel\Exceptions namespace.

Web3ApiException

The base exception. Thrown for all non-401, non-402 HTTP errors (400, 403, 404, 409, 422, 500, etc.) and for network transport failures.

Properties

PropertyTypeDescription
$messagestringHuman-readable error description (from detail field in API response)
$statusCodeintHTTP status code returned by the API (readonly)
$responseBodyarrayFull decoded JSON response body (readonly)
$previous\Throwable|nullWrapped Guzzle exception on transport errors
<?php
use Web3Sdk\Laravel\Exceptions\Web3ApiException;

try {
    $result = Web3Api::balance()->getNative(address: '0xINVALID');
} catch (Web3ApiException $e) {
    $e->getMessage();      // "Address is not a valid EVM address"
    $e->statusCode;        // 400
    $e->responseBody;      // ['detail' => 'Address is not a valid EVM address']
}

AuthException

Thrown when authentication fails (HTTP 401) and cannot be automatically recovered. Specifically raised when:

ℹ️
If a refresh token is available, the client automatically calls POST /auth/refresh on the first 401. Only if that also fails is AuthException thrown. This means your code typically only sees this exception on genuine session expiry or invalid credentials.

Properties

PropertyTypeDescription
$messagestringAuth failure detail, defaults to "Authentication failed."
$statusCodeintAlways 401
$responseBodyarrayAPI response body
<?php
use Web3Sdk\Laravel\Exceptions\AuthException;
use Web3Sdk\Laravel\Facades\Web3Api;

try {
    $me = Web3Api::auth()->me();
} catch (AuthException $e) {
    // Token expired and refresh failed — force re-login
    session()->forget(['access_token', 'refresh_token']);
    return redirect()->route('login')->with('error', 'Session expired. Please log in again.');
}

InsufficientCreditsException

Thrown when the API returns HTTP 402. The request was not executed — no credits were deducted. The exception parses the detail message to extract the required and available credit counts.

Properties

PropertyTypeDescription
$messagestringFull error message, e.g. "Insufficient credits. Need 10, have 5."
$statusCodeintAlways 402
$costintCredits required by the endpoint (readonly, parsed from message)
$balanceintCurrent credit balance of the account (readonly, parsed from message)
$responseBodyarrayRaw API response body
<?php
use Web3Sdk\Laravel\Exceptions\InsufficientCreditsException;
use Web3Sdk\Laravel\Facades\Web3Api;

try {
    $tx = Web3Api::transfer()->native(
        fromAddress: '0xSENDER...',
        privateKey:  '0xKEY...',
        toAddress:   '0xRECEIVER...',
        amount:      '1.0',
    );
} catch (InsufficientCreditsException $e) {
    $e->cost;      // 10 — this endpoint costs 10 credits
    $e->balance;   // 5  — current account balance
    $e->getMessage(); // "Insufficient credits. Need 10, have 5."

    // Redirect user to top-up page
    return redirect()->route('credits.packages')
        ->with('warning', "You need {$e->cost} credits but only have {$e->balance}.");
}

Full try/catch Pattern

The recommended pattern catches all exception types in order from most specific to most general:

<?php
use Web3Sdk\Laravel\Facades\Web3Api;
use Web3Sdk\Laravel\Exceptions\AuthException;
use Web3Sdk\Laravel\Exceptions\InsufficientCreditsException;
use Web3Sdk\Laravel\Exceptions\Web3ApiException;

try {
    $result = Web3Api::transfer()->token(
        tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
        fromAddress:  '0xSENDER...',
        privateKey:   '0xKEY...',
        toAddress:    '0xRECEIVER...',
        amount:       '100.0',
        decimals:     6,
        network:      'ethereum',
    );

    // Success — $result['tx_hash'] available
    logger()->info('Transfer submitted', ['tx_hash' => $result['tx_hash']]);

} catch (InsufficientCreditsException $e) {
    // HTTP 402 — no credits deducted; prompt user to top up
    logger()->warning('Not enough credits', ['cost' => $e->cost, 'balance' => $e->balance]);
    return response()->json([
        'error' => 'insufficient_credits',
        'needed'  => $e->cost,
        'balance' => $e->balance,
    ], 402);

} catch (AuthException $e) {
    // HTTP 401 — session expired and auto-refresh failed
    logger()->error('Auth failure', ['status' => $e->statusCode]);
    return response()->json(['error' => 'unauthenticated'], 401);

} catch (Web3ApiException $e) {
    // Any other API error (400, 403, 404, 422, 500, transport error)
    logger()->error('Web3 API error', [
        'status'  => $e->statusCode,
        'message' => $e->getMessage(),
        'body'    => $e->responseBody,
    ]);
    return response()->json([
        'error'   => 'api_error',
        'message' => $e->getMessage(),
        'status'  => $e->statusCode,
    ], 500);
}

HTTP Status Code Reference

StatusException ThrownMeaning
200–299none — successRequest succeeded; response returned as array
400Web3ApiExceptionValidation error or malformed request
401AuthExceptionMissing or invalid auth token (after auto-refresh attempt)
402InsufficientCreditsExceptionAccount has insufficient credits for this endpoint
403Web3ApiExceptionForbidden — wrong owner, inactive API key, or insufficient permissions
404Web3ApiExceptionResource not found (wallet, transaction, webhook, etc.)
409Web3ApiExceptionConflict — resource already exists (duplicate email, duplicate key name)
422Web3ApiExceptionPydantic schema validation failed — check required fields
429Web3ApiExceptionRate limit exceeded — wait and retry
500Web3ApiExceptionInternal server error
network errorWeb3ApiExceptionGuzzle transport exception wrapped as $previous
Credits are only deducted on success (HTTP 2xx). If the API returns any error status, no credits are consumed — including 402.