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:
18
crates/gsio-client/Cargo.toml
Normal file
18
crates/gsio-client/Cargo.toml
Normal 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"
|
41
crates/gsio-client/examples/basic_usage.rs
Normal file
41
crates/gsio-client/examples/basic_usage.rs
Normal 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(())
|
||||
}
|
138
crates/gsio-client/src/lib.rs
Normal file
138
crates/gsio-client/src/lib.rs
Normal 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
|
||||
}
|
20
crates/gsio-wallet/Cargo.toml
Normal file
20
crates/gsio-wallet/Cargo.toml
Normal 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"
|
65
crates/gsio-wallet/README.md
Normal file
65
crates/gsio-wallet/README.md
Normal 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
|
256
crates/gsio-wallet/src/lib.rs
Normal file
256
crates/gsio-wallet/src/lib.rs
Normal 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
|
||||
}
|
Reference in New Issue
Block a user