blockchain0x

Gem Version License: Apache-2.0 Ruby ≥ 3.0

Official Ruby SDK for Blockchain0x - the non-custodial AI-agent wallet platform on Base.

Pre-release: 0.0.1.alpha.0 ships the operational essentials - HTTP transport, api_keys resource, and Webhooks.verify. The full surface (every resource + x402 client) lands in sub-plan 21.3 Phase C follow-up rows.

Install

gem install blockchain0x --pre

Or in a Gemfile:

gem 'blockchain0x', '~> 0.0.1.alpha'

Requires Ruby 3.0 or newer.

Quick start

require 'blockchain0x'

client = Blockchain0x::Client.new(api_key: ENV.fetch('BLOCKCHAIN0X_API_KEY'))
page = client.api_keys.list
page['data'].each { |k| puts "#{k['id']}\t#{k['prefix']}" }

The client pins the network from the API-key prefix (sk_test_* → testnet, sk_live_* → mainnet). Override with Blockchain0x::Client.new(api_key: ..., network: 'mainnet') when running mixed-mode tests.

Verify webhook signatures

The single most important utility this SDK ships - drop it into the top of your webhook controller BEFORE touching the body.

# Rails example. Adapt for Sinatra / Hanami / Roda - the only
# requirement is the raw request body, NOT a parsed JSON hash.
class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token, only: :receive

  def receive
    result = Blockchain0x::Webhooks.verify(
      headers: request.headers,
      raw_body: request.raw_post,
      secret: ENV.fetch('BLOCKCHAIN0X_WEBHOOK_SECRET'),
    )
    return head(:bad_request) unless result.ok?

    # result.event_type / result.event_id / result.delivery_id populated.
    Sidekiq.redis do |r|
      r.lpush("events:#{result.event_type}", request.raw_post)
    end
    head :no_content
  end
end

The verifier:

  • Reads X-Blockchain0x-Signature in either t=<ts>,v1=<hex> or bare-hex form (some load balancers strip commas).
  • Falls back to X-Blockchain0x-Timestamp when the signature is bare.
  • Rejects with webhook.timestamp_outside_window when drift exceeds 300 seconds.
  • Constant-time compares via OpenSSL.fixed_length_secure_compare (or a manual byte-XOR fallback for older OpenSSL builds).

For exception-based flows pass raise_on_fail: true:

begin
  result = Blockchain0x::Webhooks.verify(
    headers: request.headers,
    raw_body: request.raw_post,
    secret: ENV.fetch('BLOCKCHAIN0X_WEBHOOK_SECRET'),
    raise_on_fail: true,
  )
rescue Blockchain0x::WebhookSignatureError => e
  return render json: { code: e.code }, status: :bad_request
end

Errors

Two classes:

  • Blockchain0x::Error - base class; every SDK error inherits.
  • Blockchain0x::APIKeyError - subclass for HTTP 401 / 403 envelopes whose error.code starts with apikey. (e.g. apikey.scope_insufficient, apikey.wallet_not_assigned).

Always branch on .code, never regex-match .message:

begin
  client.api_keys.list
rescue Blockchain0x::APIKeyError => e
  if e.code == 'apikey.scope_insufficient'
    # mint a fresh key with more scope
  end
end

The module helper Blockchain0x.apikey_error?(error) returns true when the exception's code begins with apikey. regardless of subclass.

Retry behaviour

The transport retries on 429 and 5xx with exponential backoff (0.5s → 1s → 2s → … → 30s cap, 3 retries by default). Retry-After is honoured when the server sends it.

POST / PATCH / DELETE requests carry an Idempotency-Key header - the SDK mints a UUID v4 if you do not supply one. Pass idempotency_key: '...' to thread a stable key across SDK retries OR across processes (e.g. a cron job that hashes its input deterministically).

Workspace keys (sub-plan 21.3)

Two key shapes exist (see docs/concept-api-key-types.md for the full decision tree):

  • Wallet-only - bound to ONE agent. Right shape for an autonomous AI agent that IS one wallet.
  • Workspace - human-operator key that can carry workspace-level scopes AND assignments to N specific wallets.

The Ruby SDK forwards both shapes through client.api_keys. Once the C-2-style create method lands beyond the alpha scaffold:

key = client.api_keys.create(
  label: 'Treasury reconciliation',
  workspace_scopes: ['read_workspace'],
  wallet_assignments: [
    { agent_id: 'agt_trading', scopes: ['read_wallet_metadata'] },
    { agent_id: 'agt_settlement', scopes: ['read_wallet_metadata'] },
  ],
  expires_in_days: 30,
)
puts key['secret']  # shown ONCE

Server-side RBAC: the minter cannot grant a scope they do not have themselves. Over-grants reject with apikey.role_insufficient_for_grants which is surfaced as a Blockchain0x::APIKeyError.

x402 (Phase C-7)

The sibling gem blockchain0x-x402 (Ruby port of @blockchain0x/x402) will ship the x402 client + Rack middleware in sub-plan 21.3 row C-7. The wire format is identical across languages so a Ruby service can accept payments from a Node client and vice-versa.

Codegen

Model classes are generated from apps/backend/openapi/openapi.yaml via openapi-generator-cli with -g ruby --global-property=models,supportingFiles=false - see codegen/README.md for the decision rationale.

Source-of-truth + distribution

Source-of-truth: this directory in Tosh-Labs/blockchain0x-app under packages/sdk-ruby/.

Public mirror: Tosh-Labs/blockchain0x-ruby (receives merges from this directory on dispatch of the mirror-sdk-ruby workflow).

Distribution: rubygems via Trusted Publisher OIDC.

License

Apache-2.0.