DiceRPC is a minimal yet powerful JSON-RPC 2.0 framework built in Rust. It allows clients and servers to communicate over HTTP or TCP using a simple request-response model, similar to how Ethereum's eth_call, eth_sendRawTransaction, and other RPC methods work.
📖 Read the Complete Guide — Detailed architecture, implementation walkthrough, and design decisions explained.
- Features
- Tech Stack
- Getting Started
- Usage Examples
- Available Handlers
- Architecture
- Feature Flags
- Testing
- Production Features
- Roadmap
- Contributing
- Resources
- License
- JSON-RPC 2.0 compliant — Full specification with
id,method,params, anderrorhandling - Multi-transport support — HTTP (Axum) and TCP with length-prefixed framing
- Built-in authentication — API key validation with pluggable strategies
- Batch request processing — Handle multiple requests concurrently
- Persistent state — In-memory store for accounts and transactions
- Metrics & observability — Request tracking, tracing, and performance monitoring
- Graceful shutdown — Signal handling (SIGTERM/SIGINT) with proper cleanup
- Custom handlers — Easy registration of your own RPC methods
- CLI client included — Test your server directly from the terminal
- Extensible architecture — Modular design for easy customization
- Rust 1.70+
- Tokio — Async runtime for concurrent request handling
- Serde & serde_json — Fast and safe JSON serialization
- Axum — Modern HTTP framework (optional with
httpfeature) - Futures — Concurrent batch request processing
- Tracing — Structured logging and diagnostics
- Anyhow — Flexible error handling
- Rust 1.70 or higher (Install from rustup.rs)
- Git
# Clone the repository
git clone https://github.com/dicethedev/DiceRPC.git
cd DiceRPC
# Build with TCP support (default)
cargo build --release
# Or build with all features (TCP + HTTP)
cargo build --release --features fullStart the server:
# Start TCP server on default port 4000
cargo run --release -- server
# Or specify a custom address
cargo run --release -- server --addr 127.0.0.1:5000Make requests from another terminal:
# Simple ping
cargo run --release -- client --method ping
# Get balance
cargo run --release -- client \
--method get_balance \
--params '{"address":"0x123abc"}'
# Send transaction
cargo run --release -- client \
--method send_tx \
--params '{"raw_tx":"0xdeadbeef"}'Build with HTTP support and run:
# Build with HTTP feature
cargo build --release --features http
# Run HTTP server (requires modifying main.rs or using examples)
TRANSPORT=http cargo run --releaseTest with curl:
curl -X POST http://localhost:3000/rpc \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "ping",
"params": {},
"id": 1
}'use dice_rpc::*;
use std::sync::Arc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create RPC server
let server = Arc::new(RpcServer::new());
// Register default handlers (ping, get_balance, send_tx)
register_default_handlers(&server).await;
// Configure and start TCP server
let config = TcpServerConfig::new("127.0.0.1:4000", server);
run_with_framing(config).await?;
Ok(())
}use dice_rpc::*;
use std::sync::Arc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let server = Arc::new(RpcServer::new());
register_default_handlers(&server).await;
// Setup authentication middleware
let auth = Arc::new(AuthMiddleware::new(AuthStrategy::ApiKeyInParams));
auth.add_key("my-secret-key-123").await;
auth.add_key("another-key-456").await;
// Start HTTP server with authentication
HttpTransport::new(server)
.with_auth(auth)
.serve("127.0.0.1:3000")
.await?;
Ok(())
}Test authenticated request:
curl -X POST http://localhost:3000/rpc \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "ping",
"params": {"api_key": "my-secret-key-123"},
"id": 1
}'use dice_rpc::*;
use serde_json::json;
use std::sync::Arc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let server = Arc::new(RpcServer::new());
let state = Arc::new(StateStore::new());
// Register a custom handler
let state_clone = state.clone();
server.register("get_user", move |params| {
let state = state_clone.clone();
async move {
let user_id = params["user_id"]
.as_str()
.ok_or_else(|| RpcErrorObj {
code: INVALID_PARAMS,
message: "Missing user_id parameter".into(),
data: None,
})?;
// Your business logic here
Ok(json!({
"user_id": user_id,
"name": "Alice",
"balance": 1000
}))
}
}).await;
// Start server
let config = TcpServerConfig::new("127.0.0.1:4000", server);
run_with_framing(config).await?;
Ok(())
}Send multiple requests at once:
# Using curl with HTTP transport
curl -X POST http://localhost:3000/rpc \
-H "Content-Type: application/json" \
-d '[
{
"jsonrpc": "2.0",
"method": "ping",
"params": {},
"id": 1
},
{
"jsonrpc": "2.0",
"method": "get_balance",
"params": {"address": "0xAlice"},
"id": 2
},
{
"jsonrpc": "2.0",
"method": "get_balance",
"params": {"address": "0xBob"},
"id": 3
}
]'use dice_rpc::*;
use std::sync::Arc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Initialize logging
metrics::init_logging();
// Create components
let server = Arc::new(RpcServer::new());
let state = Arc::new(StateStore::new());
let metrics = Arc::new(Metrics::new());
// Register stateful handlers
handlers::register_stateful_handlers(&server, state.clone()).await;
// Setup authentication
let auth = Arc::new(AuthMiddleware::new(AuthStrategy::ApiKeyInParams));
auth.add_key("dev-key-12345").await;
// Start TCP server with all features
let config = TcpServerConfig::new("127.0.0.1:4000", server)
.with_auth(auth)
.with_metrics(metrics);
run_with_framing(config).await?;
Ok(())
}DiceRPC comes with built-in handlers for common operations:
| Method | Description | Parameters | Example |
|---|---|---|---|
ping |
Health check | None | {"method": "ping", "params": {}} |
get_balance |
Get account balance | {"address": "0x..."} |
{"method": "get_balance", "params": {"address": "0xAlice"}} |
set_balance |
Set account balance (admin) | {"address": "0x...", "balance": 1000} |
{"method": "set_balance", "params": {"address": "0xAlice", "balance": 5000}} |
send_tx |
Submit transaction | {"raw_tx": "0x..."} |
{"method": "send_tx", "params": {"raw_tx": "0xdeadbeef"}} |
transfer |
Transfer funds | {"from": "0x...", "to": "0x...", "amount": 100} |
{"method": "transfer", "params": {"from": "0xAlice", "to": "0xBob", "amount": 500}} |
get_transaction |
Get transaction by ID | {"txid": "uuid"} |
{"method": "get_transaction", "params": {"txid": "abc-123"}} |
confirm_transaction |
Confirm pending tx | {"txid": "uuid"} |
{"method": "confirm_transaction", "params": {"txid": "abc-123"}} |
get_transactions |
Get txs for address | {"address": "0x..."} |
{"method": "get_transactions", "params": {"address": "0xAlice"}} |
list_accounts |
List all accounts | None | {"method": "list_accounts", "params": {}} |
DiceRPC uses a modular, layered architecture:
src/
├── rpc.rs # Core RPC server and handler registry
├── state.rs # In-memory state store (accounts & transactions)
├── transport/ # Transport layer
│ ├── tcp.rs # TCP with length-prefixed framing
│ ├── http_transport.rs # HTTP via Axum
│ ├── framing.rs # Binary framing protocol
│ └── shutdown.rs # Graceful shutdown coordinator
├── middleware/ # Middleware layer
│ └── auth.rs # Authentication strategies
├── server/ # Server implementations
│ ├── handlers.rs # Business logic handlers
│ ├── metrics.rs # Request metrics & tracing
│ └── server.rs # Basic TCP server
├── util/ # Utilities
│ └── batch.rs # Batch request handling
├── client/ # CLI client
│ └── client.rs # Command-line client
└── macros.rs # Helper macros
- Handler Registry Pattern — Dynamic method registration
- Middleware Chain — Authentication, metrics, etc.
- Transport Abstraction — Easy to add new protocols
- State Management — Thread-safe with
Arc<RwLock<>> - Graceful Shutdown — Proper cleanup on signals
For detailed architecture diagrams and flow charts, see the complete guide.
Build with specific features to minimize binary size:
# TCP only (default, ~2MB binary)
cargo build --release
# HTTP only
cargo build --release --no-default-features --features http
# Both transports
cargo build --release --features fullAvailable features:
tcp— TCP transport with framing (default)http— HTTP transport with Axumfull— All features enabled
Run the comprehensive test suite:
# Run all tests
cargo test
# Run with output
cargo test -- --nocapture
# Test specific module
cargo test rpc::tests
cargo test batch::tests
cargo test state::tests
# Run integration tests
cargo test --test '*'Test coverage includes:
- RPC request/response parsing
- Batch processing
- Authentication flows
- State management (transfers, confirmations)
- Metrics collection
- Graceful shutdown
DiceRPC is production-ready with:
- Graceful shutdown — Signal handling (SIGTERM, SIGINT, Ctrl+C)
- Request metrics — Track requests, errors, latency
- Structured logging — Tracing with
tracingcrate - Authentication — Pluggable API key validation
- Error handling — Proper error codes per JSON-RPC spec
- State persistence — Ready for database integration
- Concurrent processing — Tokio-based async/await
- Batch support — Process multiple requests in parallel
- Binary protocol — Length-prefixed framing for TCP
Future enhancements planned:
- WebSocket transport
- Database persistence (PostgreSQL, Redis)
- Rate limiting middleware
- Request/response compression (gzip, brotli)
- TLS/SSL support
- Prometheus metrics exporter
- OpenAPI/Swagger documentation
- Client libraries (JavaScript, Python)
- Load balancing support
- Circuit breaker pattern
Contributions are welcome! Here's how you can help:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please ensure:
- Code follows Rust conventions (
cargo fmt,cargo clippy) - Tests pass (
cargo test) - New features include tests and documentation
- Complete Implementation Guide — Deep dive into DiceRPC
- JSON-RPC 2.0 Specification
- Rust Async Book
- Tokio Documentation
- Axum Framework
This project is licensed under the MIT License - see the LICENSE file for details.
Built with ❤️ using Rust and the amazing ecosystem: