main.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  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. /// Swap methods
  46. mod rpc_swap;
  47. use rpc_swap::PartialSwapData;
  48. /// Blockchain methods
  49. mod rpc_blockchain;
  50. /// Wallet operation methods for darkfid's JSON-RPC
  51. mod rpc_wallet;
  52. /// CLI utility functions
  53. mod cli_util;
  54. use cli_util::{parse_token_pair, parse_value_pair};
  55. #[derive(Parser)]
  56. #[command(about = cli_desc!())]
  57. struct Args {
  58. #[arg(short, action = clap::ArgAction::Count)]
  59. /// Increase verbosity (-vvv supported)
  60. verbose: u8,
  61. #[arg(short, long, default_value = "tcp://127.0.0.1:8340")]
  62. /// darkfid JSON-RPC endpoint
  63. endpoint: Url,
  64. #[command(subcommand)]
  65. command: Subcmd,
  66. }
  67. #[derive(Subcommand)]
  68. enum Subcmd {
  69. /// Send a ping request to the darkfid RPC endpoint
  70. Ping,
  71. /// Wallet operations
  72. Wallet {
  73. #[arg(long)]
  74. /// Initialize wallet with data for Money Contract (run this first)
  75. initialize: bool,
  76. #[arg(long)]
  77. /// Generate a new keypair in the wallet
  78. keygen: bool,
  79. #[arg(long)]
  80. /// Query the wallet for known balances
  81. balance: bool,
  82. #[arg(long)]
  83. /// Get the default address in the wallet
  84. address: bool,
  85. #[arg(long)]
  86. /// Print all the secret keys from the wallet
  87. secrets: bool,
  88. #[arg(long)]
  89. /// Print the Merkle tree in the wallet
  90. tree: bool,
  91. #[arg(long)]
  92. /// Print all the coins in the wallet
  93. coins: bool,
  94. },
  95. /// Unspend a coin
  96. Unspend {
  97. /// base58-encoded coin to mark as unspent
  98. coin: String,
  99. },
  100. /// Airdrop some tokens
  101. Airdrop {
  102. /// Faucet JSON-RPC endpoint
  103. #[arg(short, long, default_value = "tcp://127.0.0.1:8340")]
  104. faucet_endpoint: Url,
  105. /// Amount to request from the faucet
  106. amount: String,
  107. /// Token ID to request from the faucet
  108. token: String,
  109. /// Optional address to send tokens to (defaults to main address in wallet)
  110. address: Option<String>,
  111. },
  112. /// Create a payment transaction
  113. Transfer {
  114. /// Amount to send
  115. amount: String,
  116. /// Token ID to send
  117. token: String,
  118. /// Recipient address
  119. recipient: String,
  120. },
  121. /// OTC atomic swap
  122. #[command(subcommand)]
  123. Otc(OtcSubcmd),
  124. /// Inspect a transaction from stdin
  125. Inspect,
  126. /// Read a transaction from stdin and broadcast it
  127. Broadcast,
  128. /// Subscribe to incoming blocks from darkfid
  129. ///
  130. /// This subscription will listen for incoming blocks from darkfid and look
  131. /// through their transactions to see if there's any that interest us.
  132. /// With `drk` we look at transactions calling the money contract so we can
  133. /// find coins sent to us and fill our wallet with the necessary metadata.
  134. Subscribe,
  135. /// Scan the blockchain and parse relevant transactions
  136. Scan {
  137. /// Slot number to start scanning from (optional)
  138. slot: Option<u64>,
  139. },
  140. }
  141. #[derive(Subcommand)]
  142. enum OtcSubcmd {
  143. /// Initialize the first half of the atomic swap
  144. Init {
  145. /// Value pair to send:recv (11.55:99.42)
  146. #[clap(short, long)]
  147. value_pair: String,
  148. /// Token pair to send:recv (f00:b4r)
  149. #[clap(short, long)]
  150. token_pair: String,
  151. },
  152. /// Build entire swap tx given the first half from stdin
  153. Join,
  154. /// Sign a transaction given from stdin as the first-half
  155. Sign,
  156. }
  157. pub struct Drk {
  158. pub rpc_client: RpcClient,
  159. }
  160. impl Drk {
  161. async fn ping(&self) -> Result<()> {
  162. let latency = Instant::now();
  163. let req = JsonRequest::new("ping", json!([]));
  164. let rep = self.rpc_client.oneshot_request(req).await?;
  165. let latency = latency.elapsed();
  166. println!("Got reply: {}", rep);
  167. println!("Latency: {:?}", latency);
  168. Ok(())
  169. }
  170. }
  171. #[async_std::main]
  172. async fn main() -> Result<()> {
  173. let args = Args::parse();
  174. if args.verbose > 0 {
  175. let log_level = get_log_level(args.verbose.into());
  176. let log_config = get_log_config();
  177. TermLogger::init(log_level, log_config, TerminalMode::Mixed, ColorChoice::Auto)?;
  178. }
  179. match args.command {
  180. Subcmd::Ping => {
  181. let rpc_client = RpcClient::new(args.endpoint)
  182. .await
  183. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  184. let drk = Drk { rpc_client };
  185. drk.ping().await.with_context(|| "Failed to ping darkfid RPC endpoint")?;
  186. Ok(())
  187. }
  188. Subcmd::Wallet { initialize, keygen, balance, address, secrets, tree, coins } => {
  189. if !initialize && !keygen && !balance && !address && !secrets && !tree && !coins {
  190. eprintln!("Error: You must use at least one flag for this subcommand");
  191. eprintln!("Run with \"wallet -h\" to see the subcommand usage.");
  192. exit(2);
  193. }
  194. let rpc_client = RpcClient::new(args.endpoint)
  195. .await
  196. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  197. let drk = Drk { rpc_client };
  198. if initialize {
  199. drk.wallet_initialize().await.with_context(|| "Failed to initialize wallet")?;
  200. return Ok(())
  201. }
  202. if keygen {
  203. drk.wallet_keygen().await.with_context(|| "Failed to generate keypair")?;
  204. return Ok(())
  205. }
  206. if balance {
  207. drk.wallet_balance().await.with_context(|| "Failed to fetch wallet balance")?;
  208. return Ok(())
  209. }
  210. if address {
  211. let address = drk
  212. .wallet_address(0)
  213. .await
  214. .with_context(|| "Failed to fetch default address")?;
  215. println!("{}", address);
  216. return Ok(())
  217. }
  218. if secrets {
  219. let v =
  220. drk.wallet_secrets().await.with_context(|| "Failed to fetch wallet secrets")?;
  221. drk.rpc_client.close().await?;
  222. for i in v {
  223. println!("{}", i);
  224. }
  225. return Ok(())
  226. }
  227. if tree {
  228. let v = drk.wallet_tree().await.with_context(|| "Failed to fetch Merkle tree")?;
  229. drk.rpc_client.close().await?;
  230. println!("{:#?}", v);
  231. return Ok(())
  232. }
  233. if coins {
  234. let coins = drk
  235. .wallet_coins(true)
  236. .await
  237. .with_context(|| "Failed to fetch coins from wallet")?;
  238. drk.rpc_client.close().await?;
  239. for i in coins {
  240. print!("{:?} ", i.0.coin.inner());
  241. if i.1 {
  242. println!("(spent)");
  243. } else {
  244. println!("(unspent)");
  245. }
  246. }
  247. return Ok(())
  248. }
  249. unreachable!()
  250. }
  251. Subcmd::Unspend { coin } => {
  252. let bytes: [u8; 32] = bs58::decode(&coin).into_vec()?.try_into().unwrap();
  253. let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() {
  254. Some(v) => v,
  255. None => return Err(anyhow!("Invalid coin")),
  256. };
  257. let coin = Coin::from(elem);
  258. let rpc_client = RpcClient::new(args.endpoint)
  259. .await
  260. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  261. let drk = Drk { rpc_client };
  262. drk.unspend_coin(&coin).await.with_context(|| "Failed to mark coin as unspent")?;
  263. Ok(())
  264. }
  265. Subcmd::Airdrop { faucet_endpoint, amount, token, address } => {
  266. let amount = f64::from_str(&amount).with_context(|| "Invalid amount")?;
  267. let token_id = TokenId::try_from(token.as_str()).with_context(|| "Invalid Token ID")?;
  268. let rpc_client = RpcClient::new(args.endpoint)
  269. .await
  270. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  271. let drk = Drk { rpc_client };
  272. let address = match address {
  273. Some(v) => PublicKey::from_str(v.as_str()).with_context(|| "Invalid address")?,
  274. None => drk.wallet_address(0).await.with_context(|| {
  275. "Failed to fetch default address, perhaps the wallet was not initialized?"
  276. })?,
  277. };
  278. let txid = drk
  279. .request_airdrop(faucet_endpoint, amount, token_id, address)
  280. .await
  281. .with_context(|| "Failed to request airdrop")?;
  282. println!("Transaction ID: {}", txid);
  283. Ok(())
  284. }
  285. Subcmd::Transfer { amount, token, recipient } => {
  286. let _ = f64::from_str(&amount).with_context(|| "Invalid amount")?;
  287. let token_id = TokenId::try_from(token.as_str()).with_context(|| "Invalid Token ID")?;
  288. let rcpt = PublicKey::from_str(&recipient).with_context(|| "Invalid recipient")?;
  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 tx = drk
  294. .transfer(&amount, token_id, rcpt)
  295. .await
  296. .with_context(|| "Failed to create payment transaction")?;
  297. println!("{}", bs58::encode(&serialize(&tx)).into_string());
  298. Ok(())
  299. }
  300. Subcmd::Otc(cmd) => {
  301. let rpc_client = RpcClient::new(args.endpoint)
  302. .await
  303. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  304. let drk = Drk { rpc_client };
  305. match cmd {
  306. OtcSubcmd::Init { value_pair, token_pair } => {
  307. let (vp_send, vp_recv) = parse_value_pair(&value_pair)?;
  308. let (tp_send, tp_recv) = parse_token_pair(&token_pair)?;
  309. let half = drk
  310. .init_swap(vp_send, tp_send, vp_recv, tp_recv)
  311. .await
  312. .with_context(|| "Failed to create swap transaction half")?;
  313. println!("{}", bs58::encode(&serialize(&half)).into_string());
  314. Ok(())
  315. }
  316. OtcSubcmd::Join => {
  317. let mut buf = String::new();
  318. stdin().read_to_string(&mut buf)?;
  319. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  320. let partial: PartialSwapData = deserialize(&bytes)?;
  321. let tx = drk
  322. .join_swap(partial)
  323. .await
  324. .with_context(|| "Failed to create a join swap transaction")?;
  325. println!("{}", bs58::encode(&serialize(&tx)).into_string());
  326. Ok(())
  327. }
  328. OtcSubcmd::Sign => {
  329. let mut buf = String::new();
  330. stdin().read_to_string(&mut buf)?;
  331. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  332. let mut tx: Transaction = deserialize(&bytes)?;
  333. drk.sign_swap(&mut tx)
  334. .await
  335. .with_context(|| "Failed to sign joined swap transaction")?;
  336. println!("{}", bs58::encode(&serialize(&tx)).into_string());
  337. Ok(())
  338. }
  339. }
  340. }
  341. Subcmd::Inspect => {
  342. let mut buf = String::new();
  343. stdin().read_to_string(&mut buf)?;
  344. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  345. let tx: Transaction = deserialize(&bytes)?;
  346. println!("{:#?}", tx);
  347. Ok(())
  348. }
  349. Subcmd::Broadcast => {
  350. eprintln!("Reading transaction from stdin...");
  351. let mut buf = String::new();
  352. stdin().read_to_string(&mut buf)?;
  353. let bytes = bs58::decode(&buf.trim()).into_vec()?;
  354. let tx = deserialize(&bytes)?;
  355. let rpc_client = RpcClient::new(args.endpoint)
  356. .await
  357. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  358. let drk = Drk { rpc_client };
  359. let txid =
  360. drk.broadcast_tx(&tx).await.with_context(|| "Failed to broadcast transaction")?;
  361. eprintln!("Transaction ID: {}", txid);
  362. Ok(())
  363. }
  364. Subcmd::Subscribe => {
  365. let rpc_client = RpcClient::new(args.endpoint.clone())
  366. .await
  367. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  368. let drk = Drk { rpc_client };
  369. drk.subscribe_blocks(args.endpoint)
  370. .await
  371. .with_context(|| "Block subscription failed")?;
  372. Ok(())
  373. }
  374. Subcmd::Scan { slot } => {
  375. let rpc_client = RpcClient::new(args.endpoint)
  376. .await
  377. .with_context(|| "Could not connect to darkfid RPC endpoint")?;
  378. let drk = Drk { rpc_client };
  379. drk.scan_blocks(slot).await.with_context(|| "Failed during scanning")?;
  380. eprintln!("Finished scanning blockchain");
  381. Ok(())
  382. }
  383. }
  384. }