main.rs 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621
  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 rand::rngs::OsRng;
  28. use smol::stream::StreamExt;
  29. use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
  30. use url::Url;
  31. use darkfi::{
  32. async_daemonize, cli_desc,
  33. rpc::{client::RpcClient, jsonrpc::JsonRequest, util::JsonValue},
  34. tx::Transaction,
  35. util::{
  36. encoding::base64,
  37. parse::{decode_base10, encode_base10},
  38. path::expand_path,
  39. },
  40. zk::halo2::Field,
  41. Result,
  42. };
  43. use darkfi_money_contract::model::{Coin, TokenId};
  44. use darkfi_sdk::{
  45. crypto::{FuncId, PublicKey, SecretKey},
  46. pasta::{group::ff::PrimeField, pallas},
  47. };
  48. use darkfi_serial::{deserialize_async, serialize_async};
  49. /// Error codes
  50. mod error;
  51. /// darkfid JSON-RPC related methods
  52. mod rpc;
  53. /// Payment methods
  54. mod transfer;
  55. /// Swap methods
  56. mod swap;
  57. use swap::PartialSwapData;
  58. /// Token methods
  59. mod token;
  60. /// CLI utility functions
  61. mod cli_util;
  62. use cli_util::{generate_completions, kaching, parse_token_pair, parse_value_pair};
  63. /// Wallet functionality related to Money
  64. mod money;
  65. use money::BALANCE_BASE10_DECIMALS;
  66. /// Wallet functionality related to Dao
  67. mod dao;
  68. use dao::DaoParams;
  69. /// Wallet functionality related to transactions history
  70. mod txs_history;
  71. /// Wallet database operations handler
  72. mod walletdb;
  73. use walletdb::{WalletDb, WalletPtr};
  74. const CONFIG_FILE: &str = "drk_config.toml";
  75. const CONFIG_FILE_CONTENTS: &str = include_str!("../drk_config.toml");
  76. // Dev Note: when adding/modifying args here,
  77. // don't forget to update cli_util::generate_completions()
  78. #[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
  79. #[serde(default)]
  80. #[structopt(name = "drk", about = cli_desc!())]
  81. struct Args {
  82. #[structopt(short, long)]
  83. /// Configuration file to use
  84. config: Option<String>,
  85. #[structopt(long, default_value = "~/.local/darkfi/drk/wallet.db")]
  86. /// Path to wallet database
  87. wallet_path: String,
  88. #[structopt(long, default_value = "changeme")]
  89. /// Password for the wallet database
  90. wallet_pass: String,
  91. #[structopt(short, long, default_value = "tcp://127.0.0.1:8340")]
  92. /// darkfid JSON-RPC endpoint
  93. endpoint: Url,
  94. #[structopt(subcommand)]
  95. /// Sub command to execute
  96. command: Subcmd,
  97. #[structopt(short, long)]
  98. /// Set log file to ouput into
  99. log: Option<String>,
  100. #[structopt(short, parse(from_occurrences))]
  101. /// Increase verbosity (-vvv supported)
  102. verbose: u8,
  103. }
  104. // Dev Note: when adding/modifying commands here,
  105. // don't forget to update cli_util::generate_completions()
  106. #[derive(Clone, Debug, Deserialize, StructOpt)]
  107. enum Subcmd {
  108. /// Fun
  109. Kaching,
  110. /// Send a ping request to the darkfid RPC endpoint
  111. Ping,
  112. /// Generate a SHELL completion script and print to stdout
  113. Completions {
  114. /// The Shell you want to generate script for
  115. shell: String,
  116. },
  117. /// Wallet operations
  118. Wallet {
  119. #[structopt(long)]
  120. /// Initialize wallet database
  121. initialize: bool,
  122. #[structopt(long)]
  123. /// Generate a new keypair in the wallet
  124. keygen: bool,
  125. #[structopt(long)]
  126. /// Query the wallet for known balances
  127. balance: bool,
  128. #[structopt(long)]
  129. /// Get the default address in the wallet
  130. address: bool,
  131. #[structopt(long)]
  132. /// Print all the addresses in the wallet
  133. addresses: bool,
  134. #[structopt(long)]
  135. /// Set the default address in the wallet
  136. default_address: Option<usize>,
  137. #[structopt(long)]
  138. /// Print all the secret keys from the wallet
  139. secrets: bool,
  140. #[structopt(long)]
  141. /// Import secret keys from stdin into the wallet, separated by newlines
  142. import_secrets: bool,
  143. #[structopt(long)]
  144. /// Print the Merkle tree in the wallet
  145. tree: bool,
  146. #[structopt(long)]
  147. /// Print all the coins in the wallet
  148. coins: bool,
  149. },
  150. /// Unspend a coin
  151. Unspend {
  152. /// base58-encoded coin to mark as unspent
  153. coin: String,
  154. },
  155. /// Create a payment transaction
  156. Transfer {
  157. /// Amount to send
  158. amount: String,
  159. /// Token ID to send
  160. token: String,
  161. /// Recipient address
  162. recipient: String,
  163. },
  164. /// OTC atomic swap
  165. Otc {
  166. #[structopt(subcommand)]
  167. /// Sub command to execute
  168. command: OtcSubcmd,
  169. },
  170. /// Inspect a transaction from stdin
  171. Inspect,
  172. /// Read a transaction from stdin and broadcast it
  173. Broadcast,
  174. /// This subscription will listen for incoming blocks from darkfid and look
  175. /// through their transactions to see if there's any that interest us.
  176. /// With `drk` we look at transactions calling the money contract so we can
  177. /// find coins sent to us and fill our wallet with the necessary metadata.
  178. Subscribe,
  179. /// DAO functionalities
  180. Dao {
  181. #[structopt(subcommand)]
  182. /// Sub command to execute
  183. command: DaoSubcmd,
  184. },
  185. /// Scan the blockchain and parse relevant transactions
  186. Scan {
  187. #[structopt(long)]
  188. /// Reset Merkle tree and start scanning from first block
  189. reset: bool,
  190. #[structopt(long)]
  191. /// List all available checkpoints
  192. list: bool,
  193. #[structopt(long)]
  194. /// Reset Merkle tree to checkpoint index and start scanning
  195. checkpoint: Option<u64>,
  196. },
  197. /// Explorer related subcommands
  198. Explorer {
  199. #[structopt(subcommand)]
  200. /// Sub command to execute
  201. command: ExplorerSubcmd,
  202. },
  203. /// Manage Token aliases
  204. Alias {
  205. #[structopt(subcommand)]
  206. /// Sub command to execute
  207. command: AliasSubcmd,
  208. },
  209. /// Token functionalities
  210. Token {
  211. #[structopt(subcommand)]
  212. /// Sub command to execute
  213. command: TokenSubcmd,
  214. },
  215. }
  216. #[derive(Clone, Debug, Deserialize, StructOpt)]
  217. enum OtcSubcmd {
  218. /// Initialize the first half of the atomic swap
  219. Init {
  220. /// Value pair to send:recv (11.55:99.42)
  221. #[structopt(short, long)]
  222. value_pair: String,
  223. /// Token pair to send:recv (f00:b4r)
  224. #[structopt(short, long)]
  225. token_pair: String,
  226. },
  227. /// Build entire swap tx given the first half from stdin
  228. Join,
  229. /// Inspect a swap half or the full swap tx from stdin
  230. Inspect,
  231. /// Sign a transaction given from stdin as the first-half
  232. Sign,
  233. }
  234. #[derive(Clone, Debug, Deserialize, StructOpt)]
  235. enum DaoSubcmd {
  236. /// Create DAO parameters
  237. Create {
  238. /// The minimum amount of governance tokens needed to open a proposal for this DAO
  239. proposer_limit: String,
  240. /// Minimal threshold of participating total tokens needed for a proposal to pass
  241. quorum: String,
  242. /// The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)
  243. approval_ratio: f64,
  244. /// DAO's governance token ID
  245. gov_token_id: String,
  246. },
  247. /// View DAO data from stdin
  248. View,
  249. /// Import DAO data from stdin
  250. Import {
  251. /// Named identifier for the DAO
  252. dao_name: String,
  253. },
  254. /// List imported DAOs (or info about a specific one)
  255. List {
  256. /// Numeric identifier for the DAO (optional)
  257. dao_alias: Option<String>,
  258. },
  259. /// Show the balance of a DAO
  260. Balance {
  261. /// Name or numeric identifier for the DAO
  262. dao_alias: String,
  263. },
  264. /// Mint an imported DAO on-chain
  265. Mint {
  266. /// Name or numeric identifier for the DAO
  267. dao_alias: String,
  268. },
  269. /// Create a proposal for a DAO
  270. Propose {
  271. /// Name or numeric identifier for the DAO
  272. dao_alias: String,
  273. /// Pubkey to send tokens to with proposal success
  274. recipient: String,
  275. /// Amount to send from DAO with proposal success
  276. amount: String,
  277. /// Token ID to send from DAO with proposal success
  278. token: String,
  279. },
  280. /// List DAO proposals
  281. Proposals {
  282. /// Name or numeric identifier for the DAO
  283. dao_alias: String,
  284. },
  285. /// View a DAO proposal data
  286. Proposal {
  287. /// Name or numeric identifier for the DAO
  288. dao_alias: String,
  289. /// Numeric identifier for the proposal
  290. proposal_id: u64,
  291. },
  292. /// Vote on a given proposal
  293. Vote {
  294. /// Name or numeric identifier for the DAO
  295. dao_alias: String,
  296. /// Numeric identifier for the proposal
  297. proposal_id: u64,
  298. /// Vote (0 for NO, 1 for YES)
  299. vote: u8,
  300. /// Vote weight (amount of governance tokens)
  301. vote_weight: String,
  302. },
  303. /// Execute a DAO proposal
  304. Exec {
  305. /// Name or numeric identifier for the DAO
  306. dao_alias: String,
  307. /// Numeric identifier for the proposal
  308. proposal_id: u64,
  309. },
  310. }
  311. #[derive(Clone, Debug, Deserialize, StructOpt)]
  312. enum ExplorerSubcmd {
  313. /// Fetch a blockchain transaction by hash
  314. FetchTx {
  315. /// Transaction hash
  316. tx_hash: String,
  317. #[structopt(long)]
  318. /// Print the full transaction information
  319. full: bool,
  320. #[structopt(long)]
  321. /// Encode transaction to base58
  322. encode: bool,
  323. },
  324. /// Read a transaction from stdin and simulate it
  325. SimulateTx,
  326. /// Fetch broadcasted transactions history
  327. TxsHistory {
  328. /// Fetch specific history record (optional)
  329. tx_hash: Option<String>,
  330. #[structopt(long)]
  331. /// Encode specific history record transaction to base58
  332. encode: bool,
  333. },
  334. }
  335. #[derive(Clone, Debug, Deserialize, StructOpt)]
  336. enum AliasSubcmd {
  337. /// Create a Token alias
  338. Add {
  339. /// Token alias
  340. alias: String,
  341. /// Token to create alias for
  342. token: String,
  343. },
  344. /// Print alias info of optional arguments.
  345. /// If no argument is provided, list all the aliases in the wallet.
  346. Show {
  347. /// Token alias to search for
  348. #[structopt(short, long)]
  349. alias: Option<String>,
  350. /// Token to search alias for
  351. #[structopt(short, long)]
  352. token: Option<String>,
  353. },
  354. /// Remove a Token alias
  355. Remove {
  356. /// Token alias to remove
  357. alias: String,
  358. },
  359. }
  360. #[derive(Clone, Debug, Deserialize, StructOpt)]
  361. enum TokenSubcmd {
  362. /// Import a mint authority secret from stdin
  363. Import,
  364. /// Generate a new mint authority
  365. GenerateMint,
  366. /// List token IDs with available mint authorities
  367. List,
  368. /// Mint tokens
  369. Mint {
  370. /// Token ID to mint
  371. token: String,
  372. /// Amount to mint
  373. amount: String,
  374. /// Recipient of the minted tokens
  375. recipient: String,
  376. },
  377. /// Freeze a token mint
  378. Freeze {
  379. /// Token ID to freeze
  380. token: String,
  381. },
  382. }
  383. /// CLI-util structure
  384. pub struct Drk {
  385. /// Wallet database operations handler
  386. pub wallet: WalletPtr,
  387. /// JSON-RPC client to execute requests to darkfid daemon
  388. pub rpc_client: RpcClient,
  389. }
  390. impl Drk {
  391. async fn new(
  392. wallet_path: String,
  393. wallet_pass: String,
  394. endpoint: Url,
  395. ex: Arc<smol::Executor<'static>>,
  396. ) -> Result<Self> {
  397. // Script kiddies protection
  398. if wallet_pass == "changeme" {
  399. eprintln!("Please don't use default wallet password...");
  400. exit(2);
  401. }
  402. // Initialize wallet
  403. let wallet_path = expand_path(&wallet_path)?;
  404. if !wallet_path.exists() {
  405. if let Some(parent) = wallet_path.parent() {
  406. fs::create_dir_all(parent)?;
  407. }
  408. }
  409. let wallet = match WalletDb::new(Some(wallet_path), Some(&wallet_pass)) {
  410. Ok(w) => w,
  411. Err(e) => {
  412. eprintln!("Error initializing wallet: {e:?}");
  413. exit(2);
  414. }
  415. };
  416. // Initialize rpc client
  417. let rpc_client = RpcClient::new(endpoint, ex).await?;
  418. Ok(Self { wallet, rpc_client })
  419. }
  420. /// Initialize wallet with tables for drk
  421. async fn initialize_wallet(&self) -> Result<()> {
  422. let wallet_schema = include_str!("../wallet.sql");
  423. if let Err(e) = self.wallet.exec_batch_sql(wallet_schema).await {
  424. eprintln!("Error initializing wallet: {e:?}");
  425. exit(2);
  426. }
  427. Ok(())
  428. }
  429. /// Auxiliary function to ping configured darkfid daemon for liveness.
  430. async fn ping(&self) -> Result<()> {
  431. println!("Executing ping request to darkfid...");
  432. let latency = Instant::now();
  433. let req = JsonRequest::new("ping", JsonValue::Array(vec![]));
  434. let rep = self.rpc_client.oneshot_request(req).await?;
  435. let latency = latency.elapsed();
  436. println!("Got reply: {rep:?}");
  437. println!("Latency: {latency:?}");
  438. Ok(())
  439. }
  440. }
  441. async_daemonize!(realmain);
  442. async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
  443. match args.command {
  444. Subcmd::Kaching => {
  445. kaching().await;
  446. Ok(())
  447. }
  448. Subcmd::Ping => {
  449. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  450. drk.ping().await
  451. }
  452. Subcmd::Completions { shell } => generate_completions(&shell),
  453. Subcmd::Wallet {
  454. initialize,
  455. keygen,
  456. balance,
  457. address,
  458. addresses,
  459. default_address,
  460. secrets,
  461. import_secrets,
  462. tree,
  463. coins,
  464. } => {
  465. if !initialize &&
  466. !keygen &&
  467. !balance &&
  468. !address &&
  469. !addresses &&
  470. default_address.is_none() &&
  471. !secrets &&
  472. !tree &&
  473. !coins &&
  474. !import_secrets
  475. {
  476. eprintln!("Error: You must use at least one flag for this subcommand");
  477. eprintln!("Run with \"wallet -h\" to see the subcommand usage.");
  478. exit(2);
  479. }
  480. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  481. if initialize {
  482. drk.initialize_wallet().await?;
  483. if let Err(e) = drk.initialize_money().await {
  484. eprintln!("Failed to initialize Money: {e:?}");
  485. exit(2);
  486. }
  487. if let Err(e) = drk.initialize_dao().await {
  488. eprintln!("Failed to initialize DAO: {e:?}");
  489. exit(2);
  490. }
  491. return Ok(())
  492. }
  493. if keygen {
  494. if let Err(e) = drk.money_keygen().await {
  495. eprintln!("Failed to generate keypair: {e:?}");
  496. exit(2);
  497. }
  498. return Ok(())
  499. }
  500. if balance {
  501. let balmap = drk.money_balance().await?;
  502. let aliases_map = drk.get_aliases_mapped_by_token().await?;
  503. // Create a prettytable with the new data:
  504. let mut table = Table::new();
  505. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  506. table.set_titles(row!["Token ID", "Aliases", "Balance"]);
  507. for (token_id, balance) in balmap.iter() {
  508. let aliases = match aliases_map.get(token_id) {
  509. Some(a) => a,
  510. None => "-",
  511. };
  512. table.add_row(row![
  513. token_id,
  514. aliases,
  515. encode_base10(*balance, BALANCE_BASE10_DECIMALS)
  516. ]);
  517. }
  518. if table.is_empty() {
  519. println!("No unspent balances found");
  520. } else {
  521. println!("{table}");
  522. }
  523. return Ok(())
  524. }
  525. if address {
  526. let address = match drk.default_address().await {
  527. Ok(a) => a,
  528. Err(e) => {
  529. eprintln!("Failed to fetch default address: {e:?}");
  530. exit(2);
  531. }
  532. };
  533. println!("{address}");
  534. return Ok(())
  535. }
  536. if addresses {
  537. let addresses = drk.addresses().await?;
  538. // Create a prettytable with the new data:
  539. let mut table = Table::new();
  540. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  541. table.set_titles(row!["Key ID", "Public Key", "Secret Key", "Is Default"]);
  542. for (key_id, public_key, secret_key, is_default) in addresses {
  543. let is_default = match is_default {
  544. 1 => "*",
  545. _ => "",
  546. };
  547. table.add_row(row![key_id, public_key, secret_key, is_default]);
  548. }
  549. if table.is_empty() {
  550. println!("No addresses found");
  551. } else {
  552. println!("{table}");
  553. }
  554. return Ok(())
  555. }
  556. if let Some(idx) = default_address {
  557. if let Err(e) = drk.set_default_address(idx).await {
  558. eprintln!("Failed to set default address: {e:?}");
  559. exit(2);
  560. }
  561. return Ok(())
  562. }
  563. if secrets {
  564. let v = drk.get_money_secrets().await?;
  565. for i in v {
  566. println!("{i}");
  567. }
  568. return Ok(())
  569. }
  570. if import_secrets {
  571. let mut secrets = vec![];
  572. let lines = stdin().lines();
  573. for (i, line) in lines.enumerate() {
  574. if let Ok(line) = line {
  575. let bytes = bs58::decode(&line.trim()).into_vec()?;
  576. let Ok(secret) = deserialize_async(&bytes).await else {
  577. println!("Warning: Failed to deserialize secret on line {i}");
  578. continue
  579. };
  580. secrets.push(secret);
  581. }
  582. }
  583. let pubkeys = match drk.import_money_secrets(secrets).await {
  584. Ok(p) => p,
  585. Err(e) => {
  586. eprintln!("Failed to import secret keys into wallet: {e:?}");
  587. exit(2);
  588. }
  589. };
  590. for key in pubkeys {
  591. println!("{key}");
  592. }
  593. return Ok(())
  594. }
  595. if tree {
  596. let tree = drk.get_money_tree().await?;
  597. println!("{tree:#?}");
  598. return Ok(())
  599. }
  600. if coins {
  601. let coins = drk.get_coins(true).await?;
  602. let aliases_map = drk.get_aliases_mapped_by_token().await?;
  603. if coins.is_empty() {
  604. return Ok(())
  605. }
  606. let mut table = Table::new();
  607. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  608. table.set_titles(row![
  609. "Coin",
  610. "Spent",
  611. "Token ID",
  612. "Aliases",
  613. "Value",
  614. "Spend Hook",
  615. "User Data"
  616. ]);
  617. for coin in coins {
  618. let aliases = match aliases_map.get(&coin.0.note.token_id.to_string()) {
  619. Some(a) => a,
  620. None => "-",
  621. };
  622. let spend_hook = if coin.0.note.spend_hook != FuncId::none() {
  623. bs58::encode(&serialize_async(&coin.0.note.spend_hook.inner()).await)
  624. .into_string()
  625. .to_string()
  626. } else {
  627. String::from("-")
  628. };
  629. let user_data = if coin.0.note.user_data != pallas::Base::ZERO {
  630. bs58::encode(&serialize_async(&coin.0.note.user_data).await)
  631. .into_string()
  632. .to_string()
  633. } else {
  634. String::from("-")
  635. };
  636. table.add_row(row![
  637. bs58::encode(&serialize_async(&coin.0.coin.inner()).await)
  638. .into_string()
  639. .to_string(),
  640. coin.1,
  641. coin.0.note.token_id,
  642. aliases,
  643. format!(
  644. "{} ({})",
  645. coin.0.note.value,
  646. encode_base10(coin.0.note.value, BALANCE_BASE10_DECIMALS)
  647. ),
  648. spend_hook,
  649. user_data
  650. ]);
  651. }
  652. println!("{table}");
  653. return Ok(())
  654. }
  655. unreachable!()
  656. }
  657. Subcmd::Unspend { coin } => {
  658. let bytes: [u8; 32] = match bs58::decode(&coin).into_vec()?.try_into() {
  659. Ok(b) => b,
  660. Err(e) => {
  661. eprintln!("Invalid coin: {e:?}");
  662. exit(2);
  663. }
  664. };
  665. let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
  666. Some(v) => v,
  667. None => {
  668. eprintln!("Invalid coin");
  669. exit(2);
  670. }
  671. };
  672. let coin = Coin::from(elem);
  673. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  674. if let Err(e) = drk.unspend_coin(&coin).await {
  675. eprintln!("Failed to mark coin as unspent: {e:?}");
  676. exit(2);
  677. }
  678. Ok(())
  679. }
  680. Subcmd::Transfer { amount, token, recipient } => {
  681. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  682. if let Err(e) = f64::from_str(&amount) {
  683. eprintln!("Invalid amount: {e:?}");
  684. exit(2);
  685. }
  686. let rcpt = match PublicKey::from_str(&recipient) {
  687. Ok(r) => r,
  688. Err(e) => {
  689. eprintln!("Invalid recipient: {e:?}");
  690. exit(2);
  691. }
  692. };
  693. let token_id = match drk.get_token(token).await {
  694. Ok(t) => t,
  695. Err(e) => {
  696. eprintln!("Invalid token alias: {e:?}");
  697. exit(2);
  698. }
  699. };
  700. let tx = match drk.transfer(&amount, token_id, rcpt).await {
  701. Ok(t) => t,
  702. Err(e) => {
  703. eprintln!("Failed to create payment transaction: {e:?}");
  704. exit(2);
  705. }
  706. };
  707. println!("{}", base64::encode(&serialize_async(&tx).await));
  708. Ok(())
  709. }
  710. Subcmd::Otc { command } => {
  711. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  712. match command {
  713. OtcSubcmd::Init { value_pair, token_pair } => {
  714. let (vp_send, vp_recv) = parse_value_pair(&value_pair)?;
  715. let (tp_send, tp_recv) = parse_token_pair(&drk, &token_pair).await?;
  716. let half = match drk.init_swap(vp_send, tp_send, vp_recv, tp_recv).await {
  717. Ok(h) => h,
  718. Err(e) => {
  719. eprintln!("Failed to create swap transaction half: {e:?}");
  720. exit(2);
  721. }
  722. };
  723. println!("{}", base64::encode(&serialize_async(&half).await));
  724. Ok(())
  725. }
  726. OtcSubcmd::Join => {
  727. let mut buf = String::new();
  728. stdin().read_to_string(&mut buf)?;
  729. let Some(bytes) = base64::decode(buf.trim()) else {
  730. eprintln!("Failed to decode partial swap data");
  731. exit(2);
  732. };
  733. let partial: PartialSwapData = deserialize_async(&bytes).await?;
  734. let tx = match drk.join_swap(partial).await {
  735. Ok(tx) => tx,
  736. Err(e) => {
  737. eprintln!("Failed to create a join swap transaction: {e:?}");
  738. exit(2);
  739. }
  740. };
  741. println!("{}", base64::encode(&serialize_async(&tx).await));
  742. Ok(())
  743. }
  744. OtcSubcmd::Inspect => {
  745. let mut buf = String::new();
  746. stdin().read_to_string(&mut buf)?;
  747. let Some(bytes) = base64::decode(buf.trim()) else {
  748. eprintln!("Failed to decode swap transaction");
  749. exit(2);
  750. };
  751. if let Err(e) = drk.inspect_swap(bytes).await {
  752. eprintln!("Failed to inspect swap: {e:?}");
  753. exit(2);
  754. };
  755. Ok(())
  756. }
  757. OtcSubcmd::Sign => {
  758. let mut buf = String::new();
  759. stdin().read_to_string(&mut buf)?;
  760. let Some(bytes) = base64::decode(buf.trim()) else {
  761. eprintln!("Failed to decode swap transaction");
  762. exit(1);
  763. };
  764. let mut tx: Transaction = deserialize_async(&bytes).await?;
  765. if let Err(e) = drk.sign_swap(&mut tx).await {
  766. eprintln!("Failed to sign joined swap transaction: {e:?}");
  767. exit(2);
  768. };
  769. println!("{}", base64::encode(&serialize_async(&tx).await));
  770. Ok(())
  771. }
  772. }
  773. }
  774. Subcmd::Dao { command } => match command {
  775. DaoSubcmd::Create { proposer_limit, quorum, approval_ratio, gov_token_id } => {
  776. if let Err(e) = f64::from_str(&proposer_limit) {
  777. eprintln!("Invalid proposer limit: {e:?}");
  778. exit(2);
  779. }
  780. if let Err(e) = f64::from_str(&quorum) {
  781. eprintln!("Invalid quorum: {e:?}");
  782. exit(2);
  783. }
  784. let proposer_limit = decode_base10(&proposer_limit, BALANCE_BASE10_DECIMALS, true)?;
  785. let quorum = decode_base10(&quorum, BALANCE_BASE10_DECIMALS, true)?;
  786. if approval_ratio > 1.0 {
  787. eprintln!("Error: Approval ratio cannot be >1.0");
  788. exit(2);
  789. }
  790. let approval_ratio_base = 100_u64;
  791. let approval_ratio_quot = (approval_ratio * approval_ratio_base as f64) as u64;
  792. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  793. let gov_token_id = match drk.get_token(gov_token_id).await {
  794. Ok(g) => g,
  795. Err(e) => {
  796. eprintln!("Invalid Token ID: {e:?}");
  797. exit(2);
  798. }
  799. };
  800. let secret_key = SecretKey::random(&mut OsRng);
  801. let bulla_blind = pallas::Base::random(&mut OsRng);
  802. let dao_params = DaoParams {
  803. proposer_limit,
  804. quorum,
  805. approval_ratio_base,
  806. approval_ratio_quot,
  807. gov_token_id,
  808. secret_key,
  809. bulla_blind,
  810. };
  811. let encoded = bs58::encode(&serialize_async(&dao_params).await).into_string();
  812. println!("{encoded}");
  813. Ok(())
  814. }
  815. DaoSubcmd::View => {
  816. let mut buf = String::new();
  817. stdin().read_to_string(&mut buf)?;
  818. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  819. let dao_params: DaoParams = deserialize_async(&bytes).await?;
  820. println!("{dao_params}");
  821. Ok(())
  822. }
  823. DaoSubcmd::Import { dao_name } => {
  824. let mut buf = String::new();
  825. stdin().read_to_string(&mut buf)?;
  826. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  827. let dao_params: DaoParams = deserialize_async(&bytes).await?;
  828. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  829. if let Err(e) = drk.import_dao(dao_name, dao_params).await {
  830. eprintln!("Failed to import DAO: {e:?}");
  831. exit(2);
  832. }
  833. Ok(())
  834. }
  835. DaoSubcmd::List { dao_alias } => {
  836. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  837. // We cannot use .map() since get_dao_id() uses ?
  838. let dao_id = match dao_alias {
  839. Some(alias) => Some(drk.get_dao_id(&alias).await?),
  840. None => None,
  841. };
  842. if let Err(e) = drk.dao_list(dao_id).await {
  843. eprintln!("Failed to list DAO: {e:?}");
  844. exit(2);
  845. }
  846. Ok(())
  847. }
  848. DaoSubcmd::Balance { dao_alias } => {
  849. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  850. let dao_id = drk.get_dao_id(&dao_alias).await?;
  851. let balmap = match drk.dao_balance(dao_id).await {
  852. Ok(b) => b,
  853. Err(e) => {
  854. eprintln!("Failed to fetch DAO balance: {e:?}");
  855. exit(2);
  856. }
  857. };
  858. let aliases_map = match drk.get_aliases_mapped_by_token().await {
  859. Ok(a) => a,
  860. Err(e) => {
  861. eprintln!("Failed to fetch wallet aliases: {e:?}");
  862. exit(2);
  863. }
  864. };
  865. // Create a prettytable with the new data:
  866. let mut table = Table::new();
  867. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  868. table.set_titles(row!["Token ID", "Aliases", "Balance"]);
  869. for (token_id, balance) in balmap.iter() {
  870. let aliases = match aliases_map.get(token_id) {
  871. Some(a) => a,
  872. None => "-",
  873. };
  874. table.add_row(row![
  875. token_id,
  876. aliases,
  877. encode_base10(*balance, BALANCE_BASE10_DECIMALS)
  878. ]);
  879. }
  880. if table.is_empty() {
  881. println!("No unspent balances found");
  882. } else {
  883. println!("{table}");
  884. }
  885. Ok(())
  886. }
  887. DaoSubcmd::Mint { dao_alias } => {
  888. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  889. let dao_id = drk.get_dao_id(&dao_alias).await?;
  890. let tx = match drk.dao_mint(dao_id).await {
  891. Ok(tx) => tx,
  892. Err(e) => {
  893. eprintln!("Failed to mint DAO: {e:?}");
  894. exit(2);
  895. }
  896. };
  897. println!("{}", base64::encode(&serialize_async(&tx).await));
  898. Ok(())
  899. }
  900. DaoSubcmd::Propose { dao_alias, recipient, amount, token } => {
  901. if let Err(e) = f64::from_str(&amount) {
  902. eprintln!("Invalid amount: {e:?}");
  903. exit(2);
  904. }
  905. let amount = decode_base10(&amount, BALANCE_BASE10_DECIMALS, true)?;
  906. let rcpt = match PublicKey::from_str(&recipient) {
  907. Ok(r) => r,
  908. Err(e) => {
  909. eprintln!("Invalid recipient: {e:?}");
  910. exit(2);
  911. }
  912. };
  913. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  914. let dao_id = drk.get_dao_id(&dao_alias).await?;
  915. let token_id = match drk.get_token(token).await {
  916. Ok(t) => t,
  917. Err(e) => {
  918. eprintln!("Invalid token alias: {e:?}");
  919. exit(2);
  920. }
  921. };
  922. let tx = match drk.dao_propose(dao_id, rcpt, amount, token_id).await {
  923. Ok(tx) => tx,
  924. Err(e) => {
  925. eprintln!("Failed to create DAO proposal: {e:?}");
  926. exit(2);
  927. }
  928. };
  929. println!("{}", base64::encode(&serialize_async(&tx).await));
  930. Ok(())
  931. }
  932. DaoSubcmd::Proposals { dao_alias } => {
  933. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  934. let dao_id = drk.get_dao_id(&dao_alias).await?;
  935. let proposals = drk.get_dao_proposals(dao_id).await?;
  936. for proposal in proposals {
  937. println!("[{}] {:?}", proposal.id, proposal.bulla());
  938. }
  939. Ok(())
  940. }
  941. DaoSubcmd::Proposal { dao_alias, proposal_id } => {
  942. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  943. let dao_id = drk.get_dao_id(&dao_alias).await?;
  944. let proposals = drk.get_dao_proposals(dao_id).await?;
  945. let Some(proposal) = proposals.iter().find(|x| x.id == proposal_id) else {
  946. eprintln!("No such DAO proposal found");
  947. exit(2);
  948. };
  949. println!("{proposal}");
  950. let votes = drk.get_dao_proposal_votes(proposal_id).await?;
  951. println!("votes:");
  952. for vote in votes {
  953. let option = if vote.vote_option { "yes" } else { "no " };
  954. eprintln!(" {option} {}", vote.all_vote_value);
  955. }
  956. Ok(())
  957. }
  958. DaoSubcmd::Vote { dao_alias, proposal_id, vote, vote_weight } => {
  959. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  960. let dao_id = drk.get_dao_id(&dao_alias).await?;
  961. if let Err(e) = f64::from_str(&vote_weight) {
  962. eprintln!("Invalid vote weight: {e:?}");
  963. exit(2);
  964. }
  965. let weight = decode_base10(&vote_weight, BALANCE_BASE10_DECIMALS, true)?;
  966. if vote > 1 {
  967. eprintln!("Vote can be either 0 (NO) or 1 (YES)");
  968. exit(2);
  969. }
  970. let vote = vote != 0;
  971. let tx = match drk.dao_vote(dao_id, proposal_id, vote, weight).await {
  972. Ok(tx) => tx,
  973. Err(e) => {
  974. eprintln!("Failed to create DAO Vote transaction: {e:?}");
  975. exit(2);
  976. }
  977. };
  978. // TODO: Write our_vote in the proposal sql.
  979. println!("{}", bs58::encode(&serialize_async(&tx).await).into_string());
  980. Ok(())
  981. }
  982. DaoSubcmd::Exec { dao_alias, proposal_id } => {
  983. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  984. let dao_id = drk.get_dao_id(&dao_alias).await?;
  985. let dao = drk.get_dao_by_id(dao_id).await?;
  986. let proposal = drk.get_dao_proposal_by_id(proposal_id).await?;
  987. assert!(proposal.dao_bulla == dao.bulla());
  988. let tx = match drk.dao_exec(dao, proposal).await {
  989. Ok(tx) => tx,
  990. Err(e) => {
  991. eprintln!("Failed to execute DAO proposal: {e:?}");
  992. exit(2);
  993. }
  994. };
  995. println!("{}", base64::encode(&serialize_async(&tx).await));
  996. Ok(())
  997. }
  998. },
  999. Subcmd::Inspect => {
  1000. let mut buf = String::new();
  1001. stdin().read_to_string(&mut buf)?;
  1002. let Some(bytes) = base64::decode(buf.trim()) else {
  1003. eprintln!("Failed to decode transaction");
  1004. exit(1);
  1005. };
  1006. let tx: Transaction = deserialize_async(&bytes).await?;
  1007. println!("{tx:#?}");
  1008. Ok(())
  1009. }
  1010. Subcmd::Broadcast => {
  1011. println!("Reading transaction from stdin...");
  1012. let mut buf = String::new();
  1013. stdin().read_to_string(&mut buf)?;
  1014. let Some(bytes) = base64::decode(buf.trim()) else {
  1015. eprintln!("Failed to decode transaction");
  1016. exit(1);
  1017. };
  1018. let tx = deserialize_async(&bytes).await?;
  1019. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  1020. let txid = match drk.broadcast_tx(&tx).await {
  1021. Ok(t) => t,
  1022. Err(e) => {
  1023. eprintln!("Failed to broadcast transaction: {e:?}");
  1024. exit(2);
  1025. }
  1026. };
  1027. println!("Transaction ID: {txid}");
  1028. Ok(())
  1029. }
  1030. Subcmd::Subscribe => {
  1031. let drk =
  1032. Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
  1033. .await?;
  1034. if let Err(e) = drk.subscribe_blocks(args.endpoint, ex).await {
  1035. eprintln!("Block subscription failed: {e:?}");
  1036. exit(2);
  1037. }
  1038. Ok(())
  1039. }
  1040. Subcmd::Scan { reset, list, checkpoint } => {
  1041. let drk =
  1042. Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
  1043. .await?;
  1044. if reset {
  1045. println!("Reset requested.");
  1046. if let Err(e) = drk.scan_blocks(true).await {
  1047. eprintln!("Failed during scanning: {e:?}");
  1048. exit(2);
  1049. }
  1050. println!("Finished scanning blockchain");
  1051. return Ok(())
  1052. }
  1053. if list {
  1054. println!("List requested.");
  1055. // TODO: implement
  1056. unimplemented!()
  1057. }
  1058. if let Some(c) = checkpoint {
  1059. println!("Checkpoint requested: {c}");
  1060. // TODO: implement
  1061. unimplemented!()
  1062. }
  1063. if let Err(e) = drk.scan_blocks(false).await {
  1064. eprintln!("Failed during scanning: {e:?}");
  1065. exit(2);
  1066. }
  1067. println!("Finished scanning blockchain");
  1068. Ok(())
  1069. }
  1070. Subcmd::Explorer { command } => match command {
  1071. ExplorerSubcmd::FetchTx { tx_hash, full, encode } => {
  1072. let tx_hash = blake3::Hash::from_hex(&tx_hash)?;
  1073. let drk =
  1074. Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
  1075. .await?;
  1076. let tx = match drk.get_tx(&tx_hash).await {
  1077. Ok(tx) => tx,
  1078. Err(e) => {
  1079. eprintln!("Failed to fetch transaction: {e:?}");
  1080. exit(2);
  1081. }
  1082. };
  1083. let Some(tx) = tx else {
  1084. println!("Transaction was not found");
  1085. exit(1);
  1086. };
  1087. // Make sure the tx is correct
  1088. assert_eq!(tx.hash()?, tx_hash);
  1089. if encode {
  1090. println!("{}", base64::encode(&serialize_async(&tx).await));
  1091. exit(1)
  1092. }
  1093. println!("Transaction ID: {tx_hash}");
  1094. if full {
  1095. println!("{tx:?}");
  1096. }
  1097. Ok(())
  1098. }
  1099. ExplorerSubcmd::SimulateTx => {
  1100. println!("Reading transaction from stdin...");
  1101. let mut buf = String::new();
  1102. stdin().read_to_string(&mut buf)?;
  1103. let Some(bytes) = base64::decode(buf.trim()) else {
  1104. eprintln!("Failed to decode transaction");
  1105. exit(1);
  1106. };
  1107. let tx = deserialize_async(&bytes).await?;
  1108. let drk =
  1109. Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
  1110. .await?;
  1111. let is_valid = match drk.simulate_tx(&tx).await {
  1112. Ok(b) => b,
  1113. Err(e) => {
  1114. eprintln!("Failed to simulate tx: {e:?}");
  1115. exit(2);
  1116. }
  1117. };
  1118. println!("Transaction ID: {}", tx.hash()?);
  1119. println!("State: {}", if is_valid { "valid" } else { "invalid" });
  1120. Ok(())
  1121. }
  1122. ExplorerSubcmd::TxsHistory { tx_hash, encode } => {
  1123. let drk =
  1124. Drk::new(args.wallet_path, args.wallet_pass, args.endpoint.clone(), ex.clone())
  1125. .await?;
  1126. if let Some(c) = tx_hash {
  1127. let (tx_hash, status, tx) = drk.get_tx_history_record(&c).await?;
  1128. if encode {
  1129. println!("{}", base64::encode(&serialize_async(&tx).await));
  1130. exit(1)
  1131. }
  1132. println!("Transaction ID: {tx_hash}");
  1133. println!("Status: {status}");
  1134. println!("{tx:?}");
  1135. return Ok(())
  1136. }
  1137. let map = match drk.get_txs_history().await {
  1138. Ok(m) => m,
  1139. Err(e) => {
  1140. eprintln!("Failed to retrieve transactions history records: {e:?}");
  1141. exit(2);
  1142. }
  1143. };
  1144. // Create a prettytable with the new data:
  1145. let mut table = Table::new();
  1146. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  1147. table.set_titles(row!["Transaction Hash", "Status"]);
  1148. for (txs_hash, status) in map.iter() {
  1149. table.add_row(row![txs_hash, status]);
  1150. }
  1151. if table.is_empty() {
  1152. println!("No transactions found");
  1153. } else {
  1154. println!("{table}");
  1155. }
  1156. Ok(())
  1157. }
  1158. },
  1159. Subcmd::Alias { command } => match command {
  1160. AliasSubcmd::Add { alias, token } => {
  1161. if alias.chars().count() > 5 {
  1162. eprintln!("Error: Alias exceeds 5 characters");
  1163. exit(2);
  1164. }
  1165. let token_id = match TokenId::from_str(token.as_str()) {
  1166. Ok(t) => t,
  1167. Err(e) => {
  1168. eprintln!("Invalid Token ID: {e:?}");
  1169. exit(2);
  1170. }
  1171. };
  1172. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  1173. if let Err(e) = drk.add_alias(alias, token_id).await {
  1174. eprintln!("Failed to add alias: {e:?}");
  1175. exit(2);
  1176. }
  1177. Ok(())
  1178. }
  1179. AliasSubcmd::Show { alias, token } => {
  1180. let token_id = match token {
  1181. Some(t) => match TokenId::from_str(t.as_str()) {
  1182. Ok(t) => Some(t),
  1183. Err(e) => {
  1184. eprintln!("Invalid Token ID: {e:?}");
  1185. exit(2);
  1186. }
  1187. },
  1188. None => None,
  1189. };
  1190. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  1191. let map = drk.get_aliases(alias, token_id).await?;
  1192. // Create a prettytable with the new data:
  1193. let mut table = Table::new();
  1194. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  1195. table.set_titles(row!["Alias", "Token ID"]);
  1196. for (alias, token_id) in map.iter() {
  1197. table.add_row(row![alias, token_id]);
  1198. }
  1199. if table.is_empty() {
  1200. println!("No aliases found");
  1201. } else {
  1202. println!("{table}");
  1203. }
  1204. Ok(())
  1205. }
  1206. AliasSubcmd::Remove { alias } => {
  1207. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  1208. if let Err(e) = drk.remove_alias(alias).await {
  1209. eprintln!("Failed to remove alias: {e:?}");
  1210. exit(2);
  1211. }
  1212. Ok(())
  1213. }
  1214. },
  1215. Subcmd::Token { command } => match command {
  1216. TokenSubcmd::Import => {
  1217. let mut buf = String::new();
  1218. stdin().read_to_string(&mut buf)?;
  1219. let mint_authority = match SecretKey::from_str(buf.trim()) {
  1220. Ok(ma) => ma,
  1221. Err(e) => {
  1222. eprintln!("Invalid secret key: {e:?}");
  1223. exit(2);
  1224. }
  1225. };
  1226. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  1227. if let Err(e) = drk.import_mint_authority(mint_authority).await {
  1228. eprintln!("Importing mint authority failed: {e:?}");
  1229. exit(2);
  1230. };
  1231. let token_id = TokenId::derive(mint_authority);
  1232. println!("Successfully imported mint authority for token ID: {token_id}");
  1233. Ok(())
  1234. }
  1235. TokenSubcmd::GenerateMint => {
  1236. let mint_authority = SecretKey::random(&mut OsRng);
  1237. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  1238. if let Err(e) = drk.import_mint_authority(mint_authority).await {
  1239. eprintln!("Importing mint authority failed: {e:?}");
  1240. exit(2);
  1241. };
  1242. // TODO: see TokenAttributes struct. I'm not sure how to restructure this rn.
  1243. let token_id = TokenId::derive(mint_authority);
  1244. println!("Successfully imported mint authority for token ID: {token_id}");
  1245. Ok(())
  1246. }
  1247. TokenSubcmd::List => {
  1248. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  1249. let tokens = drk.list_tokens().await?;
  1250. let aliases_map = match drk.get_aliases_mapped_by_token().await {
  1251. Ok(map) => map,
  1252. Err(e) => {
  1253. eprintln!("Failed to fetch wallet aliases: {e:?}");
  1254. exit(2);
  1255. }
  1256. };
  1257. let mut table = Table::new();
  1258. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  1259. table.set_titles(row!["Token ID", "Aliases", "Mint Authority", "Frozen"]);
  1260. for (token_id, authority, frozen) in tokens {
  1261. let aliases = match aliases_map.get(&token_id.to_string()) {
  1262. Some(a) => a,
  1263. None => "-",
  1264. };
  1265. table.add_row(row![token_id, aliases, authority, frozen]);
  1266. }
  1267. if table.is_empty() {
  1268. println!("No tokens found");
  1269. } else {
  1270. println!("{table}");
  1271. }
  1272. Ok(())
  1273. }
  1274. // TODO: Mint directly into DAO treasury
  1275. TokenSubcmd::Mint { token, amount, recipient } => {
  1276. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  1277. if let Err(e) = f64::from_str(&amount) {
  1278. eprintln!("Invalid amount: {e:?}");
  1279. exit(2);
  1280. }
  1281. let _rcpt = match PublicKey::from_str(&recipient) {
  1282. Ok(r) => r,
  1283. Err(e) => {
  1284. eprintln!("Invalid recipient: {e:?}");
  1285. exit(2);
  1286. }
  1287. };
  1288. let _token_id = match drk.get_token(token).await {
  1289. Ok(t) => t,
  1290. Err(e) => {
  1291. eprintln!("Invalid Token ID: {e:?}");
  1292. exit(2);
  1293. }
  1294. };
  1295. panic!("temporarily disabled due to change of API for drk.mint_token() fn");
  1296. //let tx = match drk.mint_token(&amount, rcpt, token_id).await {
  1297. // Ok(tx) => tx,
  1298. // Err(e) => {
  1299. // eprintln!("Failed to create token mint transaction: {e:?}");
  1300. // exit(2);
  1301. // }
  1302. //};
  1303. //println!("{}", base64::encode(&serialize_async(&tx).await));
  1304. //Ok(())
  1305. }
  1306. TokenSubcmd::Freeze { token } => {
  1307. let drk = Drk::new(args.wallet_path, args.wallet_pass, args.endpoint, ex).await?;
  1308. let _token_id = match drk.get_token(token).await {
  1309. Ok(t) => t,
  1310. Err(e) => {
  1311. eprintln!("Invalid Token ID: {e:?}");
  1312. exit(2);
  1313. }
  1314. };
  1315. panic!("temporarily disabled due to change of API for drk.mint_token() fn");
  1316. //let tx = match drk.freeze_token(token_id).await {
  1317. // Ok(tx) => tx,
  1318. // Err(e) => {
  1319. // eprintln!("Failed to create token freeze transaction: {e:?}");
  1320. // exit(2);
  1321. // }
  1322. //};
  1323. //println!("{}", base64::encode(&serialize_async(&tx).await));
  1324. //Ok(())
  1325. }
  1326. },
  1327. }
  1328. }