main.rs 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699
  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. /*
  399. /// Deploy a smart contract
  400. Deploy {
  401. /// Path to deploy authority
  402. deploy_auth: String,
  403. /// Path to contract wasm bincode
  404. wasm_path: String,
  405. },
  406. */
  407. }
  408. /// CLI-util structure
  409. pub struct Drk {
  410. /// Wallet database operations handler
  411. pub wallet: WalletPtr,
  412. /// JSON-RPC client to execute requests to darkfid daemon
  413. pub rpc_client: Option<RpcClient>,
  414. }
  415. impl Drk {
  416. async fn new(
  417. wallet_path: String,
  418. wallet_pass: String,
  419. endpoint: Option<Url>,
  420. ex: Arc<smol::Executor<'static>>,
  421. ) -> Result<Self> {
  422. // Script kiddies protection
  423. if wallet_pass == "changeme" {
  424. eprintln!("Please don't use default wallet password...");
  425. exit(2);
  426. }
  427. // Initialize wallet
  428. let wallet_path = expand_path(&wallet_path)?;
  429. if !wallet_path.exists() {
  430. if let Some(parent) = wallet_path.parent() {
  431. fs::create_dir_all(parent)?;
  432. }
  433. }
  434. let wallet = match WalletDb::new(Some(wallet_path), Some(&wallet_pass)) {
  435. Ok(w) => w,
  436. Err(e) => {
  437. eprintln!("Error initializing wallet: {e:?}");
  438. exit(2);
  439. }
  440. };
  441. // Initialize rpc client
  442. let rpc_client = if let Some(endpoint) = endpoint {
  443. Some(RpcClient::new(endpoint, ex).await?)
  444. } else {
  445. None
  446. };
  447. Ok(Self { wallet, rpc_client })
  448. }
  449. /// Initialize wallet with tables for drk
  450. async fn initialize_wallet(&self) -> Result<()> {
  451. let wallet_schema = include_str!("../wallet.sql");
  452. if let Err(e) = self.wallet.exec_batch_sql(wallet_schema).await {
  453. eprintln!("Error initializing wallet: {e:?}");
  454. exit(2);
  455. }
  456. Ok(())
  457. }
  458. /// Auxiliary function to ping configured darkfid daemon for liveness.
  459. async fn ping(&self) -> Result<()> {
  460. println!("Executing ping request to darkfid...");
  461. let latency = Instant::now();
  462. let req = JsonRequest::new("ping", JsonValue::Array(vec![]));
  463. let rep = self.rpc_client.as_ref().unwrap().oneshot_request(req).await?;
  464. let latency = latency.elapsed();
  465. println!("Got reply: {rep:?}");
  466. println!("Latency: {latency:?}");
  467. Ok(())
  468. }
  469. }
  470. async_daemonize!(realmain);
  471. async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
  472. match args.command {
  473. Subcmd::Kaching => {
  474. kaching().await;
  475. Ok(())
  476. }
  477. Subcmd::Ping => {
  478. let drk = Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  479. drk.ping().await
  480. }
  481. Subcmd::Completions { shell } => generate_completions(&shell),
  482. Subcmd::Wallet {
  483. initialize,
  484. keygen,
  485. balance,
  486. address,
  487. addresses,
  488. default_address,
  489. secrets,
  490. import_secrets,
  491. tree,
  492. coins,
  493. } => {
  494. if !initialize &&
  495. !keygen &&
  496. !balance &&
  497. !address &&
  498. !addresses &&
  499. default_address.is_none() &&
  500. !secrets &&
  501. !tree &&
  502. !coins &&
  503. !import_secrets
  504. {
  505. eprintln!("Error: You must use at least one flag for this subcommand");
  506. eprintln!("Run with \"wallet -h\" to see the subcommand usage.");
  507. exit(2);
  508. }
  509. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  510. if initialize {
  511. drk.initialize_wallet().await?;
  512. if let Err(e) = drk.initialize_money().await {
  513. eprintln!("Failed to initialize Money: {e:?}");
  514. exit(2);
  515. }
  516. if let Err(e) = drk.initialize_dao().await {
  517. eprintln!("Failed to initialize DAO: {e:?}");
  518. exit(2);
  519. }
  520. if let Err(e) = drk.initialize_deployooor().await {
  521. eprintln!("Failed to initialize Deployooor: {e:?}");
  522. exit(2);
  523. }
  524. return Ok(())
  525. }
  526. if keygen {
  527. if let Err(e) = drk.money_keygen().await {
  528. eprintln!("Failed to generate keypair: {e:?}");
  529. exit(2);
  530. }
  531. return Ok(())
  532. }
  533. if balance {
  534. let balmap = drk.money_balance().await?;
  535. let aliases_map = drk.get_aliases_mapped_by_token().await?;
  536. // Create a prettytable with the new data:
  537. let mut table = Table::new();
  538. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  539. table.set_titles(row!["Token ID", "Aliases", "Balance"]);
  540. for (token_id, balance) in balmap.iter() {
  541. let aliases = match aliases_map.get(token_id) {
  542. Some(a) => a,
  543. None => "-",
  544. };
  545. table.add_row(row![
  546. token_id,
  547. aliases,
  548. encode_base10(*balance, BALANCE_BASE10_DECIMALS)
  549. ]);
  550. }
  551. if table.is_empty() {
  552. println!("No unspent balances found");
  553. } else {
  554. println!("{table}");
  555. }
  556. return Ok(())
  557. }
  558. if address {
  559. let address = match drk.default_address().await {
  560. Ok(a) => a,
  561. Err(e) => {
  562. eprintln!("Failed to fetch default address: {e:?}");
  563. exit(2);
  564. }
  565. };
  566. println!("{address}");
  567. return Ok(())
  568. }
  569. if addresses {
  570. let addresses = drk.addresses().await?;
  571. // Create a prettytable with the new data:
  572. let mut table = Table::new();
  573. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  574. table.set_titles(row!["Key ID", "Public Key", "Secret Key", "Is Default"]);
  575. for (key_id, public_key, secret_key, is_default) in addresses {
  576. let is_default = match is_default {
  577. 1 => "*",
  578. _ => "",
  579. };
  580. table.add_row(row![key_id, public_key, secret_key, is_default]);
  581. }
  582. if table.is_empty() {
  583. println!("No addresses found");
  584. } else {
  585. println!("{table}");
  586. }
  587. return Ok(())
  588. }
  589. if let Some(idx) = default_address {
  590. if let Err(e) = drk.set_default_address(idx).await {
  591. eprintln!("Failed to set default address: {e:?}");
  592. exit(2);
  593. }
  594. return Ok(())
  595. }
  596. if secrets {
  597. let v = drk.get_money_secrets().await?;
  598. for i in v {
  599. println!("{i}");
  600. }
  601. return Ok(())
  602. }
  603. if import_secrets {
  604. let mut secrets = vec![];
  605. let lines = stdin().lines();
  606. for (i, line) in lines.enumerate() {
  607. if let Ok(line) = line {
  608. let bytes = bs58::decode(&line.trim()).into_vec()?;
  609. let Ok(secret) = deserialize_async(&bytes).await else {
  610. println!("Warning: Failed to deserialize secret on line {i}");
  611. continue
  612. };
  613. secrets.push(secret);
  614. }
  615. }
  616. let pubkeys = match drk.import_money_secrets(secrets).await {
  617. Ok(p) => p,
  618. Err(e) => {
  619. eprintln!("Failed to import secret keys into wallet: {e:?}");
  620. exit(2);
  621. }
  622. };
  623. for key in pubkeys {
  624. println!("{key}");
  625. }
  626. return Ok(())
  627. }
  628. if tree {
  629. let tree = drk.get_money_tree().await?;
  630. println!("{tree:#?}");
  631. return Ok(())
  632. }
  633. if coins {
  634. let coins = drk.get_coins(true).await?;
  635. let aliases_map = drk.get_aliases_mapped_by_token().await?;
  636. if coins.is_empty() {
  637. return Ok(())
  638. }
  639. let mut table = Table::new();
  640. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  641. table.set_titles(row![
  642. "Coin",
  643. "Spent",
  644. "Token ID",
  645. "Aliases",
  646. "Value",
  647. "Spend Hook",
  648. "User Data"
  649. ]);
  650. for coin in coins {
  651. let aliases = match aliases_map.get(&coin.0.note.token_id.to_string()) {
  652. Some(a) => a,
  653. None => "-",
  654. };
  655. let spend_hook = if coin.0.note.spend_hook != FuncId::none() {
  656. bs58::encode(&serialize_async(&coin.0.note.spend_hook.inner()).await)
  657. .into_string()
  658. .to_string()
  659. } else {
  660. String::from("-")
  661. };
  662. let user_data = if coin.0.note.user_data != pallas::Base::ZERO {
  663. bs58::encode(&serialize_async(&coin.0.note.user_data).await)
  664. .into_string()
  665. .to_string()
  666. } else {
  667. String::from("-")
  668. };
  669. table.add_row(row![
  670. bs58::encode(&serialize_async(&coin.0.coin.inner()).await)
  671. .into_string()
  672. .to_string(),
  673. coin.1,
  674. coin.0.note.token_id,
  675. aliases,
  676. format!(
  677. "{} ({})",
  678. coin.0.note.value,
  679. encode_base10(coin.0.note.value, BALANCE_BASE10_DECIMALS)
  680. ),
  681. spend_hook,
  682. user_data
  683. ]);
  684. }
  685. println!("{table}");
  686. return Ok(())
  687. }
  688. unreachable!()
  689. }
  690. Subcmd::Unspend { coin } => {
  691. let bytes: [u8; 32] = match bs58::decode(&coin).into_vec()?.try_into() {
  692. Ok(b) => b,
  693. Err(e) => {
  694. eprintln!("Invalid coin: {e:?}");
  695. exit(2);
  696. }
  697. };
  698. let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
  699. Some(v) => v,
  700. None => {
  701. eprintln!("Invalid coin");
  702. exit(2);
  703. }
  704. };
  705. let coin = Coin::from(elem);
  706. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  707. if let Err(e) = drk.unspend_coin(&coin).await {
  708. eprintln!("Failed to mark coin as unspent: {e:?}");
  709. exit(2);
  710. }
  711. Ok(())
  712. }
  713. Subcmd::Transfer { amount, token, recipient } => {
  714. let drk = Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  715. if let Err(e) = f64::from_str(&amount) {
  716. eprintln!("Invalid amount: {e:?}");
  717. exit(2);
  718. }
  719. let rcpt = match PublicKey::from_str(&recipient) {
  720. Ok(r) => r,
  721. Err(e) => {
  722. eprintln!("Invalid recipient: {e:?}");
  723. exit(2);
  724. }
  725. };
  726. let token_id = match drk.get_token(token).await {
  727. Ok(t) => t,
  728. Err(e) => {
  729. eprintln!("Invalid token alias: {e:?}");
  730. exit(2);
  731. }
  732. };
  733. let tx = match drk.transfer(&amount, token_id, rcpt).await {
  734. Ok(t) => t,
  735. Err(e) => {
  736. eprintln!("Failed to create payment transaction: {e:?}");
  737. exit(2);
  738. }
  739. };
  740. println!("{}", base64::encode(&serialize_async(&tx).await));
  741. Ok(())
  742. }
  743. Subcmd::Otc { command } => match command {
  744. OtcSubcmd::Init { value_pair, token_pair } => {
  745. let drk =
  746. Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  747. let (vp_send, vp_recv) = parse_value_pair(&value_pair)?;
  748. let (tp_send, tp_recv) = parse_token_pair(&drk, &token_pair).await?;
  749. let half = match drk.init_swap(vp_send, tp_send, vp_recv, tp_recv).await {
  750. Ok(h) => h,
  751. Err(e) => {
  752. eprintln!("Failed to create swap transaction half: {e:?}");
  753. exit(2);
  754. }
  755. };
  756. println!("{}", base64::encode(&serialize_async(&half).await));
  757. Ok(())
  758. }
  759. OtcSubcmd::Join => {
  760. let mut buf = String::new();
  761. stdin().read_to_string(&mut buf)?;
  762. let Some(bytes) = base64::decode(buf.trim()) else {
  763. eprintln!("Failed to decode partial swap data");
  764. exit(2);
  765. };
  766. let partial: PartialSwapData = deserialize_async(&bytes).await?;
  767. let drk =
  768. Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  769. let tx = match drk.join_swap(partial).await {
  770. Ok(tx) => tx,
  771. Err(e) => {
  772. eprintln!("Failed to create a join swap transaction: {e:?}");
  773. exit(2);
  774. }
  775. };
  776. println!("{}", base64::encode(&serialize_async(&tx).await));
  777. Ok(())
  778. }
  779. OtcSubcmd::Inspect => {
  780. let mut buf = String::new();
  781. stdin().read_to_string(&mut buf)?;
  782. let Some(bytes) = base64::decode(buf.trim()) else {
  783. eprintln!("Failed to decode swap transaction");
  784. exit(2);
  785. };
  786. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  787. if let Err(e) = drk.inspect_swap(bytes).await {
  788. eprintln!("Failed to inspect swap: {e:?}");
  789. exit(2);
  790. };
  791. Ok(())
  792. }
  793. OtcSubcmd::Sign => {
  794. let mut buf = String::new();
  795. stdin().read_to_string(&mut buf)?;
  796. let Some(bytes) = base64::decode(buf.trim()) else {
  797. eprintln!("Failed to decode swap transaction");
  798. exit(1);
  799. };
  800. let mut tx: Transaction = deserialize_async(&bytes).await?;
  801. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  802. if let Err(e) = drk.sign_swap(&mut tx).await {
  803. eprintln!("Failed to sign joined swap transaction: {e:?}");
  804. exit(2);
  805. };
  806. println!("{}", base64::encode(&serialize_async(&tx).await));
  807. Ok(())
  808. }
  809. },
  810. Subcmd::Dao { command } => match command {
  811. DaoSubcmd::Create { proposer_limit, quorum, approval_ratio, gov_token_id } => {
  812. if let Err(e) = f64::from_str(&proposer_limit) {
  813. eprintln!("Invalid proposer limit: {e:?}");
  814. exit(2);
  815. }
  816. if let Err(e) = f64::from_str(&quorum) {
  817. eprintln!("Invalid quorum: {e:?}");
  818. exit(2);
  819. }
  820. let proposer_limit = decode_base10(&proposer_limit, BALANCE_BASE10_DECIMALS, true)?;
  821. let quorum = decode_base10(&quorum, BALANCE_BASE10_DECIMALS, true)?;
  822. if approval_ratio > 1.0 {
  823. eprintln!("Error: Approval ratio cannot be >1.0");
  824. exit(2);
  825. }
  826. let approval_ratio_base = 100_u64;
  827. let approval_ratio_quot = (approval_ratio * approval_ratio_base as f64) as u64;
  828. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  829. let gov_token_id = match drk.get_token(gov_token_id).await {
  830. Ok(g) => g,
  831. Err(e) => {
  832. eprintln!("Invalid Token ID: {e:?}");
  833. exit(2);
  834. }
  835. };
  836. let secret_key = SecretKey::random(&mut OsRng);
  837. let bulla_blind = pallas::Base::random(&mut OsRng);
  838. let dao_params = DaoParams {
  839. proposer_limit,
  840. quorum,
  841. approval_ratio_base,
  842. approval_ratio_quot,
  843. gov_token_id,
  844. secret_key,
  845. bulla_blind,
  846. };
  847. let encoded = bs58::encode(&serialize_async(&dao_params).await).into_string();
  848. println!("{encoded}");
  849. Ok(())
  850. }
  851. DaoSubcmd::View => {
  852. let mut buf = String::new();
  853. stdin().read_to_string(&mut buf)?;
  854. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  855. let dao_params: DaoParams = deserialize_async(&bytes).await?;
  856. println!("{dao_params}");
  857. Ok(())
  858. }
  859. DaoSubcmd::Import { dao_name } => {
  860. let mut buf = String::new();
  861. stdin().read_to_string(&mut buf)?;
  862. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  863. let dao_params: DaoParams = deserialize_async(&bytes).await?;
  864. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  865. if let Err(e) = drk.import_dao(dao_name, dao_params).await {
  866. eprintln!("Failed to import DAO: {e:?}");
  867. exit(2);
  868. }
  869. Ok(())
  870. }
  871. DaoSubcmd::List { dao_alias } => {
  872. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  873. // We cannot use .map() since get_dao_id() uses ?
  874. let dao_id = match dao_alias {
  875. Some(alias) => Some(drk.get_dao_id(&alias).await?),
  876. None => None,
  877. };
  878. if let Err(e) = drk.dao_list(dao_id).await {
  879. eprintln!("Failed to list DAO: {e:?}");
  880. exit(2);
  881. }
  882. Ok(())
  883. }
  884. DaoSubcmd::Balance { dao_alias } => {
  885. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  886. let dao_id = drk.get_dao_id(&dao_alias).await?;
  887. let balmap = match drk.dao_balance(dao_id).await {
  888. Ok(b) => b,
  889. Err(e) => {
  890. eprintln!("Failed to fetch DAO balance: {e:?}");
  891. exit(2);
  892. }
  893. };
  894. let aliases_map = match drk.get_aliases_mapped_by_token().await {
  895. Ok(a) => a,
  896. Err(e) => {
  897. eprintln!("Failed to fetch wallet aliases: {e:?}");
  898. exit(2);
  899. }
  900. };
  901. // Create a prettytable with the new data:
  902. let mut table = Table::new();
  903. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  904. table.set_titles(row!["Token ID", "Aliases", "Balance"]);
  905. for (token_id, balance) in balmap.iter() {
  906. let aliases = match aliases_map.get(token_id) {
  907. Some(a) => a,
  908. None => "-",
  909. };
  910. table.add_row(row![
  911. token_id,
  912. aliases,
  913. encode_base10(*balance, BALANCE_BASE10_DECIMALS)
  914. ]);
  915. }
  916. if table.is_empty() {
  917. println!("No unspent balances found");
  918. } else {
  919. println!("{table}");
  920. }
  921. Ok(())
  922. }
  923. DaoSubcmd::Mint { dao_alias } => {
  924. let drk =
  925. Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  926. let dao_id = drk.get_dao_id(&dao_alias).await?;
  927. let tx = match drk.dao_mint(dao_id).await {
  928. Ok(tx) => tx,
  929. Err(e) => {
  930. eprintln!("Failed to mint DAO: {e:?}");
  931. exit(2);
  932. }
  933. };
  934. println!("{}", base64::encode(&serialize_async(&tx).await));
  935. Ok(())
  936. }
  937. DaoSubcmd::Propose { dao_alias, recipient, amount, token } => {
  938. if let Err(e) = f64::from_str(&amount) {
  939. eprintln!("Invalid amount: {e:?}");
  940. exit(2);
  941. }
  942. let amount = decode_base10(&amount, BALANCE_BASE10_DECIMALS, true)?;
  943. let rcpt = match PublicKey::from_str(&recipient) {
  944. Ok(r) => r,
  945. Err(e) => {
  946. eprintln!("Invalid recipient: {e:?}");
  947. exit(2);
  948. }
  949. };
  950. let drk =
  951. Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  952. let dao_id = drk.get_dao_id(&dao_alias).await?;
  953. let token_id = match drk.get_token(token).await {
  954. Ok(t) => t,
  955. Err(e) => {
  956. eprintln!("Invalid token alias: {e:?}");
  957. exit(2);
  958. }
  959. };
  960. let tx = match drk.dao_propose(dao_id, rcpt, amount, token_id).await {
  961. Ok(tx) => tx,
  962. Err(e) => {
  963. eprintln!("Failed to create DAO proposal: {e:?}");
  964. exit(2);
  965. }
  966. };
  967. println!("{}", base64::encode(&serialize_async(&tx).await));
  968. Ok(())
  969. }
  970. DaoSubcmd::Proposals { dao_alias } => {
  971. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  972. let dao_id = drk.get_dao_id(&dao_alias).await?;
  973. let proposals = drk.get_dao_proposals(dao_id).await?;
  974. for proposal in proposals {
  975. println!("[{}] {:?}", proposal.id, proposal.bulla());
  976. }
  977. Ok(())
  978. }
  979. DaoSubcmd::Proposal { dao_alias, proposal_id } => {
  980. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  981. let dao_id = drk.get_dao_id(&dao_alias).await?;
  982. let proposals = drk.get_dao_proposals(dao_id).await?;
  983. let Some(proposal) = proposals.iter().find(|x| x.id == proposal_id) else {
  984. eprintln!("No such DAO proposal found");
  985. exit(2);
  986. };
  987. println!("{proposal}");
  988. let votes = drk.get_dao_proposal_votes(proposal_id).await?;
  989. println!("votes:");
  990. for vote in votes {
  991. let option = if vote.vote_option { "yes" } else { "no " };
  992. eprintln!(" {option} {}", vote.all_vote_value);
  993. }
  994. Ok(())
  995. }
  996. DaoSubcmd::Vote { dao_alias, proposal_id, vote, vote_weight } => {
  997. let drk =
  998. Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  999. let dao_id = drk.get_dao_id(&dao_alias).await?;
  1000. if let Err(e) = f64::from_str(&vote_weight) {
  1001. eprintln!("Invalid vote weight: {e:?}");
  1002. exit(2);
  1003. }
  1004. let weight = decode_base10(&vote_weight, BALANCE_BASE10_DECIMALS, true)?;
  1005. if vote > 1 {
  1006. eprintln!("Vote can be either 0 (NO) or 1 (YES)");
  1007. exit(2);
  1008. }
  1009. let vote = vote != 0;
  1010. let tx = match drk.dao_vote(dao_id, proposal_id, vote, weight).await {
  1011. Ok(tx) => tx,
  1012. Err(e) => {
  1013. eprintln!("Failed to create DAO Vote transaction: {e:?}");
  1014. exit(2);
  1015. }
  1016. };
  1017. // TODO: Write our_vote in the proposal sql.
  1018. println!("{}", bs58::encode(&serialize_async(&tx).await).into_string());
  1019. Ok(())
  1020. }
  1021. DaoSubcmd::Exec { dao_alias, proposal_id } => {
  1022. let drk =
  1023. Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  1024. let dao_id = drk.get_dao_id(&dao_alias).await?;
  1025. let dao = drk.get_dao_by_id(dao_id).await?;
  1026. let proposal = drk.get_dao_proposal_by_id(proposal_id).await?;
  1027. assert!(proposal.dao_bulla == dao.bulla());
  1028. let tx = match drk.dao_exec(dao, proposal).await {
  1029. Ok(tx) => tx,
  1030. Err(e) => {
  1031. eprintln!("Failed to execute DAO proposal: {e:?}");
  1032. exit(2);
  1033. }
  1034. };
  1035. println!("{}", base64::encode(&serialize_async(&tx).await));
  1036. Ok(())
  1037. }
  1038. },
  1039. Subcmd::Inspect => {
  1040. let mut buf = String::new();
  1041. stdin().read_to_string(&mut buf)?;
  1042. let Some(bytes) = base64::decode(buf.trim()) else {
  1043. eprintln!("Failed to decode transaction");
  1044. exit(1);
  1045. };
  1046. let tx: Transaction = deserialize_async(&bytes).await?;
  1047. println!("{tx:#?}");
  1048. Ok(())
  1049. }
  1050. Subcmd::Broadcast => {
  1051. println!("Reading transaction from stdin...");
  1052. let mut buf = String::new();
  1053. stdin().read_to_string(&mut buf)?;
  1054. let Some(bytes) = base64::decode(buf.trim()) else {
  1055. eprintln!("Failed to decode transaction");
  1056. exit(1);
  1057. };
  1058. let tx = deserialize_async(&bytes).await?;
  1059. let drk = Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  1060. let txid = match drk.broadcast_tx(&tx).await {
  1061. Ok(t) => t,
  1062. Err(e) => {
  1063. eprintln!("Failed to broadcast transaction: {e:?}");
  1064. exit(2);
  1065. }
  1066. };
  1067. println!("Transaction ID: {txid}");
  1068. Ok(())
  1069. }
  1070. Subcmd::Subscribe => {
  1071. let drk = Drk::new(
  1072. args.wallet_path,
  1073. args.wallet_pass,
  1074. Some(args.endpoint.clone()),
  1075. ex.clone(),
  1076. )
  1077. .await?;
  1078. if let Err(e) = drk.subscribe_blocks(args.endpoint, ex).await {
  1079. eprintln!("Block subscription failed: {e:?}");
  1080. exit(2);
  1081. }
  1082. Ok(())
  1083. }
  1084. Subcmd::Scan { reset, list, checkpoint } => {
  1085. let drk = Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  1086. if reset {
  1087. println!("Reset requested.");
  1088. if let Err(e) = drk.scan_blocks(true).await {
  1089. eprintln!("Failed during scanning: {e:?}");
  1090. exit(2);
  1091. }
  1092. println!("Finished scanning blockchain");
  1093. return Ok(())
  1094. }
  1095. if list {
  1096. println!("List requested.");
  1097. // TODO: implement
  1098. unimplemented!()
  1099. }
  1100. if let Some(c) = checkpoint {
  1101. println!("Checkpoint requested: {c}");
  1102. // TODO: implement
  1103. unimplemented!()
  1104. }
  1105. if let Err(e) = drk.scan_blocks(false).await {
  1106. eprintln!("Failed during scanning: {e:?}");
  1107. exit(2);
  1108. }
  1109. println!("Finished scanning blockchain");
  1110. Ok(())
  1111. }
  1112. Subcmd::Explorer { command } => match command {
  1113. ExplorerSubcmd::FetchTx { tx_hash, full, encode } => {
  1114. let tx_hash = TransactionHash(*blake3::Hash::from_hex(&tx_hash)?.as_bytes());
  1115. let drk =
  1116. Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  1117. let tx = match drk.get_tx(&tx_hash).await {
  1118. Ok(tx) => tx,
  1119. Err(e) => {
  1120. eprintln!("Failed to fetch transaction: {e:?}");
  1121. exit(2);
  1122. }
  1123. };
  1124. let Some(tx) = tx else {
  1125. println!("Transaction was not found");
  1126. exit(1);
  1127. };
  1128. // Make sure the tx is correct
  1129. assert_eq!(tx.hash(), tx_hash);
  1130. if encode {
  1131. println!("{}", base64::encode(&serialize_async(&tx).await));
  1132. exit(1)
  1133. }
  1134. println!("Transaction ID: {tx_hash}");
  1135. if full {
  1136. println!("{tx:?}");
  1137. }
  1138. Ok(())
  1139. }
  1140. ExplorerSubcmd::SimulateTx => {
  1141. println!("Reading transaction from stdin...");
  1142. let mut buf = String::new();
  1143. stdin().read_to_string(&mut buf)?;
  1144. let Some(bytes) = base64::decode(buf.trim()) else {
  1145. eprintln!("Failed to decode transaction");
  1146. exit(1);
  1147. };
  1148. let tx = deserialize_async(&bytes).await?;
  1149. let drk =
  1150. Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  1151. let is_valid = match drk.simulate_tx(&tx).await {
  1152. Ok(b) => b,
  1153. Err(e) => {
  1154. eprintln!("Failed to simulate tx: {e:?}");
  1155. exit(2);
  1156. }
  1157. };
  1158. println!("Transaction ID: {}", tx.hash());
  1159. println!("State: {}", if is_valid { "valid" } else { "invalid" });
  1160. Ok(())
  1161. }
  1162. ExplorerSubcmd::TxsHistory { tx_hash, encode } => {
  1163. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  1164. if let Some(c) = tx_hash {
  1165. let (tx_hash, status, tx) = drk.get_tx_history_record(&c).await?;
  1166. if encode {
  1167. println!("{}", base64::encode(&serialize_async(&tx).await));
  1168. exit(1)
  1169. }
  1170. println!("Transaction ID: {tx_hash}");
  1171. println!("Status: {status}");
  1172. println!("{tx:?}");
  1173. return Ok(())
  1174. }
  1175. let map = match drk.get_txs_history().await {
  1176. Ok(m) => m,
  1177. Err(e) => {
  1178. eprintln!("Failed to retrieve transactions history records: {e:?}");
  1179. exit(2);
  1180. }
  1181. };
  1182. // Create a prettytable with the new data:
  1183. let mut table = Table::new();
  1184. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  1185. table.set_titles(row!["Transaction Hash", "Status"]);
  1186. for (txs_hash, status) in map.iter() {
  1187. table.add_row(row![txs_hash, status]);
  1188. }
  1189. if table.is_empty() {
  1190. println!("No transactions found");
  1191. } else {
  1192. println!("{table}");
  1193. }
  1194. Ok(())
  1195. }
  1196. },
  1197. Subcmd::Alias { command } => match command {
  1198. AliasSubcmd::Add { alias, token } => {
  1199. if alias.chars().count() > 5 {
  1200. eprintln!("Error: Alias exceeds 5 characters");
  1201. exit(2);
  1202. }
  1203. let token_id = match TokenId::from_str(token.as_str()) {
  1204. Ok(t) => t,
  1205. Err(e) => {
  1206. eprintln!("Invalid Token ID: {e:?}");
  1207. exit(2);
  1208. }
  1209. };
  1210. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  1211. if let Err(e) = drk.add_alias(alias, token_id).await {
  1212. eprintln!("Failed to add alias: {e:?}");
  1213. exit(2);
  1214. }
  1215. Ok(())
  1216. }
  1217. AliasSubcmd::Show { alias, token } => {
  1218. let token_id = match token {
  1219. Some(t) => match TokenId::from_str(t.as_str()) {
  1220. Ok(t) => Some(t),
  1221. Err(e) => {
  1222. eprintln!("Invalid Token ID: {e:?}");
  1223. exit(2);
  1224. }
  1225. },
  1226. None => None,
  1227. };
  1228. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  1229. let map = drk.get_aliases(alias, token_id).await?;
  1230. // Create a prettytable with the new data:
  1231. let mut table = Table::new();
  1232. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  1233. table.set_titles(row!["Alias", "Token ID"]);
  1234. for (alias, token_id) in map.iter() {
  1235. table.add_row(row![alias, token_id]);
  1236. }
  1237. if table.is_empty() {
  1238. println!("No aliases found");
  1239. } else {
  1240. println!("{table}");
  1241. }
  1242. Ok(())
  1243. }
  1244. AliasSubcmd::Remove { alias } => {
  1245. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  1246. if let Err(e) = drk.remove_alias(alias).await {
  1247. eprintln!("Failed to remove alias: {e:?}");
  1248. exit(2);
  1249. }
  1250. Ok(())
  1251. }
  1252. },
  1253. Subcmd::Token { command } => match command {
  1254. TokenSubcmd::Import => {
  1255. let mut buf = String::new();
  1256. stdin().read_to_string(&mut buf)?;
  1257. let mint_authority = match SecretKey::from_str(buf.trim()) {
  1258. Ok(ma) => ma,
  1259. Err(e) => {
  1260. eprintln!("Invalid secret key: {e:?}");
  1261. exit(2);
  1262. }
  1263. };
  1264. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  1265. if let Err(e) = drk.import_mint_authority(mint_authority).await {
  1266. eprintln!("Importing mint authority failed: {e:?}");
  1267. exit(2);
  1268. };
  1269. let token_id = TokenId::derive(mint_authority);
  1270. println!("Successfully imported mint authority for token ID: {token_id}");
  1271. Ok(())
  1272. }
  1273. TokenSubcmd::GenerateMint => {
  1274. let mint_authority = SecretKey::random(&mut OsRng);
  1275. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  1276. if let Err(e) = drk.import_mint_authority(mint_authority).await {
  1277. eprintln!("Importing mint authority failed: {e:?}");
  1278. exit(2);
  1279. };
  1280. // TODO: see TokenAttributes struct. I'm not sure how to restructure this rn.
  1281. let token_id = TokenId::derive(mint_authority);
  1282. println!("Successfully imported mint authority for token ID: {token_id}");
  1283. Ok(())
  1284. }
  1285. TokenSubcmd::List => {
  1286. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  1287. let tokens = drk.list_tokens().await?;
  1288. let aliases_map = match drk.get_aliases_mapped_by_token().await {
  1289. Ok(map) => map,
  1290. Err(e) => {
  1291. eprintln!("Failed to fetch wallet aliases: {e:?}");
  1292. exit(2);
  1293. }
  1294. };
  1295. let mut table = Table::new();
  1296. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  1297. table.set_titles(row!["Token ID", "Aliases", "Mint Authority", "Frozen"]);
  1298. for (token_id, authority, frozen) in tokens {
  1299. let aliases = match aliases_map.get(&token_id.to_string()) {
  1300. Some(a) => a,
  1301. None => "-",
  1302. };
  1303. table.add_row(row![token_id, aliases, authority, frozen]);
  1304. }
  1305. if table.is_empty() {
  1306. println!("No tokens found");
  1307. } else {
  1308. println!("{table}");
  1309. }
  1310. Ok(())
  1311. }
  1312. // TODO: Mint directly into DAO treasury
  1313. TokenSubcmd::Mint { token, amount, recipient } => {
  1314. let drk =
  1315. Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  1316. if let Err(e) = f64::from_str(&amount) {
  1317. eprintln!("Invalid amount: {e:?}");
  1318. exit(2);
  1319. }
  1320. let _rcpt = match PublicKey::from_str(&recipient) {
  1321. Ok(r) => r,
  1322. Err(e) => {
  1323. eprintln!("Invalid recipient: {e:?}");
  1324. exit(2);
  1325. }
  1326. };
  1327. let _token_id = match drk.get_token(token).await {
  1328. Ok(t) => t,
  1329. Err(e) => {
  1330. eprintln!("Invalid Token ID: {e:?}");
  1331. exit(2);
  1332. }
  1333. };
  1334. panic!("temporarily disabled due to change of API for drk.mint_token() fn");
  1335. //let tx = match drk.mint_token(&amount, rcpt, token_id).await {
  1336. // Ok(tx) => tx,
  1337. // Err(e) => {
  1338. // eprintln!("Failed to create token mint transaction: {e:?}");
  1339. // exit(2);
  1340. // }
  1341. //};
  1342. //println!("{}", base64::encode(&serialize_async(&tx).await));
  1343. //Ok(())
  1344. }
  1345. TokenSubcmd::Freeze { token } => {
  1346. let drk =
  1347. Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
  1348. let _token_id = match drk.get_token(token).await {
  1349. Ok(t) => t,
  1350. Err(e) => {
  1351. eprintln!("Invalid Token ID: {e:?}");
  1352. exit(2);
  1353. }
  1354. };
  1355. panic!("temporarily disabled due to change of API for drk.mint_token() fn");
  1356. //let tx = match drk.freeze_token(token_id).await {
  1357. // Ok(tx) => tx,
  1358. // Err(e) => {
  1359. // eprintln!("Failed to create token freeze transaction: {e:?}");
  1360. // exit(2);
  1361. // }
  1362. //};
  1363. //println!("{}", base64::encode(&serialize_async(&tx).await));
  1364. //Ok(())
  1365. }
  1366. },
  1367. Subcmd::Contract { command } => match command {
  1368. ContractSubcmd::GenerateDeploy => {
  1369. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  1370. if let Err(e) = drk.deploy_auth_keygen().await {
  1371. eprintln!("Error creating deploy auth keypair: {:?}", e);
  1372. exit(1);
  1373. }
  1374. Ok(())
  1375. }
  1376. ContractSubcmd::List => {
  1377. let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?;
  1378. let auths = drk.list_deploy_auth().await?;
  1379. let mut table = Table::new();
  1380. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  1381. table.set_titles(row!["Contract ID", "Frozen"]);
  1382. for (contract_id, frozen) in auths {
  1383. table.add_row(row![contract_id, frozen]);
  1384. }
  1385. if table.is_empty() {
  1386. eprintln!("No deploy authorities found");
  1387. } else {
  1388. println!("{table}");
  1389. }
  1390. Ok(())
  1391. }
  1392. },
  1393. }
  1394. }