Soroban integration
The adapter crate (crates/adapters/soroban) is the boundary between
local deterministic simulation and actual Stellar/Soroban execution:
Crucible Simulator
├── LocalAdapter -> local deterministic environment (fast, default)
└── TestnetAdapter -> Soroban RPC on Stellar Testnet (slow, optional)
The rule from the spec is absolute: the local simulator must never require Testnet access. It doesn’t — the default adapter executes operations through the simulator itself.
The adapter trait
Higher layers depend only on SorobanAdapter, not on any concrete
execution environment:
#![allow(unused)]
fn main() {
pub trait SorobanAdapter {
fn name(&self) -> &'static str; // "local" | "testnet"
fn simulate(&mut self, request: &AdapterRequest) // normalized invocation
-> Result<AdapterResponse, AdapterError>;
}
}
AdapterRequestnormalizes a lifecycle operation into a kind + numeric arguments + optional amount/reference, so callers (tooling, future contracts) do not depend on the simulator’s internal operation types.AdapterResponsereturns the outcome summary, event names, ledger sequence, and resulting state root — everything an outer layer needs.AdapterErrorcarries the structured simulator error code (e.g.insufficient_balance,consumed_commitment) through translation, so callers can branch on machine-readable codes rather than string content.
LocalAdapter — the default
LocalAdapter wraps a Simulator and maps normalized requests onto the
high-level API. It is deterministic by construction and fully covered
end to end: register → deposit → transfer → withdraw through the adapter
produces the expected events, balances, and errors.
TestnetAdapter — the boundary, not the wiring
TestnetAdapter exists today so downstream code can be written against
the interface now and swapped later. Every call returns
AdapterError::Unimplemented — a structured error, not a silent success —
until the Soroban SDK integration lands. Concretely, the roadmap work is:
- Build a real Soroban client (
contract invocation → simulation → response parsing → event extraction) using the shapes inclient.rs(ContractInvocation,ContractResult). - Translate contract responses and RPC errors into
AdapterResponse/AdapterErrorusing the same vocabulary the local adapter uses, so scenario expectations do not change. - Run the testnet suites separately from the deterministic suites (see
docs/testnet.md).
Because both adapters implement the same trait over the same request shapes, switching execution environments later is a configuration change, not a code change.