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 #[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 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}