use serde::{Deserialize, Serialize};
use std::fs;
use crate::{
commands::HEADING_TRANSACTION,
print::Print,
signer::{self, Signer},
utils::deprecate_message,
xdr::{
self, FeeBumpTransaction, FeeBumpTransactionEnvelope, SequenceNumber, Transaction,
TransactionEnvelope, TransactionV1Envelope, VecM,
},
Pwd,
};
use network::Network;
pub mod address;
pub mod alias;
pub mod data;
pub mod key;
pub mod locator;
pub mod network;
pub mod sc_address;
pub mod secret;
pub mod sign_with;
pub mod upgrade_check;
pub mod utils;
use crate::config::locator::cli_config_file;
pub use address::UnresolvedMuxedAccount;
pub use alias::UnresolvedContract;
pub use sc_address::UnresolvedScAddress;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Network(#[from] network::Error),
#[error(transparent)]
Secret(#[from] secret::Error),
#[error(transparent)]
Locator(#[from] locator::Error),
#[error(transparent)]
Rpc(#[from] soroban_rpc::Error),
#[error(transparent)]
Signer(#[from] signer::Error),
#[error(transparent)]
SignWith(#[from] sign_with::Error),
#[error(transparent)]
StellarStrkey(#[from] stellar_strkey::DecodeError),
#[error(transparent)]
Address(#[from] address::Error),
}
#[derive(Debug, clap::Args, Clone, Default)]
#[group(skip)]
pub struct Args {
#[command(flatten)]
pub network: network::Args,
#[arg(
long,
short = 's',
visible_alias = "source",
env = "STELLAR_ACCOUNT",
help_heading = HEADING_TRANSACTION
)]
pub source_account: UnresolvedMuxedAccount,
#[command(flatten)]
pub locator: locator::Args,
#[command(flatten)]
pub sign_with: sign_with::Args,
#[arg(long, env = "STELLAR_FEE", help_heading = HEADING_TRANSACTION)]
pub fee: Option<u32>,
#[arg(
long,
env = "STELLAR_INCLUSION_FEE",
help_heading = HEADING_TRANSACTION
)]
pub inclusion_fee: Option<u32>,
}
impl Args {
pub fn source_account(&self) -> Result<xdr::MuxedAccount, Error> {
Ok(self
.source_account
.resolve_muxed_account(&self.locator, self.hd_path())?)
}
pub fn key_pair(&self) -> Result<ed25519_dalek::SigningKey, Error> {
let key = &self.source_account.resolve_secret(&self.locator)?;
Ok(key.key_pair(self.hd_path())?)
}
pub async fn sign(&self, tx: Transaction, quiet: bool) -> Result<TransactionEnvelope, Error> {
let tx_env = TransactionEnvelope::Tx(TransactionV1Envelope {
tx,
signatures: VecM::default(),
});
Ok(self
.sign_with
.sign_tx_env(
&tx_env,
&self.locator,
&self.network.get(&self.locator)?,
quiet,
Some(&self.source_account),
)
.await?)
}
pub async fn sign_fee_bump(
&self,
tx: FeeBumpTransaction,
quiet: bool,
) -> Result<TransactionEnvelope, Error> {
let tx_env = TransactionEnvelope::TxFeeBump(FeeBumpTransactionEnvelope {
tx,
signatures: VecM::default(),
});
Ok(self
.sign_with
.sign_tx_env(
&tx_env,
&self.locator,
&self.network.get(&self.locator)?,
quiet,
Some(&self.source_account),
)
.await?)
}
pub async fn sign_soroban_authorizations(
&self,
tx: &Transaction,
signers: &[Signer],
print: &Print,
) -> Result<Option<Transaction>, Error> {
let network = self.get_network()?;
let client = network.rpc_client()?;
let latest_ledger = client.get_latest_ledger().await?.sequence;
let seq_num = latest_ledger + 60; Ok(signer::sign_soroban_authorizations(
tx,
signers,
seq_num,
&network.network_passphrase,
self.sign_with.auto_sign,
print,
)
.await?)
}
pub fn get_network(&self) -> Result<Network, Error> {
Ok(self.network.get(&self.locator)?)
}
pub fn get_inclusion_fee(&self) -> Result<u32, Error> {
if self.fee.is_some() {
deprecate_message(
Print::new(false),
"--fee [env: STELLAR_FEE]",
"Use `--inclusion-fee [env: STELLAR_INCLUSION_FEE]` instead.",
);
}
Ok(self.inclusion_fee.or(self.fee).unwrap_or(100))
}
pub async fn next_sequence_number(
&self,
account: impl Into<xdr::AccountId>,
) -> Result<SequenceNumber, Error> {
let network = self.get_network()?;
let client = network.rpc_client()?;
Ok((client
.get_account(&account.into().to_string())
.await?
.seq_num
.0
+ 1)
.into())
}
pub fn hd_path(&self) -> Option<u32> {
self.sign_with.hd_path
}
}
impl Pwd for Args {
fn set_pwd(&mut self, pwd: &std::path::Path) {
self.locator.set_pwd(pwd);
}
}
#[derive(Debug, clap::Args, Clone, Default)]
#[group(skip)]
pub struct ArgsLocatorAndNetwork {
#[command(flatten)]
pub network: network::Args,
#[command(flatten)]
pub locator: locator::Args,
}
impl ArgsLocatorAndNetwork {
pub fn get_network(&self) -> Result<Network, Error> {
Ok(self.network.get(&self.locator)?)
}
}
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Config {
pub defaults: Defaults,
}
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Defaults {
pub network: Option<String>,
pub identity: Option<String>,
pub inclusion_fee: Option<u32>,
}
impl Config {
pub fn new() -> Result<Config, locator::Error> {
Self::load(&cli_config_file()?)
}
pub fn load(path: &std::path::Path) -> Result<Config, locator::Error> {
if path.exists() {
let data = fs::read_to_string(path).map_err(|_| locator::Error::FileRead {
path: path.to_path_buf(),
})?;
Ok(toml::from_str(&data)?)
} else {
Ok(Config::default())
}
}
#[must_use]
pub fn set_network(mut self, s: &str) -> Self {
self.defaults.network = Some(s.to_string());
self
}
#[must_use]
pub fn set_identity(mut self, s: &str) -> Self {
self.defaults.identity = Some(s.to_string());
self
}
#[must_use]
pub fn set_inclusion_fee(mut self, uint: u32) -> Self {
self.defaults.inclusion_fee = Some(uint);
self
}
#[must_use]
pub fn unset_identity(mut self) -> Self {
self.defaults.identity = None;
self
}
#[must_use]
pub fn unset_network(mut self) -> Self {
self.defaults.network = None;
self
}
#[must_use]
pub fn unset_inclusion_fee(mut self) -> Self {
self.defaults.inclusion_fee = None;
self
}
pub fn save(&self) -> Result<(), locator::Error> {
self.save_to(&cli_config_file()?)
}
pub fn save_to(&self, path: &std::path::Path) -> Result<(), locator::Error> {
let toml_string = toml::to_string(&self)?;
let path = locator::ensure_directory(path.to_path_buf())?;
locator::write_hardened_file(&path, toml_string.as_bytes())?;
Ok(())
}
}