main.rs 37 KB

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