Skip to main content

soroban_cli/commands/tx/
args.rs

1use crate::utils::XDR_DEPTH_LIMIT;
2use crate::{
3    commands::{global, txn_result::TxnEnvelopeResult, HEADING_TRANSACTION},
4    config::{
5        self,
6        address::{self, UnresolvedMuxedAccount},
7        data, network, secret,
8    },
9    rpc::{self, Client, GetTransactionResponse},
10    tx::builder::{self, asset, TxExt},
11    xdr::{self, Limits, WriteXdr},
12};
13
14#[derive(Debug, clap::Args, Clone)]
15#[group(skip)]
16pub struct Args {
17    #[clap(flatten)]
18    pub config: config::Args,
19    /// Build the transaction and only write the base64 xdr to stdout
20    #[arg(long, help_heading = HEADING_TRANSACTION)]
21    pub build_only: bool,
22}
23
24#[derive(thiserror::Error, Debug)]
25pub enum Error {
26    #[error(transparent)]
27    Rpc(#[from] rpc::Error),
28    #[error(transparent)]
29    Config(#[from] config::Error),
30    #[error(transparent)]
31    Network(#[from] network::Error),
32    #[error(transparent)]
33    Secret(#[from] secret::Error),
34    #[error(transparent)]
35    Tx(#[from] builder::Error),
36    #[error(transparent)]
37    Data(#[from] data::Error),
38    #[error(transparent)]
39    Xdr(#[from] xdr::Error),
40    #[error(transparent)]
41    Address(#[from] address::Error),
42    #[error(transparent)]
43    Asset(#[from] asset::Error),
44    #[error(transparent)]
45    TxXdr(#[from] super::xdr::Error),
46    #[error("invalid price format: {0}")]
47    InvalidPrice(String),
48    #[error("invalid path: {0}")]
49    InvalidPath(String),
50    #[error("invalid pool ID format: {0}")]
51    InvalidPoolId(String),
52    #[error("invalid hex for {name}: {hex}")]
53    InvalidHex { name: String, hex: String },
54}
55
56impl Args {
57    pub async fn tx(&self, body: impl Into<xdr::OperationBody>) -> Result<xdr::Transaction, Error> {
58        let source_account = self.source_account()?;
59        let seq_num = self
60            .config
61            .next_sequence_number(source_account.clone().account_id())
62            .await?;
63
64        // Once we have a way to add operations this will be updated to allow for a different source account
65        let operation = xdr::Operation {
66            source_account: None,
67            body: body.into(),
68        };
69        Ok(xdr::Transaction::new_tx(
70            source_account,
71            self.config.get_inclusion_fee()?,
72            seq_num,
73            operation,
74        ))
75    }
76
77    pub fn client(&self) -> Result<Client, Error> {
78        let network = self.config.get_network()?;
79        Ok(Client::new(&network.rpc_url)?)
80    }
81
82    pub async fn handle(
83        &self,
84        op: impl Into<xdr::OperationBody>,
85        global_args: &global::Args,
86    ) -> Result<TxnEnvelopeResult<GetTransactionResponse>, Error> {
87        let tx = self.tx(op).await?;
88        self.handle_tx(tx, global_args).await
89    }
90
91    pub async fn handle_and_print(
92        &self,
93        op: impl Into<xdr::OperationBody>,
94        global_args: &global::Args,
95    ) -> Result<(), Error> {
96        let res = self.handle(op, global_args).await?;
97        if let TxnEnvelopeResult::TxnEnvelope(tx) = res {
98            println!("{}", tx.to_xdr_base64(Limits::depth(XDR_DEPTH_LIMIT))?);
99        }
100        Ok(())
101    }
102
103    pub async fn handle_tx(
104        &self,
105        tx: xdr::Transaction,
106        args: &global::Args,
107    ) -> Result<TxnEnvelopeResult<GetTransactionResponse>, Error> {
108        let network = self.config.get_network()?;
109        let client = Client::new(&network.rpc_url)?;
110        if self.build_only {
111            return Ok(TxnEnvelopeResult::TxnEnvelope(Box::new(tx.into())));
112        }
113
114        let print = crate::print::Print::new(args.quiet);
115        let signed_tx = self.config.sign(tx, args.quiet).await?;
116        let txn_resp = crate::tx::send_transaction_polling_with_events(
117            &client,
118            &signed_tx,
119            &network.network_passphrase,
120            &print,
121        )
122        .await?;
123
124        if !args.no_cache {
125            data::write(txn_resp.clone().try_into().unwrap(), &network.rpc_uri()?)?;
126        }
127
128        Ok(TxnEnvelopeResult::Res(txn_resp))
129    }
130
131    pub fn source_account(&self) -> Result<xdr::MuxedAccount, Error> {
132        Ok(self.config.source_account()?)
133    }
134
135    pub fn resolve_muxed_address(
136        &self,
137        address: &UnresolvedMuxedAccount,
138    ) -> Result<xdr::MuxedAccount, Error> {
139        Ok(address.resolve_muxed_account(&self.config.locator, self.config.hd_path())?)
140    }
141
142    pub fn resolve_account_id(
143        &self,
144        address: &UnresolvedMuxedAccount,
145    ) -> Result<xdr::AccountId, Error> {
146        Ok(address
147            .resolve_muxed_account(&self.config.locator, self.config.hd_path())?
148            .account_id())
149    }
150
151    pub fn add_op(
152        &self,
153        op_body: impl Into<xdr::OperationBody>,
154        tx_env: xdr::TransactionEnvelope,
155        op_source: Option<&address::UnresolvedMuxedAccount>,
156    ) -> Result<xdr::TransactionEnvelope, Error> {
157        let mut source_account = None;
158        if let Some(account) = op_source {
159            source_account =
160                Some(account.resolve_muxed_account(&self.config.locator, self.config.hd_path())?);
161        }
162        let op = xdr::Operation {
163            source_account,
164            body: op_body.into(),
165        };
166        Ok(super::xdr::add_op(tx_env, op)?)
167    }
168
169    pub fn resolve_asset(&self, asset: &builder::Asset) -> Result<xdr::Asset, Error> {
170        Ok(asset.resolve(&self.config.locator)?)
171    }
172
173    pub fn resolve_signer_key(
174        &self,
175        signer_account: &UnresolvedMuxedAccount,
176    ) -> Result<xdr::SignerKey, Error> {
177        let resolved_account = self.resolve_account_id(signer_account)?;
178        let signer_key = match resolved_account.0 {
179            xdr::PublicKey::PublicKeyTypeEd25519(uint256) => xdr::SignerKey::Ed25519(uint256),
180        };
181        Ok(signer_key)
182    }
183}