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