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, LocalKey, Signer, SignerKind},
11 xdr::{self, SequenceNumber, Transaction, TransactionEnvelope},
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
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 Config(#[from] locator::Error),
39 #[error(transparent)]
40 Rpc(#[from] soroban_rpc::Error),
41 #[error(transparent)]
42 Signer(#[from] signer::Error),
43 #[error(transparent)]
44 StellarStrkey(#[from] stellar_strkey::DecodeError),
45 #[error(transparent)]
46 Address(#[from] address::Error),
47}
48
49#[derive(Debug, clap::Args, Clone, Default)]
50#[group(skip)]
51pub struct Args {
52 #[command(flatten)]
53 pub network: network::Args,
54
55 #[arg(long, short = 's', visible_alias = "source", env = "STELLAR_ACCOUNT")]
56 pub source_account: UnresolvedMuxedAccount,
63
64 #[arg(long)]
65 pub hd_path: Option<usize>,
67
68 #[command(flatten)]
69 pub locator: locator::Args,
70}
71
72impl Args {
73 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_with_local_key(&self, tx: Transaction) -> Result<TransactionEnvelope, Error> {
87 self.sign(tx).await
88 }
89
90 #[allow(clippy::unused_async)]
91 pub async fn sign(&self, tx: Transaction) -> Result<TransactionEnvelope, Error> {
92 let key = self.key_pair()?;
93 let network = &self.get_network()?;
94 let signer = Signer {
95 kind: SignerKind::Local(LocalKey { key }),
96 print: Print::new(false),
97 };
98 Ok(signer.sign_tx(tx, network).await?)
99 }
100
101 pub async fn sign_soroban_authorizations(
102 &self,
103 tx: &Transaction,
104 signers: &[ed25519_dalek::SigningKey],
105 ) -> Result<Option<Transaction>, Error> {
106 let network = self.get_network()?;
107 let source_key = self.key_pair()?;
108 let client = network.rpc_client()?;
109 let latest_ledger = client.get_latest_ledger().await?.sequence;
110 let seq_num = latest_ledger + 60; Ok(signer::sign_soroban_authorizations(
112 tx,
113 &source_key,
114 signers,
115 seq_num,
116 &network.network_passphrase,
117 )?)
118 }
119
120 pub fn get_network(&self) -> Result<Network, Error> {
121 Ok(self.network.get(&self.locator)?)
122 }
123
124 pub async fn next_sequence_number(
125 &self,
126 account: impl Into<xdr::AccountId>,
127 ) -> Result<SequenceNumber, Error> {
128 let network = self.get_network()?;
129 let client = network.rpc_client()?;
130 Ok((client
131 .get_account(&account.into().to_string())
132 .await?
133 .seq_num
134 .0
135 + 1)
136 .into())
137 }
138}
139
140impl Pwd for Args {
141 fn set_pwd(&mut self, pwd: &std::path::Path) {
142 self.locator.set_pwd(pwd);
143 }
144}
145
146#[derive(Debug, clap::Args, Clone, Default)]
147#[group(skip)]
148pub struct ArgsLocatorAndNetwork {
149 #[command(flatten)]
150 pub network: network::Args,
151
152 #[command(flatten)]
153 pub locator: locator::Args,
154}
155
156impl ArgsLocatorAndNetwork {
157 pub fn get_network(&self) -> Result<Network, Error> {
158 Ok(self.network.get(&self.locator)?)
159 }
160}
161
162#[derive(Serialize, Deserialize, Debug, Default)]
163pub struct Config {
164 pub defaults: Defaults,
165}
166
167#[derive(Serialize, Deserialize, Debug, Default)]
168pub struct Defaults {
169 pub network: Option<String>,
170 pub identity: Option<String>,
171}
172
173impl Config {
174 pub fn new() -> Result<Config, locator::Error> {
175 let path = locator::config_file()?;
176
177 if path.exists() {
178 let data = fs::read_to_string(&path).map_err(|_| locator::Error::FileRead { path })?;
179 Ok(toml::from_str(&data)?)
180 } else {
181 Ok(Config::default())
182 }
183 }
184
185 #[must_use]
186 pub fn set_network(mut self, s: &str) -> Self {
187 self.defaults.network = Some(s.to_string());
188 self
189 }
190
191 #[must_use]
192 pub fn set_identity(mut self, s: &str) -> Self {
193 self.defaults.identity = Some(s.to_string());
194 self
195 }
196
197 pub fn save(&self) -> Result<(), locator::Error> {
198 let toml_string = toml::to_string(&self)?;
199 let path = locator::config_file()?;
200 let mut file = File::create(locator::ensure_directory(path)?)?;
202 file.write_all(toml_string.as_bytes())?;
203
204 Ok(())
205 }
206}