utility_cli_rs/transaction_signature_options/
mod.rs1use serde::Deserialize;
2use strum::{EnumDiscriminants, EnumIter, EnumMessage};
3
4use crate::common::JsonRpcClientExt;
5
6pub mod sign_later;
7pub mod sign_with_access_key_file;
8pub mod sign_with_keychain;
9#[cfg(feature = "ledger")]
10pub mod sign_with_ledger;
11pub mod sign_with_legacy_keychain;
12pub mod sign_with_private_key;
13pub mod sign_with_seed_phrase;
14
15pub const META_TRANSACTION_VALID_FOR_DEFAULT: u64 = 1000;
16
17#[derive(Debug, EnumDiscriminants, Clone, interactive_clap::InteractiveClap)]
18#[interactive_clap(context = crate::commands::TransactionContext)]
19#[strum_discriminants(derive(EnumMessage, EnumIter))]
20pub enum SignWith {
22 #[strum_discriminants(strum(
23 message = "sign-with-keychain - Sign the transaction with a key saved in the secure keychain"
24 ))]
25 SignWithKeychain(self::sign_with_keychain::SignKeychain),
27 #[strum_discriminants(strum(
28 message = "sign-with-legacy-keychain - Sign the transaction with a key saved in legacy keychain (compatible with the old unc CLI)"
29 ))]
30 SignWithLegacyKeychain(self::sign_with_legacy_keychain::SignLegacyKeychain),
32 #[cfg(feature = "ledger")]
33 #[strum_discriminants(strum(
34 message = "sign-with-ledger - Sign the transaction with Ledger Nano device"
35 ))]
36 SignWithLedger(self::sign_with_ledger::SignLedger),
38 #[strum_discriminants(strum(
39 message = "sign-with-plaintext-private-key - Sign the transaction with a plaintext private key"
40 ))]
41 SignWithPlaintextPrivateKey(self::sign_with_private_key::SignPrivateKey),
43 #[strum_discriminants(strum(
44 message = "sign-with-access-key-file - Sign the transaction using the account access key file (access-key-file.json)"
45 ))]
46 SignWithAccessKeyFile(self::sign_with_access_key_file::SignAccessKeyFile),
48 #[strum_discriminants(strum(
49 message = "sign-with-seed-phrase - Sign the transaction using the seed phrase"
50 ))]
51 SignWithSeedPhrase(self::sign_with_seed_phrase::SignSeedPhrase),
53 #[strum_discriminants(strum(
54 message = "sign-later - Prepare an unsigned transaction to sign it later"
55 ))]
56 SignLater(self::sign_later::Display),
58}
59
60#[derive(Debug, EnumDiscriminants, Clone, interactive_clap::InteractiveClap)]
61#[interactive_clap(context = SubmitContext)]
62#[strum_discriminants(derive(EnumMessage, EnumIter))]
63#[interactive_clap(skip_default_from_cli)]
64pub enum Submit {
66 #[strum_discriminants(strum(message = "send - Send the transaction to the network"))]
67 Send,
69 #[strum_discriminants(strum(
70 message = "display - Print the signed transaction to terminal (if you want to send it later)"
71 ))]
72 Display,
74}
75
76impl interactive_clap::FromCli for Submit {
77 type FromCliContext = SubmitContext;
78 type FromCliError = color_eyre::eyre::Error;
79
80 fn from_cli(
81 mut optional_clap_variant: Option<<Self as interactive_clap::ToCli>::CliVariant>,
82 context: Self::FromCliContext,
83 ) -> interactive_clap::ResultFromCli<
84 <Self as interactive_clap::ToCli>::CliVariant,
85 Self::FromCliError,
86 >
87 where
88 Self: Sized + interactive_clap::ToCli,
89 {
90 let mut storage_message = String::new();
91
92 if optional_clap_variant.is_none() {
93 match Self::choose_variant(context.clone()) {
94 interactive_clap::ResultFromCli::Ok(cli_submit) => {
95 optional_clap_variant = Some(cli_submit)
96 }
97 result => return result,
98 }
99 }
100
101 match optional_clap_variant {
102 Some(CliSubmit::Send) => match context.signed_transaction_or_signed_delegate_action {
103 SignedTransactionOrSignedDelegateAction::SignedTransaction(signed_transaction) => {
104 if let Err(report) = (context.on_before_sending_transaction_callback)(
105 &signed_transaction,
106 &context.network_config,
107 &mut storage_message,
108 ) {
109 return interactive_clap::ResultFromCli::Err(
110 optional_clap_variant,
111 color_eyre::Report::msg(report),
112 );
113 };
114
115 eprintln!("Transaction sent ...");
116 let transaction_hash = loop {
117 let transaction_info_result = context.network_config.json_rpc_client()
118 .blocking_call(
119 unc_jsonrpc_client::methods::broadcast_tx_async::RpcBroadcastTxAsyncRequest{
120 signed_transaction: signed_transaction.clone()
121 }
122 );
123 match transaction_info_result {
124 Ok(response) => {
125 break response;
126 }
127 Err(err) => match crate::common::rpc_async_transaction_error(err) {
128 Ok(_) => std::thread::sleep(std::time::Duration::from_millis(100)),
129 Err(report) => {
130 return interactive_clap::ResultFromCli::Err(
131 optional_clap_variant,
132 color_eyre::Report::msg(report),
133 )
134 }
135 },
136 };
137 };
138 if let Err(report) = crate::common::print_async_transaction_status(
139 &transaction_hash,
140 &context.network_config,
141 ) {
142 return interactive_clap::ResultFromCli::Err(
143 optional_clap_variant,
144 color_eyre::Report::msg(report),
145 );
146 };
147
148 eprintln!("Processing transaction...\nPlease wait for 6 blocks to confirm, use command: unc transaction view-status <tx_hash>");
150
151 eprintln!("{storage_message}");
188 interactive_clap::ResultFromCli::Ok(CliSubmit::Send)
189 }
190 SignedTransactionOrSignedDelegateAction::SignedDelegateAction(
191 signed_delegate_action,
192 ) => {
193 let client = reqwest::blocking::Client::new();
194 let json_payload = serde_json::json!({
195 "signed_delegate_action": crate::types::signed_delegate_action::SignedDelegateActionAsBase64::from(
196 signed_delegate_action
197 ).to_string()
198 });
199 match client
200 .post(
201 context
202 .network_config
203 .meta_transaction_relayer_url
204 .expect("Internal error: Meta-transaction relayer URL must be Some() at this point"),
205 )
206 .json(&json_payload)
207 .send()
208 {
209 Ok(relayer_response) => {
210 if relayer_response.status().is_success() {
211 let response_text = match relayer_response.text() {
212 Ok(text) => text,
213 Err(report) => {
214 return interactive_clap::ResultFromCli::Err(
215 optional_clap_variant,
216 color_eyre::Report::msg(report),
217 )
218 }
219 };
220 println!("Relayer Response text: {}", response_text);
221 } else {
222 println!(
223 "Request failed with status code: {}",
224 relayer_response.status()
225 );
226 }
227 }
228 Err(report) => {
229 return interactive_clap::ResultFromCli::Err(
230 optional_clap_variant,
231 color_eyre::Report::msg(report),
232 )
233 }
234 }
235 eprintln!("{storage_message}");
236 interactive_clap::ResultFromCli::Ok(CliSubmit::Send)
237 }
238 },
239 Some(CliSubmit::Display) => {
240 match context.signed_transaction_or_signed_delegate_action {
241 SignedTransactionOrSignedDelegateAction::SignedTransaction(
242 signed_transaction,
243 ) => {
244 if let Err(report) = (context.on_before_sending_transaction_callback)(
245 &signed_transaction,
246 &context.network_config,
247 &mut storage_message,
248 ) {
249 return interactive_clap::ResultFromCli::Err(
250 optional_clap_variant,
251 color_eyre::Report::msg(report),
252 );
253 };
254 eprintln!(
255 "\nSigned transaction (serialized as base64):\n{}\n",
256 crate::types::signed_transaction::SignedTransactionAsBase64::from(
257 signed_transaction
258 )
259 );
260 eprintln!(
261 "This base64-encoded signed transaction is ready to be sent to the network. You can call RPC server directly, or use a helper command on unc CLI:\n$ {} transaction send-signed-transaction\n",
262 crate::common::get_unc_exec_path()
263 );
264 eprintln!("{storage_message}");
265 interactive_clap::ResultFromCli::Ok(CliSubmit::Display)
266 }
267 SignedTransactionOrSignedDelegateAction::SignedDelegateAction(
268 signed_delegate_action,
269 ) => {
270 eprintln!(
271 "\nSigned delegate action (serialized as base64):\n{}\n",
272 crate::types::signed_delegate_action::SignedDelegateActionAsBase64::from(
273 signed_delegate_action
274 )
275 );
276 eprintln!(
277 "This base64-encoded signed delegate action is ready to be sent to the meta-transaction relayer. There is a helper command on unc CLI that can do that:\n$ {} transaction send-meta-transaction\n",
278 crate::common::get_unc_exec_path()
279 );
280 eprintln!("{storage_message}");
281 interactive_clap::ResultFromCli::Ok(CliSubmit::Display)
282 }
283 }
284 }
285 None => unreachable!("Unexpected error"),
286 }
287 }
288}
289
290#[derive(Debug, Deserialize)]
291pub struct AccountKeyPair {
292 pub public_key: unc_crypto::PublicKey,
293 pub private_key: unc_crypto::SecretKey,
294}
295
296pub type OnBeforeSendingTransactionCallback = std::sync::Arc<
297 dyn Fn(
298 &unc_primitives::transaction::SignedTransaction,
299 &crate::config::NetworkConfig,
300 &mut String,
301 ) -> crate::CliResult,
302>;
303
304pub type OnAfterSendingTransactionCallback = std::sync::Arc<
305 dyn Fn(
306 &unc_primitives::views::FinalExecutionOutcomeView,
307 &crate::config::NetworkConfig,
308 ) -> crate::CliResult,
309>;
310
311#[derive(Clone)]
312pub struct SubmitContext {
313 pub network_config: crate::config::NetworkConfig,
314 pub global_context: crate::GlobalContext,
315 pub signed_transaction_or_signed_delegate_action: SignedTransactionOrSignedDelegateAction,
316 pub on_before_sending_transaction_callback: OnBeforeSendingTransactionCallback,
317 pub on_after_sending_transaction_callback: OnAfterSendingTransactionCallback,
318}
319
320#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
321pub enum SignedTransactionOrSignedDelegateAction {
322 SignedTransaction(unc_primitives::transaction::SignedTransaction),
323 SignedDelegateAction(unc_primitives::action::delegate::SignedDelegateAction),
324}
325
326impl From<unc_primitives::transaction::SignedTransaction>
327 for SignedTransactionOrSignedDelegateAction
328{
329 fn from(signed_transaction: unc_primitives::transaction::SignedTransaction) -> Self {
330 Self::SignedTransaction(signed_transaction)
331 }
332}
333
334impl From<unc_primitives::action::delegate::SignedDelegateAction>
335 for SignedTransactionOrSignedDelegateAction
336{
337 fn from(
338 signed_delegate_action: unc_primitives::action::delegate::SignedDelegateAction,
339 ) -> Self {
340 Self::SignedDelegateAction(signed_delegate_action)
341 }
342}
343
344pub fn get_signed_delegate_action(
345 unsigned_transaction: unc_primitives::transaction::Transaction,
346 public_key: &unc_crypto::PublicKey,
347 private_key: unc_crypto::SecretKey,
348 max_block_height: u64,
349) -> unc_primitives::action::delegate::SignedDelegateAction {
350 use unc_primitives::signable_message::{SignableMessage, SignableMessageType};
351
352 let actions = unsigned_transaction
353 .actions
354 .into_iter()
355 .map(unc_primitives::action::delegate::NonDelegateAction::try_from)
356 .collect::<Result<_, _>>()
357 .expect("Internal error: can not convert the action to non delegate action (delegate action can not be delegated again).");
358 let delegate_action = unc_primitives::action::delegate::DelegateAction {
359 sender_id: unsigned_transaction.signer_id.clone(),
360 receiver_id: unsigned_transaction.receiver_id,
361 actions,
362 nonce: unsigned_transaction.nonce,
363 max_block_height,
364 public_key: unsigned_transaction.public_key,
365 };
366
367 let signable = SignableMessage::new(&delegate_action, SignableMessageType::DelegateAction);
369 let signer =
370 unc_crypto::InMemorySigner::from_secret_key(unsigned_transaction.signer_id, private_key);
371 let signature = signable.sign(&signer);
372
373 eprintln!("\nYour delegating action was signed successfully.");
374 eprintln!("Note that the signed transaction is valid until block {max_block_height}. You can change the validity of a transaction by setting a flag in the command: --meta-transaction-valid-for 2000");
375 eprintln!("Public key: {}", public_key);
376 eprintln!("Signature: {}", signature);
377
378 unc_primitives::action::delegate::SignedDelegateAction {
379 delegate_action,
380 signature,
381 }
382}