main.rs 50 KB

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