1use std::str::FromStr;
2
3use async_trait::async_trait;
4use clap::{error::ErrorKind, CommandFactory, FromArgMatches, Parser};
5
6use crate::{config, print::Print, utils::deprecate_message};
7
8pub mod cache;
9pub mod cfg;
10pub mod completion;
11pub mod container;
12pub mod contract;
13pub mod doctor;
14pub mod env;
15pub mod events;
16pub mod fee_stats;
17pub mod fees;
18pub mod global;
19pub mod keys;
20pub mod ledger;
21pub mod message;
22pub mod network;
23pub mod plugin;
24pub mod snapshot;
25pub mod tx;
26pub mod version;
27
28pub mod txn_result;
29
30pub const HEADING_RPC: &str = "Options (RPC)";
31pub const HEADING_ARCHIVE: &str = "Options (Archive)";
32pub const HEADING_GLOBAL: &str = "Options (Global)";
33const ABOUT: &str =
34 "Work seamlessly with Stellar accounts, contracts, and assets from the command line.
35
36- Generate and manage keys and accounts
37- Build, deploy, and interact with contracts
38- Deploy asset contracts
39- Stream events
40- Start local testnets
41- Decode, encode XDR
42- More!
43
44For additional information see:
45
46- Stellar Docs: https://developers.stellar.org
47- Smart Contract Docs: https://developers.stellar.org/docs/build/smart-contracts/overview
48- CLI Docs: https://developers.stellar.org/docs/tools/developer-tools/cli/stellar-cli";
49
50const LONG_ABOUT: &str = "
52
53To get started generate a new identity:
54
55 stellar keys generate alice
56
57Use keys with the `--source` flag in other commands.
58
59Commands that work with contracts are organized under the `contract` subcommand. List them:
60
61 stellar contract --help
62
63Use contracts like a CLI:
64
65 stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- --help
66
67Anything after the `--` double dash (the \"slop\") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the contract schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it:
68
69 stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- hello --to world
70";
71
72#[derive(Parser, Debug)]
73#[command(
74 name = "stellar",
75 about = ABOUT,
76 version = version::long(),
77 long_about = ABOUT.to_string() + LONG_ABOUT,
78 disable_help_subcommand = true,
79)]
80pub struct Root {
81 #[clap(flatten)]
82 pub global_args: global::Args,
83
84 #[command(subcommand)]
85 pub cmd: Cmd,
86}
87
88impl Root {
89 pub fn new() -> Result<Self, Error> {
90 Self::try_parse().map_err(|e| {
91 if std::env::args().any(|s| s == "--list") {
92 let print = Print::new(std::env::args().any(|s| s == "--quiet" || s == "-q"));
93 deprecate_message(print, "--list", "Use `stellar plugin ls` instead.");
94 let _ = plugin::ls::Cmd.run();
95 std::process::exit(0);
96 }
97
98 match e.kind() {
99 ErrorKind::InvalidSubcommand => match plugin::default::run() {
100 Ok(()) => Error::Clap(e),
101 Err(e) => Error::PluginDefault(e),
102 },
103 _ => Error::Clap(e),
104 }
105 })
106 }
107
108 pub fn from_arg_matches<I, T>(itr: I) -> Result<Self, clap::Error>
109 where
110 I: IntoIterator<Item = T>,
111 T: Into<std::ffi::OsString> + Clone,
112 {
113 Self::from_arg_matches_mut(&mut Self::command().get_matches_from(itr))
114 }
115
116 pub async fn run(&mut self) -> Result<(), Error> {
117 let print = Print::new(self.global_args.quiet);
118
119 if self.global_args.locator.global {
120 deprecate_message(
121 print,
122 "--global",
123 "Global configuration is now the default behavior.",
124 );
125 }
126
127 match &mut self.cmd {
128 Cmd::Completion(completion) => completion.run(),
129 Cmd::Plugin(plugin) => plugin.run(&self.global_args).await?,
130 Cmd::Contract(contract) => contract.run(&self.global_args).await?,
131 Cmd::Doctor(doctor) => doctor.run(&self.global_args).await?,
132 Cmd::Config(config) => config.run()?,
133 Cmd::Events(events) => events.run().await?,
134 Cmd::Xdr(xdr) => xdr.run()?,
135 Cmd::Strkey(strkey) => strkey.run()?,
136 Cmd::Network(network) => network.run(&self.global_args).await?,
137 Cmd::Container(container) => container.run(&self.global_args).await?,
138 Cmd::Snapshot(snapshot) => snapshot.run(&self.global_args).await?,
139 Cmd::Version(version) => version.run(),
140 Cmd::Keys(id) => id.run(&self.global_args).await?,
141 Cmd::Tx(tx) => tx.run(&self.global_args).await?,
142 Cmd::Ledger(ledger) => ledger.run(&self.global_args).await?,
143 Cmd::Message(message) => message.run(&self.global_args).await?,
144 Cmd::Cache(cache) => cache.run()?,
145 Cmd::Env(env) => env.run(&self.global_args)?,
146 Cmd::Fees(env) => env.run(&self.global_args).await?,
147 Cmd::FeeStats(env) => env.run(&self.global_args).await?,
148 }
149 Ok(())
150 }
151}
152
153impl FromStr for Root {
154 type Err = clap::Error;
155
156 fn from_str(s: &str) -> Result<Self, Self::Err> {
157 Self::from_arg_matches(s.split_whitespace())
158 }
159}
160
161#[derive(Parser, Debug)]
162pub enum Cmd {
163 #[command(subcommand)]
165 Contract(contract::Cmd),
166
167 Doctor(doctor::Cmd),
169
170 Events(events::Cmd),
172
173 Env(env::Cmd),
182
183 #[command(subcommand)]
185 Keys(keys::Cmd),
186
187 #[command(subcommand)]
189 Network(network::Cmd),
190
191 #[command(subcommand)]
193 Container(container::Cmd),
194
195 #[command(subcommand)]
197 Config(cfg::Cmd),
198
199 #[command(subcommand)]
201 Snapshot(snapshot::Cmd),
202
203 #[command(subcommand)]
205 Tx(tx::Cmd),
206
207 Xdr(stellar_xdr::cli::Root),
209
210 Strkey(stellar_strkey::cli::Root),
212
213 #[command(long_about = completion::LONG_ABOUT)]
215 Completion(completion::Cmd),
216
217 #[command(subcommand)]
219 Cache(cache::Cmd),
220
221 Version(version::Cmd),
223
224 #[command(subcommand)]
226 Plugin(plugin::Cmd),
227
228 #[command(subcommand)]
230 Ledger(ledger::Cmd),
231
232 #[command(subcommand)]
234 Message(message::Cmd),
235
236 FeeStats(fee_stats::Cmd),
238
239 #[command(subcommand)]
241 Fees(fees::Cmd),
242}
243
244#[derive(thiserror::Error, Debug)]
245pub enum Error {
246 #[error(transparent)]
248 Contract(#[from] contract::Error),
249
250 #[error(transparent)]
251 Doctor(#[from] doctor::Error),
252
253 #[error(transparent)]
254 Events(#[from] events::Error),
255
256 #[error(transparent)]
257 Keys(#[from] keys::Error),
258
259 #[error(transparent)]
260 Xdr(#[from] stellar_xdr::cli::Error),
261
262 #[error(transparent)]
263 Strkey(#[from] stellar_strkey::cli::Error),
264
265 #[error(transparent)]
266 Clap(#[from] clap::error::Error),
267
268 #[error(transparent)]
269 Plugin(#[from] plugin::Error),
270
271 #[error(transparent)]
272 PluginDefault(#[from] plugin::default::Error),
273
274 #[error(transparent)]
275 Network(#[from] network::Error),
276
277 #[error(transparent)]
278 Container(#[from] container::Error),
279
280 #[error(transparent)]
281 Config(#[from] cfg::Error),
282
283 #[error(transparent)]
284 Snapshot(#[from] snapshot::Error),
285
286 #[error(transparent)]
287 Tx(#[from] tx::Error),
288
289 #[error(transparent)]
290 Cache(#[from] cache::Error),
291
292 #[error(transparent)]
293 Env(#[from] env::Error),
294
295 #[error(transparent)]
296 Ledger(#[from] ledger::Error),
297
298 #[error(transparent)]
299 Message(#[from] message::Error),
300
301 #[error(transparent)]
302 FeeStats(#[from] fee_stats::Error),
303
304 #[error(transparent)]
305 Fees(#[from] fees::Error),
306}
307
308#[async_trait]
309pub trait NetworkRunnable {
310 type Error;
311 type Result;
312
313 async fn run_against_rpc_server(
314 &self,
315 global_args: Option<&global::Args>,
316 config: Option<&config::Args>,
317 ) -> Result<Self::Result, Self::Error>;
318}