Skip to main content
Both rails are live in sandbox today. Base Sepolia (eip155:84532) and Stellar testnet (stellar:testnet) settle real USDC through the same Sly governance loop. Mainnet for both rails (Base + Stellar pubnet) ships with production access — contact us once you’ve worked through the production access checklist.

What “rail-neutral” actually means

The four Sly primitives — Identity, Governance, Policy, Receipts — work the same on every rail. A receipt minted on Base and a receipt minted on Stellar carry the same identity fields, the same canonical encoding, the same HMAC algorithm, and the same offline verifier. The rail is a setting, not an integration.
   Identity        Governance       Policy         Receipts
       │               │               │              │
       ▼               ▼               ▼              ▼
┌─────────────────────────────────────────────────────────┐
│         Sly rail-neutral protocol surface               │
└─────────────────────────────────────────────────────────┘
       │                                       │
       ▼                                       ▼
  ┌────────┐                              ┌─────────┐
  │  Base  │                              │ Stellar │
  │   L2   │                              │ Soroban │
  └────────┘                              └─────────┘
   EIP-191                                   SEP-10
   USDC                                      USDC
   basescan                                  stellar.expert
The same /v1/agents/:id/... endpoints, the same dashboard surfaces, the same receipt shape. Only chain and the address format differ.

The two production rails

Base (EVM L2)

Coinbase’s L2 on Optimism Stack. Mid-large settlements. EVM ecosystem. ~2s finality. EIP-191 key-control proofs.

Stellar (Soroban)

Stellar Development Foundation’s smart-contract chain. Agent micro-payments. Fee-sponsored settlement. ~5s finality. SEP-10 key-control proofs.

Side by side

PropertyBaseStellar
Sandbox networkeip155:84532 (Base Sepolia)stellar:testnet
Mainnet networkeip155:8453 (Base)stellar:pubnet
x402 SDK@x402/evm@x402/stellar
Settlement latency~2 s~5 s
Per-tx fee~$0.001 gas (paid in ETH)~$0.00001 — fees sponsored by facilitator
USDC contract0x036…CbB4 Sepolia · 0x833…9913 mainnetSAC CBIELTK…DAMA testnet · SAC CCW67TSZ…SJMI75 mainnet
Address format0x + 20 bytes (EIP-55 checksum)G… 56-char base32 (Ed25519 pubkey)
Key-control proofEIP-191 signed challengeSEP-10 web auth challenge
Block explorerbasescan.org / sepolia.basescan.orgstellar.expert
Receipt anchoringEAS (Ethereum Attestation Service)AttestProtocol on Soroban — wired, pending upstream contract init
Fiat off-rampCoinbase, US-centric475K+ MoneyGram locations · Yellowcard · Flutterwave
Best forSettlement size $0.10+, EVM ecosystemSub-cent agent calls, EMEA/Africa fiat edge
Picking a rail by hand? The short rule: Base for mid/large settles in EVM-heavy stacks; Stellar for sub-cent agent micropayments and emerging-markets edges. Better: let selectRail() pick for you.

selectRail() — the decision function

Rail-neutrality at the platform level becomes a single pure function at the code level. selectRail() takes an intent, the tenant’s allow-list, the agent’s allow-list, and current network conditions, intersects them, scores the survivors, and returns the chosen rail plus the human-readable reasons it won.
import { selectRail } from '@sly_ai/sdk';

const decision = selectRail({
  intent: { kind: 'x402.pay', target_currency: 'USDC', max_amount: 0.01 },
  tenant: { allowed_rails: ['stellar:testnet', 'base-sepolia', 'base'] },
  agent:  { allowed_rails: ['stellar:testnet', 'base-sepolia'] },
});

// decision = {
//   chosen: 'stellar:testnet',
//   reasons: ['picked:fee_sponsored', 'picked:cheaper_fee', 'picked:faster_finality'],
//   rejected: []
// }
The reasons land in the receipt’s rail_selection field. The receipt explains itself.
{
  "receipt_id": "rcpt_…",
  "chain": "stellar:testnet",
  "rail_selection": {
    "chosen": "stellar:testnet",
    "reasons": ["picked:fee_sponsored", "picked:cheaper_fee", "picked:faster_finality"],
    "rejected": []
  },

}

