feat(wallet): implement wallet send with k256 signing
- Loads encrypted keystore, derives sender address from verifying key - Fetches current nonce via nu_getAccount RPC - Builds TokenTransfer payload, signs SHA-256(tx_id) with secp256k1 - Broadcasts via nu_sendRawTx, prints returned tx_id - Add sha2 dependency Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
61fb9fe2c8
commit
2335932753
3 changed files with 39 additions and 3 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1034,6 +1034,7 @@ dependencies = [
|
|||
"rpassword",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -21,3 +21,4 @@ dirs = "5"
|
|||
argon2 = "0.5"
|
||||
aes-gcm = "0.10"
|
||||
rpassword = "7"
|
||||
sha2 = "0.10"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use k256::ecdsa::SigningKey;
|
||||
use k256::ecdsa::{signature::Signer, Signature, SigningKey};
|
||||
use rand::rngs::OsRng;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::keystore;
|
||||
|
||||
|
|
@ -17,7 +18,7 @@ pub enum WalletCmd {
|
|||
#[arg(long, default_value = "default")]
|
||||
label: String,
|
||||
},
|
||||
/// Send NUT tokens (stub — Faz 1 tx signing)
|
||||
/// Send Shell tokens to another address
|
||||
Send {
|
||||
#[arg(long)] to: String,
|
||||
#[arg(long)] amount: u64,
|
||||
|
|
@ -57,7 +58,40 @@ pub async fn run(cmd: WalletCmd) -> Result<()> {
|
|||
}
|
||||
|
||||
WalletCmd::Send { to, amount, from } => {
|
||||
println!("TODO: nu wallet send --to {to} --amount {amount} Shell (from: {from}) — Faz 1");
|
||||
let rpc_url = std::env::var("NU_RPC_URL")
|
||||
.unwrap_or_else(|_| "http://localhost:8545".to_string());
|
||||
let client = crate::rpc::Client::new(&rpc_url);
|
||||
|
||||
let dir = crate::config::keystore_dir();
|
||||
let path = dir.join(format!("{from}.key"));
|
||||
let password = keystore::prompt_password(false)?;
|
||||
let key_bytes = keystore::load_encrypted(&path, &password)?;
|
||||
let key = SigningKey::from_bytes(key_bytes.as_slice().into())?;
|
||||
let sender = hex::encode(key.verifying_key().to_sec1_bytes());
|
||||
|
||||
let account = client.call("nu_getAccount", vec![serde_json::json!(sender)]).await?;
|
||||
let nonce: u64 = account["nonce"].as_u64().unwrap_or(0);
|
||||
|
||||
let payload = serde_json::json!({
|
||||
"TokenTransfer": { "to": to, "amount": amount }
|
||||
});
|
||||
let payload_bytes = serde_json::to_vec(&payload)?;
|
||||
let tx_id = hex::encode(Sha256::digest(&payload_bytes));
|
||||
|
||||
let sig: Signature = key.sign(tx_id.as_bytes());
|
||||
let sig_bytes = sig.to_bytes().to_vec();
|
||||
|
||||
let tx = serde_json::json!({
|
||||
"tx_id": tx_id,
|
||||
"sender": sender,
|
||||
"nonce": nonce,
|
||||
"fee": 0,
|
||||
"sig": sig_bytes,
|
||||
"payload": payload,
|
||||
});
|
||||
|
||||
let result = client.call("nu_sendRawTx", vec![tx]).await?;
|
||||
println!("Sent: {}", result["tx_id"].as_str().unwrap_or("?"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue