1use serde::{Deserialize, Serialize};
2use std::{
3 fs::{self, File},
4 io::Write,
5};
6
7use crate::{
8 print::Print,
9 signer::{self, Signer},
10 utils::deprecate_message,
11 xdr::{self, SequenceNumber, Transaction, TransactionEnvelope, TransactionV1Envelope, VecM},
12 Pwd,
13};
14use network::Network;
15
16pub mod address;
17pub mod alias;
18pub mod data;
19pub mod key;
20pub mod locator;
21pub mod network;
22pub mod sc_address;
23pub mod secret;
24pub mod sign_with;
25pub mod upgrade_check;
26
27use crate::config::locator::cli_config_file;
28pub use address::UnresolvedMuxedAccount;
29pub use alias::UnresolvedContract;
30pub use sc_address::UnresolvedScAddress;
31
32#[derive(thiserror::Error, Debug)]
33pub enum Error {
34 #[error(transparent)]
35 Network(#[from] network::Error),
36 #[error(transparent)]
37 Secret(#[from] secret::Error),
38 #[error(transparent)]
39 Locator(#[from] locator::Error),
40 #[error(transparent)]
41 Rpc(#[from] soroban_rpc::Error),
42 #[error(transparent)]
43 Signer(#[from] signer::Error),
44 #[error(transparent)]
45 SignWith(#[from] sign_with::Error),
46 #[error(transparent)]
47 StellarStrkey(#[from] stellar_strkey::DecodeError),
48 #[error(transparent)]
49 Address(#[from] address::Error),
50}
51
52#[derive(Debug, clap::Args, Clone, Default)]
53#[group(skip)]
54pub struct Args {
55 #[command(flatten)]
56 pub network: network::Args,
57
58 #[arg(long, short = 's', visible_alias = "source", env = "STELLAR_ACCOUNT")]
59 pub source_account: UnresolvedMuxedAccount,
66
67 #[command(flatten)]
68 pub locator: locator::Args,
69
70 #[command(flatten)]
71 pub sign_with: sign_with::Args,
72
73 #[arg(long, env = "STELLAR_FEE")]
75 pub fee: Option<u32>,
76
77 #[arg(long, env = "STELLAR_INCLUSION_FEE")]
79 pub inclusion_fee: Option<u32>,
80}
81
82impl Args {
83 pub async fn source_account(&self) -> Result<xdr::MuxedAccount, Error> {
85 Ok(self
86 .source_account
87 .resolve_muxed_account(&self.locator, self.hd_path())
88 .await?)
89 }
90
91 pub async fn source_signer(&self) -> Result<Signer, Error> {
92 let print = Print::new(true);
93 let secret = &self.source_account.resolve_secret(&self.locator)?;
94 Ok(secret.signer(None, print).await?)
95 }
96
97 pub fn key_pair(&self) -> Result<ed25519_dalek::SigningKey, Error> {
98 let key = &self.source_account.resolve_secret(&self.locator)?;
99 Ok(key.key_pair(self.hd_path())?)
100 }
101
102 pub async fn sign(&self, tx: Transaction, quiet: bool) -> Result<TransactionEnvelope, Error> {
103 let tx_env = TransactionEnvelope::Tx(TransactionV1Envelope {
104 tx,
105 signatures: VecM::default(),
106 });
107 Ok(self
108 .sign_with
109 .sign_tx_env(
110 &tx_env,
111 &self.locator,
112 &self.network.get(&self.locator)?,
113 quiet,
114 Some(&self.source_account),
115 )
116 .await?)
117 }
118
119 pub async fn sign_soroban_authorizations(
120 &self,
121 tx: &Transaction,
122 signers: &[Signer],
123 ) -> Result<Option<Transaction>, Error> {
124 let network = self.get_network()?;
125 let source_signer = self.source_signer().await?;
126 let client = network.rpc_client()?;
127 let latest_ledger = client.get_latest_ledger().await?.sequence;
128 let seq_num = latest_ledger + 60; Ok(signer::sign_soroban_authorizations(
130 tx,
131 &source_signer,
132 signers,
133 seq_num,
134 &network.network_passphrase,
135 )?)
136 }
137
138 pub fn get_network(&self) -> Result<Network, Error> {
139 Ok(self.network.get(&self.locator)?)
140 }
141
142 pub fn get_inclusion_fee(&self) -> Result<u32, Error> {
150 if self.fee.is_some() {
151 deprecate_message(
152 Print::new(false),
153 "--fee [env: STELLAR_FEE]",
154 "Use `--inclusion-fee [env: STELLAR_INCLUSION_FEE]` instead.",
155 );
156 }
157 Ok(self.inclusion_fee.or(self.fee).unwrap_or(100))
158 }
159
160 pub async fn next_sequence_number(
161 &self,
162 account: impl Into<xdr::AccountId>,
163 ) -> Result<SequenceNumber, Error> {
164 let network = self.get_network()?;
165 let client = network.rpc_client()?;
166 Ok((client
167 .get_account(&account.into().to_string())
168 .await?
169 .seq_num
170 .0
171 + 1)
172 .into())
173 }
174
175 pub fn hd_path(&self) -> Option<usize> {
176 self.sign_with.hd_path
177 }
178}
179
180impl Pwd for Args {
181 fn set_pwd(&mut self, pwd: &std::path::Path) {
182 self.locator.set_pwd(pwd);
183 }
184}
185
186#[derive(Debug, clap::Args, Clone, Default)]
187#[group(skip)]
188pub struct ArgsLocatorAndNetwork {
189 #[command(flatten)]
190 pub network: network::Args,
191
192 #[command(flatten)]
193 pub locator: locator::Args,
194}
195
196impl ArgsLocatorAndNetwork {
197 pub fn get_network(&self) -> Result<Network, Error> {
198 Ok(self.network.get(&self.locator)?)
199 }
200}
201
202#[derive(Serialize, Deserialize, Debug, Default)]
203pub struct Config {
204 pub defaults: Defaults,
205}
206
207#[derive(Serialize, Deserialize, Debug, Default)]
208pub struct Defaults {
209 pub network: Option<String>,
210 pub identity: Option<String>,
211 pub inclusion_fee: Option<u32>,
212}
213
214impl Config {
215 pub fn new() -> Result<Config, locator::Error> {
216 let path = cli_config_file()?;
217
218 if path.exists() {
219 let data = fs::read_to_string(&path).map_err(|_| locator::Error::FileRead { path })?;
220 Ok(toml::from_str(&data)?)
221 } else {
222 Ok(Config::default())
223 }
224 }
225
226 #[must_use]
227 pub fn set_network(mut self, s: &str) -> Self {
228 self.defaults.network = Some(s.to_string());
229 self
230 }
231
232 #[must_use]
233 pub fn set_identity(mut self, s: &str) -> Self {
234 self.defaults.identity = Some(s.to_string());
235 self
236 }
237
238 #[must_use]
239 pub fn set_inclusion_fee(mut self, uint: u32) -> Self {
240 self.defaults.inclusion_fee = Some(uint);
241 self
242 }
243
244 #[must_use]
245 pub fn unset_identity(mut self) -> Self {
246 self.defaults.identity = None;
247 self
248 }
249
250 #[must_use]
251 pub fn unset_network(mut self) -> Self {
252 self.defaults.network = None;
253 self
254 }
255
256 #[must_use]
257 pub fn unset_inclusion_fee(mut self) -> Self {
258 self.defaults.inclusion_fee = None;
259 self
260 }
261
262 pub fn save(&self) -> Result<(), locator::Error> {
263 let toml_string = toml::to_string(&self)?;
264 let path = cli_config_file()?;
265 let mut file = File::create(locator::ensure_directory(path)?)?;
267 file.write_all(toml_string.as_bytes())?;
268
269 Ok(())
270 }
271}