main.rs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 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. io::{stdin, Read},
  20. process::exit,
  21. str::FromStr,
  22. time::Instant,
  23. };
  24. use anyhow::{anyhow, Context, Result};
  25. use clap::{CommandFactory, Parser, Subcommand};
  26. use clap_complete::{generate, Shell};
  27. use darkfi::{tx::Transaction, util::parse::decode_base10, zk::halo2::Field};
  28. use darkfi_money_contract::client::Coin;
  29. use darkfi_sdk::{
  30. crypto::{PublicKey, SecretKey, TokenId},
  31. pasta::{group::ff::PrimeField, pallas},
  32. };
  33. use darkfi_serial::{deserialize, serialize};
  34. use prettytable::{format, row, Table};
  35. use rand::rngs::OsRng;
  36. use serde_json::json;
  37. use simplelog::{ColorChoice, TermLogger, TerminalMode};
  38. use url::Url;
  39. use darkfi::{
  40. cli_desc,
  41. rpc::{client::RpcClient, jsonrpc::JsonRequest},
  42. util::{
  43. cli::{get_log_config, get_log_level},
  44. parse::encode_base10,
  45. },
  46. };
  47. /// Airdrop methods
  48. mod rpc_airdrop;
  49. /// Payment methods
  50. mod rpc_transfer;
  51. /// Swap methods
  52. mod rpc_swap;
  53. use rpc_swap::PartialSwapData;
  54. /// DAO methods
  55. mod rpc_dao;
  56. /// Blockchain methods
  57. mod rpc_blockchain;
  58. /// CLI utility functions
  59. mod cli_util;
  60. use cli_util::{parse_token_pair, parse_value_pair};
  61. /// Wallet functionality related to DAO
  62. mod wallet_dao;
  63. use wallet_dao::DaoParams;
  64. /// Wallet functionality related to Money
  65. mod wallet_money;
  66. #[derive(Parser)]
  67. #[command(about = cli_desc!())]
  68. struct Args {
  69. #[arg(short, action = clap::ArgAction::Count)]
  70. /// Increase verbosity (-vvv supported)
  71. verbose: u8,
  72. #[arg(short, long, default_value = "tcp://127.0.0.1:8340")]
  73. /// darkfid JSON-RPC endpoint
  74. endpoint: Url,
  75. #[command(subcommand)]
  76. command: Subcmd,
  77. }
  78. #[derive(Subcommand)]
  79. enum Subcmd {
  80. /// Send a ping request to the darkfid RPC endpoint
  81. Ping,
  82. /// Generate a SHELL completion script and print to stdout
  83. Completions {
  84. /// The Shell you want to generate script for
  85. shell: Shell,
  86. },
  87. /// Wallet operations
  88. Wallet {
  89. #[arg(long)]
  90. /// Initialize wallet with data for Money Contract (run this first)
  91. initialize: bool,
  92. #[arg(long)]
  93. /// Generate a new keypair in the wallet
  94. keygen: bool,
  95. #[arg(long)]
  96. /// Query the wallet for known balances
  97. balance: bool,
  98. #[arg(long)]
  99. /// Get the default address in the wallet
  100. address: bool,
  101. #[arg(long)]
  102. /// Print all the secret keys from the wallet
  103. secrets: bool,
  104. #[arg(long)]
  105. /// Import secret keys from stdin into the wallet, separated by newlines
  106. import_secrets: bool,
  107. #[arg(long)]
  108. /// Print the Merkle tree in the wallet
  109. tree: bool,
  110. #[arg(long)]
  111. /// Print all the coins in the wallet
  112. coins: bool,
  113. },
  114. /// Unspend a coin
  115. Unspend {
  116. /// base58-encoded coin to mark as unspent
  117. coin: String,
  118. },
  119. /// Airdrop some tokens
  120. Airdrop {
  121. /// Faucet JSON-RPC endpoint
  122. #[arg(short, long, default_value = "tls://faucetd.testnet.dark.fi:18340")]
  123. faucet_endpoint: Url,
  124. /// Amount to request from the faucet
  125. amount: String,
  126. /// Token ID to request from the faucet
  127. token: String,
  128. /// Optional address to send tokens to (defaults to main address in wallet)
  129. address: Option<String>,
  130. },
  131. /// Create a payment transaction
  132. Transfer {
  133. /// Amount to send
  134. amount: String,
  135. /// Token ID to send
  136. token: String,
  137. /// Recipient address
  138. recipient: String,
  139. /// Mark if this is being sent to a DAO
  140. #[clap(long)]
  141. dao: bool,
  142. /// DAO bulla, if the tokens are being sent to a DAO
  143. dao_bulla: Option<String>,
  144. },
  145. /// OTC atomic swap
  146. #[command(subcommand)]
  147. Otc(OtcSubcmd),
  148. /// Inspect a transaction from stdin
  149. Inspect,
  150. /// Read a transaction from stdin and broadcast it
  151. Broadcast,
  152. /// Subscribe to incoming blocks from darkfid
  153. ///
  154. /// This subscription will listen for incoming blocks from darkfid and look
  155. /// through their transactions to see if there's any that interest us.
  156. /// With `drk` we look at transactions calling the money contract so we can
  157. /// find coins sent to us and fill our wallet with the necessary metadata.
  158. Subscribe,
  159. /// DAO functionalities
  160. #[command(subcommand)]
  161. Dao(DaoSubcmd),
  162. /// Scan the blockchain and parse relevant transactions
  163. Scan {
  164. #[arg(long)]
  165. /// Reset Merkle tree and start scanning from first slot
  166. reset: bool,
  167. #[arg(long)]
  168. /// List all available checkpoints
  169. list: bool,
  170. #[arg(short, long)]
  171. /// Reset Merkle tree to checkpoint index and start scanning
  172. checkpoint: Option<u64>,
  173. },
  174. /// Explorer related subcommands
  175. #[command(subcommand)]
  176. Explorer(ExplorerSubcmd),
  177. /// Manage Token aliases
  178. #[command(subcommand)]
  179. Alias(AliasSubcmd),
  180. }
  181. #[derive(Subcommand)]
  182. enum OtcSubcmd {
  183. /// Initialize the first half of the atomic swap
  184. Init {
  185. /// Value pair to send:recv (11.55:99.42)
  186. #[clap(short, long)]
  187. value_pair: String,
  188. /// Token pair to send:recv (f00:b4r)
  189. #[clap(short, long)]
  190. token_pair: String,
  191. },
  192. /// Build entire swap tx given the first half from stdin
  193. Join,
  194. /// Inspect a swap half or the full swap tx from stdin
  195. Inspect,
  196. /// Sign a transaction given from stdin as the first-half
  197. Sign,
  198. }
  199. #[derive(Subcommand)]
  200. enum DaoSubcmd {
  201. /// Create DAO parameters
  202. Create {
  203. /// The minimum amount of governance tokens needed to open a proposal for this DAO
  204. proposer_limit: String,
  205. /// Minimal threshold of participating total tokens needed for a proposal to pass
  206. quorum: String,
  207. /// The ratio of winning votes/total votes needed for a proposal to pass (2 decimals),
  208. approval_ratio: f64,
  209. /// DAO's governance token ID
  210. gov_token_id: String,
  211. },
  212. /// View DAO data from stdin
  213. View,
  214. /// Import DAO data from stdin
  215. Import {
  216. /// Named identifier for the DAO
  217. dao_name: String,
  218. },
  219. /// List imported DAOs (or info about a specific one)
  220. List {
  221. /// Numeric identifier for the DAO (optional)
  222. dao_id: Option<u64>,
  223. },
  224. /// Show the balance of a DAO
  225. Balance {
  226. /// Numeric identifier for the DAO
  227. dao_id: u64,
  228. },
  229. /// Mint an imported DAO on-chain
  230. Mint {
  231. /// Numeric identifier for the DAO
  232. dao_id: u64,
  233. },
  234. /// Create a proposal for a DAO
  235. Propose {
  236. /// Numeric identifier for the DAO
  237. dao_id: u64,
  238. /// Pubkey to send tokens to with proposal success
  239. recipient: String,
  240. /// Amount to send from DAO with proposal success
  241. amount: String,
  242. /// Token ID to send from DAO with proposal success
  243. token_id: String,
  244. },
  245. /// List DAO proposals
  246. Proposals {
  247. /// Numeric identifier for the DAO
  248. dao_id: u64,
  249. },
  250. /// View a DAO proposal data
  251. Proposal {
  252. /// Numeric identifier for the DAO
  253. dao_id: u64,
  254. /// Numeric identifier for the proposal
  255. proposal_id: u64,
  256. },
  257. /// Vote on a given proposal
  258. Vote {
  259. /// Numeric identifier for the DAO
  260. dao_id: u64,
  261. /// Numeric identifier for the proposal
  262. proposal_id: u64,
  263. /// Vote (0 for NO, 1 for YES)
  264. vote: u8,
  265. /// Vote weight (amount of governance tokens)
  266. vote_weight: String,
  267. },
  268. /// Execute a DAO proposal
  269. Exec {
  270. /// Numeric identifier for the DAO
  271. dao_id: u64,
  272. /// Numeric identifier for the proposal
  273. proposal_id: u64,
  274. },
  275. }
  276. #[derive(Subcommand)]
  277. enum ExplorerSubcmd {
  278. /// Fetch a blockchain transaction by hash
  279. FetchTx {
  280. /// Transaction hash
  281. tx_hash: String,
  282. },
  283. /// Read a transaction from stdin and simulate it
  284. SimulateTx,
  285. }
  286. #[derive(Subcommand)]
  287. enum AliasSubcmd {
  288. /// Create a Token alias
  289. Add {
  290. /// Token alias
  291. alias: String,
  292. /// Token to create alias for
  293. token: String,
  294. },
  295. /// Print alias info of optional arguments.
  296. /// If no argument is provided, list all the aliases in the wallet.
  297. Show {
  298. /// Token alias to search for
  299. #[clap(short, long)]
  300. alias: Option<String>,
  301. /// Token to search alias for
  302. #[clap(short, long)]
  303. token: Option<String>,
  304. },
  305. /// Remove a Token alias
  306. Remove {
  307. /// Token alias to remove
  308. alias: String,
  309. },
  310. }
  311. pub struct Drk {
  312. pub rpc_client: RpcClient,
  313. }
  314. impl Drk {
  315. async fn new(endpoint: Url) -> Result<Self> {
  316. let rpc_client = RpcClient::new(endpoint).await?;
  317. Ok(Self { rpc_client })
  318. }
  319. async fn ping(&self) -> Result<()> {
  320. let latency = Instant::now();
  321. let req = JsonRequest::new("ping", json!([]));
  322. let rep = self.rpc_client.oneshot_request(req).await?;
  323. let latency = latency.elapsed();
  324. println!("Got reply: {}", rep);
  325. println!("Latency: {:?}", latency);
  326. Ok(())
  327. }
  328. }
  329. #[async_std::main]
  330. async fn main() -> Result<()> {
  331. let args = Args::parse();
  332. if args.verbose > 0 {
  333. let log_level = get_log_level(args.verbose.into());
  334. let log_config = get_log_config();
  335. TermLogger::init(log_level, log_config, TerminalMode::Mixed, ColorChoice::Auto)?;
  336. }
  337. match args.command {
  338. Subcmd::Ping => {
  339. let drk = Drk::new(args.endpoint).await?;
  340. drk.ping().await.with_context(|| "Failed to ping darkfid RPC endpoint")?;
  341. Ok(())
  342. }
  343. Subcmd::Completions { shell } => {
  344. let mut cmd = Args::command();
  345. generate(shell, &mut cmd, "./drk", &mut std::io::stdout());
  346. Ok(())
  347. }
  348. Subcmd::Wallet {
  349. initialize,
  350. keygen,
  351. balance,
  352. address,
  353. secrets,
  354. import_secrets,
  355. tree,
  356. coins,
  357. } => {
  358. if !initialize &&
  359. !keygen &&
  360. !balance &&
  361. !address &&
  362. !secrets &&
  363. !tree &&
  364. !coins &&
  365. !import_secrets
  366. {
  367. eprintln!("Error: You must use at least one flag for this subcommand");
  368. eprintln!("Run with \"wallet -h\" to see the subcommand usage.");
  369. exit(2);
  370. }
  371. let drk = Drk::new(args.endpoint).await?;
  372. if initialize {
  373. drk.initialize_money().await?;
  374. drk.initialize_dao().await?;
  375. return Ok(())
  376. }
  377. if keygen {
  378. drk.money_keygen().await.with_context(|| "Failed to generate keypair")?;
  379. return Ok(())
  380. }
  381. if balance {
  382. let balmap =
  383. drk.money_balance().await.with_context(|| "Failed to fetch wallet balance")?;
  384. let aliases_map = drk
  385. .get_aliases_mapped_by_token()
  386. .await
  387. .with_context(|| "Failed to fetch wallet aliases")?;
  388. // Create a prettytable with the new data:
  389. let mut table = Table::new();
  390. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  391. table.set_titles(row!["Token ID", "Aliases", "Balance"]);
  392. for (token_id, balance) in balmap.iter() {
  393. let aliases = match aliases_map.get(token_id) {
  394. Some(a) => a,
  395. None => "-",
  396. };
  397. // FIXME: Don't hardcode to 8 decimals
  398. table.add_row(row![token_id, aliases, encode_base10(*balance, 8)]);
  399. }
  400. if table.is_empty() {
  401. println!("No unspent balances found");
  402. } else {
  403. println!("{}", table);
  404. }
  405. return Ok(())
  406. }
  407. if address {
  408. let address = drk
  409. .wallet_address(1) // <-- TODO: Use is_default from the sql table
  410. .await
  411. .with_context(|| "Failed to fetch default address")?;
  412. println!("{}", address);
  413. return Ok(())
  414. }
  415. if secrets {
  416. let v = drk
  417. .get_money_secrets()
  418. .await
  419. .with_context(|| "Failed to fetch wallet secrets")?;
  420. drk.rpc_client.close().await?;
  421. for i in v {
  422. println!("{}", i);
  423. }
  424. return Ok(())
  425. }
  426. if import_secrets {
  427. let mut secrets = vec![];
  428. let lines = stdin().lines();
  429. for (i, line) in lines.enumerate() {
  430. if let Ok(line) = line {
  431. let bytes = bs58::decode(&line.trim()).into_vec()?;
  432. let Ok(secret) = deserialize(&bytes) else {
  433. eprintln!("Warning: Failed to deserialize secret on line {}", i);
  434. continue
  435. };
  436. secrets.push(secret);
  437. }
  438. }
  439. let pubkeys = drk
  440. .import_money_secrets(secrets)
  441. .await
  442. .with_context(|| "Failed to import secret keys into wallet")?;
  443. drk.rpc_client.close().await?;
  444. for key in pubkeys {
  445. println!("{}", key);
  446. }
  447. return Ok(())
  448. }
  449. if tree {
  450. let v =
  451. drk.get_money_tree().await.with_context(|| "Failed to fetch Merkle tree")?;
  452. drk.rpc_client.close().await?;
  453. println!("{:#?}", v);
  454. return Ok(())
  455. }
  456. if coins {
  457. let coins = drk
  458. .get_coins(true)
  459. .await
  460. .with_context(|| "Failed to fetch coins from wallet")?;
  461. let aliases_map = drk
  462. .get_aliases_mapped_by_token()
  463. .await
  464. .with_context(|| "Failed to fetch wallet aliases")?;
  465. drk.rpc_client.close().await?;
  466. if coins.is_empty() {
  467. return Ok(())
  468. }
  469. let mut table = Table::new();
  470. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  471. table.set_titles(row![
  472. "Coin",
  473. "Spent",
  474. "Token ID",
  475. "Aliases",
  476. "Value",
  477. "Spend Hook",
  478. "User Data"
  479. ]);
  480. let zero = pallas::Base::zero();
  481. for coin in coins {
  482. let aliases = match aliases_map.get(&coin.0.note.token_id.to_string()) {
  483. Some(a) => a,
  484. None => "-",
  485. };
  486. let spend_hook = if coin.0.note.spend_hook != zero {
  487. format!(
  488. "{}",
  489. bs58::encode(&serialize(&coin.0.note.spend_hook)).into_string()
  490. )
  491. } else {
  492. String::from("-")
  493. };
  494. let user_data = if coin.0.note.user_data != zero {
  495. format!(
  496. "{}",
  497. bs58::encode(&serialize(&coin.0.note.user_data)).into_string()
  498. )
  499. } else {
  500. String::from("-")
  501. };
  502. table.add_row(row![
  503. format!("{}", bs58::encode(&serialize(&coin.0.coin.inner())).into_string()),
  504. coin.1,
  505. coin.0.note.token_id,
  506. aliases,
  507. format!("{} ({})", coin.0.note.value, encode_base10(coin.0.note.value, 8)),
  508. spend_hook,
  509. user_data
  510. ]);
  511. }
  512. println!("{}", table);
  513. return Ok(())
  514. }
  515. unreachable!()
  516. }
  517. Subcmd::Unspend { coin } => {
  518. let bytes: [u8; 32] = bs58::decode(&coin).into_vec()?.try_into().unwrap();
  519. let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
  520. Some(v) => v,
  521. None => return Err(anyhow!("Invalid coin")),
  522. };
  523. let coin = Coin::from(elem);
  524. let drk = Drk::new(args.endpoint).await?;
  525. drk.unspend_coin(&coin).await.with_context(|| "Failed to mark coin as unspent")?;
  526. Ok(())
  527. }
  528. Subcmd::Airdrop { faucet_endpoint, amount, token, address } => {
  529. let amount = f64::from_str(&amount).with_context(|| "Invalid amount")?;
  530. let drk = Drk::new(args.endpoint).await?;
  531. let token_id = drk.get_token(token).await.with_context(|| "Invalid Token ID")?;
  532. let address = match address {
  533. Some(v) => PublicKey::from_str(v.as_str()).with_context(|| "Invalid address")?,
  534. None => drk.wallet_address(1).await.with_context(|| {
  535. "Failed to fetch default address, perhaps the wallet was not initialized?"
  536. })?,
  537. };
  538. let txid = drk
  539. .request_airdrop(faucet_endpoint, amount, token_id, address)
  540. .await
  541. .with_context(|| "Failed to request airdrop")?;
  542. println!("Transaction ID: {}", txid);
  543. Ok(())
  544. }
  545. Subcmd::Transfer { amount, token, recipient, dao, dao_bulla } => {
  546. let _ = f64::from_str(&amount).with_context(|| "Invalid amount")?;
  547. let rcpt = PublicKey::from_str(&recipient).with_context(|| "Invalid recipient")?;
  548. let drk = Drk::new(args.endpoint).await?;
  549. let token_id = drk.get_token(token).await.with_context(|| "Invalid Token ID")?;
  550. let tx = drk
  551. .transfer(&amount, token_id, rcpt, dao, dao_bulla)
  552. .await
  553. .with_context(|| "Failed to create payment transaction")?;
  554. println!("{}", bs58::encode(&serialize(&tx)).into_string());
  555. Ok(())
  556. }
  557. Subcmd::Otc(cmd) => {
  558. let drk = Drk::new(args.endpoint).await?;
  559. match cmd {
  560. OtcSubcmd::Init { value_pair, token_pair } => {
  561. let (vp_send, vp_recv) = parse_value_pair(&value_pair)?;
  562. let (tp_send, tp_recv) = parse_token_pair(&token_pair)?;
  563. let half = drk
  564. .init_swap(vp_send, tp_send, vp_recv, tp_recv)
  565. .await
  566. .with_context(|| "Failed to create swap transaction half")?;
  567. println!("{}", bs58::encode(&serialize(&half)).into_string());
  568. Ok(())
  569. }
  570. OtcSubcmd::Join => {
  571. let mut buf = String::new();
  572. stdin().read_to_string(&mut buf)?;
  573. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  574. let partial: PartialSwapData = deserialize(&bytes)?;
  575. let tx = drk
  576. .join_swap(partial)
  577. .await
  578. .with_context(|| "Failed to create a join swap transaction")?;
  579. println!("{}", bs58::encode(&serialize(&tx)).into_string());
  580. Ok(())
  581. }
  582. OtcSubcmd::Inspect => {
  583. let mut buf = String::new();
  584. stdin().read_to_string(&mut buf)?;
  585. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  586. drk.inspect_swap(bytes).await.with_context(|| "Failed to inspect swap")?;
  587. Ok(())
  588. }
  589. OtcSubcmd::Sign => {
  590. let mut buf = String::new();
  591. stdin().read_to_string(&mut buf)?;
  592. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  593. let mut tx: Transaction = deserialize(&bytes)?;
  594. drk.sign_swap(&mut tx)
  595. .await
  596. .with_context(|| "Failed to sign joined swap transaction")?;
  597. println!("{}", bs58::encode(&serialize(&tx)).into_string());
  598. Ok(())
  599. }
  600. }
  601. }
  602. Subcmd::Inspect => {
  603. let mut buf = String::new();
  604. stdin().read_to_string(&mut buf)?;
  605. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  606. let tx: Transaction = deserialize(&bytes)?;
  607. println!("{:#?}", tx);
  608. Ok(())
  609. }
  610. Subcmd::Broadcast => {
  611. eprintln!("Reading transaction from stdin...");
  612. let mut buf = String::new();
  613. stdin().read_to_string(&mut buf)?;
  614. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  615. let tx = deserialize(&bytes)?;
  616. let drk = Drk::new(args.endpoint).await?;
  617. let txid =
  618. drk.broadcast_tx(&tx).await.with_context(|| "Failed to broadcast transaction")?;
  619. println!("Transaction ID: {}", txid);
  620. Ok(())
  621. }
  622. Subcmd::Subscribe => {
  623. let drk = Drk::new(args.endpoint.clone()).await?;
  624. drk.subscribe_blocks(args.endpoint)
  625. .await
  626. .with_context(|| "Block subscription failed")?;
  627. Ok(())
  628. }
  629. Subcmd::Scan { reset, list, checkpoint } => {
  630. let drk = Drk::new(args.endpoint).await?;
  631. if reset {
  632. eprintln!("Reset requested.");
  633. drk.scan_blocks(true).await.with_context(|| "Failed during scanning")?;
  634. return Ok(())
  635. }
  636. if list {
  637. eprintln!("List requested.");
  638. // TODO: implement
  639. return Ok(())
  640. }
  641. if let Some(c) = checkpoint {
  642. eprintln!("Checkpoint requested: {}", c);
  643. // TODO: implement
  644. return Ok(())
  645. }
  646. drk.scan_blocks(false).await.with_context(|| "Failed during scanning")?;
  647. eprintln!("Finished scanning blockchain");
  648. Ok(())
  649. }
  650. Subcmd::Dao(cmd) => match cmd {
  651. DaoSubcmd::Create { proposer_limit, quorum, approval_ratio, gov_token_id } => {
  652. let _ = f64::from_str(&proposer_limit).with_context(|| "Invalid proposer limit")?;
  653. let _ = f64::from_str(&quorum).with_context(|| "Invalid quorum")?;
  654. let proposer_limit = decode_base10(&proposer_limit, 8, true)?;
  655. let quorum = decode_base10(&quorum, 8, true)?;
  656. if approval_ratio > 1.0 {
  657. eprintln!("Error: Approval ratio cannot be >1.0");
  658. exit(1);
  659. }
  660. let approval_ratio_base = 100_u64;
  661. let approval_ratio_quot = (approval_ratio * approval_ratio_base as f64) as u64;
  662. let drk = Drk::new(args.endpoint).await?;
  663. let gov_token_id =
  664. drk.get_token(gov_token_id).await.with_context(|| "Invalid Token ID")?;
  665. let secret_key = SecretKey::random(&mut OsRng);
  666. let bulla_blind = pallas::Base::random(&mut OsRng);
  667. let dao_params = DaoParams {
  668. proposer_limit,
  669. quorum,
  670. approval_ratio_base,
  671. approval_ratio_quot,
  672. gov_token_id,
  673. secret_key,
  674. bulla_blind,
  675. };
  676. let encoded = bs58::encode(&serialize(&dao_params)).into_string();
  677. println!("{}", encoded);
  678. Ok(())
  679. }
  680. DaoSubcmd::View => {
  681. let mut buf = String::new();
  682. stdin().read_to_string(&mut buf)?;
  683. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  684. let dao_params: DaoParams = deserialize(&bytes)?;
  685. println!("{}", dao_params);
  686. Ok(())
  687. }
  688. DaoSubcmd::Import { dao_name } => {
  689. let mut buf = String::new();
  690. stdin().read_to_string(&mut buf)?;
  691. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  692. let dao_params: DaoParams = deserialize(&bytes)?;
  693. let drk = Drk::new(args.endpoint).await?;
  694. drk.import_dao(dao_name, dao_params)
  695. .await
  696. .with_context(|| "Failed to import DAO")?;
  697. Ok(())
  698. }
  699. DaoSubcmd::List { dao_id } => {
  700. let drk = Drk::new(args.endpoint).await?;
  701. drk.dao_list(dao_id).await.with_context(|| "Failed to list DAO")?;
  702. Ok(())
  703. }
  704. DaoSubcmd::Balance { dao_id } => {
  705. let drk = Drk::new(args.endpoint).await?;
  706. let balmap =
  707. drk.dao_balance(dao_id).await.with_context(|| "Failed to fetch DAO balance")?;
  708. let aliases_map = drk
  709. .get_aliases_mapped_by_token()
  710. .await
  711. .with_context(|| "Failed to fetch wallet aliases")?;
  712. // Create a prettytable with the new data:
  713. let mut table = Table::new();
  714. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  715. table.set_titles(row!["Token ID", "Aliases", "Balance"]);
  716. for (token_id, balance) in balmap.iter() {
  717. let aliases = match aliases_map.get(token_id) {
  718. Some(a) => a,
  719. None => "-",
  720. };
  721. // FIXME: Don't hardcode to 8 decimals
  722. table.add_row(row![token_id, aliases, encode_base10(*balance, 8)]);
  723. }
  724. if table.is_empty() {
  725. println!("No unspent balances found");
  726. } else {
  727. println!("{}", table);
  728. }
  729. return Ok(())
  730. }
  731. DaoSubcmd::Mint { dao_id } => {
  732. let drk = Drk::new(args.endpoint).await?;
  733. let tx = drk.dao_mint(dao_id).await.with_context(|| "Failed to mint DAO")?;
  734. println!("{}", bs58::encode(&serialize(&tx)).into_string());
  735. Ok(())
  736. }
  737. DaoSubcmd::Propose { dao_id, recipient, amount, token_id } => {
  738. let _ = f64::from_str(&amount).with_context(|| "Invalid amount")?;
  739. let amount = decode_base10(&amount, 8, true)?;
  740. let rcpt = PublicKey::from_str(&recipient).with_context(|| "Invalid recipient")?;
  741. let drk = Drk::new(args.endpoint).await?;
  742. let token_id = drk.get_token(token_id).await.with_context(|| "Invalid Token ID")?;
  743. let tx = drk
  744. .dao_propose(dao_id, rcpt, amount, token_id)
  745. .await
  746. .with_context(|| "Failed to create DAO proposal")?;
  747. println!("{}", bs58::encode(&serialize(&tx)).into_string());
  748. Ok(())
  749. }
  750. DaoSubcmd::Proposals { dao_id } => {
  751. let drk = Drk::new(args.endpoint).await?;
  752. let proposals = drk.get_dao_proposals(dao_id).await?;
  753. for proposal in proposals {
  754. println!("[{}] {:?}", proposal.id, proposal.bulla());
  755. }
  756. Ok(())
  757. }
  758. DaoSubcmd::Proposal { dao_id, proposal_id } => {
  759. let drk = Drk::new(args.endpoint).await?;
  760. let proposals = drk.get_dao_proposals(dao_id).await?;
  761. let Some(proposal) = proposals.iter().find(|x| x.id == proposal_id) else {
  762. eprintln!("No such DAO proposal found");
  763. exit(1);
  764. };
  765. println!("{}", proposal);
  766. Ok(())
  767. }
  768. DaoSubcmd::Vote { dao_id, proposal_id, vote, vote_weight } => {
  769. let drk = Drk::new(args.endpoint).await?;
  770. let _ = f64::from_str(&vote_weight).with_context(|| "Invalid vote weight")?;
  771. let weight = decode_base10(&vote_weight, 8, true)?;
  772. if vote > 1 {
  773. eprintln!("Vote can be either 0 (NO) or 1 (YES)");
  774. exit(1);
  775. }
  776. let vote = vote != 0;
  777. let tx = drk
  778. .dao_vote(dao_id, proposal_id, vote, weight)
  779. .await
  780. .with_context(|| "Failed to create DAO Vote transaction")?;
  781. // TODO: Write our_vote in the proposal sql.
  782. println!("{}", bs58::encode(&serialize(&tx)).into_string());
  783. Ok(())
  784. }
  785. DaoSubcmd::Exec { dao_id, proposal_id } => {
  786. let drk = Drk::new(args.endpoint).await?;
  787. let dao = drk.get_dao_by_id(dao_id).await?;
  788. let proposal = drk.get_dao_proposal_by_id(proposal_id).await?;
  789. assert!(proposal.dao_bulla == dao.bulla());
  790. let tx = drk
  791. .dao_exec(dao, proposal)
  792. .await
  793. .with_context(|| "Failed to execute DAO proposal")?;
  794. println!("{}", bs58::encode(&serialize(&tx)).into_string());
  795. Ok(())
  796. }
  797. },
  798. Subcmd::Explorer(cmd) => match cmd {
  799. ExplorerSubcmd::FetchTx { tx_hash } => {
  800. let tx_hash = blake3::Hash::from_hex(&tx_hash)?;
  801. let drk = Drk::new(args.endpoint).await?;
  802. let tx = if let Some(tx) =
  803. drk.get_tx(&tx_hash).await.with_context(|| "Failed to fetch transaction")?
  804. {
  805. tx
  806. } else {
  807. eprintln!("Tx not found");
  808. return Ok(())
  809. };
  810. // Make sure the tx is correct
  811. assert_eq!(tx.hash(), tx_hash);
  812. println!("Transaction ID: {}", tx_hash);
  813. let is_err = drk
  814. .was_erroneous_tx(&tx_hash)
  815. .await
  816. .with_context(|| "Failed to get tx state")?;
  817. println!("State: {}", if is_err { "failed" } else { "passed" });
  818. Ok(())
  819. }
  820. ExplorerSubcmd::SimulateTx => {
  821. eprintln!("Reading transaction from stdin...");
  822. let mut buf = String::new();
  823. stdin().read_to_string(&mut buf)?;
  824. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  825. let tx = deserialize(&bytes)?;
  826. let drk = Drk::new(args.endpoint).await?;
  827. let is_valid =
  828. drk.simulate_tx(&tx).await.with_context(|| "Failed to simulate tx")?;
  829. println!("Transaction ID: {}", tx.hash());
  830. println!("State: {}", if is_valid { "valid" } else { "invalid" });
  831. Ok(())
  832. }
  833. },
  834. Subcmd::Alias(cmd) => match cmd {
  835. AliasSubcmd::Add { alias, token } => {
  836. if alias.chars().count() > 5 {
  837. eprintln!("Error: Alias exceeds 5 characters");
  838. return Ok(())
  839. }
  840. let token_id =
  841. TokenId::try_from(token.as_str()).with_context(|| "Invalid Token ID")?;
  842. let drk = Drk::new(args.endpoint).await?;
  843. drk.add_alias(alias, token_id).await?;
  844. Ok(())
  845. }
  846. AliasSubcmd::Show { alias, token } => {
  847. let token_id = match token {
  848. Some(t) => {
  849. Some(TokenId::try_from(t.as_str()).with_context(|| "Invalid Token ID")?)
  850. }
  851. None => None,
  852. };
  853. let drk = Drk::new(args.endpoint).await?;
  854. let map = drk.get_aliases(alias, token_id).await?;
  855. // Create a prettytable with the new data:
  856. let mut table = Table::new();
  857. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  858. table.set_titles(row!["Alias", "Token ID"]);
  859. for (alias, token_id) in map.iter() {
  860. table.add_row(row![alias, token_id]);
  861. }
  862. if table.is_empty() {
  863. println!("No aliases found");
  864. } else {
  865. println!("{}", table);
  866. }
  867. Ok(())
  868. }
  869. AliasSubcmd::Remove { alias } => {
  870. let drk = Drk::new(args.endpoint).await?;
  871. drk.remove_alias(alias).await?;
  872. Ok(())
  873. }
  874. },
  875. }
  876. }