main.rs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{
  19. fs,
  20. io::{stdin, Read},
  21. process::exit,
  22. str::FromStr,
  23. sync::Arc,
  24. time::Instant,
  25. };
  26. use prettytable::{format, row, Table};
  27. use smol::stream::StreamExt;
  28. use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
  29. use url::Url;
  30. use darkfi::{
  31. async_daemonize, cli_desc,
  32. rpc::{client::RpcClient, jsonrpc::JsonRequest, util::JsonValue},
  33. tx::Transaction,
  34. util::{parse::encode_base10, path::expand_path},
  35. Result,
  36. };
  37. use darkfi_money_contract::model::Coin;
  38. use darkfi_sdk::{
  39. crypto::TokenId,
  40. pasta::{group::ff::PrimeField, pallas},
  41. };
  42. use darkfi_serial::{deserialize, serialize};
  43. /// Error codes
  44. mod error;
  45. /// darkfid JSON-RPC related methods
  46. mod rpc;
  47. /// CLI utility functions
  48. mod cli_util;
  49. use cli_util::kaching;
  50. /// Wallet functionality related to Money
  51. mod money;
  52. use money::BALANCE_BASE10_DECIMALS;
  53. /// Wallet functionality related to Dao
  54. mod dao;
  55. /// Wallet functionality related to transactions history
  56. mod txs_history;
  57. /// Wallet database operations handler
  58. mod walletdb;
  59. use walletdb::{WalletDb, WalletPtr};
  60. const CONFIG_FILE: &str = "drk_config.toml";
  61. const CONFIG_FILE_CONTENTS: &str = include_str!("../drk_config.toml");
  62. #[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
  63. #[serde(default)]
  64. #[structopt(name = "drk", about = cli_desc!())]
  65. struct Args {
  66. #[structopt(short, long)]
  67. /// Configuration file to use
  68. config: Option<String>,
  69. #[structopt(long, default_value = "~/.local/darkfi/drk/wallet.db")]
  70. /// Path to wallet database
  71. wallet_path: String,
  72. #[structopt(long, default_value = "changeme")]
  73. /// Password for the wallet database
  74. wallet_pass: String,
  75. #[structopt(short, long, default_value = "tcp://127.0.0.1:8340")]
  76. /// darkfid JSON-RPC endpoint
  77. endpoint: Url,
  78. #[structopt(subcommand)]
  79. /// Sub command to execute
  80. command: Subcmd,
  81. #[structopt(short, long)]
  82. /// Set log file to ouput into
  83. log: Option<String>,
  84. #[structopt(short, parse(from_occurrences))]
  85. /// Increase verbosity (-vvv supported)
  86. verbose: u8,
  87. }
  88. #[derive(Clone, Debug, Deserialize, StructOpt)]
  89. enum Subcmd {
  90. /// Fun
  91. Kaching,
  92. /// Send a ping request to the darkfid RPC endpoint
  93. Ping,
  94. // TODO: shell completions
  95. /// Wallet operations
  96. Wallet {
  97. #[structopt(long)]
  98. /// Initialize wallet database
  99. initialize: bool,
  100. #[structopt(long)]
  101. /// Generate a new keypair in the wallet
  102. keygen: bool,
  103. #[structopt(long)]
  104. /// Query the wallet for known balances
  105. balance: bool,
  106. #[structopt(long)]
  107. /// Get the default address in the wallet
  108. address: bool,
  109. #[structopt(long)]
  110. /// Print all the addresses in the wallet
  111. addresses: bool,
  112. #[structopt(long)]
  113. /// Set the default address in the wallet
  114. default_address: Option<usize>,
  115. #[structopt(long)]
  116. /// Print all the secret keys from the wallet
  117. secrets: bool,
  118. #[structopt(long)]
  119. /// Import secret keys from stdin into the wallet, separated by newlines
  120. import_secrets: bool,
  121. #[structopt(long)]
  122. /// Print the Merkle tree in the wallet
  123. tree: bool,
  124. #[structopt(long)]
  125. /// Print all the coins in the wallet
  126. coins: bool,
  127. },
  128. /// Unspend a coin
  129. Unspend {
  130. /// base58-encoded coin to mark as unspent
  131. coin: String,
  132. },
  133. // TODO: Transfer
  134. // TODO: OTC
  135. /// Inspect a transaction from stdin
  136. Inspect,
  137. /// Read a transaction from stdin and broadcast it
  138. Broadcast,
  139. /// This subscription will listen for incoming blocks from darkfid and look
  140. /// through their transactions to see if there's any that interest us.
  141. /// With `drk` we look at transactions calling the money contract so we can
  142. /// find coins sent to us and fill our wallet with the necessary metadata.
  143. Subscribe,
  144. // TODO: DAO
  145. /// Scan the blockchain and parse relevant transactions
  146. Scan {
  147. #[structopt(long)]
  148. /// Reset Merkle tree and start scanning from first block
  149. reset: bool,
  150. #[structopt(long)]
  151. /// List all available checkpoints
  152. list: bool,
  153. #[structopt(long)]
  154. /// Reset Merkle tree to checkpoint index and start scanning
  155. checkpoint: Option<u64>,
  156. },
  157. /// Explorer related subcommands
  158. Explorer {
  159. #[structopt(subcommand)]
  160. /// Sub command to execute
  161. command: ExplorerSubcmd,
  162. },
  163. /// Manage Token aliases
  164. Alias {
  165. #[structopt(subcommand)]
  166. /// Sub command to execute
  167. command: AliasSubcmd,
  168. },
  169. // TODO: Token
  170. }
  171. #[derive(Clone, Debug, Deserialize, StructOpt)]
  172. enum ExplorerSubcmd {
  173. /// Fetch a blockchain transaction by hash
  174. FetchTx {
  175. /// Transaction hash
  176. tx_hash: String,
  177. #[structopt(long)]
  178. /// Print the full transaction information
  179. full: bool,
  180. #[structopt(long)]
  181. /// Encode transaction to base58
  182. encode: bool,
  183. },
  184. /// Read a transaction from stdin and simulate it
  185. SimulateTx,
  186. /// Fetch broadcasted transactions history
  187. TxsHistory {
  188. /// Fetch specific history record (optional)
  189. tx_hash: Option<String>,
  190. #[structopt(long)]
  191. /// Encode specific history record transaction
  192. /// to base58.
  193. encode: bool,
  194. },
  195. }
  196. #[derive(Clone, Debug, Deserialize, StructOpt)]
  197. enum AliasSubcmd {
  198. /// Create a Token alias
  199. Add {
  200. /// Token alias
  201. alias: String,
  202. /// Token to create alias for
  203. token: String,
  204. },
  205. /// Print alias info of optional arguments.
  206. /// If no argument is provided, list all the aliases in the wallet.
  207. Show {
  208. /// Token alias to search for
  209. #[structopt(short, long)]
  210. alias: Option<String>,
  211. /// Token to search alias for
  212. #[structopt(short, long)]
  213. token: Option<String>,
  214. },
  215. /// Remove a Token alias
  216. Remove {
  217. /// Token alias to remove
  218. alias: String,
  219. },
  220. }
  221. /// CLI-util structure
  222. pub struct Drk {
  223. /// Wallet database operations handler
  224. pub wallet: WalletPtr,
  225. /// JSON-RPC client to execute requests to darkfid daemon
  226. pub rpc_client: RpcClient,
  227. }
  228. impl Drk {
  229. async fn new(
  230. wallet_path: String,
  231. wallet_pass: String,
  232. endpoint: Url,
  233. ex: Arc<smol::Executor<'static>>,
  234. ) -> Result<Self> {
  235. // Initialize wallet
  236. let wallet_path = expand_path(&wallet_path)?;
  237. if !wallet_path.exists() {
  238. if let Some(parent) = wallet_path.parent() {
  239. fs::create_dir_all(parent)?;
  240. }
  241. }
  242. let wallet = match WalletDb::new(Some(wallet_path), Some(&wallet_pass)) {
  243. Ok(w) => w,
  244. Err(e) => {
  245. eprintln!("Error initializing wallet: {e:?}");
  246. exit(2);
  247. }
  248. };
  249. // Initialize rpc client
  250. let rpc_client = RpcClient::new(endpoint, ex).await?;
  251. Ok(Self { wallet, rpc_client })
  252. }
  253. /// Initialize wallet with tables for drk
  254. async fn initialize_wallet(&self) -> Result<()> {
  255. let wallet_schema = include_str!("../wallet.sql");
  256. if let Err(e) = self.wallet.exec_batch_sql(wallet_schema).await {
  257. eprintln!("Error initializing wallet: {e:?}");
  258. exit(2);
  259. }
  260. Ok(())
  261. }
  262. /// Auxilliary function to ping configured darkfid daemon for liveness.
  263. async fn ping(&self) -> Result<()> {
  264. eprintln!("Executing ping request to darkfid...");
  265. let latency = Instant::now();
  266. let req = JsonRequest::new("ping", JsonValue::Array(vec![]));
  267. let rep = self.rpc_client.oneshot_request(req).await?;
  268. let latency = latency.elapsed();
  269. eprintln!("Got reply: {rep:?}");
  270. eprintln!("Latency: {latency:?}");
  271. Ok(())
  272. }
  273. }
  274. async_daemonize!(realmain);
  275. async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
  276. match args.command {
  277. Subcmd::Kaching => {
  278. kaching().await;
  279. Ok(())
  280. }
  281. Subcmd::Ping => {
  282. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  283. drk.ping().await
  284. }
  285. Subcmd::Wallet {
  286. initialize,
  287. keygen,
  288. balance,
  289. address,
  290. addresses,
  291. default_address,
  292. secrets,
  293. import_secrets,
  294. tree,
  295. coins,
  296. } => {
  297. if !initialize &&
  298. !keygen &&
  299. !balance &&
  300. !address &&
  301. !addresses &&
  302. default_address.is_none() &&
  303. !secrets &&
  304. !tree &&
  305. !coins &&
  306. !import_secrets
  307. {
  308. eprintln!("Error: You must use at least one flag for this subcommand");
  309. eprintln!("Run with \"wallet -h\" to see the subcommand usage.");
  310. exit(2);
  311. }
  312. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  313. if initialize {
  314. drk.initialize_wallet().await?;
  315. if let Err(e) = drk.initialize_money().await {
  316. eprintln!("Failed to initialize Money: {e:?}");
  317. exit(2);
  318. }
  319. if let Err(e) = drk.initialize_dao().await {
  320. eprintln!("Failed to initialize DAO: {e:?}");
  321. exit(2);
  322. }
  323. return Ok(())
  324. }
  325. if keygen {
  326. if let Err(e) = drk.money_keygen().await {
  327. eprintln!("Failed to generate keypair: {e:?}");
  328. exit(2);
  329. }
  330. return Ok(())
  331. }
  332. if balance {
  333. let balmap = drk.money_balance().await?;
  334. let aliases_map = drk.get_aliases_mapped_by_token().await?;
  335. // Create a prettytable with the new data:
  336. let mut table = Table::new();
  337. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  338. table.set_titles(row!["Token ID", "Aliases", "Balance"]);
  339. for (token_id, balance) in balmap.iter() {
  340. let aliases = match aliases_map.get(token_id) {
  341. Some(a) => a,
  342. None => "-",
  343. };
  344. table.add_row(row![
  345. token_id,
  346. aliases,
  347. encode_base10(*balance, BALANCE_BASE10_DECIMALS)
  348. ]);
  349. }
  350. if table.is_empty() {
  351. eprintln!("No unspent balances found");
  352. } else {
  353. eprintln!("{table}");
  354. }
  355. return Ok(())
  356. }
  357. if address {
  358. let address = match drk.default_address().await {
  359. Ok(a) => a,
  360. Err(e) => {
  361. eprintln!("Failed to fetch default address: {e:?}");
  362. exit(2);
  363. }
  364. };
  365. eprintln!("{address}");
  366. return Ok(())
  367. }
  368. if addresses {
  369. let addresses = drk.addresses().await?;
  370. // Create a prettytable with the new data:
  371. let mut table = Table::new();
  372. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  373. table.set_titles(row!["Key ID", "Public Key", "Secret Key", "Is Default"]);
  374. for (key_id, public_key, secret_key, is_default) in addresses {
  375. let is_default = match is_default {
  376. 1 => "*",
  377. _ => "",
  378. };
  379. table.add_row(row![key_id, public_key, secret_key, is_default]);
  380. }
  381. if table.is_empty() {
  382. eprintln!("No addresses found");
  383. } else {
  384. eprintln!("{table}");
  385. }
  386. return Ok(())
  387. }
  388. if let Some(idx) = default_address {
  389. if let Err(e) = drk.set_default_address(idx).await {
  390. eprintln!("Failed to set default address: {e:?}");
  391. exit(2);
  392. }
  393. return Ok(())
  394. }
  395. if secrets {
  396. let v = drk.get_money_secrets().await?;
  397. for i in v {
  398. eprintln!("{i}");
  399. }
  400. return Ok(())
  401. }
  402. if import_secrets {
  403. let mut secrets = vec![];
  404. let lines = stdin().lines();
  405. for (i, line) in lines.enumerate() {
  406. if let Ok(line) = line {
  407. let bytes = bs58::decode(&line.trim()).into_vec()?;
  408. let Ok(secret) = deserialize(&bytes) else {
  409. eprintln!("Warning: Failed to deserialize secret on line {i}");
  410. continue
  411. };
  412. secrets.push(secret);
  413. }
  414. }
  415. let pubkeys = match drk.import_money_secrets(secrets).await {
  416. Ok(p) => p,
  417. Err(e) => {
  418. eprintln!("Failed to import secret keys into wallet: {e:?}");
  419. exit(2);
  420. }
  421. };
  422. for key in pubkeys {
  423. eprintln!("{key}");
  424. }
  425. return Ok(())
  426. }
  427. if tree {
  428. let tree = drk.get_money_tree().await?;
  429. eprintln!("{tree:#?}");
  430. return Ok(())
  431. }
  432. if coins {
  433. let coins = drk.get_coins(true).await?;
  434. let aliases_map = drk.get_aliases_mapped_by_token().await?;
  435. if coins.is_empty() {
  436. return Ok(())
  437. }
  438. let mut table = Table::new();
  439. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  440. table.set_titles(row![
  441. "Coin",
  442. "Spent",
  443. "Token ID",
  444. "Aliases",
  445. "Value",
  446. "Spend Hook",
  447. "User Data"
  448. ]);
  449. let zero = pallas::Base::zero();
  450. for coin in coins {
  451. let aliases = match aliases_map.get(&coin.0.note.token_id.to_string()) {
  452. Some(a) => a,
  453. None => "-",
  454. };
  455. let spend_hook = if coin.0.note.spend_hook != zero {
  456. bs58::encode(&serialize(&coin.0.note.spend_hook)).into_string().to_string()
  457. } else {
  458. String::from("-")
  459. };
  460. let user_data = if coin.0.note.user_data != zero {
  461. bs58::encode(&serialize(&coin.0.note.user_data)).into_string().to_string()
  462. } else {
  463. String::from("-")
  464. };
  465. table.add_row(row![
  466. bs58::encode(&serialize(&coin.0.coin.inner())).into_string().to_string(),
  467. coin.1,
  468. coin.0.note.token_id,
  469. aliases,
  470. format!("{} ({})", coin.0.note.value, encode_base10(coin.0.note.value, 8)),
  471. spend_hook,
  472. user_data
  473. ]);
  474. }
  475. eprintln!("{table}");
  476. return Ok(())
  477. }
  478. unreachable!()
  479. }
  480. Subcmd::Unspend { coin } => {
  481. let bytes: [u8; 32] = match bs58::decode(&coin).into_vec()?.try_into() {
  482. Ok(b) => b,
  483. Err(e) => {
  484. eprintln!("Invalid coin: {e:?}");
  485. exit(2);
  486. }
  487. };
  488. let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
  489. Some(v) => v,
  490. None => {
  491. eprintln!("Invalid coin");
  492. exit(2);
  493. }
  494. };
  495. let coin = Coin::from(elem);
  496. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  497. if let Err(e) = drk.unspend_coin(&coin).await {
  498. eprintln!("Failed to mark coin as unspent: {e:?}");
  499. exit(2);
  500. }
  501. Ok(())
  502. }
  503. Subcmd::Inspect => {
  504. let mut buf = String::new();
  505. stdin().read_to_string(&mut buf)?;
  506. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  507. let tx: Transaction = deserialize(&bytes)?;
  508. eprintln!("{tx:#?}");
  509. Ok(())
  510. }
  511. Subcmd::Broadcast => {
  512. eprintln!("Reading transaction from stdin...");
  513. let mut buf = String::new();
  514. stdin().read_to_string(&mut buf)?;
  515. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  516. let tx = deserialize(&bytes)?;
  517. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  518. let txid = match drk.broadcast_tx(&tx).await {
  519. Ok(t) => t,
  520. Err(e) => {
  521. eprintln!("Failed to broadcast transaction: {e:?}");
  522. exit(2);
  523. }
  524. };
  525. eprintln!("Transaction ID: {txid}");
  526. Ok(())
  527. }
  528. Subcmd::Subscribe => {
  529. let drk =
  530. Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
  531. .await?;
  532. if let Err(e) = drk.subscribe_blocks(args.endpoint, ex).await {
  533. eprintln!("Block subscription failed: {e:?}");
  534. exit(2);
  535. }
  536. Ok(())
  537. }
  538. Subcmd::Scan { reset, list, checkpoint } => {
  539. let drk =
  540. Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
  541. .await?;
  542. if reset {
  543. eprintln!("Reset requested.");
  544. if let Err(e) = drk.scan_blocks(true).await {
  545. eprintln!("Failed during scanning: {e:?}");
  546. exit(2);
  547. }
  548. eprintln!("Finished scanning blockchain");
  549. return Ok(())
  550. }
  551. if list {
  552. eprintln!("List requested.");
  553. // TODO: implement
  554. return Ok(())
  555. }
  556. if let Some(c) = checkpoint {
  557. eprintln!("Checkpoint requested: {c}");
  558. // TODO: implement
  559. return Ok(())
  560. }
  561. if let Err(e) = drk.scan_blocks(false).await {
  562. eprintln!("Failed during scanning: {e:?}");
  563. exit(2);
  564. }
  565. eprintln!("Finished scanning blockchain");
  566. Ok(())
  567. }
  568. Subcmd::Explorer { command } => match command {
  569. ExplorerSubcmd::FetchTx { tx_hash, full, encode } => {
  570. let tx_hash = blake3::Hash::from_hex(&tx_hash)?;
  571. let drk =
  572. Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
  573. .await?;
  574. let tx = match drk.get_tx(&tx_hash).await {
  575. Ok(tx) => tx,
  576. Err(e) => {
  577. eprintln!("Failed to fetch transaction: {e:?}");
  578. exit(2);
  579. }
  580. };
  581. let Some(tx) = tx else {
  582. eprintln!("Transaction was not found");
  583. exit(1);
  584. };
  585. // Make sure the tx is correct
  586. assert_eq!(tx.hash()?, tx_hash);
  587. if encode {
  588. eprintln!("{}", bs58::encode(&serialize(&tx)).into_string());
  589. exit(1)
  590. }
  591. eprintln!("Transaction ID: {tx_hash}");
  592. if full {
  593. eprintln!("{tx:?}");
  594. }
  595. Ok(())
  596. }
  597. ExplorerSubcmd::SimulateTx => {
  598. eprintln!("Reading transaction from stdin...");
  599. let mut buf = String::new();
  600. stdin().read_to_string(&mut buf)?;
  601. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  602. let tx = deserialize(&bytes)?;
  603. let drk =
  604. Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
  605. .await?;
  606. let is_valid = match drk.simulate_tx(&tx).await {
  607. Ok(b) => b,
  608. Err(e) => {
  609. eprintln!("Failed to simulate tx: {e:?}");
  610. exit(2);
  611. }
  612. };
  613. eprintln!("Transaction ID: {}", tx.hash()?);
  614. eprintln!("State: {}", if is_valid { "valid" } else { "invalid" });
  615. Ok(())
  616. }
  617. ExplorerSubcmd::TxsHistory { tx_hash, encode } => {
  618. let drk =
  619. Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
  620. .await?;
  621. if let Some(c) = tx_hash {
  622. let (tx_hash, status, tx) = drk.get_tx_history_record(&c).await?;
  623. if encode {
  624. println!("{}", bs58::encode(&serialize(&tx)).into_string());
  625. exit(1)
  626. }
  627. eprintln!("Transaction ID: {tx_hash}");
  628. eprintln!("Status: {status}");
  629. eprintln!("{tx:?}");
  630. return Ok(())
  631. }
  632. let map = match drk.get_txs_history().await {
  633. Ok(m) => m,
  634. Err(e) => {
  635. eprintln!("Failed to retrieve transactions history records: {e:?}");
  636. exit(2);
  637. }
  638. };
  639. // Create a prettytable with the new data:
  640. let mut table = Table::new();
  641. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  642. table.set_titles(row!["Transaction Hash", "Status"]);
  643. for (txs_hash, status) in map.iter() {
  644. table.add_row(row![txs_hash, status]);
  645. }
  646. if table.is_empty() {
  647. eprintln!("No transactions found");
  648. } else {
  649. eprintln!("{table}");
  650. }
  651. Ok(())
  652. }
  653. },
  654. Subcmd::Alias { command } => match command {
  655. AliasSubcmd::Add { alias, token } => {
  656. if alias.chars().count() > 5 {
  657. eprintln!("Error: Alias exceeds 5 characters");
  658. exit(2);
  659. }
  660. let token_id = match TokenId::from_str(token.as_str()) {
  661. Ok(t) => t,
  662. Err(e) => {
  663. eprintln!("Invalid Token ID: {e:?}");
  664. exit(2);
  665. }
  666. };
  667. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  668. if let Err(e) = drk.add_alias(alias, token_id).await {
  669. eprintln!("Failed to add alias: {e:?}");
  670. exit(2);
  671. }
  672. Ok(())
  673. }
  674. AliasSubcmd::Show { alias, token } => {
  675. let token_id = match token {
  676. Some(t) => match TokenId::from_str(t.as_str()) {
  677. Ok(t) => Some(t),
  678. Err(e) => {
  679. eprintln!("Invalid Token ID: {e:?}");
  680. exit(2);
  681. }
  682. },
  683. None => None,
  684. };
  685. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  686. let map = drk.get_aliases(alias, token_id).await?;
  687. // Create a prettytable with the new data:
  688. let mut table = Table::new();
  689. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  690. table.set_titles(row!["Alias", "Token ID"]);
  691. for (alias, token_id) in map.iter() {
  692. table.add_row(row![alias, token_id]);
  693. }
  694. if table.is_empty() {
  695. eprintln!("No aliases found");
  696. } else {
  697. eprintln!("{table}");
  698. }
  699. Ok(())
  700. }
  701. AliasSubcmd::Remove { alias } => {
  702. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  703. if let Err(e) = drk.remove_alias(alias).await {
  704. eprintln!("Failed to remove alias: {e:?}");
  705. exit(2);
  706. }
  707. Ok(())
  708. }
  709. },
  710. }
  711. }