Solana Settlement
Configure and operate automated usage billing and SPL token settlement between consumers and providers.
OpenTela can automatically settle usage bills between consumers and providers using the OTELA SPL token on Solana. When a head node dispatches requests to worker nodes, both sides independently track usage metrics (tokens generated, GPU milliseconds consumed, etc.). Once usage crosses a configurable threshold, the head node reconciles the measurements, calculates the amount owed, and submits an SPL token transfer on-chain.
This document explains how to configure and operate the settlement system.
Overview
The settlement flow works as follows:
- A consumer sends requests through a head node to one or more provider worker nodes.
- Both the head node and worker node independently extract usage metrics from HTTP response headers (
X-Usage-Tokens,X-Usage-GPU-Ms, etc.). - Usage records accumulate locally until a threshold is reached (value or time).
- The head node reconciles its measurements against the worker's via CRDT-shared aggregates.
- If both sides agree (within a configurable tolerance), the head node looks up the provider's rate, calculates the payment, and submits an SPL token transfer to the provider's wallet.
- The transaction is confirmed on-chain, and a payment record is stored in the CRDT to prevent duplicate billing.
Consumer Wallet ──(OTELA tokens)──> Provider Wallet
↑ ↑
│ │
Head Node (Payment Processor)
│
┌───┴────┐
│ 1. Aggregate Usage
│ 2. Reconcile (head vs worker)
│ 3. Lookup Provider Rate
│ 4. Calculate Amount
│ 5. Build SPL Transfer Tx
│ 6. Sign with Consumer Key
│ 7. Submit to Solana
└──────────────────────────Prerequisites
- OpenTela binary installed (see Installation)
- A Solana wallet with OTELA tokens (see Wallet & Ownership)
- The provider (worker node) must also have a Solana wallet
- Access to a Solana RPC endpoint (mainnet, devnet, or testnet)
Step 1: Enable billing
Edit ~/.config/opentela/cfg.yaml on the head node to enable the billing system:
billing:
enabled: true
value_threshold: 10000000 # trigger settlement after this many usage units
max_interval_minutes: 60 # or after this many minutes, whichever comes first
dispute_threshold_pct: 10 # flag if head/worker measurements differ by >10%You can also enable billing via environment variable:
export OF_BILLING_ENABLED=true| Config key | Env var | Default | Description |
|---|---|---|---|
billing.enabled | OF_BILLING_ENABLED | false | Enable the settlement system |
billing.value_threshold | OF_BILLING_VALUE_THRESHOLD | 10000000 | Usage value that triggers a settlement |
billing.max_interval_minutes | OF_BILLING_MAX_INTERVAL_MINUTES | 60 | Maximum time between settlements |
billing.dispute_threshold_pct | OF_BILLING_DISPUTE_THRESHOLD_PCT | 10 | Percentage difference that flags a dispute |
Step 2: Configure the OTELA token and Solana RPC
The settlement system uses the SPL token mint configured under solana.mint. The default points to the OTELA token:
solana:
rpc: "https://api.mainnet-beta.solana.com" # or https://api.devnet.solana.com for testing
mint: "EsmcTrdLkFqV3mv4CjLF3AmCx132ixfFSYYRWD78cDzR" # OTELA SPL token mint
skip_verification: falseFor testing, use the devnet endpoint. For production, use a mainnet-beta endpoint or a dedicated RPC provider.
Step 4: Set up provider rates
Providers set their own prices per service and metric. Rates are loaded from a YAML file:
# ~/.config/opentela/rates.yaml (or wherever rates.config_path points)
providers:
- address: "ProviderSolanaAddress1"
services:
- name: "llm"
metrics:
- name: "tokens"
price_per_1000: 1000 # 1000 base units per 1000 tokens
- name: "gpu_ms"
price_per_1000: 500 # 500 base units per 1000 ms of GPU timePoint the config to this file:
rates:
default_per_1000_tokens: 1000 # fallback if provider has no explicit rate
default_per_gpu_ms: 1 # fallback for GPU time
config_path: "/home/user/.config/opentela/rates.yaml"If a provider has no explicit rate configured, the system falls back to the default_per_1000_tokens or default_per_gpu_ms values.
Step 5: Fund the consumer wallet
The head node's wallet (the consumer) needs OTELA tokens to pay providers. Check the balance:
./otela wallet balanceWallet: 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CerBTpfXoA4
RPC: https://api.devnet.solana.com
SOL balance: 1.500000000 SOL
Token (EsmcTrdLkFqV3mv4CjLF3AmCx132ixfFSYYRWD78cDzR): 50000 (50000.000000000)The consumer wallet also needs a small amount of SOL for transaction fees (~0.000005 SOL per transfer) and potentially for creating Associated Token Accounts (~0.002 SOL per new provider).
For devnet testing, you can request SOL from the faucet:
./otela wallet airdrop --solana.rpc https://api.devnet.solana.com 2Step 6: Start the nodes
Start the head node with billing enabled:
./otela start --mode standalone --public-addr {YOUR_IP} --seed 0Start worker nodes as usual (see Spin Up). The billing system operates transparently — worker nodes do not need any special configuration beyond having a wallet.
Once requests flow through the network, you will see settlement logs on the head node:
INFO settlement: payment confirmed sig=3Kf8x... amount=5000 to=ProviderAddress...How usage tracking works
Services report usage through HTTP response headers. When a worker node proxies a request to a local service (e.g., vLLM), OpenTela reads the response headers:
X-Usage-Tokens: 1234
X-Usage-GPU-Ms: 5000Both the head node (which receives the proxied response) and the worker node (which made the local request) independently extract and store these metrics. This dual-attestation design ensures neither side can unilaterally inflate or deflate usage.
Aggregation
Usage records accumulate in local buckets keyed by (peer_id, service, metric_name). A settlement is triggered when either condition is met:
- The accumulated value exceeds
billing.value_threshold - The time since the last settlement exceeds
billing.max_interval_minutes
Reconciliation
Before payment, the head node's aggregate is compared with the worker node's aggregate (shared via CRDT). If the measurements differ by more than billing.dispute_threshold_pct, the record is flagged as disputed and no payment is made. Disputed records are logged for manual review.
If the difference is within tolerance, the resolved value is the average of both measurements.
Error handling
The settlement system handles common failure scenarios:
| Scenario | Behavior |
|---|---|
| Insufficient OTELA balance | Pre-flight check fails, no transactions submitted |
| Provider has no token account | ATA created automatically (costs ~0.002 SOL) |
| Transaction fails on-chain | Retried up to 3 times with exponential backoff |
| Transaction not confirmed | Polls for up to 60 seconds before timing out |
| Disputed usage (>threshold) | Skipped, logged for review |
| Provider rate not found | Falls back to default platform rate |
Idempotency
Payment records are stored by transaction signature in the CRDT under /billing/payments/{signature}. Before submitting a new payment, the system checks for existing records to prevent double-billing.
Testing on devnet
For end-to-end testing without real funds:
- Point
solana.rpctohttps://api.devnet.solana.com - Request SOL from the faucet:
./otela wallet airdrop 2 - Deploy or use an existing test SPL token on devnet
- Update
solana.mintto the test token's mint - Start the cluster and send requests — settlements will appear in the logs
Full configuration reference
# Billing and settlement
billing:
enabled: true
value_threshold: 10000000
max_interval_minutes: 60
dispute_threshold_pct: 10
# Solana connection and token
solana:
rpc: "https://api.devnet.solana.com"
mint: "EsmcTrdLkFqV3mv4CjLF3AmCx132ixfFSYYRWD78cDzR"
skip_verification: false
# Provider rates
rates:
default_per_1000_tokens: 1000
default_per_gpu_ms: 1
config_path: "/etc/opentela/rates.yaml"