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    rpc::{self, Client, GetTransactionResponse},
9    tx::builder::{self, asset, TxExt},
10    xdr::{self, Limits, WriteXdr},
11};
12
13#[derive(Debug, clap::Args, Clone)]
14#[group(skip)]
15pub struct Args {
16    #[clap(flatten)]
17    pub config: config::Args,
18    /// Build the transaction and only write the base64 xdr to stdout
19    #[arg(long)]
20    pub build_only: bool,
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
63        // Once we have a way to add operations this will be updated to allow for a different source account
64        let operation = xdr::Operation {
65            source_account: None,
66            body: body.into(),
67        };
68        Ok(xdr::Transaction::new_tx(
69            source_account,
70            self.config.get_inclusion_fee()?,
71            seq_num,
72            operation,
73        ))
74    }
75
76    pub fn client(&self) -> Result<Client, Error> {
77        let network = self.config.get_network()?;
78        Ok(Client::new(&network.rpc_url)?)
79    }
80
81    pub async fn handle(
82        &self,
83        op: impl Into<xdr::OperationBody>,
84        global_args: &global::Args,
85    ) -> Result<TxnEnvelopeResult<GetTransactionResponse>, Error> {
86        let tx = self.tx(op).await?;
87        self.handle_tx(tx, global_args).await
88    }
89
90    pub async fn handle_and_print(
91        &self,
92        op: impl Into<xdr::OperationBody>,
93        global_args: &global::Args,
94    ) -> Result<(), Error> {
95        let res = self.handle(op, global_args).await?;
96        if let TxnEnvelopeResult::TxnEnvelope(tx) = res {
97            println!("{}", tx.to_xdr_base64(Limits::none())?);
98        }
99        Ok(())
100    }
101
102    pub async fn handle_tx(
103        &self,
104        tx: xdr::Transaction,
105        args: &global::Args,
106    ) -> Result<TxnEnvelopeResult<GetTransactionResponse>, Error> {
107        let network = self.config.get_network()?;
108        let client = Client::new(&network.rpc_url)?;
109        if self.build_only {
110            return Ok(TxnEnvelopeResult::TxnEnvelope(Box::new(tx.into())));
111        }
112
113        let txn_resp = client
114            .send_transaction_polling(&self.config.sign(tx, args.quiet).await?)
115            .await?;
116
117        if !args.no_cache {
118            data::write(txn_resp.clone().try_into().unwrap(), &network.rpc_uri()?)?;
119        }
120
121        Ok(TxnEnvelopeResult::Res(txn_resp))
122    }
123
124    pub async fn source_account(&self) -> Result<xdr::MuxedAccount, Error> {
125        Ok(self.config.source_account().await?)
126    }
127
128    pub fn resolve_muxed_address(
129        &self,
130        address: &UnresolvedMuxedAccount,
131    ) -> Result<xdr::MuxedAccount, Error> {
132        Ok(address.resolve_muxed_account_sync(&self.config.locator, self.config.hd_path())?)
133    }
134
135    pub fn resolve_account_id(
136        &self,
137        address: &UnresolvedMuxedAccount,
138    ) -> Result<xdr::AccountId, Error> {
139        Ok(address
140            .resolve_muxed_account_sync(&self.config.locator, self.config.hd_path())?
141            .account_id())
142    }
143
144    pub async fn add_op(
145        &self,
146        op_body: impl Into<xdr::OperationBody>,
147        tx_env: xdr::TransactionEnvelope,
148        op_source: Option<&address::UnresolvedMuxedAccount>,
149    ) -> Result<xdr::TransactionEnvelope, Error> {
150        let mut source_account = None;
151        if let Some(account) = op_source {
152            source_account = Some(
153                account
154                    .resolve_muxed_account(&self.config.locator, self.config.hd_path())
155                    .await?,
156            );
157        }
158        let op = xdr::Operation {
159            source_account,
160            body: op_body.into(),
161        };
162        Ok(super::xdr::add_op(tx_env, op)?)
163    }
164
165    pub fn resolve_asset(&self, asset: &builder::Asset) -> Result<xdr::Asset, Error> {
166        Ok(asset.resolve(&self.config.locator)?)
167    }
168
169    pub fn resolve_signer_key(
170        &self,
171        signer_account: &UnresolvedMuxedAccount,
172    ) -> Result<xdr::SignerKey, Error> {
173        let resolved_account = self.resolve_account_id(signer_account)?;
174        let signer_key = match resolved_account.0 {
175            xdr::PublicKey::PublicKeyTypeEd25519(uint256) => xdr::SignerKey::Ed25519(uint256),
176        };
177        Ok(signer_key)
178    }
179}