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