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