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