altius-tx-sdk 0.1.0

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

use crate::transaction::{Signature, TxUsdMultiToken};
use crate::Result;
use alloy_primitives::{Address, B256};
use rand::RngCore;

/// A transaction signer
#[derive(Debug)]
pub struct Signer {
    private_key: [u8; 32],
    chain_id: u64,
}

impl 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 mut private_key = [0u8; 32];
        private_key.copy_from_slice(&bytes);

        Ok(Self { private_key, chain_id: 0 })
    }

    /// Get the signer's address
    pub fn address(&self) -> Address {
        // Derive address from private key using secp256k1
        // For simplicity, we'll use a simplified approach
        // In production, use proper secp256k1 key derivation
        let hash = alloy_primitives::keccak256(&self.private_key);
        Address::from_slice(&hash[12..32])
    }

    /// Get the chain ID
    pub fn chain_id(&self) -> u64 {
        self.chain_id
    }

    /// Set the chain ID
    pub fn with_chain_id(mut self, chain_id: u64) -> Self {
        self.chain_id = chain_id;
        self
    }

    /// Sign a message hash and return signature components
    pub fn sign_hash(&self, hash: &B256) -> Result<Signature> {
        // Use simple signing for now
        // In production, use proper secp256k1 ECDSA signing
        let mut msg_hash = [0u8; 32];
        msg_hash.copy_from_slice(hash.as_slice());

        // For demo purposes, return a placeholder signature
        // In production, use proper ECDSA signing with k256 crate
        Ok(Signature::new(
            27,
            B256::from_slice(&self.private_key),
            B256::from_slice(&alloy_primitives::keccak256(&self.private_key)[..32]),
        ))
    }

    /// Sign a TxUsdMultiToken and return the encoded raw transaction
    pub fn sign_and_encode(&self, tx: TxUsdMultiToken) -> Result<String> {
        let hash = tx.signature_hash();
        let signature = self.sign_hash(&hash)?;

        let signed = crate::transaction::SignedTxUsdMultiToken {
            tx,
            signature,
        };

        let encoded = signed.encode_2718();
        Ok(format!("0x{}", hex::encode(&encoded)))
    }
}

/// A wallet with address and optional private key
#[derive(Debug, Clone)]
pub struct Wallet {
    /// The wallet address (0x-prefixed hex)
    pub address: String,
    /// The private key (0x-prefixed hex), only present for newly generated wallets
    pub private_key: Option<String>,
}

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

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

    /// Import a wallet from a private key
    pub fn from_private_key(private_key: &str) -> Result<Self> {
        let signer = Signer::from_private_key(private_key)?;
        Ok(Self {
            address: signer.address().to_string(),
            private_key: None,
        })
    }
}