311 lines
9.7 KiB
Rust
311 lines
9.7 KiB
Rust
use crate::cli::*;
|
||
use crate::client::nac_lens::NrpcClient;
|
||
use crate::config::Config;
|
||
use crate::error::{CliError, Result};
|
||
use crate::utils::*;
|
||
use colored::Colorize;
|
||
use dialoguer::{Password, Confirm};
|
||
use serde_json::json;
|
||
use secp256k1::{Secp256k1, Message, SecretKey};
|
||
use sha3::{Digest, Sha3_384};
|
||
use std::fs;
|
||
|
||
pub async fn execute(cmd: &TransactionCommands, _cli: &Cli) -> Result<()> {
|
||
match cmd {
|
||
TransactionCommands::Send { from, to, amount, gas_limit, gas_price } => {
|
||
send_transaction(from, to, amount, *gas_limit, *gas_price).await
|
||
}
|
||
TransactionCommands::Show { tx_hash } => {
|
||
show_transaction(tx_hash).await
|
||
}
|
||
TransactionCommands::List { address, limit } => {
|
||
list_transactions(address, *limit).await
|
||
}
|
||
TransactionCommands::Sign { tx_file, private_key } => {
|
||
sign_transaction_file(tx_file, private_key).await
|
||
}
|
||
TransactionCommands::Broadcast { signed_tx } => {
|
||
broadcast_transaction(signed_tx).await
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 发送交易
|
||
async fn send_transaction(
|
||
from: &str,
|
||
to: &str,
|
||
amount: &str,
|
||
gas_limit: Option<u64>,
|
||
gas_price: Option<u64>,
|
||
) -> Result<()> {
|
||
// 验证地址格式
|
||
validate_address(from)?;
|
||
validate_address(to)?;
|
||
|
||
// 解析金额
|
||
let value: u128 = amount.parse()
|
||
.map_err(|_| CliError::InvalidInput(format!("无效的金额: {}", amount)))?;
|
||
|
||
print_info("准备发送交易...");
|
||
println!();
|
||
println!("{:12} {}", "从:".bold(), from.green());
|
||
println!("{:12} {}", "到:".bold(), to.green());
|
||
println!("{:12} {} NAC", "金额:".bold(), amount.cyan());
|
||
println!();
|
||
|
||
// 确认
|
||
let confirmed = Confirm::new()
|
||
.with_prompt("确认发送交易?")
|
||
.default(true)
|
||
.interact()
|
||
.map_err(|e| CliError::Io(format!("读取确认失败: {}", e)))?;
|
||
|
||
if !confirmed {
|
||
print_info("已取消交易");
|
||
return Ok(());
|
||
}
|
||
|
||
// 获取密码
|
||
let password = Password::new()
|
||
.with_prompt("请输入账户密码")
|
||
.interact()
|
||
.map_err(|e| CliError::Io(format!("读取密码失败: {}", e)))?;
|
||
|
||
// 从keystore导出私钥
|
||
let manager = KeystoreManager::default()?;
|
||
let private_key = manager.export(from, &password)?;
|
||
|
||
// 加载配置
|
||
let config = Config::load()
|
||
.map_err(|_| CliError::Config("未找到配置文件,请先运行 'nac config init'".to_string()))?;
|
||
|
||
// 创建RPC客户端
|
||
let client = NrpcClient::new(config.get_current_rpc_url());
|
||
|
||
// 获取nonce
|
||
print_info("获取账户nonce...");
|
||
let nonce = client.get_nonce(from).await?;
|
||
|
||
// 构建交易
|
||
let gas_limit = gas_limit.unwrap_or(21000);
|
||
let gas_price = gas_price.unwrap_or(1000000000);
|
||
|
||
let tx = json!({
|
||
"from": from,
|
||
"to": to,
|
||
"value": value.to_string(),
|
||
"data": "0x",
|
||
"nonce": nonce,
|
||
"gas_limit": gas_limit,
|
||
"gas_price": gas_price,
|
||
});
|
||
|
||
// 签名交易
|
||
print_info("签名交易...");
|
||
let signed_tx = sign_tx(&tx, &private_key)?;
|
||
|
||
// 发送交易
|
||
print_info("发送交易到网络...");
|
||
let tx_hash = client.send_transaction(&signed_tx).await?;
|
||
|
||
print_success("交易已发送!");
|
||
println!();
|
||
println!("{}", "交易哈希:".bold());
|
||
println!(" {}", tx_hash.cyan());
|
||
println!();
|
||
println!("提示: 使用 'nac tx show {}' 查询交易状态", tx_hash);
|
||
println!();
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 显示交易详情
|
||
async fn show_transaction(tx_hash: &str) -> Result<()> {
|
||
// 验证哈希格式
|
||
validate_hash(tx_hash)?;
|
||
|
||
// 加载配置
|
||
let config = Config::load()
|
||
.map_err(|_| CliError::Config("未找到配置文件,请先运行 'nac config init'".to_string()))?;
|
||
|
||
// 创建RPC客户端
|
||
let client = NrpcClient::new(config.get_current_rpc_url());
|
||
|
||
print_info(&format!("正在查询交易 {}...", tx_hash));
|
||
|
||
// 查询交易
|
||
let tx = client.get_transaction(tx_hash).await?;
|
||
|
||
println!();
|
||
println!("{}", "交易详情".bold());
|
||
println!("{}", "=".repeat(80));
|
||
println!();
|
||
println!("{}", serde_json::to_string_pretty(&tx).unwrap_or_default());
|
||
println!();
|
||
|
||
// 查询收据
|
||
print_info("查询交易收据...");
|
||
match client.get_transaction_receipt(tx_hash).await {
|
||
Ok(receipt) => {
|
||
println!();
|
||
println!("{}", "交易收据".bold());
|
||
println!("{}", "=".repeat(80));
|
||
println!();
|
||
println!("{}", serde_json::to_string_pretty(&receipt).unwrap_or_default());
|
||
println!();
|
||
|
||
// 如果有宪法收据,也显示
|
||
if let Some(cr_id) = receipt.get("constitutional_receipt_id") {
|
||
if let Some(cr_id_str) = cr_id.as_str() {
|
||
print_info("查询宪法收据...");
|
||
|
||
match client.get_constitutional_receipt(cr_id_str).await {
|
||
Ok(cr) => {
|
||
println!();
|
||
println!("{}", "宪法收据 (Constitutional Receipt)".bold());
|
||
println!("{}", "=".repeat(80));
|
||
println!();
|
||
println!("{}", serde_json::to_string_pretty(&cr).unwrap_or_default());
|
||
println!();
|
||
}
|
||
Err(e) => {
|
||
print_warning(&format!("查询宪法收据失败: {}", e));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Err(e) => {
|
||
print_warning(&format!("查询收据失败: {}", e));
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 列出交易历史
|
||
async fn list_transactions(address: &str, limit: usize) -> Result<()> {
|
||
validate_address(address)?;
|
||
|
||
print_info(&format!("查询地址 {} 的交易历史(最近{}笔)...", address, limit));
|
||
println!();
|
||
print_warning("此功能需要节点支持交易历史查询API");
|
||
println!();
|
||
|
||
// TODO: 实现交易历史查询
|
||
// 需要节点提供 nac_account_getTransactions 方法
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 签名交易文件
|
||
async fn sign_transaction_file(tx_file: &str, private_key: &str) -> Result<()> {
|
||
// 读取交易文件
|
||
let tx_json = fs::read_to_string(tx_file)
|
||
.map_err(|e| CliError::Io(format!("读取交易文件失败: {}", e)))?;
|
||
|
||
let tx: serde_json::Value = serde_json::from_str(&tx_json)
|
||
.map_err(|e| CliError::Json(e))?;
|
||
|
||
// 签名
|
||
print_info("签名交易...");
|
||
let signed_tx = sign_tx(&tx, private_key)?;
|
||
|
||
// 输出签名后的交易
|
||
println!();
|
||
println!("{}", "已签名交易:".bold());
|
||
println!("{}", signed_tx);
|
||
println!();
|
||
println!("提示: 使用 'nac tx broadcast <signed_tx>' 广播交易");
|
||
println!();
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 广播已签名交易
|
||
async fn broadcast_transaction(signed_tx: &str) -> Result<()> {
|
||
// 加载配置
|
||
let config = Config::load()
|
||
.map_err(|_| CliError::Config("未找到配置文件,请先运行 'nac config init'".to_string()))?;
|
||
|
||
// 创建RPC客户端
|
||
let client = NrpcClient::new(config.get_current_rpc_url());
|
||
|
||
print_info("广播交易到网络...");
|
||
let tx_hash = client.send_transaction(signed_tx).await?;
|
||
|
||
print_success("交易已广播!");
|
||
println!();
|
||
println!("{}", "交易哈希:".bold());
|
||
println!(" {}", tx_hash.cyan());
|
||
println!();
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 签名交易
|
||
fn sign_tx(tx: &serde_json::Value, private_key: &str) -> Result<String> {
|
||
// 解析私钥
|
||
let secret_bytes = hex::decode(private_key)
|
||
.map_err(|e| CliError::Crypto(format!("私钥格式错误: {}", e)))?;
|
||
|
||
let secret_key = SecretKey::from_slice(&secret_bytes)
|
||
.map_err(|e| CliError::Crypto(format!("无效的私钥: {}", e)))?;
|
||
|
||
// 序列化交易数据
|
||
let tx_bytes = serde_json::to_vec(tx)
|
||
.map_err(|e| CliError::Crypto(format!("序列化交易失败: {}", e)))?;
|
||
|
||
// 计算交易哈希(SHA3-384)
|
||
let mut hasher = Sha3_384::new();
|
||
hasher.update(&tx_bytes);
|
||
let hash = hasher.finalize();
|
||
|
||
// 取前32字节用于签名(secp256k1需要32字节消息)
|
||
let message_bytes = &hash[..32];
|
||
let message = Message::from_digest_slice(message_bytes)
|
||
.map_err(|e| CliError::Crypto(format!("创建消息失败: {}", e)))?;
|
||
|
||
// 签名
|
||
let secp = Secp256k1::new();
|
||
let signature = secp.sign_ecdsa(&message, &secret_key);
|
||
|
||
// 构建签名后的交易
|
||
let mut signed_tx = tx.clone();
|
||
if let Some(obj) = signed_tx.as_object_mut() {
|
||
obj.insert("signature".to_string(), json!(hex::encode(signature.serialize_compact())));
|
||
}
|
||
|
||
// 返回签名后的交易(hex编码)
|
||
let signed_bytes = serde_json::to_vec(&signed_tx)
|
||
.map_err(|e| CliError::Crypto(format!("序列化签名交易失败: {}", e)))?;
|
||
|
||
Ok(format!("0x{}", hex::encode(signed_bytes)))
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_sign_transaction() {
|
||
let tx = json!({
|
||
"from": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
||
"to": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
|
||
"value": "1000000000000000000",
|
||
"data": "0x",
|
||
"nonce": 0,
|
||
"gas_limit": 21000,
|
||
"gas_price": 1000000000,
|
||
});
|
||
|
||
let private_key = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
|
||
|
||
let result = sign_tx(&tx, private_key);
|
||
assert!(result.is_ok());
|
||
|
||
let signed = result.unwrap();
|
||
assert!(signed.starts_with("0x"));
|
||
}
|
||
}
|