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