This document provides a comprehensive overview of Nexum's architecture, component organization, and design patterns.
Nexum is a high-performance Ethereum provider written in Rust and compiled to WebAssembly. It provides both a browser extension and a terminal-based interface (TUI) for interacting with Ethereum networks. The codebase is organized into multiple focused crates that handle different concerns: APDU smart card operations, Keycard integration, core RPC functionality, and extension components.
Key Technologies:
- Language: Rust
- Platforms: WebAssembly (browser), native (terminal)
- License: AGPL-3.0-or-later
- Primary dependencies: Alloy (Ethereum library), jsonrpsee (JSON-RPC), tokio (async runtime)
The project uses a Cargo workspace with the following member organization:
nexum/
├── crates/
│ ├── apdu/ # Smart card APDU protocol layer
│ │ ├── core/ # Core APDU types and traits
│ │ ├── globalplatform/ # GlobalPlatform applet commands
│ │ ├── macros/ # APDU code generation macros
│ │ └── pcsc/ # PC/SC transport (physical card readers)
│ │
│ ├── keycard/ # Keycard (smart card) integration
│ │ ├── keycard/ # Core Keycard protocol and operations
│ │ ├── signer/ # Ethereum transaction signing with Keycard
│ │ └── cli/ # Keycard command-line utility
│ │
│ └── nexum/ # Main Nexum components
│ ├── primitives/ # Shared types and protocols (WASM)
│ ├── rpc/ # JSON-RPC server and namespaces
│ ├── tui/ # Terminal user interface (ratatui)
│ └── extension/ # Browser extension components
│ ├── chrome-sys/ # Chrome API bindings (WASM)
│ ├── worker/ # Service worker (WASM)
│ ├── injector/ # Content script injector (WASM)
│ ├── injected/ # Injected frame script (WASM)
│ └── browser-ui/ # Extension popup UI (Leptos/WASM)
Default members for cargo build: rpc and tui (the CLI applications)
Purpose: Abstraction over Application Protocol Data Unit (APDU) communication with smart cards following ISO/IEC 7816-4 standard.
-
Main types:
ApduCommand: Represents APDU commands (CLA, INS, P1, P2, data, expected length)ApduResponse: Represents card responses with payload and status wordStatusWord: ISO status codes (e.g., 0x9000 = success)
-
Key abstractions:
CardTransport: Trait for different transport mechanisms (PC/SC, mock, etc.)CommandProcessor: Pipeline for transforming commands (e.g., get response handling)Executor: Execute commands with optional secure channel wrappingSecureChannel: Encrypted/MAC'd communication with cardsProcessorPipeline: Chain multiple processors for complex workflows
-
Features:
std(default): Enables error types and features requiring stdlonger_payloads: Support for extended APDU with larger payloads
- Commands for GlobalPlatform-compliant smart cards
- Application lifecycle management (install, delete, select)
- Used to interact with Keycard applets
- Physical connection to smart card readers via PC/SC interface
PcscTransport: Low-level transport implementationPcscDeviceManager: Manages available card readers- Supports event-driven card detection and connection monitoring
- Procedural macros to reduce boilerplate for APDU command builders
- Generates serialization/deserialization code for command structures
Purpose: High-level Ethereum-specific smart card operations using Keycard protocol.
-
Main type:
Keycard<T>- Generic over transport type -
Key functionalities:
- Secure channel establishment and cryptographic operations
- Derive and manage Ethereum keypairs
- Sign transactions and messages with BIP32 hierarchical determinism
- PIN/PUK management for card security
- Pairing for multi-device trust
-
Dependencies on APDU:
- Uses
CardExecutorfrom apdu-core for APDU command execution - Implements secure channel wrapping using ISO 7816-4 mechanisms
- Crypto: AES-128, CBC-MAC for secure channels; PBKDF2 for key derivation
- Uses
- Implements
alloy::signers::Signertrait for seamless integration with Alloy - Provides async transaction signing with Keycard
- Supports EIP-712 typed data signing
- Used by both CLI tools and RPC server when signing is needed
- Utility for direct Keycard operations
- Commands for PIN management, key derivation, transaction signing
- Useful for Keycard initialization and management
-
File structure:
frame.rs:ConnectionState,FrameState- tracks extension connection and chain infoprotocol.rs: Message types for inter-component communication
-
Key types:
ProtocolMessage: Wrapper aroundMessageTypewith "nexum" protocol identifierMessageType: Enum for EthEvent, Request, Response variantsRequestWithId,ResponseWithId: JSON-RPC 2.0 style request/response with IDs- Conversion functions between JsValue and Rust types (via serde-wasm-bindgen)
-
Purpose: Single source of truth for types shared between extension components and with web pages
-
Components:
RpcServerBuilder: Configures and builds the server with chains and providersnamespaces/:eth.rs: Ethereum namespace (account management, signing, sending transactions)net.rs: Network utilitieswallet.rs: Wallet-specific methodsweb3.rs: Web3 utilities
-
Architecture:
- Built on
jsonrpsee(0.24.7) with hyper/tokio for HTTP/WS - Middleware support for custom logic (CallerContext)
- Interactive request handling:
enum InteractiveRequestfor user-facing operations - Fillers pattern from Alloy:
ChainIdFiller,GasFiller,NonceFiller,BlobGasFiller
- Built on
-
Key entry points:
RpcServerBuilder::new()- Create builder.chain(NamedChain, Url)- Add blockchain RPC endpoint.build()- Returns(ServerHandle, Receiver<InteractiveRequest>)- Interactive requests like
EthRequestAccounts,EthSignTransactionare sent to the client for user confirmation
-
Technology: ratatui (TUI framework), crossterm (cross-platform terminal control), tokio
-
Architecture:
- Main event loop:
EventStreamfor keyboard/terminal events - Tab-based UI with multiple views (config, signers, etc.)
- Integration with
RpcServerfor receiving interactive requests nexum_rpc::RpcServerBuildercreates server listening on configurable host:port
- Main event loop:
-
Key components:
Config: TOML-based configuration managementsigners: Account management (Ledger support viaload_ledger_accounts())- Response channel: Sends
InteractiveResponseback to RPC server for request fulfillment - Logging to file (
nxm.log) via custom writer
Browser extension is built with multiple WASM components coordinating via Chrome APIs and message passing.
Web Page
↓
[injected.js] ← frame script (WASM, eip6963/provider)
↓ (message event)
[injector.js] ← content script (WASM, message relay)
↓ (chrome.runtime.sendMessage)
[worker.js] ← service worker (WASM, state/RPC relay)
↓ (chrome.runtime.connect)
[browser-ui/index.html] ← popup panel (Leptos/WASM)
↓
[upstream RPC] (ws://127.0.0.1:1250/...)
-
Modules:
runtime.rs:addOnMessageListener(),sendMessage(),getURL()- extension messagingtabs.rs:query(),get_active_tab(),send_message_to_tab()- tab managementport.rs: Long-lived message ports for popup↔worker communicationalarms.rs: Periodic alarms for status checksidle.rs: Detect browser idle stateaction.rs: Set extension icon/popup
-
Pattern: Thin WASM-bindgen wrappers around Chrome extension APIs, using futures for async operations
-
Entry point:
initialize_extension()- called from manifest's service worker script -
Architecture:
- Builder pattern:
ExtensionBuilder→Extensionstruct - Manages
ExtensionState(tab origins, connection state) - Maintains
Providerfor RPC communication
- Builder pattern:
-
Modules:
provider.rs: JSON-RPC client connection to upstream (hardcoded:ws://127.0.0.1:1250/sepolia)- Methods:
init(),reset(),verify_connection(),on_connect(),on_disconnect() - Monitoring loop with periodic heartbeat
- Methods:
state.rs: Extension state managementevents/: Handles Chrome API eventsruntime.rs: Background script lifecycle eventstabs.rs: Tab activation/removalidle.rs: Browser idle state changesalarm.rs: Periodic status checks
subscription.rs: Event subscription logic
-
Key constants:
EXTENSION_PORT_NAME = "frame_connect"- for popup communicationCLIENT_STATUS_ALARM_KEY = "check-client-status"- periodic heartbeat (0.5 min)UPSTREAM_URL = "ws://127.0.0.1:1250/sepolia"- hardcoded RPC endpoint
-
Entry point:
#[wasm_bindgen(start)] run() -
Responsibilities:
- Runs in content script context (has access to both page and extension)
- Injects
injected.jsas a<script>tag into the page - Sets up event listener for
chrome.runtime.onMessageevents - Relays page → extension messages via
chrome_sys::runtime::send_message() - Relays extension → page messages via
window.postMessage()
-
Flow:
- Listen for
messageevents from page context - Validate payload is
ProtocolMessage - Forward to extension via Chrome runtime
- Receive responses from extension via runtime listener
- Post back to page via
window.postMessage()
- Listen for
-
Entry point:
initialize_provider()- called from script tag -
Responsibilities:
- Runs in page's window context (same origin policy)
- Creates
EthereumProviderinstance - Exposes provider as
window.ethereum - Implements EIP-1193 and EIP-6963 standards
-
Key behaviors:
- Handles existing
window.ethereum(respects configurable property descriptor) - Uses reflection API to safely set properties
- Communicates with injector via
window.postMessage()/ message events
- Handles existing
-
Provider implementation:
eip6963.rs: Announces provider via EIP-6963 eventprovider.rs: EthereumProvider struct implementing RPC method routing
-
Technology: Leptos (reactive web framework), compiled to WASM
-
Entry point:
#[component] App()- root component -
Architecture:
- Leptos signals for reactive state:
active_tab,frame_state,mm_appear,is_injected_tab - Connects to worker via
chrome_sys::runtime::connect()usingEXTENSION_PORT_NAME - Periodic polling (1-second interval) via
send_message_to_tab()to get chain ID
- Leptos signals for reactive state:
-
Modules:
pages/: Page components (Settings page is primary)panels/: Conditional panels (Connected, Not Connected, Unsupported Tab)components/: Reusable UI components (buttons, logos, overlays)helper.rs: Utility functionsconstants.rs: Configuration values
-
State management:
FrameState: Tracks frame connection status and available chains per tab- Communicates with background worker for persistent state
All inter-component communication uses the ProtocolMessage structure (from primitives):
{
"protocol": "nexum",
"message": {
"EthEvent": { "event": "...", "args": [...] } |
"Request": { "id": "...", "method": "...", "params": [...] } |
"Response": { "id": "...", "result": {...} or "error": {...} }
}
}-
DApp → Extension RPC Call:
- Page calls
window.ethereum.request({ method: "eth_accounts" }) - Injected script sends Request via ProtocolMessage
- Injector relays to worker
- Worker sends to upstream RPC (1250)
- Response flows back through same path
- Page calls
-
Provider Status Update:
- Worker checks connection health (alarms, idle detection)
- Emits
EthEventwith connection state change - Browser-UI receives via port message listener
- UI re-renders with new connection state
- Default:
cdylib(dynamic library for WASM) - Profile: Release builds use:
- LTO (Link-Time Optimization)
opt-level = "z"(optimize for size)wasm-opt = falsein wasm-pack config (disables wasm-opt postprocessing)
# Default (RPC + TUI)
cargo build --release
# WASM components
wasm-pack build -t web --release crates/nexum/extension/worker
wasm-pack build -t web --release crates/nexum/extension/injector
wasm-pack build -t web --release crates/nexum/extension/injected
# Note: browser-ui uses leptos-specific build (handled by Leptos tooling)ExtensionBuilderfor Extension initializationRpcServerBuilderfor RPC server configuration- Fluent API for composable configuration
Keycard<T: CardExecutor>allows different transport backends (PC/SC, mock, etc.)Provider<T: CardTransport>in APDU layer- Facilitates testing and swapping implementations
- Tokio runtime for blocking operations
wasm_bindgen_futuresfor WASM-compatible async- Futures-based APIs throughout (
.awaitsyntax)
Result<T>type aliases return customErrorenumthiserrorfor ergonomic error definitions- Error context via
ResultExttrait (eyre integration) - Panic hooks for WASM debugging
Arc<Mutex<T>>for shared mutable state in WASM (async-lock crate)- Atomic types (
AtomicBool) for simple flags - Immutable types for primitives/protocol definitions
tracingcrate for structured loggingwasm-tracingfor WASM console output- Span-based context tracking
- Log output to file in TUI (
nxm.log)
-
TUI:
crates/nexum/tui/src/main.rs- Entrypoint:
#[tokio::main] async fn main() - Initializes ratatui terminal, spawns RPC server, runs event loop
- Entrypoint:
-
RPC Server: Invoked by TUI via
RpcServerBuilder::new().build().await- Listens on configurable host:port (default: 127.0.0.1:1248)
- Serves HTTP and WebSocket JSON-RPC endpoints
-
Keycard CLI:
crates/keycard/cli/src/(CLI utility for Keycard operations)
-
Worker: Called from manifest background script
- Entrypoint:
worker.initialize_extension() - Async initialization, spawns event listeners
- Entrypoint:
-
Injector: Auto-runs on all pages (content script)
- Entrypoint:
#[wasm_bindgen(start)] run() - Injects provider script
- Entrypoint:
-
Injected: Injected into page context
- Entrypoint:
initialize_provider() - Must be called after script loads
- Entrypoint:
-
Browser-UI: Popup panel
- Entrypoint: Leptos App component
- Auto-rendered in popup context
- Located:
crates/nexum/extension/public/manifest.json - Service worker:
worker.js(compiled WASM module) - Content script:
injector.js(compiled WASM module) - Action popup:
index.html(browser-ui output) - Requires Chrome 116+
- Permissions: tabs, activeTab, alarms, idle, scripting
- Host permissions: http:/// and https:///
- TUI: TOML-based config in user config directory (figment + toml)
- Extension: Hardcoded upstream RPC URLs (ws://127.0.0.1:1250/{chain})
- Logging: Configurable via
RUST_LOGenv var (tracing_subscriber)
- APDU tests in
crates/apdu/core/src/lib.rs(lib reexport tests) - Keycard tests in
crates/keycard/keycard/src/(private test modules) - Integration tests in
crates/apdu/pcsc/tests/
wasm-bindgen-testfor WASM unit tests- Tests run via
wasm-pack test --headless --firefox
- RPC server can run standalone:
cargo run -p nexum-rpc -- --host 127.0.0.1 --port 1248 - TUI requires running RPC server on 1250 for upstream
- Extension popup can be tested with Chrome DevTools in extension page
- tokio: Async runtime (full features enabled)
- hyper: HTTP transport
- jsonrpsee: JSON-RPC 2.0 server framework
- alloy: Ethereum library (signers, providers, primitives)
- alloy-chains: Chain ID definitions
- k256: Secp256k1 curve operations
- AES, CBC-MAC: Smart card secure channels
- PBKDF2, SHA2: Key derivation
- coins-bip39, coins-bip32: Hierarchical determinism
- wasm-bindgen: WASM ↔ JS boundary layer
- web-sys: Web platform APIs
- js-sys: JavaScript object access
- gloo-utils: Utility functions for WASM
- leptos: Reactive web framework
- wasm-tracing: Logging bridge
- ratatui: Terminal UI framework
- crossterm: Cross-platform terminal control
- styled: CSS-in-Rust for browser-ui
- serde, serde_json: Serialization framework
- serde-wasm-bindgen: Serde integration for WASM
- Module Organization:
lib.rsfiles typically export submodules;mod.rsin directories - Error Types: Dedicated
error.rsper crate with customErrorenum - Async Functions:
.awaitrequired; futures returned from most functions - WASM Boundaries: All serializable types implement
Serialize/Deserialize - Workspace Inheritance: Dependencies, versions, lints defined in root Cargo.toml
- Logging: Span-based tracing with
#[tracing::span]anddebug!(),info!()macros
- Smart Card Operations: Start at
crates/apdu/core/src/lib.rs - Keycard Integration: Check
crates/keycard/keycard/src/lib.rs - RPC Protocol: Review
crates/nexum/rpc/src/rpc.rs - Extension State: See
crates/nexum/extension/worker/src/state.rs - Type Definitions: Look in
crates/nexum/primitives/src/ - UI Components: Navigate
crates/nexum/extension/browser-ui/src/ - Manifest & Build: Check
crates/nexum/extension/public/manifest.json