Introduction
When building Soroban smart contracts, developers often need to interact with Stellar RPC to query ledger data, fetch contract events, simulate transactions, or submit them to the network. Stellar Lab provides a powerful web based API explorer for this, but as a developer who likes to make my workflow fit my needs, I just need a fast, keyboard driven interface to test methods, explore parameters, and inspect responses right from the terminal.
That's exactly what Stellar TUI provides: a clean terminal user interface for exploring and executing all Stellar RPC methods. Select a method, fill in parameters, hit send, and inspect the JSON response. All without leaving the terminal!
Why Rust? Why Ratatui? Why a TUI?
I chose Rust because it lets me keep improving my skills while building something useful. The strong type system and ownership model help catch errors at compile time, and libraries like tokio and reqwest make async HTTP calls easy. The result is a single binary with no runtime dependencies.
Ratatui had been on my radar for a while, so this project was a good excuse to try it out. It's a solid TUI library with built in components like tables, forms, and modals.
A terminal interface is fast and efficient. Everything happens in the terminal without needing a browser or extra tooling.
Stellar RPC Integration
This is the core of Stellar TUI and explains how the project connects to Stellar's RPC endpoints.
The JSON-RPC 2.0 Client
The HTTP client is built on reqwest and handles sending requests:
// src/rpc/mod.rs
pub struct RpcClient {
endpoint: String,
client: reqwest::Client,
}
impl RpcClient {
pub async fn call<Req, Resp>(
&self,
method: &str,
params: Req,
) -> anyhow::Result<RpcResponse<Resp>>
where
Req: Serialize,
Resp: DeserializeOwned,
{
let request = RpcRequest::new(method, params);
let response = self.client
.post(&self.endpoint)
.json(&request)
.send()
.await?
.error_for_status()?;
let body = response.json::<RpcResponse<Resp>>().await?;
Ok(body)
}
}
This client sends JSON-RPC 2.0 requests over HTTP POST. The generic call method accepts any serializable parameters and deserializes the response into any type needed.
Request and Response Types
The JSON-RPC 2.0 envelope looks like this:
// src/rpc/types.rs
#[derive(Debug, Serialize)]
pub struct RpcRequest<P> {
pub jsonrpc: &'static str, // Always "2.0"
pub id: u64, // Request ID
pub method: String, // e.g., "getEvents"
pub params: P, // Method-specific parameters
}
#[derive(Debug, Deserialize)]
pub struct RpcResponse<T> {
pub jsonrpc: String,
pub id: u64,
pub result: Option<T>, // Success response
pub error: Option<RpcError>, // Error response
}
Method Execution Flow
Pressing r executes a request. Here is what happens:
// src/app/features/runtime.rs
pub fn execute_request(&mut self) {
let method = &self.methods[self.selected_method];
let form = &self.request_forms[self.selected_method];
// Build JSON-RPC params from form input
let params = match method.build_params(form) {
Ok(value) => value,
Err(message) => {
self.set_timed_status(format!("Params error: {}", message), 5);
return;
}
};
let endpoint = self.settings.active_network().endpoint.clone();
// Execute async HTTP call in background
tokio::spawn(async move {
let client = RpcClient::new(endpoint);
let result = client
.call::<Value, Value>(&method_name, params)
.await
.map_err(|e| e.to_string());
// Response handled via channel
});
}
The request runs in the background using tokio::spawn, so the UI keeps working while waiting for the RPC response.
Network Management
Stellar TUI comes with Stellar Testnet set up by default. Pressing n switches between networks, though this shortcut only becomes useful once custom endpoints have been added through the Settings modal (s). Each network's RPC URL is stored in a config.json, so switching between Testnet, Mainnet, Futurenet, or any other custom Stellar RPC endpoint is easy.
Supported RPC Methods
Stellar TUI supports all 12 Stellar RPC methods available in the API.
For simple methods like getHealth, getFeeStats, getLatestLedger, getNetwork, and getVersionInfo, no parameters are required. The getEvents method offers filtering by ledger range, contract IDs, event topics, and event types with pagination support. Methods like getLedgerEntries, getLedgers, getTransaction, and getTransactions provide access to on chain data with cursor based pagination. For transaction operations, both sendTransaction and simulateTransaction handle raw XDR envelopes, with simulation supporting options for instruction leeway and auth modes.
Example: Building getEvents Parameters
The getEvents method demonstrates how complex RPC parameters are handled, filtering by ledger range, contract IDs, event topics, and more:
// src/app/methods/method/get_events.rs
pub fn build(form: &FormState) -> Result<Value, String> {
let start_ledger = parse_optional_u64(form, "startLedger")?;
let end_ledger = parse_optional_u64(form, "endLedger")?;
let cursor = parse_optional_string(form, "cursor");
let limit = parse_optional_u64(form, "limit")?;
let event_type = parse_optional_string(form, "type");
let contract_ids = parse_list(form, "contractIds");
let topics = parse_optional_json(form, "topics")?;
// Validation: contractIds/topics require explicit event type
if (!contract_ids.is_empty() || topics.is_some()) && event_type.is_none() {
return Err("Event type is required when using contractIds or topics".to_string());
}
// Build pagination object
let mut pagination = serde_json::Map::new();
if let Some(value) = limit {
pagination.insert("limit".to_string(), json!(value));
}
if let Some(value) = cursor {
pagination.insert("cursor".to_string(), json!(value));
}
// Build filters for contract events
let mut filters = serde_json::Map::new();
if let Some(value) = event_type {
filters.insert("type".to_string(), json!(value));
}
if !contract_ids.is_empty() {
filters.insert("contractIds".to_string(), json!(contract_ids));
}
// ... more parameter building
}
This shows how the TUI translates user input from forms into properly structured JSON-RPC params.
Installation
Quick Start
- Install using
Cargo:
# Install
cargo install stellar-tui
# Run
stellar-tui
- Install from source:
# Clone and install from the repository
git clone https://github.com/padparadscho/stellar-tui.git
cd stellar-tui
cargo install --path .
# Run
stellar-tui
Workflow
- Select a method: Use arrow keys or mouse wheel to browse the method list.
- Fill parameters: Tab to the request pane, type values.
-
Execute: Press
r/Ctrl+Rto send the request. - Inspect response: View formatted JSON in the response pane.
Essential Keybindings
| Key | Action |
|---|---|
Tab |
Cycle focus between panes |
r / Ctrl+R
|
Execute request |
n / Ctrl+N
|
Switch network |
f / Ctrl+F
|
Toggle fullscreen |
i / Ctrl+I
|
Method documentation |
s / Ctrl+S
|
Settings |
q / Ctrl+Q
|
Quit |
Features
- Three pane layout: Methods list, request form, response viewer.
- Structured request forms with type badges, inline validation, and contextual hints.
- Network management for switching between RPC endpoints.
- Method documentation with links to official Stellar docs.
- Fullscreen toggle for request and response panes.
- Response features: Search (regex), pagination, clipboard copy.
- Responsive layout that adapts to narrow terminals (< 110 columns).
- Mouse support for pane focus, navigation, and scrolling.
Future Development Plans
To get more people using the Stellar TUI and build some interest from potential contributors, here are the features I'm planning to work on next:
- Request and response history with timestamps.
- Bookmarks for frequently used data like contract addresses.
- Network validation for testing connection before adding new endpoints.
- Export and import configuration for sharing settings across machines.
- A simple theming system to set custom palettes.
- Expanded test coverage beyond smoke tests.
- Accessibility improvements.
- Documentation improvements.
Conclusion
Building the Stellar TUI has been a fun side project that helped me learn more about Rust. My goal was to build something for the Stellar developer community and contribute my part to the ecosystem. It has been rewarding to watch the TUI grow from a simple idea into a tool that actually works. There's still plenty of work to do!
If this sounds interesting, give Stellar TUI a try:
cargo install stellar-tui
Questions, suggestions, or contributions? Open an issue on GitHub. Stars are always welcome!




Top comments (0)