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,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
}