Division of Responsibilities Between the tinywallet-bus Contract and the Host
The tinywallet-bus contract defines static vocabulary and pure validation logic, while the OpenHuman host handles all runtime network operations, endpoint resolution, and policy enforcement.
The tinywallet-bus crate in the tinyhumansai/openhuman repository implements a wire contract pattern that strictly separates portable domain logic from heavy infrastructure dependencies. This division of responsibilities between the tinywallet-bus contract and the host ensures that modules remain lightweight and compilable without pulling in networking crates, while the host retains full control over transport policies, secret handling, and fail-over semantics.
Core Architectural Split: Contract vs. Host
The architecture follows a strict "what" versus "how" separation. The contract defines data shapes and pure functions, while the host provides the runtime environment that executes I/O operations.
- Contract side (
tinywallet-bus): Contains only static definitions, constants, and pure logic. It compiles withouttokio,reqwest, or cryptographic networking libraries. - Host side (OpenHuman core): Implements the
Transporttrait, resolves RPC endpoints, handles retries, and manages secrets. This is where all async I/O and network-specific error handling occurs.
This split allows the tinywallet-bus crate (located at vendor/tinywallet/crates/tinywallet-bus) to serve as a domain vocabulary that any TinyWallet module can import without bloating its dependency tree.
Responsibilities of the tinywallet-bus Contract
Static Vocabulary and Constants
The contract houses all RPC method identifiers and member constants used across the TinyWallet domain. These are defined in the methods module and other constant tables, ensuring consistent naming between modules and hosts. As documented in AGENTS.md (lines 263-267), this vocabulary is shared with any TinyWallet module through the contract crate.
Serializable Request and Response Types
All EIP-712 payloads, ERC-20 calldata definitions, and address validation structs live in the contract. These types are serializable and provide the data schemas that the host will eventually transmit over the wire. They define the shape of network requests without performing any actual network operations.
Pure Validation Helpers
The contract provides I/O-free validation functions that check address formats and data integrity. For example, Bitcoin address validation resides in tinywallet_bus::address::btc::validate, which performs P2WPKH format checks without external dependencies. Host code in src/openhuman/web3/wallet/chains/btc.rs (lines 63-84) invokes these pure functions to validate inputs before network transmission.
Responsibilities of the OpenHuman Host
Transport Abstraction and Network Handling
The host implements the Transport trait defined in the contract, providing the concrete OpenHumanTransport struct in src/openhuman/web3/wallet/transport.rs. This implementation maps NetworkId values to HTTP endpoints, executes JSON-RPC calls, and manages the shared HTTP client. Unlike the contract, the host code is fully async and depends on heavy crates like reqwest and tokio.
Endpoint Resolution and Failover Logic
When a module requests a network operation, the host performs endpoint resolution through the resolve function and rpc_url_for_* helpers. It applies environment overrides, selects fallback Solana endpoints, and manages the fail-over chain. This runtime decision-making stays entirely within the host's transport.rs module, keeping the contract agnostic of specific RPC URLs or deployment environments.
Security and Error Classification
The host handles URL redaction via the redact_rpc_url helper to prevent API keys from appearing in logs. It also classifies transport failures through the classify function, distinguishing between retryable network errors and authoritative RPC failures. This classification feeds into OpenHuman's retry semantics and fail-over logic, ensuring robust error handling that remains invisible to the contract layer.
Implementation Examples
The following examples demonstrate how the contract and host interact in practice.
First, the contract provides pure address validation:
// Contract side: tinywallet-bus
use tinywallet_bus::address::btc;
// Returns Ok(addr) if valid main-net P2WPKH
pub fn validate_btc_address(addr: &str) -> Result<String, String> {
tinywallet_bus::address::btc::validate(addr)
.map_err(|e| e.to_string())
}
The host implements the transport interface that actually performs the network call:
// Host side: src/openhuman/web3/wallet/transport.rs
use tinywallet_bus::rpc::{NetworkId, Transport, TransportResult};
#[derive(Debug, Clone, Copy, Default)]
pub struct OpenHumanTransport;
#[async_trait::async_trait]
impl Transport for OpenHumanTransport {
async fn json_rpc(
&self,
network: NetworkId,
method: &str,
params: serde_json::Value,
) -> TransportResult<serde_json::Value> {
// Resolve concrete RPC URL
let url = resolve(network)?;
// Perform HTTP call using OpenHuman's client
rpc_call_to::<Value>(&url, method, params)
.await
.map_err(|msg| classify(network, msg))
}
}
Finally, host code combines both layers—using contract validation before host transport:
// Host side: using contract and transport together
let addr = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8c9".to_string();
let validated = validate_btc_address(&addr)?; // contract validation
let network = tinywallet_bus::NetworkId::chain(tinywallet_bus::Chain::Btc);
let transport = OpenHumanTransport::default();
let result = transport
.json_rpc(network, "getBalance", json!({ "address": validated }))
.await?;
Summary
- tinywallet-bus acts as the wire contract, containing only static vocabulary, serializable types, and pure validation logic without I/O dependencies.
- OpenHuman host implements all runtime logic including the
Transporttrait, endpoint resolution, URL redaction, and error classification insrc/openhuman/web3/wallet/transport.rs. - Host code delegates to contract functions for input validation (e.g.,
tinywallet_bus::address::btc::validate) before performing network operations. - This separation allows modules to compile without heavy networking crates (
reqwest,tokio, crypto libraries), while the host retains full control over network policies and secret handling. - Transaction assembly and broadcasting occur in host modules like
src/openhuman/web3/wallet/ops.rsandsrc/openhuman/web3/wallet/chains/, utilizing contract-defined data structures.
Frequently Asked Questions
What is the purpose of the tinywallet-bus contract?
The tinywallet-bus contract serves as a portable domain vocabulary that defines RPC method names, request/response types, and pure validation helpers. It allows TinyWallet modules to share consistent data structures without importing heavy networking or cryptographic dependencies, ensuring the module remains lightweight and compilable in constrained environments.
How does the host implement the Transport trait?
The host provides the OpenHumanTransport struct in src/openhuman/web3/wallet/transport.rs that implements the Transport trait defined in the contract. This implementation handles concrete HTTP operations, endpoint resolution via the resolve function, and error classification through the classify function, bridging the contract's abstract network interface with OpenHuman's actual RPC infrastructure.
Why separate pure validation from network operations?
Separating pure validation from I/O allows the contract to perform address format checks and data validation without depending on async runtimes or network libraries. This design keeps the contract crate dependency-free and portable, while the host handles the mutable, async, and fallible aspects of network communication separately.
Where is endpoint resolution logic located?
Endpoint resolution and fail-over logic reside entirely within the host codebase, specifically in src/openhuman/web3/wallet/transport.rs. The resolve function and rpc_url_for_* helpers determine the correct RPC URL for a given NetworkId, apply environment-specific overrides, and manage fallback endpoints, keeping the contract layer agnostic of deployment-specific configuration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →