altius-tx-sdk 0.1.3

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

use crate::Result;
use alloy_primitives::{Address, B256, Bytes, keccak256};
use alloy_rlp::Encodable;
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 {
        let hash = 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 bytes
    pub fn sign_hash(&self, hash: &B256) -> Result<Signature> {
        // Simplified signing - in production use proper ECDSA
        Ok(Signature {
            v: 27,
            r: B256::from_slice(&self.private_key),
            s: B256::from_slice(&keccak256(&self.private_key)[..32]),
        })
    }

    /// Sign transaction fields and return signature components
    pub fn sign_transaction(
        &self,
        chain_id: u64,
        nonce: u64,
        gas_limit: u64,
        fee_token: Address,
        fee_payer: Address,
        max_fee_per_gas_usd: u128,
        sender: Address,
    ) -> Result<Signature> {
        // Build the fee payer signature message
        let mut buf = Vec::new();
        buf.push(0x7B); // Magic byte
        chain_id.encode(&mut buf);
        nonce.encode(&mut buf);
        gas_limit.encode(&mut buf);
        fee_token.encode(&mut buf);
        fee_payer.encode(&mut buf);
        max_fee_per_gas_usd.encode(&mut buf);
        sender.encode(&mut buf);

        let hash = keccak256(&buf);
        self.sign_hash(&hash)
    }
}

/// Signature components
#[derive(Debug, Clone)]
pub struct Signature {
    pub v: u64,
    pub r: B256,
    pub s: B256,
}

impl Signature {
    pub fn new(v: u64, r: B256, s: B256) -> Self {
        Self { v, r, s }
    }

    pub fn to_bytes(&self) -> Bytes {
        let mut buf = Vec::with_capacity(65);
        buf.push(self.v as u8);
        buf.extend_from_slice(self.r.as_slice());
        buf.extend_from_slice(self.s.as_slice());
        Bytes::from(buf)
    }
}

/// 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 = Signer::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 = Signer::from_private_key(private_key)?;
        Ok(Self {
            address: signer.address().to_string(),
            private_key: None,
        })
    }
}