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