soroban_cli/config/
mod.rs1use 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 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 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; 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 let mut file = File::create(locator::ensure_directory(path)?)?;
216 file.write_all(toml_string.as_bytes())?;
217
218 Ok(())
219 }
220}