main.rs 41 KB

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