main.rs 51 KB

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