Add gsio-client and gsio-wallet crates with initial implementations

- Introduced `gsio-client` crate for interacting with GSIO nodes, including ledger entry management and node discovery.
- Introduced `gsio-wallet` crate for key management, transaction creation, and wallet functionality.
- Updated workspace configuration to include new crates.
This commit is contained in:
geoffsee
2025-06-15 13:34:12 -04:00
parent 2f2ddc7f6d
commit 4c0848e2f3
10 changed files with 1146 additions and 49 deletions

View File

@@ -0,0 +1,18 @@
[package]
name = "gsio-client"
version = "0.1.0"
publish = false
edition = "2024"
license = "MIT"
[dependencies]
tokio = { version = "1.45.1", features = ["rt-multi-thread", "macros", "time", "net"] }
tracing = { version = "0.1.41" }
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1.7.0", features = ["v4", "serde"] }
chrono = { version = "0.4.35", features = ["serde"] }
reqwest = { version = "0.11", features = ["json"] }
thiserror = "1.0"
futures = "0.3.31"

View File

@@ -0,0 +1,41 @@
use gsio_client::{GsioClient, GsioClientError};
use serde_json::json;
use tracing_subscriber::FmtSubscriber;
#[tokio::main]
async fn main() -> Result<(), GsioClientError> {
// Initialize tracing
tracing::subscriber::set_global_default(FmtSubscriber::default())
.expect("Failed to set tracing subscriber");
// Create a new client
let client = GsioClient::new("http://localhost:3000")?;
println!("Created GSIO client");
// Add an entry to the ledger
let entry_data = json!({
"message": "Hello, GSIO!",
"timestamp": chrono::Utc::now().to_rfc3339(),
});
let entry = client.add_ledger_entry(entry_data).await?;
println!("Added ledger entry: {:?}", entry);
// Get all entries in the ledger
let entries = client.get_ledger().await?;
println!("Ledger entries:");
for entry in entries {
println!(" - {}: {}", entry.id, entry.data);
}
// Get all known nodes
let nodes = client.get_known_nodes().await?;
println!("Known nodes:");
for node in nodes {
println!(" - {}", node);
}
println!("Example completed successfully");
Ok(())
}

View File

