utility_cli_rs/transaction_signature_options/
mod.rs

1use 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))]
20/// Select a tool for signing the transaction:
21pub enum SignWith {
22    #[strum_discriminants(strum(
23        message = "sign-with-keychain               - Sign the transaction with a key saved in the secure keychain"
24    ))]
25    /// Sign the transaction with a key saved in keychain
26    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    /// Sign the transaction with a key saved in legacy keychain (compatible with the old unc CLI)
31    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    /// Sign the transaction with Ledger Nano device
37    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    /// Sign the transaction with a plaintext private key
42    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    /// Sign the transaction using the account access key file (access-key-file.json)
47    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    /// Sign the transaction using the seed phrase
52    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    /// Prepare unsigned transaction to sign it later
57    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)]
64/// How would you like to proceed?
65pub enum Submit {
66    #[strum_discriminants(strum(message = "send      - Send the transaction to the network"))]
67    /// Send the transaction to the network
68    Send,
69    #[strum_discriminants(strum(
70        message = "display   - Print the signed transaction to terminal (if you want to send it later)"
71    ))]
72    /// Print the signed transaction to terminal (if you want to send it later)
73    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                    // FIXME: 异步调用后续逻辑处理, 在Final后,后续逻辑处理
149                    eprintln!("Processing transaction...\nPlease wait for 6 blocks to confirm, use command: unc transaction view-status <tx_hash>");
150
151                    // let transaction_info = loop {
152                    //     let transaction_info_result = context.network_config.json_rpc_client()
153                    //     .blocking_call(
154                    //         unc_jsonrpc_client::methods::tx::RpcTransactionStatusRequest {
155                    //             transaction_info:
156                    //                 unc_jsonrpc_client::methods::tx::TransactionInfo::TransactionId {
157                    //                     tx_hash: transaction_hash.into(),
158                    //                     sender_account_id: signed_transaction.transaction.signer_id.clone(),
159                    //                 },
160                    //         },
161                    //     );
162                    //     match transaction_info_result {
163                    //         Ok(response) => {
164                    //             break response;
165                    //         }
166                    //         Err(err) => match crate::common::rpc_transaction_error(err) {
167                    //             Ok(_) => std::thread::sleep(std::time::Duration::from_millis(100)),
168                    //             Err(report) => {
169                    //                 return interactive_clap::ResultFromCli::Err(
170                    //                     optional_clap_variant,
171                    //                     color_eyre::Report::msg(report),
172                    //                 )
173                    //             }
174                    //         },
175                    //     };
176                    // };
177
178                    // if let Err(report) = (context.on_after_sending_transaction_callback)(
179                    //     &transaction_info,
180                    //     &context.network_config,
181                    // ) {
182                    //     return interactive_clap::ResultFromCli::Err(
183                    //         optional_clap_variant,
184                    //         color_eyre::Report::msg(report),
185                    //     );
186                    // };
187                    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    // create a new signature here signing the delegate action + discriminant
368    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}