Compare commits
5 commits
61fb9fe2c8
...
16a4999fdf
| Author | SHA256 | Date | |
|---|---|---|---|
| 16a4999fdf | |||
| e6616d1cf9 | |||
| 5d4a4c97c1 | |||
| 2cbbcb00f0 | |||
| 2335932753 |
9 changed files with 301 additions and 18 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -7,6 +7,17 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.4.0] — 2026-04-24
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `nu wallet send --to <addr> --amount <shell>`: k256 imzalı TokenTransfer tx gönderir
|
||||||
|
- `nu story submit`: NodeSubmit tx; UUID temp_id üretir, entry_fee=25_000 Shell
|
||||||
|
- `nu story register`: VoteRegister tx; stake_lock=10_000 Shell
|
||||||
|
- `nu story vote --node-id <id> --approve <bool>`: VoteCast tx gönderir
|
||||||
|
- `nu node stake / unstake`: StakeOp tx gönderir
|
||||||
|
- `nu node register --stake <amount>`: ValidatorRegister tx gönderir
|
||||||
|
- Tüm tx-gönderme komutlarında: keystore'dan key yükle → nonce'ı RPC'den al → SHA-256(payload) imzala → nu_sendRawTx
|
||||||
|
|
||||||
## [0.3.0] — 2026-04-24
|
## [0.3.0] — 2026-04-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
13
Cargo.lock
generated
13
Cargo.lock
generated
|
|
@ -1034,7 +1034,9 @@ dependencies = [
|
||||||
"rpassword",
|
"rpassword",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1908,6 +1910,17 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.23.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.4.2",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|
|
||||||
|
|
@ -21,3 +21,5 @@ dirs = "5"
|
||||||
argon2 = "0.5"
|
argon2 = "0.5"
|
||||||
aes-gcm = "0.10"
|
aes-gcm = "0.10"
|
||||||
rpassword = "7"
|
rpassword = "7"
|
||||||
|
sha2 = "0.10"
|
||||||
|
uuid = { version = "1", features = ["v4"] }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod wallet;
|
pub mod wallet;
|
||||||
pub mod node;
|
pub mod node;
|
||||||
|
pub mod nft;
|
||||||
pub mod story;
|
pub mod story;
|
||||||
pub mod genesis;
|
pub mod genesis;
|
||||||
|
|
|
||||||
80
src/commands/nft.rs
Normal file
80
src/commands/nft.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Subcommand;
|
||||||
|
use k256::ecdsa::{signature::Signer, Signature, SigningKey};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::{keystore, rpc::Client};
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum NftCmd {
|
||||||
|
/// Transfer an NFT to another address
|
||||||
|
Transfer {
|
||||||
|
#[arg(long)] nft_id: String,
|
||||||
|
#[arg(long)] to: String,
|
||||||
|
#[arg(long, default_value = "default")] from: String,
|
||||||
|
},
|
||||||
|
/// Claim an exclusive collection from a lineage of NFTs you own
|
||||||
|
Claim {
|
||||||
|
/// NFT ids forming the lineage path, from root to deepest (e.g. 0,1,11,115)
|
||||||
|
#[arg(long, value_delimiter = ',')] nft_ids: Vec<String>,
|
||||||
|
#[arg(long, default_value = "default")] from: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(cmd: NftCmd, rpc: &Client) -> Result<()> {
|
||||||
|
match cmd {
|
||||||
|
NftCmd::Transfer { nft_id, to, from } => {
|
||||||
|
let (key, sender) = load_key(&from)?;
|
||||||
|
let account = rpc.call("nu_getAccount", vec![json!(sender)]).await?;
|
||||||
|
let nonce: u64 = account["nonce"].as_u64().unwrap_or(0);
|
||||||
|
|
||||||
|
let payload = json!({ "NftTransfer": { "nft_id": nft_id, "to": to } });
|
||||||
|
let result = send_tx(&key, &sender, nonce, payload, rpc).await?;
|
||||||
|
println!("Transferred NFT {nft_id}: {}", result["tx_id"].as_str().unwrap_or("?"));
|
||||||
|
}
|
||||||
|
NftCmd::Claim { nft_ids, from } => {
|
||||||
|
let (key, sender) = load_key(&from)?;
|
||||||
|
let account = rpc.call("nu_getAccount", vec![json!(sender)]).await?;
|
||||||
|
let nonce: u64 = account["nonce"].as_u64().unwrap_or(0);
|
||||||
|
|
||||||
|
let payload = json!({ "CollectionClaim": { "nft_ids": nft_ids } });
|
||||||
|
let result = send_tx(&key, &sender, nonce, payload, rpc).await?;
|
||||||
|
println!("Collection claimed: {}", result["tx_id"].as_str().unwrap_or("?"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_key(label: &str) -> Result<(SigningKey, String)> {
|
||||||
|
let dir = crate::config::keystore_dir();
|
||||||
|
let path = dir.join(format!("{label}.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());
|
||||||
|
Ok((key, sender))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_tx(
|
||||||
|
key: &SigningKey,
|
||||||
|
sender: &str,
|
||||||
|
nonce: u64,
|
||||||
|
payload: serde_json::Value,
|
||||||
|
rpc: &Client,
|
||||||
|
) -> Result<serde_json::Value> {
|
||||||
|
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 = json!({
|
||||||
|
"tx_id": tx_id,
|
||||||
|
"sender": sender,
|
||||||
|
"nonce": nonce,
|
||||||
|
"fee": 0,
|
||||||
|
"sig": sig_bytes,
|
||||||
|
"payload": payload,
|
||||||
|
});
|
||||||
|
Ok(rpc.call("nu_sendRawTx", vec![tx]).await?)
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
use k256::ecdsa::{signature::Signer, Signature, SigningKey};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use crate::rpc::Client;
|
use crate::{keystore, rpc::Client};
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum NodeCmd {
|
pub enum NodeCmd {
|
||||||
|
|
@ -15,13 +17,20 @@ pub enum NodeCmd {
|
||||||
Account {
|
Account {
|
||||||
address: String,
|
address: String,
|
||||||
},
|
},
|
||||||
/// Register as validator (stub — Faz 1)
|
/// Stake Shell tokens (StakeOp)
|
||||||
Stake {
|
Stake {
|
||||||
#[arg(long)] amount: u64,
|
#[arg(long)] amount: u64,
|
||||||
|
#[arg(long, default_value = "default")] from: String,
|
||||||
},
|
},
|
||||||
/// Unstake from validator set (stub — Faz 1)
|
/// Unstake Shell tokens (StakeOp)
|
||||||
Unstake {
|
Unstake {
|
||||||
#[arg(long)] amount: u64,
|
#[arg(long)] amount: u64,
|
||||||
|
#[arg(long, default_value = "default")] from: String,
|
||||||
|
},
|
||||||
|
/// Register as a validator (ValidatorRegister)
|
||||||
|
Register {
|
||||||
|
#[arg(long)] stake: u64,
|
||||||
|
#[arg(long, default_value = "default")] from: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,8 +58,64 @@ pub async fn run(cmd: NodeCmd, rpc: &Client) -> Result<()> {
|
||||||
println!("Nonce : {}", account["nonce"].as_u64().unwrap_or(0));
|
println!("Nonce : {}", account["nonce"].as_u64().unwrap_or(0));
|
||||||
println!("PoN : {}", account["pon_score"].as_f64().unwrap_or(1.0));
|
println!("PoN : {}", account["pon_score"].as_f64().unwrap_or(1.0));
|
||||||
}
|
}
|
||||||
NodeCmd::Stake { amount } => println!("TODO: stake {amount} Shell (Faz 1)"),
|
NodeCmd::Stake { amount, from } => {
|
||||||
NodeCmd::Unstake { amount } => println!("TODO: unstake {amount} Shell (Faz 1)"),
|
let result = stake_op(rpc, &from, true, amount).await?;
|
||||||
|
println!("Staked {} Shell: {}", amount, result["tx_id"].as_str().unwrap_or("?"));
|
||||||
|
}
|
||||||
|
NodeCmd::Unstake { amount, from } => {
|
||||||
|
let result = stake_op(rpc, &from, false, amount).await?;
|
||||||
|
println!("Unstaked {} Shell: {}", amount, result["tx_id"].as_str().unwrap_or("?"));
|
||||||
|
}
|
||||||
|
NodeCmd::Register { stake, from } => {
|
||||||
|
let (key, sender) = load_key(&from)?;
|
||||||
|
let account = rpc.call("nu_getAccount", vec![json!(sender)]).await?;
|
||||||
|
let nonce: u64 = account["nonce"].as_u64().unwrap_or(0);
|
||||||
|
|
||||||
|
let payload = json!({ "ValidatorRegister": { "stake": stake } });
|
||||||
|
let result = send_tx(&key, &sender, nonce, payload, rpc).await?;
|
||||||
|
println!("Registered as validator: {}", result["tx_id"].as_str().unwrap_or("?"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn stake_op(rpc: &Client, label: &str, stake: bool, amount: u64) -> Result<serde_json::Value> {
|
||||||
|
let (key, sender) = load_key(label)?;
|
||||||
|
let account = rpc.call("nu_getAccount", vec![json!(sender)]).await?;
|
||||||
|
let nonce: u64 = account["nonce"].as_u64().unwrap_or(0);
|
||||||
|
let payload = json!({ "StakeOp": { "stake": stake, "amount": amount } });
|
||||||
|
send_tx(&key, &sender, nonce, payload, rpc).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_key(label: &str) -> Result<(SigningKey, String)> {
|
||||||
|
let dir = crate::config::keystore_dir();
|
||||||
|
let path = dir.join(format!("{label}.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());
|
||||||
|
Ok((key, sender))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_tx(
|
||||||
|
key: &SigningKey,
|
||||||
|
sender: &str,
|
||||||
|
nonce: u64,
|
||||||
|
payload: serde_json::Value,
|
||||||
|
rpc: &Client,
|
||||||
|
) -> Result<serde_json::Value> {
|
||||||
|
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 = json!({
|
||||||
|
"tx_id": tx_id,
|
||||||
|
"sender": sender,
|
||||||
|
"nonce": nonce,
|
||||||
|
"fee": 0,
|
||||||
|
"sig": sig_bytes,
|
||||||
|
"payload": payload,
|
||||||
|
});
|
||||||
|
Ok(rpc.call("nu_sendRawTx", vec![tx]).await?)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
use k256::ecdsa::{signature::Signer, Signature, SigningKey};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use crate::rpc::Client;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{keystore, rpc::Client};
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum StoryCmd {
|
pub enum StoryCmd {
|
||||||
|
|
@ -20,20 +24,23 @@ pub enum StoryCmd {
|
||||||
},
|
},
|
||||||
/// List nodes currently open for voting
|
/// List nodes currently open for voting
|
||||||
Pending,
|
Pending,
|
||||||
/// Submit a new story node (stub — requires wallet integration)
|
/// Submit a new story node
|
||||||
Submit {
|
Submit {
|
||||||
#[arg(long)] story_id: String,
|
#[arg(long)] story_id: String,
|
||||||
#[arg(long)] parent_id: String,
|
#[arg(long)] parent_id: String,
|
||||||
#[arg(long)] ipfs_hash: String,
|
#[arg(long)] ipfs_hash: String,
|
||||||
|
#[arg(long, default_value = "default")] from: String,
|
||||||
},
|
},
|
||||||
/// Register to vote on a node (stub — Faz 1)
|
/// Register to vote on a node (locks stake)
|
||||||
Register {
|
Register {
|
||||||
#[arg(long)] node_id: String,
|
#[arg(long)] node_id: String,
|
||||||
|
#[arg(long, default_value = "default")] from: String,
|
||||||
},
|
},
|
||||||
/// Cast a vote (stub — Faz 1)
|
/// Cast a vote on a node
|
||||||
Vote {
|
Vote {
|
||||||
#[arg(long)] node_id: String,
|
#[arg(long)] node_id: String,
|
||||||
#[arg(long)] approve: bool,
|
#[arg(long)] approve: bool,
|
||||||
|
#[arg(long, default_value = "default")] from: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,15 +62,79 @@ pub async fn run(cmd: StoryCmd, rpc: &Client) -> Result<()> {
|
||||||
let result = rpc.call("nu_listPendingVotes", vec![]).await?;
|
let result = rpc.call("nu_listPendingVotes", vec![]).await?;
|
||||||
println!("{}", serde_json::to_string_pretty(&result)?);
|
println!("{}", serde_json::to_string_pretty(&result)?);
|
||||||
}
|
}
|
||||||
StoryCmd::Submit { story_id, parent_id, ipfs_hash } => {
|
StoryCmd::Submit { story_id, parent_id, ipfs_hash, from } => {
|
||||||
println!("TODO: submit node story={story_id} parent={parent_id} hash={ipfs_hash} (requires wallet — Faz 1)");
|
let (key, sender) = load_key(&from)?;
|
||||||
|
let account = rpc.call("nu_getAccount", vec![json!(sender)]).await?;
|
||||||
|
let nonce: u64 = account["nonce"].as_u64().unwrap_or(0);
|
||||||
|
let temp_id = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
|
let payload = json!({
|
||||||
|
"NodeSubmit": {
|
||||||
|
"story_id": story_id,
|
||||||
|
"parent_node_id": parent_id,
|
||||||
|
"content_hash": ipfs_hash,
|
||||||
|
"temp_id": temp_id,
|
||||||
|
"entry_fee": 25_000u64,
|
||||||
}
|
}
|
||||||
StoryCmd::Register { node_id } => {
|
});
|
||||||
println!("TODO: register vote for node={node_id} (Faz 1)");
|
let result = send_tx(&key, &sender, nonce, payload, rpc).await?;
|
||||||
|
println!("Submitted: tx={} temp_id={temp_id}", result["tx_id"].as_str().unwrap_or("?"));
|
||||||
}
|
}
|
||||||
StoryCmd::Vote { node_id, approve } => {
|
StoryCmd::Register { node_id, from } => {
|
||||||
println!("TODO: vote node={node_id} approve={approve} (Faz 1)");
|
let (key, sender) = load_key(&from)?;
|
||||||
|
let account = rpc.call("nu_getAccount", vec![json!(sender)]).await?;
|
||||||
|
let nonce: u64 = account["nonce"].as_u64().unwrap_or(0);
|
||||||
|
|
||||||
|
let payload = json!({
|
||||||
|
"VoteRegister": { "node_id": node_id, "stake_lock": 10_000u64 }
|
||||||
|
});
|
||||||
|
let result = send_tx(&key, &sender, nonce, payload, rpc).await?;
|
||||||
|
println!("Registered: {}", result["tx_id"].as_str().unwrap_or("?"));
|
||||||
|
}
|
||||||
|
StoryCmd::Vote { node_id, approve, from } => {
|
||||||
|
let (key, sender) = load_key(&from)?;
|
||||||
|
let account = rpc.call("nu_getAccount", vec![json!(sender)]).await?;
|
||||||
|
let nonce: u64 = account["nonce"].as_u64().unwrap_or(0);
|
||||||
|
|
||||||
|
let payload = json!({
|
||||||
|
"VoteCast": { "node_id": node_id, "approve": approve }
|
||||||
|
});
|
||||||
|
let result = send_tx(&key, &sender, nonce, payload, rpc).await?;
|
||||||
|
println!("Vote cast: {}", result["tx_id"].as_str().unwrap_or("?"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_key(label: &str) -> Result<(SigningKey, String)> {
|
||||||
|
let dir = crate::config::keystore_dir();
|
||||||
|
let path = dir.join(format!("{label}.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());
|
||||||
|
Ok((key, sender))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_tx(
|
||||||
|
key: &SigningKey,
|
||||||
|
sender: &str,
|
||||||
|
nonce: u64,
|
||||||
|
payload: serde_json::Value,
|
||||||
|
rpc: &Client,
|
||||||
|
) -> Result<serde_json::Value> {
|
||||||
|
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 = json!({
|
||||||
|
"tx_id": tx_id,
|
||||||
|
"sender": sender,
|
||||||
|
"nonce": nonce,
|
||||||
|
"fee": 0,
|
||||||
|
"sig": sig_bytes,
|
||||||
|
"payload": payload,
|
||||||
|
});
|
||||||
|
Ok(rpc.call("nu_sendRawTx", vec![tx]).await?)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use k256::ecdsa::SigningKey;
|
use k256::ecdsa::{signature::Signer, Signature, SigningKey};
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
use crate::keystore;
|
use crate::keystore;
|
||||||
|
|
||||||
|
|
@ -17,7 +18,7 @@ pub enum WalletCmd {
|
||||||
#[arg(long, default_value = "default")]
|
#[arg(long, default_value = "default")]
|
||||||
label: String,
|
label: String,
|
||||||
},
|
},
|
||||||
/// Send NUT tokens (stub — Faz 1 tx signing)
|
/// Send Shell tokens to another address
|
||||||
Send {
|
Send {
|
||||||
#[arg(long)] to: String,
|
#[arg(long)] to: String,
|
||||||
#[arg(long)] amount: u64,
|
#[arg(long)] amount: u64,
|
||||||
|
|
@ -57,7 +58,40 @@ pub async fn run(cmd: WalletCmd) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletCmd::Send { to, amount, from } => {
|
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(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,11 @@ enum Command {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
action: commands::story::StoryCmd,
|
action: commands::story::StoryCmd,
|
||||||
},
|
},
|
||||||
|
/// NFT operations
|
||||||
|
Nft {
|
||||||
|
#[command(subcommand)]
|
||||||
|
action: commands::nft::NftCmd,
|
||||||
|
},
|
||||||
/// Genesis operations (dev only)
|
/// Genesis operations (dev only)
|
||||||
Genesis {
|
Genesis {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
|
@ -50,6 +55,7 @@ async fn main() -> Result<()> {
|
||||||
Command::Wallet { action } => commands::wallet::run(action).await?,
|
Command::Wallet { action } => commands::wallet::run(action).await?,
|
||||||
Command::Node { action } => commands::node::run(action, &rpc).await?,
|
Command::Node { action } => commands::node::run(action, &rpc).await?,
|
||||||
Command::Story { action } => commands::story::run(action, &rpc).await?,
|
Command::Story { action } => commands::story::run(action, &rpc).await?,
|
||||||
|
Command::Nft { action } => commands::nft::run(action, &rpc).await?,
|
||||||
Command::Genesis{ action } => commands::genesis::run(action, &rpc).await?,
|
Command::Genesis{ action } => commands::genesis::run(action, &rpc).await?,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue