soroban_cli/commands/tx/
args.rs

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