feat(nft): add nft transfer and collection claim CLI commands
- nu nft transfer --nft-id <id> --to <addr>: signed NftTransfer tx - nu nft claim --nft-ids 0,1,11,115: signed CollectionClaim tx with comma-separated path Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e6616d1cf9
commit
16a4999fdf
3 changed files with 87 additions and 0 deletions
|
|
@ -1,4 +1,5 @@
|
|||
pub mod wallet;
|
||||
pub mod node;
|
||||
pub mod nft;
|
||||
pub mod story;
|
||||
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?)
|
||||
}
|
||||
|
|
@ -34,6 +34,11 @@ enum Command {
|
|||
#[command(subcommand)]
|
||||
action: commands::story::StoryCmd,
|
||||
},
|
||||
/// NFT operations
|
||||
Nft {
|
||||
#[command(subcommand)]
|
||||
action: commands::nft::NftCmd,
|
||||
},
|
||||
/// Genesis operations (dev only)
|
||||
Genesis {
|
||||
#[command(subcommand)]
|
||||
|
|
@ -50,6 +55,7 @@ async fn main() -> Result<()> {
|
|||
Command::Wallet { action } => commands::wallet::run(action).await?,
|
||||
Command::Node { action } => commands::node::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?,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue