soroban_cli/config/
mod.rs

1use clap::{arg, command};
2use serde::{Deserialize, Serialize};
3use std::{
4    fs::{self, File},
5    io::Write,
6};
7
8use crate::{
9    print::Print,
10    signer::{self, Signer},
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    /// Account that where transaction originates from. Alias `source`.
60    /// Can be an identity (--source alice), a public key (--source GDKW...),
61    /// a muxed account (--source MDA…), a secret key (--source SC36…),
62    /// or a seed phrase (--source "kite urban…").
63    /// If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to
64    /// sign the final transaction. In that case, trying to sign with public key will fail.
65    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
74impl Args {
75    // TODO: Replace PublicKey with MuxedAccount once https://github.com/stellar/rs-stellar-xdr/pull/396 is merged.
76    pub async fn source_account(&self) -> Result<xdr::MuxedAccount, Error> {
77        Ok(self
78            .source_account
79            .resolve_muxed_account(&self.locator, self.hd_path())
80            .await?)
81    }
82
83    pub async fn source_signer(&self) -> Result<Signer, Error> {
84        let print = Print::new(true);
85        let secret = &self.source_account.resolve_secret(&self.locator)?;
86        Ok(secret.signer(None, print).await?)
87    }
88
89    pub fn key_pair(&self) -> Result<ed25519_dalek::SigningKey, Error> {
90        let key = &self.source_account.resolve_secret(&self.locator)?;
91        Ok(key.key_pair(self.hd_path())?)
92    }
93
94    pub async fn sign(&self, tx: Transaction) -> Result<TransactionEnvelope, Error> {
95        let tx_env = TransactionEnvelope::Tx(TransactionV1Envelope {
96            tx,
97            signatures: VecM::default(),
98        });
99        Ok(self
100            .sign_with
101            .sign_tx_env(
102                &tx_env,
103                &self.locator,
104                &self.network.get(&self.locator)?,
105                false,
106                Some(&self.source_account),
107            )
108            .await?)
109    }
110
111    pub async fn sign_soroban_authorizations(
112        &self,
113        tx: &Transaction,
114        signers: &[Signer],
115    ) -> Result<Option<Transaction>, Error> {
116        let network = self.get_network()?;
117        let source_signer = self.source_signer().await?;
118        let client = network.rpc_client()?;
119        let latest_ledger = client.get_latest_ledger().await?.sequence;
120        let seq_num = latest_ledger + 60; // ~ 5 min
121        Ok(signer::sign_soroban_authorizations(
122            tx,
123            &source_signer,
124            signers,
125            seq_num,
126            &network.network_passphrase,
127        )?)
128    }
129
130    pub fn get_network(&self) -> Result<Network, Error> {
131        Ok(self.network.get(&self.locator)?)
132    }
133
134    pub async fn next_sequence_number(
135        &self,
136        account: impl Into<xdr::AccountId>,
137    ) -> Result<SequenceNumber, Error> {
138        let network = self.get_network()?;
139        let client = network.rpc_client()?;
140        Ok((client
141            .get_account(&account.into().to_string())
142            .await?
143            .seq_num
144            .0
145            + 1)
146        .into())
147    }
148
149    pub fn hd_path(&self) -> Option<usize> {
150        self.sign_with.hd_path
151    }
152}
153
154impl Pwd for Args {
155    fn set_pwd(&mut self, pwd: &std::path::Path) {
156        self.locator.set_pwd(pwd);
157    }
158}
159
160#[derive(Debug, clap::Args, Clone, Default)]
161#[group(skip)]
162pub struct ArgsLocatorAndNetwork {
163    #[command(flatten)]
164    pub network: network::Args,
165
166    #[command(flatten)]
167    pub locator: locator::Args,
168}
169
170impl ArgsLocatorAndNetwork {
171    pub fn get_network(&self) -> Result<Network, Error> {
172        Ok(self.network.get(&self.locator)?)
173    }
174}
175
176#[derive(Serialize, Deserialize, Debug, Default)]
177pub struct Config {
178    pub defaults: Defaults,
179}
180
181#[derive(Serialize, Deserialize, Debug, Default)]
182pub struct Defaults {
183    pub network: Option<String>,
184    pub identity: Option<String>,
185}
186
187impl Config {
188    pub fn new() -> Result<Config, locator::Error> {
189        let path = cli_config_file()?;
190
191        if path.exists() {
192            let data = fs::read_to_string(&path).map_err(|_| locator::Error::FileRead { path })?;
193            Ok(toml::from_str(&data)?)
194        } else {
195            Ok(Config::default())
196        }
197    }
198
199    #[must_use]
200    pub fn set_network(mut self, s: &str) -> Self {
201        self.defaults.network = Some(s.to_string());
202        self
203    }
204
205    #[must_use]
206    pub fn set_identity(mut self, s: &str) -> Self {
207        self.defaults.identity = Some(s.to_string());
208        self
209    }
210
211    pub fn save(&self) -> Result<(), locator::Error> {
212        let toml_string = toml::to_string(&self)?;
213        let path = cli_config_file()?;
214        // Depending on the platform, this function may fail if the full directory path does not exist
215        let mut file = File::create(locator::ensure_directory(path)?)?;
216        file.write_all(toml_string.as_bytes())?;
217
218        Ok(())
219    }
220}