main.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2022 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::{Parser, Subcommand};
  26. use darkfi::tx::Transaction;
  27. use darkfi_money_contract::client::Coin;
  28. use darkfi_sdk::{
  29. crypto::{PublicKey, TokenId},
  30. pasta::{group::ff::PrimeField, pallas},
  31. };
  32. use darkfi_serial::{deserialize, serialize};
  33. use serde_json::json;
  34. use simplelog::{ColorChoice, TermLogger, TerminalMode};
  35. use url::Url;
  36. use darkfi::{
  37. cli_desc,
  38. rpc::{client::RpcClient, jsonrpc::JsonRequest},
  39. util::cli::{get_log_config, get_log_level},
  40. };
  41. /// Airdrop methods
  42. mod rpc_airdrop;
  43. /// Payment methods
  44. mod rpc_transfer;
  45. /// Blockchain methods
  46. mod rpc_blockchain;
  47. /// Wallet operation methods for darkfid's JSON-RPC
  48. mod rpc_wallet;
  49. #[derive(Parser)]
  50. #[command(about = cli_desc!())]
  51. struct Args {
  52. #[arg(short, action = clap::ArgAction::Count)]
  53. /// Increase verbosity (-vvv supported)
  54. verbose: u8,
  55. #[arg(short, long, default_value = "tcp://127.0.0.1:8340")]
  56. /// darkfid JSON-RPC endpoint
  57. endpoint: Url,
  58. #[command(subcommand)]
  59. command: Subcmd,
  60. }
  61. #[derive(Subcommand)]
  62. enum Subcmd {
  63. /// Send a ping request to the darkfid RPC endpoint
  64. Ping,
  65. /// Wallet operations
  66. Wallet {
  67. #[arg(long)]
  68. /// Initialize wallet with data for Money Contract (run this first)
  69. initialize: bool,
  70. #[arg(long)]
  71. /// Generate a new keypair in the wallet
  72. keygen: bool,
  73. #[arg(long)]
  74. /// Query the wallet for known balances
  75. balance: bool,
  76. #[arg(long)]
  77. /// Get the default address in the wallet
  78. address: bool,
  79. #[arg(long)]
  80. /// Print all the secret keys from the wallet
  81. secrets: bool,
  82. #[arg(long)]
  83. /// Print the Merkle tree in the wallet
  84. tree: bool,
  85. #[arg(long)]
  86. /// Print all the coins in the wallet
  87. coins: bool,
  88. },
  89. /// Unspend a coin
  90. Unspend {
  91. /// base58-encoded coin to mark as unspent
  92. coin: String,
  93. },
  94. /// Airdrop some tokens
  95. Airdrop {
  96. /// Faucet JSON-RPC endpoint
  97. #[arg(short, long, default_value = "tcp://127.0.0.1:8340")]
  98. faucet_endpoint: Url,
  99. /// Amount to request from the faucet
  100. amount: String,
  101. /// Token ID to request from the faucet
  102. token: String,
  103. /// Optional address to send tokens to (defaults to main address in wallet)
  104. address: Option<String>,
  105. },
  106. /// Create a payment transaction
  107. Transfer {
  108. /// Amount to send
  109. amount: String,
  110. /// Token ID to send
  111. token: String,
  112. /// Recipient address
  113. recipient: String,
  114. },
  115. /// Inspect a transaction from stdin
  116. Inspect,
  117. /// Read a transaction from stdin and broadcast it
  118. Broadcast,
  119. /// Subscribe to incoming blocks from darkfid
  120. ///
  121. /// This subscription will listen for incoming blocks from darkfid and look
  122. /// through their transactions to see if there's any that interest us.
  123. /// With `drk` we look at transactions calling the money contract so we can
  124. /// find coins sent to us and fill our wallet with the necessary metadata.
  125. Subscribe,
  126. /// Scan the blockchain and parse relevant transactions
  127. Scan {
  128. /// Slot number to start scanning from (optional)
  129. slot: Option<u64>,
  130. },
  131. }
  132. pub struct Drk {
  133. pub rpc_client: RpcClient,
  134. }
  135. impl Drk {
  136. async fn ping(&self) -> Result<()> {
  137. let latency = Instant::now();
  138. let req = JsonRequest::new("ping", json!([]));
  139. let rep = self.rpc_client.oneshot_request(req).await?;
  140. let latency = latency.elapsed();
  141. println!("Got reply: {}", rep);
  142. println!("Latency: {:?}", latency);
  143. Ok(())
  144. }
  145. }
  146. #[async_std::main]
  147. async fn main() -> Result<()> {
  148. let args = Args::parse();
  149. if args.verbose > 0 {
  150. let log_level = get_log_level(args.verbose.into());
  151. let log_config = get_log_config();
  152. TermLogger::init(log_level, log_config, TerminalMode::Mixed, ColorChoice::Auto)?;
  153. }
  154. match args.command {
  155. Subcmd::Ping => {
  156. let rpc_client = RpcClient::new(args.endpoint)
  157. .await
  158. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  159. let drk = Drk { rpc_client };
  160. drk.ping().await.with_context(|| "Failed to ping darkfid RPC endpoint")?;
  161. Ok(())
  162. }
  163. Subcmd::Wallet { initialize, keygen, balance, address, secrets, tree, coins } => {
  164. if !initialize && !keygen && !balance && !address && !secrets && !tree && !coins {
  165. eprintln!("Error: You must use at least one flag for this subcommand");
  166. eprintln!("Run with \"wallet -h\" to see the subcommand usage.");
  167. exit(2);
  168. }
  169. let rpc_client = RpcClient::new(args.endpoint)
  170. .await
  171. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  172. let drk = Drk { rpc_client };
  173. if initialize {
  174. drk.wallet_initialize().await.with_context(|| "Failed to initialize wallet")?;
  175. return Ok(())
  176. }
  177. if keygen {
  178. drk.wallet_keygen().await.with_context(|| "Failed to generate keypair")?;
  179. return Ok(())
  180. }
  181. if balance {
  182. drk.wallet_balance().await.with_context(|| "Failed to fetch wallet balance")?;
  183. return Ok(())
  184. }
  185. if address {
  186. let address = drk
  187. .wallet_address(0)
  188. .await
  189. .with_context(|| "Failed to fetch default address")?;
  190. println!("{}", address);
  191. return Ok(())
  192. }
  193. if secrets {
  194. let v =
  195. drk.wallet_secrets().await.with_context(|| "Failed to fetch wallet secrets")?;
  196. drk.rpc_client.close().await?;
  197. for i in v {
  198. println!("{}", i);
  199. }
  200. return Ok(())
  201. }
  202. if tree {
  203. let v = drk.wallet_tree().await.with_context(|| "Failed to fetch Merkle tree")?;
  204. drk.rpc_client.close().await?;
  205. println!("{:#?}", v);
  206. return Ok(())
  207. }
  208. if coins {
  209. let coins = drk
  210. .wallet_coins(true)
  211. .await
  212. .with_context(|| "Failed to fetch coins from wallet")?;
  213. drk.rpc_client.close().await?;
  214. for i in coins {
  215. print!("{} ", bs58::encode(i.0.coin.inner().to_repr()).into_string());
  216. if i.1 {
  217. println!("(spent)");
  218. } else {
  219. println!("(unspent)");
  220. }
  221. }
  222. return Ok(())
  223. }
  224. unreachable!()
  225. }
  226. Subcmd::Unspend { coin } => {
  227. let bytes: [u8; 32] = bs58::decode(&coin).into_vec()?.try_into().unwrap();
  228. let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
  229. Some(v) => v,
  230. None => return Err(anyhow!("Invalid coin")),
  231. };
  232. let coin = Coin::from(elem);
  233. let rpc_client = RpcClient::new(args.endpoint)
  234. .await
  235. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  236. let drk = Drk { rpc_client };
  237. drk.unspend_coin(&coin).await.with_context(|| "Failed to mark coin as unspent")?;
  238. return Ok(())
  239. }
  240. Subcmd::Airdrop { faucet_endpoint, amount, token, address } => {
  241. let amount = f64::from_str(&amount).with_context(|| "Invalid amount")?;
  242. let token_id = TokenId::try_from(token.as_str()).with_context(|| "Invalid Token ID")?;
  243. let rpc_client = RpcClient::new(args.endpoint)
  244. .await
  245. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  246. let drk = Drk { rpc_client };
  247. let address = match address {
  248. Some(v) => PublicKey::from_str(v.as_str()).with_context(|| "Invalid address")?,
  249. None => drk.wallet_address(0).await.with_context(|| {
  250. "Failed to fetch default address, perhaps the wallet was not initialized?"
  251. })?,
  252. };
  253. let txid = drk
  254. .request_airdrop(faucet_endpoint, amount, token_id, address)
  255. .await
  256. .with_context(|| "Failed to request airdrop")?;
  257. println!("Transaction ID: {}", txid);
  258. Ok(())
  259. }
  260. Subcmd::Transfer { amount, token, recipient } => {
  261. let _ = f64::from_str(&amount).with_context(|| "Invalid amount")?;
  262. let token_id = TokenId::try_from(token.as_str()).with_context(|| "Invalid Token ID")?;
  263. let rcpt = PublicKey::from_str(&recipient).with_context(|| "Invalid recipient")?;
  264. let rpc_client = RpcClient::new(args.endpoint)
  265. .await
  266. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  267. let drk = Drk { rpc_client };
  268. let tx = drk
  269. .transfer(&amount, token_id, rcpt)
  270. .await
  271. .with_context(|| "Failed to create payment transaction")?;
  272. println!("{}", bs58::encode(&serialize(&tx)).into_string());
  273. Ok(())
  274. }
  275. Subcmd::Inspect => {
  276. let mut buf = String::new();
  277. stdin().read_to_string(&mut buf)?;
  278. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  279. let tx: Transaction = deserialize(&bytes)?;
  280. println!("{:#?}", tx);
  281. Ok(())
  282. }
  283. Subcmd::Broadcast => {
  284. eprintln!("Reading transaction from stdin...");
  285. let mut buf = String::new();
  286. stdin().read_to_string(&mut buf)?;
  287. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  288. let tx = deserialize(&bytes)?;
  289. let rpc_client = RpcClient::new(args.endpoint)
  290. .await
  291. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  292. let drk = Drk { rpc_client };
  293. let txid =
  294. drk.broadcast_tx(&tx).await.with_context(|| "Failed to broadcast transaction")?;
  295. eprintln!("Transaction ID: {}", txid);
  296. Ok(())
  297. }
  298. Subcmd::Subscribe => {
  299. let rpc_client = RpcClient::new(args.endpoint.clone())
  300. .await
  301. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  302. let drk = Drk { rpc_client };
  303. drk.subscribe_blocks(args.endpoint)
  304. .await
  305. .with_context(|| "Block subscription failed")?;
  306. Ok(())
  307. }
  308. Subcmd::Scan { slot } => {
  309. let rpc_client = RpcClient::new(args.endpoint)
  310. .await
  311. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  312. let drk = Drk { rpc_client };
  313. drk.scan_blocks(slot).await.with_context(|| "Failed during scanning")?;
  314. eprintln!("Finished scanning blockchain");
  315. Ok(())
  316. }
  317. }
  318. }