soroban_cli/config/
mod.rs

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    xdr::{self, SequenceNumber, Transaction, TransactionEnvelope, TransactionV1Envelope, VecM},
11    Pwd,
12};
13use network::Network;
14
15pub mod address;
16pub mod alias;
17pub mod data;
18pub mod key;
19pub mod locator;
20pub mod network;
21pub mod sc_address;
22pub mod secret;
23pub mod sign_with;
24pub mod upgrade_check;
25
26use crate::config::locator::cli_config_file;
27pub use address::UnresolvedMuxedAccount;
28pub use alias::UnresolvedContract;
29pub use sc_address::UnresolvedScAddress;
30
31#[derive(thiserror::Error, Debug)]
32pub enum Error {
33    #[error(transparent)]
34    Network(#[from] network::Error),
35    #[error(transparent)]
36    Secret(#[from] secret::Error),
37    #[error(transparent)]
38    Locator(#[from] locator::Error),
39    #[error(transparent)]
40    Rpc(#[from] soroban_rpc::Error),
41    #[error(transparent)]
42    Signer(#[from] signer::Error),
43    #[error(transparent)]
44    SignWith(#[from] sign_with::Error),
45    #[error(transparent)]
46    StellarStrkey(#[from] stellar_strkey::DecodeError),
47    #[error(transparent)]
48    Address(#[from] address::Error),
49}
50
51#[derive(Debug, clap::Args, Clone, Default)]
52#[group(skip)]
53pub struct Args {
54    #[command(flatten)]
55    pub network: network::Args,
56
57    #[arg(long, short = 's', visible_alias = "source", env = "STELLAR_ACCOUNT")]
58    /// Account that where transaction originates from. Alias `source`.
59    /// Can be an identity (--source alice), a public key (--source GDKW...),
60    /// a muxed account (--source MDA…), a secret key (--source SC36…),
61    /// or a seed phrase (--source "kite urban…").
62    /// If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to
63    /// sign the final transaction. In that case, trying to sign with public key will fail.
64    pub source_account: UnresolvedMuxedAccount,
65
66    #[command(flatten)]
67    pub locator: locator::Args,
68
69    #[command(flatten)]
70    pub sign_with: sign_with::Args,
71}
72
73impl Args {
74    // TODO: Replace PublicKey with MuxedAccount once https://github.com/stellar/rs-stellar-xdr/pull/396 is merged.
75    pub async fn source_account(&self) -> Result<xdr::MuxedAccount, Error> {
76        Ok(self
77            .source_account
78            .resolve_muxed_account(&self.locator, self.hd_path())
79            .await?)
80    }
81
82    pub async fn source_signer(&self) -> Result<Signer, Error> {
83        let print = Print::new(true);
84        let secret = &self.source_account.resolve_secret(&self.locator)?;
85        Ok(secret.signer(None, print).await?)
86    }
87
88    pub fn key_pair(&self) -> Result<ed25519_dalek::SigningKey, Error> {
89        let key = &self.source_account.resolve_secret(&self.locator)?;
90        Ok(key.key_pair(self.hd_path())?)
91    }
92
93    pub async fn sign(&self, tx: Transaction, quiet: bool) -> Result<TransactionEnvelope, Error> {
94        let tx_env = TransactionEnvelope::Tx(TransactionV1Envelope {
95            tx,
96            signatures: VecM::default(),
97        });
98        Ok(self
99            .sign_with
100            .sign_tx_env(
101                &tx_env,
102                &self.locator,
103                &self.network.get(&self.locator)?,
104                quiet,
105                Some(&self.source_account),
106            )
107            .await?)
108    }
109
110    pub async fn sign_soroban_authorizations(
111        &self,
112        tx: &Transaction,
113        signers: &[Signer],
114    ) -> Result<Option<Transaction>, Error> {
115        let network = self.get_network()?;
116        let source_signer = self.source_signer().await?;
117        let client = network.rpc_client()?;
118        let latest_ledger = client.get_latest_ledger().await?.sequence;
119        let seq_num = latest_ledger + 60; // ~ 5 min
120        Ok(signer::sign_soroban_authorizations(
121            tx,
122            &source_signer,
123            signers,
124            seq_num,
125            &network.network_passphrase,
126        )?)
127    }
128
129    pub fn get_network(&self) -> Result<Network, Error> {
130        Ok(self.network.get(&self.locator)?)
131    }
132
133    pub async fn next_sequence_number(
134        &self,
135        account: impl Into<xdr::AccountId>,
136    ) -> Result<SequenceNumber, Error> {
137        let network = self.get_network()?;
138        let client = network.rpc_client()?;
139        Ok((client
140            .get_account(&account.into().to_string())
141            .await?
142            .seq_num
143            .0
144            + 1)
145        .into())
146    }
147
148    pub fn hd_path(&self) -> Option<usize> {
149        self.sign_with.hd_path
150    }
151}
152
153impl Pwd for Args {
154    fn set_pwd(&mut self, pwd: &std::path::Path) {
155        self.locator.set_pwd(pwd);
156    }
157}
158
159#[derive(Debug, clap::Args, Clone, Default)]
160#[group(skip)]
161pub struct ArgsLocatorAndNetwork {
162    #[command(flatten)]
163    pub network: network::Args,
164
165    #[command(flatten)]
166    pub locator: locator::Args,
167}
168
169impl ArgsLocatorAndNetwork {
170    pub fn get_network(&self) -> Result<Network, Error> {
171        Ok(self.network.get(&self.locator)?)
172    }
173}
174
175#[derive(Serialize, Deserialize, Debug, Default)]
176pub struct Config {
177    pub defaults: Defaults,
178}
179
180#[derive(Serialize, Deserialize, Debug, Default)]
181pub struct Defaults {
182    pub network: Option<String>,
183    pub identity: Option<String>,
184}
185
186impl Config {
187    pub fn new() -> Result<Config, locator::Error> {
188        let path = cli_config_file()?;
189
190        if path.exists() {
191            let data = fs::read_to_string(&path).map_err(|_| locator::Error::FileRead { path })?;
192            Ok(toml::from_str(&data)?)
193        } else {
194            Ok(Config::default())
195        }
196    }
197
198    #[must_use]
199    pub fn set_network(mut self, s: &str) -> Self {
200        self.defaults.network = Some(s.to_string());
201        self
202    }
203
204    #[must_use]
205    pub fn set_identity(mut self, s: &str) -> Self {
206        self.defaults.identity = Some(s.to_string());
207        self
208    }
209
210    #[must_use]
211    pub fn unset_identity(mut self) -> Self {
212        self.defaults.identity = None;
213        self
214    }
215
216    #[must_use]
217    pub fn unset_network(mut self) -> Self {
218        self.defaults.network = None;
219        self
220    }
221
222    pub fn save(&self) -> Result<(), locator::Error> {
223        let toml_string = toml::to_string(&self)?;
224        let path = cli_config_file()?;
225        // Depending on the platform, this function may fail if the full directory path does not exist
226        let mut file = File::create(locator::ensure_directory(path)?)?;
227        file.write_all(toml_string.as_bytes())?;
228
229        Ok(())
230    }
231}