@@ -0,0 +1,138 @@
//! GSIO Client Library
//!
//! This library provides a client for interacting with GSIO nodes.
//! It allows connecting to nodes, adding entries to the ledger,
//! and retrieving ledger data.
use reqwest::{Client as HttpClient, Error as ReqwestError};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::time::Duration;
use thiserror::Error;
use tracing::{error, info};
/// Error type for GSIO client operations
#[derive(Error, Debug)]
pub enum GsioClientError {
#[error("HTTP error: {0}")]
HttpError(#[from] ReqwestError),
#[error("JSON serialization error: {0}")]
SerializationError(#[from] serde_json::Error),
#[error("Connection error: {0}")]
ConnectionError(String),
#[error("Server error: {0}")]
ServerError(String),
}
/// A ledger entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LedgerEntry {
pub id: String,
pub timestamp: String,
pub data: JsonValue,
pub node_id: String,
pub hash: String,
}
/// GSIO Client for interacting with GSIO nodes
pub struct GsioClient {
client: HttpClient,
node_url: String,
}
impl GsioClient {
/// Create a new GSIO client
pub fn new(node_url: &str) -> Result<Self, GsioClientError> {
let client = HttpClient::builder()
.timeout(Duration::from_secs(30))
.build()
.map_err(|e| GsioClientError::ConnectionError(e.to_string()))?;
Ok(Self {
client,
node_url: node_url.to_string(),
})
}
/// Add an entry to the ledger
pub async fn add_ledger_entry(&self, data: JsonValue) -> Result<LedgerEntry, GsioClientError> {
info!("Adding ledger entry: {:?}", data);
let url = format!("{}/api/ledger", self.node_url);
let response = self.client.post(&url)
.json(&data)
.send()
.await?;
if !response.status().is_success() {
let error_text = response.text().await?;
return Err(GsioClientError::ServerError(format!("Server returned error: {}", error_text)));
}
let entry: LedgerEntry = response.json().await?;
Ok(entry)
}
/// Get all entries in the ledger
pub async fn get_ledger(&self) -> Result<Vec<LedgerEntry>, GsioClientError> {
info!("Getting ledger entries");
let url = format!("{}/api/ledger", self.node_url);
let response = self.client.get(&url)
.send()
.await?;
if !response.status().is_success() {
let error_text = response.text().await?;
return Err(GsioClientError::ServerError(format!("Server returned error: {}", error_text)));
}
let entries: Vec<LedgerEntry> = response.json().await?;
Ok(entries)
}
/// Get all known nodes in the network
pub async fn get_known_nodes(&self) -> Result<Vec<String>, GsioClientError> {
info!("Getting known nodes");
let url = format!("{}/api/nodes", self.node_url);
let response = self.client.get(&url)
.send()
.await?;
if !response.status().is_success() {
let error_text = response.text().await?;
return Err(GsioClientError::ServerError(format!("Server returned error: {}", error_text)));
}
let data: JsonValue = response.json().await?;
let nodes = data.get("nodes")
.ok_or_else(|| GsioClientError::ServerError("Invalid response format".to_string()))?;
let nodes: Vec<String> = serde_json::from_value(nodes.clone())?;
Ok(nodes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_creation() {
let client = GsioClient::new("http://localhost:3000").unwrap();
assert_eq!(client.node_url, "http://localhost:3000");
}
// More tests would be added here in a real implementation
}

View File

@@ -0,0 +1,20 @@
[package]
name = "gsio-wallet"
version = "0.1.0"
publish = false
edition = "2024"
license = "MIT"
[dependencies]
tokio = { version = "1.45.1", features = ["rt-multi-thread", "macros", "time"] }
tracing = { version = "0.1.41" }
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1.7.0", features = ["v4", "serde"] }
chrono = { version = "0.4.35", features = ["serde"] }
thiserror = "1.0"
rand = "0.7.3"
rand_core = "0.5.1"
ed25519-dalek = { version = "1.0.1", features = ["serde"] }
hex = "0.4.3"

View File

@@ -0,0 +1,65 @@
# GSIO Wallet
A wallet implementation for the GSIO network.
## Features
- Key management (generate, store, retrieve keys)
- Transaction creation and signing
- Balance tracking
- Transaction history
## Usage
```rust
use gsio_wallet::{Wallet, TransactionType};
// Create a new wallet
let mut wallet = Wallet::new();
// Generate a new keypair
let address = wallet.generate_keypair().unwrap();
println!("Generated address: {}", address);
// Create a transaction
let transaction = wallet.create_transaction(
&address,
"recipient_address",
100,
10,
TransactionType::Transfer,
None,
).unwrap();
// Sign the transaction
let mut signed_transaction = transaction.clone();
wallet.sign_transaction(&mut signed_transaction).unwrap();
// Submit the transaction (in an async context)
async {
let tx_id = wallet.submit_transaction(&signed_transaction).await.unwrap();
println!("Transaction submitted: {}", tx_id);
};
// Get account balance
let balance = wallet.get_balance(&address).unwrap();
println!("Balance: {}", balance);
// Get transaction history
let history = wallet.get_transaction_history(&address).unwrap();
println!("Transaction history: {:?}", history);
```
## Implementation Details
This is a stubbed implementation of a wallet for the GSIO network. The actual implementation would need to be integrated with the GSIO network to handle real transactions and balances.
The wallet uses Ed25519 for cryptographic operations, which provides strong security for digital signatures.
## Future Improvements
- Implement wallet persistence with encryption
- Add support for multiple accounts in a single wallet
- Implement transaction verification
- Add support for different transaction types
- Integrate with the GSIO network for real transactions

View File

@@ -0,0 +1,256 @@
//! GSIO Wallet Library
//!
//! This library provides wallet functionality for the GSIO network.
//! It allows creating and managing wallets, generating and storing keys,
//! signing transactions, and tracking balances.
use chrono::{DateTime, Utc};
use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signature, Signer, SignatureError};
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use thiserror::Error;
use tracing::{debug, error, info};
use uuid::Uuid;
/// Error type for GSIO wallet operations
#[derive(Error, Debug)]
pub enum WalletError {
#[error("IO error: {0}")]
IoError(#[from] io::Error),
#[error("JSON serialization error: {0}")]
SerializationError(#[from] serde_json::Error),
#[error("Signature error: {0}")]
SignatureError(#[from] SignatureError),
#[error("Key not found: {0}")]
KeyNotFound(String),
#[error("Wallet not found: {0}")]
WalletNotFound(String),
#[error("Invalid wallet data: {0}")]
InvalidWalletData(String),
#[error("Insufficient funds: required {0}, available {1}")]
InsufficientFunds(u64, u64),
}
/// Transaction type
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TransactionType {
Transfer,
Stake,
Unstake,
// Other transaction types can be added here
}
/// Transaction status
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TransactionStatus {
Pending,
Confirmed,
Failed,
}
/// Transaction record
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Transaction {
pub id: String,
pub transaction_type: TransactionType,
pub amount: u64,
pub fee: u64,
pub sender: String,
pub recipient: String,
pub timestamp: DateTime<Utc>,
pub status: TransactionStatus,
pub signature: Option<String>,
pub data: Option<JsonValue>,
}
/// Wallet account information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Account {
pub address: String,
pub public_key: String,
pub balance: u64,
pub nonce: u64,
pub transactions: Vec<String>,
}
/// GSIO Wallet for managing keys and transactions
pub struct Wallet {
keypair: Option<Keypair>,
accounts: HashMap<String, Account>,
wallet_path: Option<PathBuf>,
}
impl Wallet {
/// Create a new empty wallet
pub fn new() -> Self {
Self {
keypair: None,
accounts: HashMap::new(),
wallet_path: None,
}
}
/// Generate a new keypair
pub fn generate_keypair(&mut self) -> Result<String, WalletError> {
let mut csprng = OsRng;
let keypair = Keypair::generate(&mut csprng);
let address = format!("gsio_{}", hex::encode(&keypair.public.to_bytes()[0..20]));
self.keypair = Some(keypair);
// Create a new account for this keypair
let account = Account {
address: address.clone(),
public_key: hex::encode(self.keypair.as_ref().unwrap().public.to_bytes()),
balance: 0,
nonce: 0,
transactions: Vec::new(),
};
self.accounts.insert(address.clone(), account);
Ok(address)
}
/// Load wallet from file
pub fn load(&mut self, path: &Path) -> Result<(), WalletError> {
// This is a stub implementation
info!("Loading wallet from: {:?}", path);
self.wallet_path = Some(path.to_path_buf());
// In a real implementation, this would load the wallet data from the file
// For now, we'll just return Ok to indicate success
Ok(())
}
/// Save wallet to file
pub fn save(&self) -> Result<(), WalletError> {
// This is a stub implementation
if let Some(path) = &self.wallet_path {
info!("Saving wallet to: {:?}", path);
// In a real implementation, this would save the wallet data to the file
} else {
return Err(WalletError::IoError(io::Error::new(
io::ErrorKind::NotFound,
"Wallet path not set",
)));
}
// For now, we'll just return Ok to indicate success
Ok(())
}
/// Get account by address
pub fn get_account(&self, address: &str) -> Result<&Account, WalletError> {
self.accounts
.get(address)
.ok_or_else(|| WalletError::WalletNotFound(address.to_string()))
}
/// Get account balance
pub fn get_balance(&self, address: &str) -> Result<u64, WalletError> {
let account = self.get_account(address)?;
Ok(account.balance)
}
/// Create a new transaction
pub fn create_transaction(
&self,
sender: &str,
recipient: &str,
amount: u64,
fee: u64,
transaction_type: TransactionType,
data: Option<JsonValue>,
) -> Result<Transaction, WalletError> {
// Check if sender account exists
let sender_account = self.get_account(sender)?;
// Check if sender has enough funds
if sender_account.balance < amount + fee {
return Err(WalletError::InsufficientFunds(
amount + fee,
sender_account.balance,
));
}
// Create transaction
let transaction = Transaction {
id: Uuid::new_v4().to_string(),
transaction_type,
amount,
fee,
sender: sender.to_string(),
recipient: recipient.to_string(),
timestamp: Utc::now(),
status: TransactionStatus::Pending,
signature: None,
data,
};
Ok(transaction)
}
/// Sign a transaction
pub fn sign_transaction(&self, transaction: &mut Transaction) -> Result<(), WalletError> {
// This is a stub implementation
if self.keypair.is_none() {
return Err(WalletError::KeyNotFound("No keypair loaded".to_string()));
}
// In a real implementation, this would sign the transaction with the keypair
// For now, we'll just set a dummy signature
transaction.signature = Some("dummy_signature".to_string());
Ok(())
}
/// Submit a transaction to the network
pub async fn submit_transaction(&self, transaction: &Transaction) -> Result<String, WalletError> {
// This is a stub implementation
info!("Submitting transaction: {:?}", transaction);
// In a real implementation, this would submit the transaction to the network
// For now, we'll just return the transaction ID to indicate success
Ok(transaction.id.clone())
}
/// Get transaction history for an account
pub fn get_transaction_history(&self, address: &str) -> Result<Vec<String>, WalletError> {
let account = self.get_account(address)?;
Ok(account.transactions.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wallet_creation() {
let wallet = Wallet::new();
assert!(wallet.keypair.is_none());
assert!(wallet.accounts.is_empty());
}
#[test]
fn test_keypair_generation() {
let mut wallet = Wallet::new();
let address = wallet.generate_keypair().unwrap();
assert!(wallet.keypair.is_some());
assert!(wallet.accounts.contains_key(&address));
}
// More tests would be added here in a real implementation
}