main.rs 41 KB

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