altius-tx-sdk 0.2.5

SDK for signing and sending Altius USD multi-token transactions
Documentation
//! Signer implementation for Altius transactions

use crate::Result;
use crate::transaction::{Signer as SignerTrait, Signature as TxSignature};
use alloy_primitives::{Address, B256};
use alloy_signer::SignerSync;
use alloy_signer_local::PrivateKeySigner;
use rand::RngCore;

/// A transaction signer using real ECDSA signing
#[derive(Debug)]
pub struct EcdsaSigner {
    signer: PrivateKeySigner,
}

impl SignerTrait for EcdsaSigner {
    fn sign_hash(&self, hash: &B256) -> std::result::Result<TxSignature, Box<dyn std::error::Error>> {
        // Sign the hash with real ECDSA using sign_hash_sync
        let signature = self.signer.sign_hash_sync(hash)?;
        // v is returned as bool from alloy, convert to u8 (0 or 1)
        let v = if signature.v() { 1 } else { 0 };
        Ok(TxSignature::new(
            signature.r().into(),
            signature.s().into(),
            v,
        ))
    }

    fn address(&self) -> Address {
        self.signer.address()
    }
}

impl EcdsaSigner {
    /// Access the underlying PrivateKeySigner for advanced use cases (e.g. EIP-1559 signing).
    pub fn inner(&self) -> &PrivateKeySigner {
        &self.signer
    }

    /// Create a signer from a private key hex string
    pub fn from_private_key(private_key: &str) -> Result<Self> {
        let key = private_key.trim_start_matches("0x");
        let bytes = hex::decode(key).map_err(|e| crate::Error::InvalidHex(e.to_string()))?;

        if bytes.len() != 32 {
            return Err(crate::Error::InvalidPrivateKey("private key must be 32 bytes".into()));
        }

        let signer = PrivateKeySigner::from_slice(&bytes)
            .map_err(|e| crate::Error::InvalidPrivateKey(e.to_string()))?;

        Ok(Self { signer })
    }
}

/// Re-export SignerTrait as Signer for API compatibility
pub use crate::transaction::Signer as Signer;

/// A wallet with address and optional private key
#[derive(Debug, Clone)]
pub struct Wallet {
    pub address: String,
    pub private_key: Option<String>,
}

impl Wallet {
    pub fn generate() -> Result<Self> {
        let mut key = [0u8; 32];
        rand::thread_rng().fill_bytes(&mut key);

        let signer = EcdsaSigner::from_private_key(&hex::encode(key))?;
        Ok(Self {
            address: signer.address().to_string(),
            private_key: Some(format!("0x{}", hex::encode(key))),
        })
    }

    pub fn from_private_key(private_key: &str) -> Result<Self> {
        let signer = EcdsaSigner::from_private_key(private_key)?;
        Ok(Self {
            address: signer.address().to_string(),
            private_key: Some(private_key.to_string()),
        })
    }
}

/// Generate a new private key
pub fn generate_private_key() -> String {
    let mut key = [0u8; 32];
    rand::thread_rng().fill_bytes(&mut key);
    format!("0x{}", hex::encode(key))
}

/// Convert private key to address
pub fn private_key_to_address(private_key: &str) -> Result<String> {
    let signer = EcdsaSigner::from_private_key(private_key)?;
    Ok(signer.address().to_string())
}