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