main.rs 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312
  1. #![allow(clippy::doc_overindented_list_items)]
  2. use std::fs;
  3. use std::path::PathBuf;
  4. use clap::{Parser, Subcommand};
  5. use getrandom::getrandom;
  6. use nockapp::utils::bytes::Byts;
  7. use nockapp::{system_data_dir, CrownError, NockApp, NockAppError, ToBytesExt};
  8. use nockvm::jets::cold::Nounable;
  9. use nockvm::noun::{Atom, Cell, IndirectAtom, Noun, D, SIG, T};
  10. use tokio::fs as tokio_fs;
  11. use tokio::net::UnixStream;
  12. use tracing::{error, info};
  13. use zkvm_jetpack::hot::produce_prover_hot_state;
  14. mod error;
  15. use kernels::wallet::KERNEL;
  16. use nockapp::driver::*;
  17. use nockapp::kernel::boot::{self, Cli as BootCli};
  18. use nockapp::noun::slab::NounSlab;
  19. use nockapp::utils::make_tas;
  20. use nockapp::wire::{Wire, WireRepr};
  21. use nockapp::{exit_driver, file_driver, markdown_driver, one_punch_driver};
  22. #[derive(Parser, Debug, Clone)]
  23. #[command(author, version, about, long_about = None)]
  24. struct WalletCli {
  25. #[command(flatten)]
  26. boot: BootCli,
  27. #[command(subcommand)]
  28. command: Commands,
  29. #[arg(long, value_name = "PATH")]
  30. nockchain_socket: Option<PathBuf>,
  31. }
  32. #[derive(Debug)]
  33. pub enum WalletWire {
  34. ListNotes,
  35. UpdateBalance,
  36. UpdateBlock,
  37. Exit,
  38. Command(Commands),
  39. }
  40. impl Wire for WalletWire {
  41. const VERSION: u64 = 1;
  42. const SOURCE: &str = "wallet";
  43. fn to_wire(&self) -> WireRepr {
  44. let tags = match self {
  45. WalletWire::ListNotes => vec!["list-notes".into()],
  46. WalletWire::UpdateBalance => vec!["update-balance".into()],
  47. WalletWire::UpdateBlock => vec!["update-block".into()],
  48. WalletWire::Exit => vec!["exit".into()],
  49. WalletWire::Command(command) => {
  50. vec!["command".into(), command.as_wire_tag().into()]
  51. }
  52. };
  53. WireRepr::new(WalletWire::SOURCE, WalletWire::VERSION, tags)
  54. }
  55. }
  56. /// Represents a Noun that the wallet kernel can handle
  57. type CommandNoun<T> = Result<(T, Operation), NockAppError>;
  58. #[derive(Subcommand, Debug, Clone)]
  59. pub enum Commands {
  60. /// Generate a new key pair
  61. Keygen,
  62. /// Derive a child key from the current master key
  63. DeriveChild {
  64. /// Type of key to derive (e.g., "pub", "priv")
  65. #[arg(short, long)]
  66. key_type: String,
  67. /// Index of the child key to derive
  68. #[arg(short, long, value_parser = clap::value_parser!(u64).range(0..=255))]
  69. index: u64,
  70. },
  71. /// Import keys from a file
  72. ImportKeys {
  73. /// Path to the jammed keys file
  74. #[arg(short, long, value_name = "FILE")]
  75. input: String,
  76. },
  77. /// Signs a transaction
  78. SignTx {
  79. /// Path to input bundle file
  80. #[arg(short, long)]
  81. draft: String,
  82. /// Optional key index to use for signing (0-255)
  83. #[arg(short, long, value_parser = clap::value_parser!(u64).range(0..=255))]
  84. index: Option<u64>,
  85. },
  86. /// Generate a master private key from a seed phrase
  87. GenMasterPrivkey {
  88. /// Seed phrase to generate master private key
  89. #[arg(short, long)]
  90. seedphrase: String,
  91. },
  92. /// Generate a master public key from a master private key
  93. GenMasterPubkey {
  94. /// Master private key to generate master public key
  95. #[arg(short, long)]
  96. master_privkey: String,
  97. },
  98. /// Perform a simple scan of the blockchain
  99. Scan {
  100. /// Master public key to scan for
  101. #[arg(short, long)]
  102. master_pubkey: String,
  103. /// Optional search depth (default 100)
  104. #[arg(short, long, default_value = "100")]
  105. search_depth: u64,
  106. /// Include timelocks in scan
  107. #[arg(long, default_value = "false")]
  108. include_timelocks: bool,
  109. /// Include multisig in scan
  110. #[arg(long, default_value = "false")]
  111. include_multisig: bool,
  112. },
  113. /// List all notes in the wallet
  114. ListNotes,
  115. /// List notes by public key
  116. ListNotesByPubkey {
  117. /// Optional public key to filter notes
  118. #[arg(short, long)]
  119. pubkey: Option<String>,
  120. },
  121. /// Perform a simple spend operation
  122. SimpleSpend {
  123. /// Names of notes to spend (comma-separated)
  124. #[arg(long)]
  125. names: String,
  126. /// Recipient addresses (comma-separated)
  127. #[arg(long)]
  128. recipients: String,
  129. /// Amounts to send (comma-separated)
  130. #[arg(long)]
  131. gifts: String,
  132. /// Transaction fee
  133. #[arg(long)]
  134. fee: u64,
  135. },
  136. /// Create a transaction from a draft file
  137. MakeTx {
  138. /// Draft file to create transaction from
  139. #[arg(short, long)]
  140. draft: String,
  141. },
  142. /// Update the wallet balance
  143. UpdateBalance,
  144. /// Import a master public key
  145. ImportMasterPubkey {
  146. /// Base58-encoded public key
  147. #[arg(short, long)]
  148. key: String,
  149. /// Base58-encoded chain code
  150. #[arg(short, long)]
  151. knot: String,
  152. },
  153. /// Lists all public keys in the wallet
  154. ListPubkeys,
  155. /// Show the seed phrase for the current master key
  156. ShowSeedphrase,
  157. /// Show the master public key
  158. ShowMasterPubkey,
  159. /// Show the master private key
  160. ShowMasterPrivkey,
  161. }
  162. impl Commands {
  163. fn as_wire_tag(&self) -> &'static str {
  164. match self {
  165. Commands::Keygen => "keygen",
  166. Commands::DeriveChild { .. } => "derive-child",
  167. Commands::ImportKeys { .. } => "import-keys",
  168. Commands::SignTx { .. } => "sign-tx",
  169. Commands::GenMasterPrivkey { .. } => "gen-master-privkey",
  170. Commands::GenMasterPubkey { .. } => "gen-master-pubkey",
  171. Commands::Scan { .. } => "scan",
  172. Commands::ListNotes => "list-notes",
  173. Commands::ListNotesByPubkey { .. } => "list-notes-by-pubkey",
  174. Commands::SimpleSpend { .. } => "simple-spend",
  175. Commands::MakeTx { .. } => "make-tx",
  176. Commands::UpdateBalance => "update-balance",
  177. Commands::ImportMasterPubkey { .. } => "import-master-pubkey",
  178. Commands::ListPubkeys => "list-pubkeys",
  179. Commands::ShowSeedphrase => "show-seedphrase",
  180. Commands::ShowMasterPubkey => "show-master-pubkey",
  181. Commands::ShowMasterPrivkey => "show-master-privkey",
  182. }
  183. }
  184. }
  185. pub struct Wallet {
  186. app: NockApp,
  187. }
  188. #[derive(Debug, Clone)]
  189. pub enum KeyType {
  190. Pub,
  191. Prv,
  192. }
  193. impl KeyType {
  194. fn to_string(&self) -> &'static str {
  195. match self {
  196. KeyType::Pub => "pub",
  197. KeyType::Prv => "prv",
  198. }
  199. }
  200. }
  201. impl Wallet {
  202. /// Creates a new `Wallet` instance with the given kernel.
  203. ///
  204. /// This wraps the kernel in a NockApp, which exposes a substrate
  205. /// for kernel interaction with IO driver semantics.
  206. ///
  207. /// # Arguments
  208. ///
  209. /// * `kernel` - The kernel to initialize the wallet with.
  210. ///
  211. /// # Returns
  212. ///
  213. /// A new `Wallet` instance with the kernel initialized
  214. /// as a NockApp.
  215. fn new(nockapp: NockApp) -> Self {
  216. Wallet { app: nockapp }
  217. }
  218. /// Wraps a command with sync-run to ensure it runs after block and balance updates
  219. ///
  220. /// # Arguments
  221. ///
  222. /// * `command_noun_slab` - The command noun to wrap
  223. /// * `operation` - The operation type (Poke or Peek)
  224. ///
  225. /// # Returns
  226. ///
  227. /// A result containing the wrapped command noun and operation, or an error
  228. fn wrap_with_sync_run(
  229. command_noun_slab: NounSlab,
  230. operation: Operation,
  231. ) -> Result<(NounSlab, Operation), NockAppError> {
  232. let original_root_noun_clone = unsafe { command_noun_slab.root() };
  233. let mut sync_slab = command_noun_slab.clone();
  234. let sync_tag = make_tas(&mut sync_slab, "sync-run");
  235. let tag_noun = sync_tag.as_noun();
  236. let sync_run_cell = Cell::new(&mut sync_slab, tag_noun, *original_root_noun_clone);
  237. let sync_run_noun = sync_run_cell.as_noun();
  238. sync_slab.set_root(sync_run_noun);
  239. Ok((sync_slab, operation))
  240. }
  241. /// Prepares a wallet command for execution.
  242. ///
  243. /// # Arguments
  244. ///
  245. /// * `command` - The command to execute.
  246. /// * `args` - The arguments for the command.
  247. /// * `operation` - The operation type (Poke or Peek).
  248. /// * `slab` - The NounSlab to use for the command.
  249. ///
  250. /// # Returns
  251. ///
  252. /// A `CommandNoun` containing the prepared NounSlab and operation.
  253. fn wallet(
  254. command: &str,
  255. args: &[Noun],
  256. operation: Operation,
  257. slab: &mut NounSlab,
  258. ) -> CommandNoun<NounSlab> {
  259. let head = make_tas(slab, command).as_noun();
  260. let tail = match args.len() {
  261. 0 => D(0),
  262. 1 => args[0],
  263. _ => T(slab, args),
  264. };
  265. let full = T(slab, &[head, tail]);
  266. slab.set_root(full);
  267. Ok((slab.clone(), operation))
  268. }
  269. /// Generates a new key pair.
  270. ///
  271. /// # Arguments
  272. ///
  273. /// * `entropy` - The entropy to use for key generation.
  274. fn keygen(entropy: &[u8; 32], sal: &[u8; 16]) -> CommandNoun<NounSlab> {
  275. let mut slab = NounSlab::new();
  276. let ent: Byts = Byts::new(entropy.to_vec());
  277. let ent_noun = ent.into_noun(&mut slab);
  278. let sal: Byts = Byts::new(sal.to_vec());
  279. let sal_noun = sal.into_noun(&mut slab);
  280. Self::wallet("keygen", &[ent_noun, sal_noun], Operation::Poke, &mut slab)
  281. }
  282. // Derives a child key from current master key.
  283. //
  284. // # Arguments
  285. //
  286. // * `key_type` - The type of key to derive (e.g., "pub", "priv")
  287. // * `index` - The index of the child key to derive
  288. // TODO: add label if necessary
  289. fn derive_child(key_type: KeyType, index: u64) -> CommandNoun<NounSlab> {
  290. let mut slab = NounSlab::new();
  291. let key_type_noun = make_tas(&mut slab, key_type.to_string()).as_noun();
  292. let index_noun = D(index);
  293. Self::wallet(
  294. "derive-child",
  295. &[key_type_noun, index_noun, SIG],
  296. Operation::Poke,
  297. &mut slab,
  298. )
  299. }
  300. /// Signs a transaction.
  301. ///
  302. /// # Arguments
  303. ///
  304. /// * `draft_path` - Path to the draft file
  305. /// * `index` - Optional index of the key to use for signing
  306. fn sign_tx(draft_path: &str, index: Option<u64>) -> CommandNoun<NounSlab> {
  307. let mut slab = NounSlab::new();
  308. // Validate index is within range (though clap should prevent this)
  309. if let Some(idx) = index {
  310. if idx > 255 {
  311. return Err(CrownError::Unknown("Key index must not exceed 255".into()).into());
  312. }
  313. }
  314. // Read and decode the input bundle
  315. let draft_data = fs::read(draft_path)
  316. .map_err(|e| CrownError::Unknown(format!("Failed to read draft: {}", e)))?;
  317. // Convert the bundle data into a noun using cue
  318. let draft_noun = slab
  319. .cue_into(draft_data.as_bytes()?)
  320. .map_err(|e| CrownError::Unknown(format!("Failed to decode draft: {}", e)))?;
  321. let index_noun = match index {
  322. Some(i) => D(i),
  323. None => D(0),
  324. };
  325. // Generate random entropy
  326. let mut entropy_bytes = [0u8; 32];
  327. getrandom(&mut entropy_bytes).map_err(|e| CrownError::Unknown(e.to_string()))?;
  328. let entropy = from_bytes(&mut slab, &entropy_bytes).as_noun();
  329. Self::wallet(
  330. "sign-tx",
  331. &[draft_noun, index_noun, entropy],
  332. Operation::Poke,
  333. &mut slab,
  334. )
  335. }
  336. /// Generates a master private key from a seed phrase.
  337. ///
  338. /// # Arguments
  339. ///
  340. /// * `seedphrase` - The seed phrase to generate the master private key from.
  341. fn gen_master_privkey(seedphrase: &str) -> CommandNoun<NounSlab> {
  342. let mut slab = NounSlab::new();
  343. let seedphrase_noun = make_tas(&mut slab, seedphrase).as_noun();
  344. Self::wallet(
  345. "gen-master-privkey",
  346. &[seedphrase_noun],
  347. Operation::Poke,
  348. &mut slab,
  349. )
  350. }
  351. /// Generates a master public key from a master private key.
  352. ///
  353. /// # Arguments
  354. ///
  355. /// * `master_privkey` - The master private key to generate the public key from.
  356. fn gen_master_pubkey(master_privkey: &str) -> CommandNoun<NounSlab> {
  357. let mut slab = NounSlab::new();
  358. let master_privkey_noun = make_tas(&mut slab, master_privkey).as_noun();
  359. Self::wallet(
  360. "gen-master-pubkey",
  361. &[master_privkey_noun],
  362. Operation::Poke,
  363. &mut slab,
  364. )
  365. }
  366. /// Imports keys.
  367. ///
  368. /// # Arguments
  369. ///
  370. /// * `input_path` - Path to jammed keys file
  371. fn import_keys(input_path: &str) -> CommandNoun<NounSlab> {
  372. let mut slab = NounSlab::new();
  373. let key_data = fs::read(input_path)
  374. .map_err(|e| CrownError::Unknown(format!("Failed to read master pubkeys: {}", e)))?;
  375. let pubkey_noun = slab
  376. .cue_into(key_data.as_bytes()?)
  377. .map_err(|e| CrownError::Unknown(format!("Failed to decode master pubkeys: {}", e)))?;
  378. Self::wallet("import-keys", &[pubkey_noun], Operation::Poke, &mut slab)
  379. }
  380. /// Performs a simple scan of the blockchain.
  381. ///
  382. /// # Arguments
  383. ///
  384. /// * `master_pubkey` - The master public key to scan for.
  385. /// * `search_depth` - How many addresses to scan (default 100)
  386. fn scan(
  387. master_pubkey: &str,
  388. search_depth: u64,
  389. include_timelocks: bool,
  390. include_multisig: bool,
  391. ) -> CommandNoun<NounSlab> {
  392. let mut slab = NounSlab::new();
  393. let master_pubkey_noun = make_tas(&mut slab, master_pubkey).as_noun();
  394. let search_depth_noun = D(search_depth);
  395. let include_timelocks_noun = D(include_timelocks as u64);
  396. let include_multisig_noun = D(include_multisig as u64);
  397. Self::wallet(
  398. "scan",
  399. &[
  400. master_pubkey_noun, search_depth_noun, include_timelocks_noun,
  401. include_multisig_noun,
  402. ],
  403. Operation::Poke,
  404. &mut slab,
  405. )
  406. }
  407. /// Performs a simple spend operation by creating transaction inputs from notes.
  408. ///
  409. /// Takes a list of note names, recipient addresses, and gift amounts to create
  410. /// transaction inputs. The fee is subtracted from the first note that has sufficient
  411. /// assets to cover both the fee and its corresponding gift amount.
  412. ///
  413. /// # Arguments
  414. ///
  415. /// * `names` - Comma-separated list of note name pairs in format "[first last]"
  416. /// Example: "[first1 last1],[first2 last2]"
  417. ///
  418. /// * `recipients` - Comma-separated list of recipient $locks
  419. /// Example: "[1 pk1],[2 pk2,pk3,pk4]"
  420. /// A simple comma-separated list is also supported: "pk1,pk2,pk3",
  421. /// where it is presumed that all recipients are single-signature,
  422. /// that is to say, it is the same as "[1 pk1],[1 pk2],[1 pk3]"
  423. ///
  424. /// * `gifts` - Comma-separated list of amounts to send to each recipient
  425. /// Example: "100,200"
  426. ///
  427. /// * `fee` - Transaction fee to be subtracted from one of the input notes
  428. ///
  429. /// # Returns
  430. ///
  431. /// Returns a `CommandNoun` containing:
  432. /// - A `NounSlab` with the encoded simple-spend command
  433. /// - The `Operation` type (Poke)
  434. ///
  435. /// # Errors
  436. ///
  437. /// Returns `NockAppError` if:
  438. /// - Name pairs are not properly formatted as "[first last]"
  439. /// - Number of names, recipients, and gifts don't match
  440. /// - Any input parsing fails
  441. ///
  442. /// # Example
  443. ///
  444. /// ```no_run
  445. /// let names = "[first1 last1],[first2 last2]";
  446. /// let recipients = "[1 pk1],[2 pk2,pk3,pk4]";
  447. /// let gifts = "100,200";
  448. /// let fee = 10;
  449. /// wallet.simple_spend(names.to_string(), recipients.to_string(), gifts.to_string(), fee)?;
  450. /// ```
  451. fn simple_spend(
  452. names: String,
  453. recipients: String,
  454. gifts: String,
  455. fee: u64,
  456. ) -> CommandNoun<NounSlab> {
  457. let mut slab = NounSlab::new();
  458. // Split the comma-separated inputs
  459. // Each name should be in format "[first last]"
  460. let names_vec: Vec<(String, String)> = names
  461. .split(',')
  462. .filter_map(|pair| {
  463. let pair = pair.trim();
  464. if pair.starts_with('[') && pair.ends_with(']') {
  465. let inner = &pair[1..pair.len() - 1];
  466. let parts: Vec<&str> = inner.split_whitespace().collect();
  467. if parts.len() == 2 {
  468. Some((parts[0].to_string(), parts[1].to_string()))
  469. } else {
  470. None
  471. }
  472. } else {
  473. None
  474. }
  475. })
  476. .collect();
  477. // Convert recipients to list of [number pubkeys] pairs
  478. let recipients_vec: Vec<(u64, Vec<String>)> = if recipients.contains('[') {
  479. // Parse complex format: "[1 pk1],[2 pk2,pk3,pk4]"
  480. recipients
  481. .split(',')
  482. .filter_map(|pair| {
  483. let pair = pair.trim();
  484. if pair.starts_with('[') && pair.ends_with(']') {
  485. let inner = &pair[1..pair.len() - 1];
  486. let mut parts = inner.splitn(2, ' ');
  487. // Parse the number
  488. let number = parts.next()?.parse().ok()?;
  489. // Parse the pubkeys
  490. let pubkeys = parts
  491. .next()?
  492. .split(',')
  493. .map(|s| s.trim().to_string())
  494. .collect();
  495. Some((number, pubkeys))
  496. } else {
  497. None
  498. }
  499. })
  500. .collect()
  501. } else {
  502. // Parse simple format: "pk1,pk2,pk3"
  503. recipients
  504. .split(',')
  505. .map(|addr| (1, vec![addr.trim().to_string()]))
  506. .collect()
  507. };
  508. let gifts_vec: Vec<u64> = gifts.split(',').filter_map(|s| s.parse().ok()).collect();
  509. // Verify equal lengths
  510. if names_vec.len() != recipients_vec.len() || names_vec.len() != gifts_vec.len() {
  511. return Err(CrownError::Unknown(
  512. "Invalid input - names, recipients, and gifts must have the same length"
  513. .to_string(),
  514. )
  515. .into());
  516. }
  517. // Convert names to list of pairs
  518. let names_noun = names_vec
  519. .into_iter()
  520. .rev()
  521. .fold(D(0), |acc, (first, last)| {
  522. // Create a tuple [first_name last_name] for each name pair
  523. let first_noun = make_tas(&mut slab, &first).as_noun();
  524. let last_noun = make_tas(&mut slab, &last).as_noun();
  525. let name_pair = T(&mut slab, &[first_noun, last_noun]);
  526. Cell::new(&mut slab, name_pair, acc).as_noun()
  527. });
  528. // Convert recipients to list
  529. let recipients_noun = recipients_vec
  530. .into_iter()
  531. .rev()
  532. .fold(D(0), |acc, (num, pubkeys)| {
  533. // Create the inner list of pubkeys
  534. let pubkeys_noun = pubkeys.into_iter().rev().fold(D(0), |acc, pubkey| {
  535. let pubkey_noun = make_tas(&mut slab, &pubkey).as_noun();
  536. Cell::new(&mut slab, pubkey_noun, acc).as_noun()
  537. });
  538. // Create the pair of [number pubkeys_list]
  539. let pair = T(&mut slab, &[D(num), pubkeys_noun]);
  540. Cell::new(&mut slab, pair, acc).as_noun()
  541. });
  542. // Convert gifts to list
  543. let gifts_noun = gifts_vec.into_iter().rev().fold(D(0), |acc, amount| {
  544. Cell::new(&mut slab, D(amount), acc).as_noun()
  545. });
  546. let fee_noun = D(fee);
  547. Self::wallet(
  548. "simple-spend",
  549. &[names_noun, recipients_noun, gifts_noun, fee_noun],
  550. Operation::Poke,
  551. &mut slab,
  552. )
  553. }
  554. fn update_balance() -> CommandNoun<NounSlab> {
  555. let mut slab = NounSlab::new();
  556. Self::wallet("update-balance", &[], Operation::Poke, &mut slab)
  557. }
  558. /// Lists all notes in the wallet.
  559. ///
  560. /// Retrieves and displays all notes from the wallet's balance, sorted by assets.
  561. fn list_notes() -> CommandNoun<NounSlab> {
  562. let mut slab = NounSlab::new();
  563. Self::wallet("list-notes", &[], Operation::Poke, &mut slab)
  564. }
  565. /// Imports a master public key.
  566. ///
  567. /// # Arguments
  568. ///
  569. /// * `key` - Base58-encoded public key
  570. /// * `knot` - Base58-encoded chain code
  571. fn import_master_pubkey(key: &str, knot: &str) -> CommandNoun<NounSlab> {
  572. let mut slab = NounSlab::new();
  573. let key_noun = make_tas(&mut slab, key).as_noun();
  574. let knot_noun = make_tas(&mut slab, knot).as_noun();
  575. Self::wallet(
  576. "import-master-pubkey",
  577. &[key_noun, knot_noun],
  578. Operation::Poke,
  579. &mut slab,
  580. )
  581. }
  582. /// Creates a transaction from a draft file.
  583. ///
  584. /// # Arguments
  585. ///
  586. /// * `draft_path` - Path to the draft file to create transaction from
  587. fn make_tx(draft_path: &str) -> CommandNoun<NounSlab> {
  588. // Read and decode the draft file
  589. let draft_data = fs::read(draft_path)
  590. .map_err(|e| CrownError::Unknown(format!("Failed to read draft file: {}", e)))?;
  591. let mut slab = NounSlab::new();
  592. let draft_noun = slab
  593. .cue_into(draft_data.as_bytes()?)
  594. .map_err(|e| CrownError::Unknown(format!("Failed to decode draft data: {}", e)))?;
  595. Self::wallet("make-tx", &[draft_noun], Operation::Poke, &mut slab)
  596. }
  597. /// Lists all public keys in the wallet.
  598. fn list_pubkeys() -> CommandNoun<NounSlab> {
  599. let mut slab = NounSlab::new();
  600. Self::wallet("list-pubkeys", &[], Operation::Poke, &mut slab)
  601. }
  602. /// Lists notes by public key
  603. fn list_notes_by_pubkey(pubkey: &str) -> CommandNoun<NounSlab> {
  604. let mut slab = NounSlab::new();
  605. let pubkey_noun = make_tas(&mut slab, pubkey).as_noun();
  606. Self::wallet(
  607. "list-notes-by-pubkey",
  608. &[pubkey_noun],
  609. Operation::Poke,
  610. &mut slab,
  611. )
  612. }
  613. /// Shows the seed phrase for the current master key.
  614. fn show_seedphrase() -> CommandNoun<NounSlab> {
  615. let mut slab = NounSlab::new();
  616. Self::wallet("show-seedphrase", &[], Operation::Poke, &mut slab)
  617. }
  618. /// Shows the master public key.
  619. fn show_master_pubkey() -> CommandNoun<NounSlab> {
  620. let mut slab = NounSlab::new();
  621. Self::wallet("show-master-pubkey", &[], Operation::Poke, &mut slab)
  622. }
  623. /// Shows the master private key.
  624. fn show_master_privkey() -> CommandNoun<NounSlab> {
  625. let mut slab = NounSlab::new();
  626. Self::wallet("show-master-privkey", &[], Operation::Poke, &mut slab)
  627. }
  628. }
  629. pub async fn wallet_data_dir() -> Result<PathBuf, NockAppError> {
  630. let wallet_data_dir = system_data_dir().join("wallet");
  631. if !wallet_data_dir.exists() {
  632. tokio_fs::create_dir_all(&wallet_data_dir)
  633. .await
  634. .map_err(|e| {
  635. CrownError::Unknown(format!("Failed to create wallet data directory: {}", e))
  636. })?;
  637. }
  638. Ok(wallet_data_dir)
  639. }
  640. #[tokio::main]
  641. async fn main() -> Result<(), NockAppError> {
  642. let cli = WalletCli::parse();
  643. boot::init_default_tracing(&cli.boot.clone()); // Init tracing early
  644. let prover_hot_state = produce_prover_hot_state();
  645. let data_dir = wallet_data_dir().await?;
  646. let kernel = boot::setup(
  647. KERNEL,
  648. Some(cli.boot.clone()),
  649. prover_hot_state.as_slice(),
  650. "wallet",
  651. Some(data_dir),
  652. )
  653. .await
  654. .map_err(|e| CrownError::Unknown(format!("Kernel setup failed: {}", e)))?;
  655. let mut wallet = Wallet::new(kernel);
  656. // Determine if this command requires chain synchronization
  657. let requires_sync = match &cli.command {
  658. // Commands that DON'T need sync
  659. Commands::Keygen
  660. | Commands::DeriveChild { .. }
  661. | Commands::ImportKeys { .. }
  662. | Commands::SignTx { .. }
  663. | Commands::MakeTx { .. }
  664. | Commands::GenMasterPrivkey { .. }
  665. | Commands::GenMasterPubkey { .. }
  666. | Commands::ImportMasterPubkey { .. }
  667. | Commands::ListPubkeys
  668. | Commands::ShowSeedphrase
  669. | Commands::ShowMasterPubkey
  670. | Commands::ShowMasterPrivkey
  671. | Commands::SimpleSpend { .. } => false,
  672. // All other commands DO need sync
  673. _ => true,
  674. };
  675. // Check if we need sync but don't have a socket
  676. if requires_sync && cli.nockchain_socket.is_none() {
  677. return Err(CrownError::Unknown(
  678. "This command requires connection to a nockchain node. Please provide --nockchain-socket"
  679. .to_string()
  680. ).into());
  681. }
  682. // Generate the command noun and operation
  683. let poke = match &cli.command {
  684. Commands::Keygen => {
  685. let mut entropy = [0u8; 32];
  686. let mut salt = [0u8; 16];
  687. getrandom(&mut entropy).map_err(|e| CrownError::Unknown(e.to_string()))?;
  688. getrandom(&mut salt).map_err(|e| CrownError::Unknown(e.to_string()))?;
  689. Wallet::keygen(&entropy, &salt)
  690. }
  691. Commands::DeriveChild { key_type, index } => {
  692. // Validate key_type is either "pub" or "priv"
  693. let key_type = match key_type.as_str() {
  694. "pub" => KeyType::Pub,
  695. "priv" => KeyType::Prv,
  696. _ => {
  697. return Err(CrownError::Unknown(
  698. "Key type must be either 'pub' or 'priv'".into(),
  699. )
  700. .into())
  701. }
  702. };
  703. Wallet::derive_child(key_type, *index)
  704. }
  705. Commands::SignTx { draft, index } => Wallet::sign_tx(draft, *index),
  706. Commands::ImportKeys { input } => Wallet::import_keys(input),
  707. Commands::GenMasterPrivkey { seedphrase } => Wallet::gen_master_privkey(seedphrase),
  708. Commands::GenMasterPubkey { master_privkey } => Wallet::gen_master_pubkey(master_privkey),
  709. Commands::Scan {
  710. master_pubkey,
  711. search_depth,
  712. include_timelocks,
  713. include_multisig,
  714. } => Wallet::scan(
  715. master_pubkey, *search_depth, *include_timelocks, *include_multisig,
  716. ),
  717. Commands::ListNotes => Wallet::list_notes(),
  718. Commands::ListNotesByPubkey { pubkey } => {
  719. if let Some(pk) = pubkey {
  720. Wallet::list_notes_by_pubkey(pk)
  721. } else {
  722. return Err(CrownError::Unknown("Public key is required".into()).into());
  723. }
  724. }
  725. Commands::SimpleSpend {
  726. names,
  727. recipients,
  728. gifts,
  729. fee,
  730. } => Wallet::simple_spend(names.clone(), recipients.clone(), gifts.clone(), *fee),
  731. Commands::MakeTx { draft } => Wallet::make_tx(draft),
  732. Commands::UpdateBalance => Wallet::update_balance(),
  733. Commands::ImportMasterPubkey { key, knot } => Wallet::import_master_pubkey(key, knot),
  734. Commands::ListPubkeys => Wallet::list_pubkeys(),
  735. Commands::ShowSeedphrase => Wallet::show_seedphrase(),
  736. Commands::ShowMasterPubkey => Wallet::show_master_pubkey(),
  737. Commands::ShowMasterPrivkey => Wallet::show_master_privkey(),
  738. }?;
  739. // If this command requires sync and we have a socket, wrap it with sync-run
  740. let final_poke = if requires_sync && cli.nockchain_socket.is_some() {
  741. Wallet::wrap_with_sync_run(poke.0, poke.1)?
  742. } else {
  743. poke
  744. };
  745. wallet
  746. .app
  747. .add_io_driver(one_punch_driver(final_poke.0, final_poke.1))
  748. .await;
  749. {
  750. if let Some(socket_path) = cli.nockchain_socket {
  751. match UnixStream::connect(&socket_path).await {
  752. Ok(stream) => {
  753. info!("Connected to nockchain NPC socket at {:?}", socket_path);
  754. wallet
  755. .app
  756. .add_io_driver(nockapp::npc_client_driver(stream))
  757. .await;
  758. }
  759. Err(e) => {
  760. error!(
  761. "Failed to connect to nockchain NPC socket at {:?}: {}\n\
  762. This could mean:\n\
  763. 1. Nockchain is not running\n\
  764. 2. The socket path is incorrect\n\
  765. 3. The socket file exists but is stale (try removing it)\n\
  766. 4. Insufficient permissions to access the socket",
  767. socket_path, e
  768. );
  769. }
  770. }
  771. }
  772. wallet.app.add_io_driver(file_driver()).await;
  773. wallet.app.add_io_driver(markdown_driver()).await;
  774. wallet.app.add_io_driver(exit_driver()).await;
  775. wallet.app.run().await?;
  776. Ok(())
  777. }
  778. }
  779. pub fn from_bytes(stack: &mut NounSlab, bytes: &[u8]) -> Atom {
  780. unsafe {
  781. let mut tas_atom = IndirectAtom::new_raw_bytes(stack, bytes.len(), bytes.as_ptr());
  782. tas_atom.normalize_as_atom()
  783. }
  784. }
  785. // TODO: all these tests need to also validate the results and not
  786. // just ensure that the wallet can be poked with the expected noun.
  787. #[allow(warnings)]
  788. #[cfg(test)]
  789. mod tests {
  790. use std::sync::Once;
  791. use nockapp::kernel::boot::{self, Cli as BootCli};
  792. use nockapp::wire::SystemWire;
  793. use nockapp::{exit_driver, Bytes};
  794. use tokio::sync::mpsc;
  795. use super::*;
  796. static INIT: Once = Once::new();
  797. fn init_tracing() {
  798. INIT.call_once(|| {
  799. let cli = boot::default_boot_cli(true);
  800. boot::init_default_tracing(&cli);
  801. });
  802. }
  803. #[tokio::test]
  804. #[cfg_attr(miri, ignore)]
  805. async fn test_keygen() -> Result<(), NockAppError> {
  806. init_tracing();
  807. let cli = BootCli::parse_from(&["--new"]);
  808. let prover_hot_state = produce_prover_hot_state();
  809. let nockapp = boot::setup(
  810. KERNEL,
  811. Some(cli.clone()),
  812. prover_hot_state.as_slice(),
  813. "wallet",
  814. None,
  815. )
  816. .await
  817. .map_err(|e| CrownError::Unknown(e.to_string()))?;
  818. let mut wallet = Wallet::new(nockapp);
  819. let mut entropy = [0u8; 32];
  820. let mut salt = [0u8; 16];
  821. getrandom(&mut entropy).map_err(|e| CrownError::Unknown(e.to_string()))?;
  822. getrandom(&mut salt).map_err(|e| CrownError::Unknown(e.to_string()))?;
  823. let (noun, op) = Wallet::keygen(&entropy, &salt)?;
  824. let wire = WalletWire::Command(Commands::Keygen).to_wire();
  825. let keygen_result = wallet.app.poke(wire, noun.clone()).await?;
  826. println!("keygen result: {:?}", keygen_result);
  827. assert!(
  828. keygen_result.len() == 2,
  829. "Expected keygen result to be a list of 2 noun slabs - markdown and exit"
  830. );
  831. let exit_cause = unsafe { keygen_result[1].root() };
  832. let code = exit_cause.as_cell()?.tail();
  833. assert!(unsafe { code.raw_equals(&D(0)) }, "Expected exit code 0");
  834. Ok(())
  835. }
  836. #[tokio::test]
  837. #[cfg_attr(miri, ignore)]
  838. async fn test_derive_child() -> Result<(), NockAppError> {
  839. init_tracing();
  840. let cli = BootCli::parse_from(&["--new"]);
  841. let prover_hot_state = produce_prover_hot_state();
  842. let nockapp = boot::setup(
  843. KERNEL,
  844. Some(cli.clone()),
  845. prover_hot_state.as_slice(),
  846. "wallet",
  847. None,
  848. )
  849. .await
  850. .map_err(|e| CrownError::Unknown(e.to_string()))?;
  851. let mut wallet = Wallet::new(nockapp);
  852. let key_type = KeyType::Prv;
  853. // Generate a new key pair
  854. let mut entropy = [0u8; 32];
  855. let mut salt = [0u8; 16];
  856. let (noun, op) = Wallet::keygen(&entropy, &salt)?;
  857. let wire = WalletWire::Command(Commands::Keygen).to_wire();
  858. let _ = wallet.app.poke(wire, noun.clone()).await?;
  859. // Derive a child key
  860. let index = 0;
  861. let (noun, op) = Wallet::derive_child(key_type.clone(), index)?;
  862. let wire = WalletWire::Command(Commands::DeriveChild {
  863. key_type: key_type.clone().to_string().to_owned(),
  864. index,
  865. })
  866. .to_wire();
  867. let derive_result = wallet.app.poke(wire, noun.clone()).await?;
  868. assert!(
  869. derive_result.len() == 1,
  870. "Expected derive result to be a list of 1 noun slab"
  871. );
  872. let exit_cause = unsafe { derive_result[0].root() };
  873. let code = exit_cause.as_cell()?.tail();
  874. assert!(unsafe { code.raw_equals(&D(0)) }, "Expected exit code 0");
  875. Ok(())
  876. }
  877. // TODO make this a real test by creating and signing a real draft
  878. #[tokio::test]
  879. #[ignore]
  880. async fn test_sign_tx() -> Result<(), NockAppError> {
  881. init_tracing();
  882. let cli = BootCli::parse_from(&[""]);
  883. let nockapp = boot::setup(KERNEL, Some(cli.clone()), &[], "wallet", None)
  884. .await
  885. .map_err(|e| CrownError::Unknown(e.to_string()))?;
  886. let mut wallet = Wallet::new(nockapp);
  887. // Create a temporary input bundle file
  888. let bundle_path = "test_bundle.jam";
  889. let test_data = vec![0u8; 32]; // TODO make this a real input bundle
  890. fs::write(bundle_path, &test_data).map_err(|e| NockAppError::IoError(e))?;
  891. let wire = WalletWire::Command(Commands::SignTx {
  892. draft: bundle_path.to_string(),
  893. index: None,
  894. })
  895. .to_wire();
  896. // Test signing with valid indices
  897. let (noun, op) = Wallet::sign_tx(bundle_path, None)?;
  898. let sign_result = wallet.app.poke(wire, noun.clone()).await?;
  899. println!("sign_result: {:?}", sign_result);
  900. let wire = WalletWire::Command(Commands::SignTx {
  901. draft: bundle_path.to_string(),
  902. index: Some(1),
  903. })
  904. .to_wire();
  905. let (noun, op) = Wallet::sign_tx(bundle_path, Some(1))?;
  906. let sign_result = wallet.app.poke(wire, noun.clone()).await?;
  907. println!("sign_result: {:?}", sign_result);
  908. let wire = WalletWire::Command(Commands::SignTx {
  909. draft: bundle_path.to_string(),
  910. index: Some(255),
  911. })
  912. .to_wire();
  913. let (noun, op) = Wallet::sign_tx(bundle_path, Some(255))?;
  914. let sign_result = wallet.app.poke(wire, noun.clone()).await?;
  915. println!("sign_result: {:?}", sign_result);
  916. // Cleanup
  917. fs::remove_file(bundle_path).map_err(|e| NockAppError::IoError(e))?;
  918. Ok(())
  919. }
  920. // Tests for Cold Side Commands
  921. #[tokio::test]
  922. #[cfg_attr(miri, ignore)]
  923. async fn test_gen_master_privkey() -> Result<(), NockAppError> {
  924. init_tracing();
  925. let cli = BootCli::parse_from(&[""]);
  926. let nockapp = boot::setup(KERNEL, Some(cli.clone()), &[], "wallet", None)
  927. .await
  928. .map_err(|e| CrownError::Unknown(e.to_string()))?;
  929. let mut wallet = Wallet::new(nockapp);
  930. let seedphrase = "correct horse battery staple";
  931. let (noun, op) = Wallet::gen_master_privkey(seedphrase)?;
  932. println!("privkey_slab: {:?}", noun);
  933. let wire = WalletWire::Command(Commands::GenMasterPrivkey {
  934. seedphrase: seedphrase.to_string(),
  935. })
  936. .to_wire();
  937. let privkey_result = wallet.app.poke(wire, noun.clone()).await?;
  938. println!("privkey_result: {:?}", privkey_result);
  939. Ok(())
  940. }
  941. #[tokio::test]
  942. #[cfg_attr(miri, ignore)]
  943. async fn test_gen_master_pubkey() -> Result<(), NockAppError> {
  944. init_tracing();
  945. let cli = BootCli::parse_from(&[""]);
  946. let nockapp = boot::setup(KERNEL, Some(cli.clone()), &[], "wallet", None)
  947. .await
  948. .map_err(|e| CrownError::Unknown(e.to_string()))?;
  949. let mut wallet = Wallet::new(nockapp);
  950. let master_privkey = "privkey123";
  951. let (noun, op) = Wallet::gen_master_pubkey(master_privkey)?;
  952. let wire = WalletWire::Command(Commands::GenMasterPubkey {
  953. master_privkey: master_privkey.to_string(),
  954. })
  955. .to_wire();
  956. let pubkey_result = wallet.app.poke(wire, noun.clone()).await?;
  957. println!("pubkey_result: {:?}", pubkey_result);
  958. Ok(())
  959. }
  960. // Tests for Hot Side Commands
  961. // TODO: fix this test by adding a real key file
  962. #[tokio::test]
  963. #[ignore]
  964. async fn test_import_keys() -> Result<(), NockAppError> {
  965. init_tracing();
  966. let cli = BootCli::parse_from(&["--new"]);
  967. let nockapp = boot::setup(KERNEL, Some(cli.clone()), &[], "wallet", None)
  968. .await
  969. .map_err(|e| CrownError::Unknown(e.to_string()))?;
  970. let mut wallet = Wallet::new(nockapp);
  971. // Create test key file
  972. let test_path = "test_keys.jam";
  973. let test_data = vec![0u8; 32]; // TODO: Use real jammed key data
  974. fs::write(test_path, &test_data).expect(&format!(
  975. "Called `expect()` at {}:{} (git sha: {})",
  976. file!(),
  977. line!(),
  978. option_env!("GIT_SHA").unwrap_or("unknown")
  979. ));
  980. let (noun, op) = Wallet::import_keys(test_path)?;
  981. let wire = SystemWire.to_wire();
  982. let import_result = wallet.app.poke(wire, noun.clone()).await?;
  983. fs::remove_file(test_path).expect(&format!(
  984. "Called `expect()` at {}:{} (git sha: {})",
  985. file!(),
  986. line!(),
  987. option_env!("GIT_SHA").unwrap_or("unknown")
  988. ));
  989. println!("import result: {:?}", import_result);
  990. assert!(
  991. !import_result.is_empty(),
  992. "Expected non-empty import result"
  993. );
  994. Ok(())
  995. }
  996. #[tokio::test]
  997. #[cfg_attr(miri, ignore)]
  998. async fn test_simple_scan() -> Result<(), NockAppError> {
  999. init_tracing();
  1000. let cli = BootCli::parse_from(&[""]);
  1001. let nockapp = boot::setup(KERNEL, Some(cli.clone()), &[], "wallet", None)
  1002. .await
  1003. .map_err(|e| CrownError::Unknown(e.to_string()))?;
  1004. let mut wallet = Wallet::new(nockapp);
  1005. let master_pubkey = "pubkey123";
  1006. let (noun, op) = Wallet::scan(master_pubkey, 100, false, false)?;
  1007. let wire = WalletWire::Command(Commands::Scan {
  1008. master_pubkey: master_pubkey.to_string(),
  1009. search_depth: 100,
  1010. include_timelocks: false,
  1011. include_multisig: false,
  1012. })
  1013. .to_wire();
  1014. let scan_result = wallet.app.poke(wire, noun.clone()).await?;
  1015. println!("scan_result: {:?}", scan_result);
  1016. Ok(())
  1017. }
  1018. // TODO: fix this test
  1019. #[tokio::test]
  1020. #[ignore]
  1021. async fn test_simple_spend_multisig_format() -> Result<(), NockAppError> {
  1022. init_tracing();
  1023. let cli = BootCli::parse_from(&[""]);
  1024. let nockapp = boot::setup(KERNEL, Some(cli.clone()), &[], "wallet", None)
  1025. .await
  1026. .map_err(|e| CrownError::Unknown(e.to_string()))?;
  1027. let mut wallet = Wallet::new(nockapp);
  1028. let names = "[first1 last1],[first2 last2]".to_string();
  1029. let recipients = "[1 pk1],[2 pk2,pk3,pk4]".to_string();
  1030. let gifts = "1,2".to_string();
  1031. let fee = 1;
  1032. let (noun, op) =
  1033. Wallet::simple_spend(names.clone(), recipients.clone(), gifts.clone(), fee)?;
  1034. let wire = WalletWire::Command(Commands::SimpleSpend {
  1035. names: names.clone(),
  1036. recipients: recipients.clone(),
  1037. gifts: gifts.clone(),
  1038. fee: fee.clone(),
  1039. })
  1040. .to_wire();
  1041. let spend_result = wallet.app.poke(wire, noun.clone()).await?;
  1042. println!("spend_result: {:?}", spend_result);
  1043. Ok(())
  1044. }
  1045. #[tokio::test]
  1046. #[cfg_attr(miri, ignore)]
  1047. async fn test_simple_spend_single_sig_format() -> Result<(), NockAppError> {
  1048. let cli = BootCli::parse_from(&[""]);
  1049. let nockapp = boot::setup(KERNEL, Some(cli.clone()), &[], "wallet", None)
  1050. .await
  1051. .map_err(|e| CrownError::Unknown(e.to_string()))?;
  1052. init_tracing();
  1053. let mut wallet = Wallet::new(nockapp);
  1054. // these should be valid names of notes in the wallet balance
  1055. let names = "[Amt4GcpYievY4PXHfffiWriJ1sYfTXFkyQsGzbzwMVzewECWDV3Ad8Q BJnaDB3koU7ruYVdWCQqkFYQ9e3GXhFsDYjJ1vSmKFdxzf6Y87DzP4n]".to_string();
  1056. let recipients = "EHmKL2U3vXfS5GYAY5aVnGdukfDWwvkQPCZXnjvZVShsSQi3UAuA4tQ".to_string();
  1057. let gifts = "0".to_string();
  1058. let fee = 0;
  1059. // generate keys
  1060. let (genkey_noun, genkey_op) = Wallet::gen_master_privkey("correct horse battery staple")?;
  1061. let (spend_noun, spend_op) =
  1062. Wallet::simple_spend(names.clone(), recipients.clone(), gifts.clone(), fee)?;
  1063. let wire1 = WalletWire::Command(Commands::GenMasterPrivkey {
  1064. seedphrase: "correct horse battery staple".to_string(),
  1065. })
  1066. .to_wire();
  1067. let genkey_result = wallet.app.poke(wire1, genkey_noun.clone()).await?;
  1068. println!("genkey_result: {:?}", genkey_result);
  1069. let wire2 = WalletWire::Command(Commands::SimpleSpend {
  1070. names: names.clone(),
  1071. recipients: recipients.clone(),
  1072. gifts: gifts.clone(),
  1073. fee: fee.clone(),
  1074. })
  1075. .to_wire();
  1076. let spend_result = wallet.app.poke(wire2, spend_noun.clone()).await?;
  1077. println!("spend_result: {:?}", spend_result);
  1078. Ok(())
  1079. }
  1080. #[tokio::test]
  1081. #[cfg_attr(miri, ignore)]
  1082. async fn test_update_balance() -> Result<(), NockAppError> {
  1083. init_tracing();
  1084. let cli = BootCli::parse_from(&["--new"]);
  1085. let nockapp = boot::setup(KERNEL, Some(cli.clone()), &[], "wallet", None)
  1086. .await
  1087. .map_err(|e| CrownError::Unknown(e.to_string()))?;
  1088. let mut wallet = Wallet::new(nockapp);
  1089. let (noun, _) = Wallet::update_balance()?;
  1090. let wire = WalletWire::Command(Commands::UpdateBalance {}).to_wire();
  1091. let update_result = wallet.app.poke(wire, noun.clone()).await?;
  1092. println!("update_result: {:?}", update_result);
  1093. Ok(())
  1094. }
  1095. #[tokio::test]
  1096. #[cfg_attr(miri, ignore)]
  1097. async fn test_list_notes() -> Result<(), NockAppError> {
  1098. init_tracing();
  1099. let cli = BootCli::parse_from(&[""]);
  1100. let nockapp = boot::setup(KERNEL, Some(cli.clone()), &[], "wallet", None)
  1101. .await
  1102. .map_err(|e| CrownError::Unknown(e.to_string()))?;
  1103. let mut wallet = Wallet::new(nockapp);
  1104. // Test listing notes
  1105. let (noun, op) = Wallet::list_notes()?;
  1106. let wire = WalletWire::Command(Commands::ListNotes {}).to_wire();
  1107. let list_result = wallet.app.poke(wire, noun.clone()).await?;
  1108. println!("list_result: {:?}", list_result);
  1109. Ok(())
  1110. }
  1111. // TODO: fix this test by adding a real draft
  1112. #[tokio::test]
  1113. #[ignore]
  1114. async fn test_make_tx_from_draft() -> Result<(), NockAppError> {
  1115. init_tracing();
  1116. let cli = BootCli::parse_from(&[""]);
  1117. let nockapp = boot::setup(KERNEL, Some(cli.clone()), &[], "wallet", None)
  1118. .await
  1119. .map_err(|e| CrownError::Unknown(e.to_string()))?;
  1120. let mut wallet = Wallet::new(nockapp);
  1121. // use the draft in .drafts/
  1122. let draft_path = ".drafts/test_draft.draft";
  1123. let test_data = vec![0u8; 32]; // TODO: Use real draft data
  1124. fs::write(draft_path, &test_data).expect(&format!(
  1125. "Called `expect()` at {}:{} (git sha: {})",
  1126. file!(),
  1127. line!(),
  1128. option_env!("GIT_SHA").unwrap_or("unknown")
  1129. ));
  1130. let (noun, op) = Wallet::make_tx(draft_path)?;
  1131. let wire = WalletWire::Command(Commands::MakeTx {
  1132. draft: draft_path.to_string(),
  1133. })
  1134. .to_wire();
  1135. let tx_result = wallet.app.poke(wire, noun.clone()).await?;
  1136. fs::remove_file(draft_path).expect(&format!(
  1137. "Called `expect()` at {}:{} (git sha: {})",
  1138. file!(),
  1139. line!(),
  1140. option_env!("GIT_SHA").unwrap_or("unknown")
  1141. ));
  1142. println!("transaction result: {:?}", tx_result);
  1143. assert!(
  1144. !tx_result.is_empty(),
  1145. "Expected non-empty transaction result"
  1146. );
  1147. Ok(())
  1148. }
  1149. }