The decision tree

intent


intersect tenant.allowed_rails ∩ agent.allowed_rails


filter candidates by intent.target_currency
  │       ┌─ USDC available on both? continue
  │       └─ only one supports it? short-circuit, reason: only_currency_support

score each remaining candidate:
  • +1 if facilitator sponsors fees on this network
  • +1 if known-cheaper per-tx fee for this intent size
  • +1 if known-faster finality
  • -1 if mainnet but tenant lacks production access


pick highest score (deterministic tiebreak by network alphabetical)


return { chosen, reasons, rejected }
Full source: apps/api/src/services/x402/select-rail.ts. Pure function, fully unit-tested, 0 side effects — replayable forever.

Allow-lists — two levers

Every agent’s effective rail allow-list is tenant.allowed_rails ∩ agent.allowed_rails. Two operators can independently dial down (never up) the rails their agents can settle on.
# Tenant-wide allow-list (Sly Operator role required)
PATCH /v1/tenant/allowed-rails
{ "allowed_rails": ["stellar:testnet", "base-sepolia", "base"] }

# Per-agent allow-list (Operator + Owner roles)
PATCH /v1/agents/:id
{ "allowed_rails": ["stellar:testnet"] }
Empty array ([]) means “no restriction” — defaults to platform-wide allow. Both fields render as Allowed Rails chip panels in the dashboard for the relevant scope.

Receipt shape, across rails

Every Sly governed payment — Base or Stellar — produces the same receipt envelope. Only chain, address format, and a couple of optional rail-specific fields vary.
{
  "receipt_id":         "rcpt_…",
  "chain":              "<eip155:84532 | stellar:testnet>",
  "asset":              "USDC",
  "amount":             "0.0100000",
  "from_address":       "<0x… | G…>",
  "to_address":         "<0x… | G…>",
  "agent_id":           "…",
  "agent_name":         "stellar-demo-buyer",
  "agent_kya_tier":     2,
  "agent_chain_proof":  "<sep10 | eip191 | asserted>",
  "agent_custody_provider": "<env_key | oz_soroban | safe_evm>",
  "rail_selection":     { "chosen": "…", "reasons": [...], "rejected": [...] },
  "policy_decision":    { "decision": "approve",  },
  "signature_mode":     "witness-hmac",
  "canonical_encoding": "json-sort-keys-v1",
  "signature":          "…HMAC-SHA256(json-sort-keys-v1)"
}
Every field above is in the canonical encoding. Every field is in the HMAC. Tampering with the rail, the address, the agent, the custody provider, or the decision invalidates the signature on either rail.

Offline verification works the same

The verify-offline.mjs script in examples/stellar-demo/ is rail-agnostic — it re-derives the canonical encoding and HMAC, then byte-compares. Works for both Base and Stellar receipts because the canonical encoding is json-sort-keys-v1 regardless of chain.
node verify-offline.mjs base-receipt.json
# → ✓ SIGNATURE VALID — receipt is verifiable offline.

node verify-offline.mjs stellar-receipt.json
# → ✓ SIGNATURE VALID — receipt is verifiable offline.
This is the layer that lets you audit a Sly settlement without trusting Sly’s API at the moment of audit. Cryptography over wire.

What’s NOT yet at parity

Three places where Base is ahead of Stellar today, by design:
CapabilityBaseStellarStatus
Mainnet settlementShips with production accessShips with production accessContact us
Smart-account custodyERC-4337 (Safe / Pimlico paths) — partialOZ Soroban smart accounts — receipt-side abstraction shipped, on-chain custody contract still being deployedWork in progress
On-chain receipt anchorEAS — productionAttestProtocol — wired but pending upstream contract initWork in progress
All three are coded against an abstraction; flipping one over is a config change, not a rebuild.

Where to go from here

Settling on Base

Full guide — endpoints, EIP-191 binding, witness receipts, dashboard surface, EAS anchoring.

Settling on Stellar

Full guide — endpoints, SEP-10 binding, custody providers, AttestProtocol anchoring, the three identity layers.

x402 protocol

The HTTP 402 micropayment protocol that both rails serve under the same Sly governance loop.

Production access

How to graduate from sandbox (both rails) to live mainnet settlement.