phase 1 scaffold: cargo workspace + 3 crates + roadmap + architecture
Repo skeleton for aldabra, the rust-native cardano lite wallet
with MCP server interface. Builds end-to-end, types in place,
real cardano primitives land next pass.
Crates:
wallet-core — pure crypto + types. mnemonic, key derivation,
signing. No I/O. Security boundary.
wallet-chain — pluggable backends. ChainBackend trait, Koios
client (stub for now). Ogmios + submit in phase 2.
wallet-mcp — the binary. stdio MCP transport via rmcp.
Phase plan in ROADMAP.md, threat model in docs/architecture.md.
This is also Sulkta's first Rust project + a real-world workout for
ci-runner's rust toolchain.
This commit is contained in:
commit
56bcceb593
11 changed files with 735 additions and 0 deletions
23
crates/wallet-chain/Cargo.toml
Normal file
23
crates/wallet-chain/Cargo.toml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# wallet-chain — pluggable backends for chain queries.
|
||||
#
|
||||
# A `ChainBackend` trait abstracts over Koios (HTTPS), Ogmios (WS/HTTP),
|
||||
# or any future option. Phase 1 ships with a Koios implementation since
|
||||
# Sulkta already runs Koios endpoints.
|
||||
#
|
||||
# Submission paths land in phase 2. Read-only queries first.
|
||||
|
||||
[package]
|
||||
name = "wallet-chain"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license-file.workspace = true
|
||||
repository.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
tokio = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
async-trait = "0.1"
|
||||
97
crates/wallet-chain/src/lib.rs
Normal file
97
crates/wallet-chain/src/lib.rs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
//! aldabra chain backends — Koios first, Ogmios next.
|
||||
//!
|
||||
//! Trait-first design: the MCP server depends on `ChainBackend`, not on
|
||||
//! a specific implementation. Swapping Koios → Ogmios is a config change.
|
||||
//!
|
||||
//! ## Phase 1
|
||||
//! Just the trait + a stub `KoiosClient` that returns hardcoded data.
|
||||
//! Real HTTP wired up next pass.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ChainError {
|
||||
#[error("network error: {0}")]
|
||||
Network(String),
|
||||
|
||||
#[error("backend returned malformed response: {0}")]
|
||||
Decode(String),
|
||||
|
||||
#[error("not yet implemented (phase 1 scaffold)")]
|
||||
NotYetImplemented,
|
||||
}
|
||||
|
||||
/// One UTXO at an address. Multi-asset bundle is a flat map of
|
||||
/// {policy_id+asset_name → quantity} for now; we'll model it more
|
||||
/// strictly when minting lands in phase 3.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Utxo {
|
||||
pub tx_hash: String,
|
||||
pub output_index: u32,
|
||||
pub lovelace: u64,
|
||||
/// Hex-encoded `policy_id || asset_name_hex` → quantity.
|
||||
/// Empty for plain ADA UTXOs.
|
||||
pub assets: std::collections::BTreeMap<String, u64>,
|
||||
}
|
||||
|
||||
/// Aggregated balance at an address.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Balance {
|
||||
pub lovelace: u64,
|
||||
pub assets: std::collections::BTreeMap<String, u64>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait ChainBackend: Send + Sync {
|
||||
async fn get_utxos(&self, address: &str) -> Result<Vec<Utxo>, ChainError>;
|
||||
async fn get_balance(&self, address: &str) -> Result<Balance, ChainError>;
|
||||
// Phase 2:
|
||||
// async fn submit_tx(&self, raw_tx_cbor: &[u8]) -> Result<String, ChainError>;
|
||||
// async fn tx_status(&self, tx_hash: &str) -> Result<TxStatus, ChainError>;
|
||||
}
|
||||
|
||||
/// Stub Koios client. Phase 1: returns deterministic placeholder data
|
||||
/// so the MCP server can be smoke-tested end-to-end without a chain
|
||||
/// dependency. Phase 2: real reqwest calls to a Koios endpoint.
|
||||
pub struct KoiosClient {
|
||||
/// Base URL — typically https://api.koios.rest/api/v1
|
||||
/// or your own self-hosted Koios deployment.
|
||||
pub base_url: String,
|
||||
}
|
||||
|
||||
impl KoiosClient {
|
||||
pub fn new(base_url: impl Into<String>) -> Self {
|
||||
Self {
|
||||
base_url: base_url.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ChainBackend for KoiosClient {
|
||||
async fn get_utxos(&self, _address: &str) -> Result<Vec<Utxo>, ChainError> {
|
||||
// TODO(phase 1): POST /address_utxos with {"_addresses": [<address>]}
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
async fn get_balance(&self, _address: &str) -> Result<Balance, ChainError> {
|
||||
// TODO(phase 1): POST /address_info, sum balances across UTXOs
|
||||
Ok(Balance {
|
||||
lovelace: 0,
|
||||
assets: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn stub_koios_returns_empty() {
|
||||
let client = KoiosClient::new("https://api.koios.rest/api/v1");
|
||||
let bal = client.get_balance("addr1...").await.unwrap();
|
||||
assert_eq!(bal.lovelace, 0);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue