main.rs 55 KB

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