near_api/common/
send.rs

1use std::sync::Arc;
2
3use near_openapi_client::types::{
4    FinalExecutionOutcomeView, JsonRpcRequestForSendTx,
5    JsonRpcResponseForRpcTransactionResponseAndRpcError, RpcSendTransactionRequest,
6    RpcTransactionResponse,
7};
8
9use near_api_types::{
10    BlockHeight, CryptoHash, Nonce, PublicKey, TxExecutionStatus,
11    transaction::{
12        PrepopulateTransaction, SignedTransaction,
13        delegate_action::{SignedDelegateAction, SignedDelegateActionAsBase64},
14        result::ExecutionFinalResult,
15    },
16};
17use reqwest::Response;
18use tracing::{debug, info};
19
20use crate::{
21    common::utils::is_critical_transaction_error,
22    config::{NetworkConfig, RetryResponse, retry},
23    errors::{
24        ExecuteMetaTransactionsError, ExecuteTransactionError, MetaSignError, SendRequestError,
25        SignerError, ValidationError,
26    },
27    signer::Signer,
28};
29
30use super::META_TRANSACTION_VALID_FOR_DEFAULT;
31
32const TX_EXECUTOR_TARGET: &str = "near_api::tx::executor";
33const META_EXECUTOR_TARGET: &str = "near_api::meta::executor";
34
35#[async_trait::async_trait]
36pub trait Transactionable: Send + Sync {
37    fn prepopulated(&self) -> PrepopulateTransaction;
38    /// Validate the transaction before sending it to the network
39    async fn validate_with_network(&self, network: &NetworkConfig) -> Result<(), ValidationError>;
40
41    /// Edit the transaction before sending it to the network.
42    /// This is useful for example to add storage deposit to the transaction
43    /// if it's needed.
44    /// Though, it won't be called if the user has presigned the transaction.
45    async fn edit_with_network(&mut self, _network: &NetworkConfig) -> Result<(), ValidationError> {
46        Ok(())
47    }
48}
49
50pub enum TransactionableOrSigned<Signed> {
51    /// A transaction that is not signed.
52    Transactionable(Box<dyn Transactionable + 'static>),
53    /// A transaction that is signed and ready to be sent to the network.
54    Signed((Signed, Box<dyn Transactionable + 'static>)),
55}
56
57impl<Signed> TransactionableOrSigned<Signed> {
58    pub fn signed(self) -> Option<Signed> {
59        match self {
60            Self::Signed((signed, _)) => Some(signed),
61            Self::Transactionable(_) => None,
62        }
63    }
64}
65
66impl<S> TransactionableOrSigned<S> {
67    pub fn transactionable(self) -> Box<dyn Transactionable> {
68        match self {
69            Self::Transactionable(tr) => tr,
70            Self::Signed((_, tr)) => tr,
71        }
72    }
73}
74
75/// The handler for signing and sending transaction to the network.
76///
77/// This is the main entry point for the transaction sending functionality.
78pub struct ExecuteSignedTransaction {
79    /// The transaction that is either not signed yet or already signed.
80    pub tr: TransactionableOrSigned<SignedTransaction>,
81    /// The signer that will be used to sign the transaction.
82    pub signer: Arc<Signer>,
83
84    pub wait_until: TxExecutionStatus,
85}
86
87impl ExecuteSignedTransaction {
88    pub fn new<T: Transactionable + 'static>(tr: T, signer: Arc<Signer>) -> Self {
89        Self {
90            tr: TransactionableOrSigned::Transactionable(Box::new(tr)),
91            signer,
92            wait_until: TxExecutionStatus::Final,
93        }
94    }
95
96    /// Changes the transaction to a [meta transaction](https://docs.near.org/concepts/abstraction/meta-transactions), allowing some 3rd party entity to execute it and
97    /// pay for the gas.
98    ///
99    /// Please note, that if you already presigned the transaction, it would require you to sign it again as a meta transaction
100    /// is a different type of transaction.
101    pub fn meta(self) -> ExecuteMetaTransaction {
102        ExecuteMetaTransaction::from_box(self.tr.transactionable(), self.signer)
103    }
104
105    pub const fn wait_until(mut self, wait_until: TxExecutionStatus) -> Self {
106        self.wait_until = wait_until;
107        self
108    }
109
110    /// Signs the transaction offline without fetching the nonce or block hash from the network.
111    ///
112    /// The transaction won't be broadcasted to the network and just stored signed in the [Self::tr] struct variable.
113    ///
114    /// This is useful if you already have the nonce and block hash, or if you want to sign the transaction
115    /// offline. Please note that incorrect nonce will lead to transaction failure.
116    pub async fn presign_offline(
117        mut self,
118        public_key: PublicKey,
119        block_hash: CryptoHash,
120        nonce: Nonce,
121    ) -> Result<Self, SignerError> {
122        let tr = match &self.tr {
123            TransactionableOrSigned::Transactionable(tr) => tr,
124            TransactionableOrSigned::Signed(_) => return Ok(self),
125        };
126
127        let signed_tr = self
128            .signer
129            .sign(tr.prepopulated(), public_key.clone(), nonce, block_hash)
130            .await?;
131
132        self.tr = TransactionableOrSigned::Signed((signed_tr, self.tr.transactionable()));
133        Ok(self)
134    }
135
136    /// Signs the transaction with the custom network configuration but doesn't broadcast it.
137    ///
138    /// Signed transaction is stored in the [Self::tr] struct variable.
139    ///
140    /// This is useful if you want to sign with non-default network configuration (e.g, custom RPC URL, sandbox).
141    /// The provided call will fetch the nonce and block hash from the given network.
142    pub async fn presign_with(
143        self,
144        network: &NetworkConfig,
145    ) -> Result<Self, ExecuteTransactionError> {
146        let tr = match &self.tr {
147            TransactionableOrSigned::Transactionable(tr) => tr,
148            TransactionableOrSigned::Signed(_) => return Ok(self),
149        };
150
151        let signer_key = self.signer.get_public_key().await?;
152        let tr = tr.prepopulated();
153        let (nonce, hash, _) = self
154            .signer
155            .fetch_tx_nonce(tr.signer_id.clone(), signer_key.clone(), network)
156            .await
157            .map_err(MetaSignError::from)?;
158        Ok(self.presign_offline(signer_key, hash, nonce).await?)
159    }
160
161    /// Signs the transaction with the default mainnet configuration. Does not broadcast it.
162    ///
163    /// Signed transaction is stored in the [Self::tr] struct variable.
164    ///
165    /// The provided call will fetch the nonce and block hash from the network.
166    pub async fn presign_with_mainnet(self) -> Result<Self, ExecuteTransactionError> {
167        let network = NetworkConfig::mainnet();
168        self.presign_with(&network).await
169    }
170
171    /// Signs the transaction with the default testnet configuration. Does not broadcast it.
172    ///
173    /// Signed transaction is stored in the [Self::tr] struct variable.
174    ///
175    /// The provided call will fetch the nonce and block hash from the network.
176    pub async fn presign_with_testnet(self) -> Result<Self, ExecuteTransactionError> {
177        let network = NetworkConfig::testnet();
178        self.presign_with(&network).await
179    }
180
181    /// Sends the transaction to the custom provided network.
182    ///
183    /// This is useful if you want to send the transaction to a non-default network configuration (e.g, custom RPC URL, sandbox).
184    /// Please note that if the transaction is not presigned, it will be signed with the network's nonce and block hash.
185    pub async fn send_to(
186        mut self,
187        network: &NetworkConfig,
188    ) -> Result<ExecutionFinalResult, ExecuteTransactionError> {
189        let (signed, transactionable) = match &mut self.tr {
190            TransactionableOrSigned::Transactionable(tr) => {
191                debug!(target: TX_EXECUTOR_TARGET, "Preparing unsigned transaction");
192                (None, tr)
193            }
194            TransactionableOrSigned::Signed((s, tr)) => {
195                debug!(target: TX_EXECUTOR_TARGET, "Using pre-signed transaction");
196                (Some(s.clone()), tr)
197            }
198        };
199
200        let wait_until = self.wait_until;
201
202        if signed.is_none() {
203            debug!(target: TX_EXECUTOR_TARGET, "Editing transaction with network config");
204            transactionable.edit_with_network(network).await?;
205        } else {
206            debug!(target: TX_EXECUTOR_TARGET, "Validating pre-signed transaction with network config");
207            transactionable.validate_with_network(network).await?;
208        }
209
210        let signed = match signed {
211            Some(s) => s,
212            None => {
213                debug!(target: TX_EXECUTOR_TARGET, "Signing transaction");
214                self.presign_with(network)
215                    .await?
216                    .tr
217                    .signed()
218                    .expect("Expect to have it signed")
219            }
220        };
221
222        info!(
223            target: TX_EXECUTOR_TARGET,
224            "Broadcasting signed transaction. Hash: {:?}, Signer: {:?}, Receiver: {:?}, Nonce: {}",
225            signed.get_hash(),
226            signed.transaction.signer_id(),
227            signed.transaction.receiver_id(),
228            signed.transaction.nonce(),
229        );
230
231        Self::send_impl(network, signed, wait_until).await
232    }
233
234    /// Sends the transaction to the default mainnet configuration.
235    ///
236    /// Please note that this will sign the transaction with the mainnet's nonce and block hash if it's not presigned yet.
237    pub async fn send_to_mainnet(self) -> Result<ExecutionFinalResult, ExecuteTransactionError> {
238        let network = NetworkConfig::mainnet();
239        self.send_to(&network).await
240    }
241
242    /// Sends the transaction to the default testnet configuration.
243    ///
244    /// Please note that this will sign the transaction with the testnet's nonce and block hash if it's not presigned yet.
245    pub async fn send_to_testnet(self) -> Result<ExecutionFinalResult, ExecuteTransactionError> {
246        let network = NetworkConfig::testnet();
247        self.send_to(&network).await
248    }
249
250    async fn send_impl(
251        network: &NetworkConfig,
252        signed_tr: SignedTransaction,
253        wait_until: TxExecutionStatus,
254    ) -> Result<ExecutionFinalResult, ExecuteTransactionError> {
255        let hash = signed_tr.get_hash();
256        let signed_tx_base64: near_openapi_client::types::SignedTransaction = signed_tr.into();
257        let result = retry(network.clone(), |client| {
258            let signed_tx_base64 = signed_tx_base64.clone();
259            async move {
260                let result = match client
261                    .send_tx(&JsonRpcRequestForSendTx {
262                        id: "0".to_string(),
263                        jsonrpc: "2.0".to_string(),
264                        method: near_openapi_client::types::JsonRpcRequestForSendTxMethod::SendTx,
265                        params: RpcSendTransactionRequest {
266                            signed_tx_base64,
267                            wait_until,
268                        },
269                    })
270                    .await
271                {
272                    Ok(result) => match result.into_inner() {
273                        JsonRpcResponseForRpcTransactionResponseAndRpcError::Variant0 {
274                            result,
275                            ..
276                        } => RetryResponse::Ok(result),
277                        JsonRpcResponseForRpcTransactionResponseAndRpcError::Variant1 {
278                            error,
279                            ..
280                        } => {
281                            if is_critical_transaction_error(&error) {
282                                RetryResponse::Critical(SendRequestError::ServerError(error))
283                            } else {
284                                RetryResponse::Retry(SendRequestError::ServerError(error))
285                            }
286                        }
287                    },
288                    Err(err) => RetryResponse::Critical(SendRequestError::ClientError(err)),
289                };
290
291                tracing::debug!(
292                    target: TX_EXECUTOR_TARGET,
293                    "Broadcasting transaction {} resulted in {:?}",
294                    hash,
295                    result
296                );
297
298                result
299            }
300        })
301        .await
302        .map_err(ExecuteTransactionError::TransactionError)?;
303
304        // TODO: check if we need to add support for that final_execution_status
305        let final_execution_outcome_view = match result {
306            // We don't use `experimental_tx`, so we can ignore that, but just to be safe
307            RpcTransactionResponse::Variant0 {
308                final_execution_status: _,
309                receipts: _,
310                receipts_outcome,
311                status,
312                transaction,
313                transaction_outcome,
314            } => FinalExecutionOutcomeView {
315                receipts_outcome,
316                status,
317                transaction,
318                transaction_outcome,
319            },
320            RpcTransactionResponse::Variant1 {
321                final_execution_status: _,
322                receipts_outcome,
323                status,
324                transaction,
325                transaction_outcome,
326            } => FinalExecutionOutcomeView {
327                receipts_outcome,
328                status,
329                transaction,
330                transaction_outcome,
331            },
332        };
333
334        Ok(ExecutionFinalResult::try_from(
335            final_execution_outcome_view,
336        )?)
337    }
338}
339
340pub struct ExecuteMetaTransaction {
341    pub tr: TransactionableOrSigned<SignedDelegateAction>,
342    pub signer: Arc<Signer>,
343    pub tx_live_for: Option<BlockHeight>,
344}
345
346impl ExecuteMetaTransaction {
347    pub fn new<T: Transactionable + 'static>(tr: T, signer: Arc<Signer>) -> Self {
348        Self {
349            tr: TransactionableOrSigned::Transactionable(Box::new(tr)),
350            signer,
351            tx_live_for: None,
352        }
353    }
354
355    pub fn from_box(tr: Box<dyn Transactionable + 'static>, signer: Arc<Signer>) -> Self {
356        Self {
357            tr: TransactionableOrSigned::Transactionable(tr),
358            signer,
359            tx_live_for: None,
360        }
361    }
362
363    /// Sets the transaction live for the given block amount.
364    ///
365    /// This is useful if you want to set the transaction to be valid for a specific amount of blocks.\
366    /// The default amount is 1000 blocks.
367    pub const fn tx_live_for(mut self, tx_live_for: BlockHeight) -> Self {
368        self.tx_live_for = Some(tx_live_for);
369        self
370    }
371
372    /// Signs the transaction offline without fetching the nonce or block hash from the network. Does not broadcast it.
373    ///
374    /// Signed transaction is stored in the [Self::tr] struct variable.
375    ///
376    /// This is useful if you already have the nonce and block hash, or if you want to sign the transaction
377    /// offline. Please note that incorrect nonce will lead to transaction failure and incorrect block height
378    /// will lead to incorrectly populated transaction live value.
379    pub async fn presign_offline(
380        mut self,
381        signer_key: PublicKey,
382        block_hash: CryptoHash,
383        nonce: Nonce,
384        block_height: BlockHeight,
385    ) -> Result<Self, ExecuteMetaTransactionsError> {
386        let tr = match &self.tr {
387            TransactionableOrSigned::Transactionable(tr) => tr,
388            TransactionableOrSigned::Signed(_) => return Ok(self),
389        };
390
391        let max_block_height = block_height
392            + self
393                .tx_live_for
394                .unwrap_or(META_TRANSACTION_VALID_FOR_DEFAULT);
395
396        let signed_tr = self
397            .signer
398            .sign_meta(
399                tr.prepopulated(),
400                signer_key,
401                nonce,
402                block_hash,
403                max_block_height,
404            )
405            .await?;
406
407        self.tr = TransactionableOrSigned::Signed((signed_tr, self.tr.transactionable()));
408        Ok(self)
409    }
410
411    /// Signs the transaction with the custom network configuration but doesn't broadcast it.
412    ///
413    /// Signed transaction is stored in the [Self::tr] struct variable.
414    ///
415    /// This is useful if you want to sign with non-default network configuration (e.g, custom RPC URL, sandbox).
416    pub async fn presign_with(
417        self,
418        network: &NetworkConfig,
419    ) -> Result<Self, ExecuteMetaTransactionsError> {
420        let tr = match &self.tr {
421            TransactionableOrSigned::Transactionable(tr) => tr,
422            TransactionableOrSigned::Signed(_) => return Ok(self),
423        };
424
425        let signer_key = self
426            .signer
427            .get_public_key()
428            .await
429            .map_err(MetaSignError::from)?;
430        let (nonce, block_hash, block_height) = self
431            .signer
432            .fetch_tx_nonce(
433                tr.prepopulated().signer_id.clone(),
434                signer_key.clone(),
435                network,
436            )
437            .await
438            .map_err(MetaSignError::from)?;
439        self.presign_offline(signer_key, block_hash, nonce, block_height)
440            .await
441    }
442
443    /// Signs the transaction with the default mainnet configuration but doesn't broadcast it.
444    ///
445    /// Signed transaction is stored in the [Self::tr] struct variable.
446    ///
447    /// The provided call will fetch the nonce and block hash, block height from the network.
448    pub async fn presign_with_mainnet(self) -> Result<Self, ExecuteMetaTransactionsError> {
449        let network = NetworkConfig::mainnet();
450        self.presign_with(&network).await
451    }
452
453    /// Signs the transaction with the default testnet configuration but doesn't broadcast it.
454    ///
455    /// Signed transaction is stored in the [Self::tr] struct variable.
456    ///
457    /// The provided call will fetch the nonce and block hash, block height from the network.
458    pub async fn presign_with_testnet(self) -> Result<Self, ExecuteMetaTransactionsError> {
459        let network = NetworkConfig::testnet();
460        self.presign_with(&network).await
461    }
462
463    /// Sends the transaction to the custom provided network.
464    ///
465    /// This is useful if you want to send the transaction to a non-default network configuration (e.g, custom RPC URL, sandbox).
466    /// Please note that if the transaction is not presigned, it will be sign with the network's nonce and block hash.
467    pub async fn send_to(
468        mut self,
469        network: &NetworkConfig,
470    ) -> Result<Response, ExecuteMetaTransactionsError> {
471        let (signed, transactionable) = match &mut self.tr {
472            TransactionableOrSigned::Transactionable(tr) => {
473                debug!(target: META_EXECUTOR_TARGET, "Preparing unsigned meta transaction");
474                (None, tr)
475            }
476            TransactionableOrSigned::Signed((s, tr)) => {
477                debug!(target: META_EXECUTOR_TARGET, "Using pre-signed meta transaction");
478                (Some(s.clone()), tr)
479            }
480        };
481
482        if signed.is_none() {
483            debug!(target: META_EXECUTOR_TARGET, "Editing meta transaction with network config");
484            transactionable.edit_with_network(network).await?;
485        } else {
486            debug!(target: META_EXECUTOR_TARGET, "Validating pre-signed meta transaction with network config");
487            transactionable.validate_with_network(network).await?;
488        }
489
490        let signed = match signed {
491            Some(s) => s,
492            None => {
493                debug!(target: META_EXECUTOR_TARGET, "Signing meta transaction");
494                self.presign_with(network)
495                    .await?
496                    .tr
497                    .signed()
498                    .expect("Expect to have it signed")
499            }
500        };
501
502        info!(
503            target: META_EXECUTOR_TARGET,
504            "Broadcasting signed meta transaction. Signer: {:?}, Receiver: {:?}, Nonce: {}, Valid until: {}",
505            signed.delegate_action.sender_id,
506            signed.delegate_action.receiver_id,
507            signed.delegate_action.nonce,
508            signed.delegate_action.max_block_height
509        );
510
511        Self::send_impl(network, signed).await
512    }
513
514    /// Sends the transaction to the default mainnet configuration.
515    ///
516    /// Please note that this will sign the transaction with the mainnet's nonce and block hash if it's not presigned yet.
517    pub async fn send_to_mainnet(self) -> Result<reqwest::Response, ExecuteMetaTransactionsError> {
518        let network = NetworkConfig::mainnet();
519        self.send_to(&network).await
520    }
521
522    /// Sends the transaction to the default testnet configuration.
523    ///
524    /// Please note that this will sign the transaction with the testnet's nonce and block hash if it's not presigned yet.
525    pub async fn send_to_testnet(self) -> Result<reqwest::Response, ExecuteMetaTransactionsError> {
526        let network = NetworkConfig::testnet();
527        self.send_to(&network).await
528    }
529
530    async fn send_impl(
531        network: &NetworkConfig,
532        tr: SignedDelegateAction,
533    ) -> Result<reqwest::Response, ExecuteMetaTransactionsError> {
534        let client = reqwest::Client::new();
535        let json_payload = serde_json::json!({
536            "signed_delegate_action": SignedDelegateActionAsBase64::from(
537                tr.clone()
538            ).to_string(),
539        });
540        debug!(
541            target: META_EXECUTOR_TARGET,
542            "Sending meta transaction to relayer. Payload: {:?}",
543            json_payload
544        );
545        let resp = client
546            .post(
547                network
548                    .meta_transaction_relayer_url
549                    .clone()
550                    .ok_or(ExecuteMetaTransactionsError::RelayerIsNotDefined)?,
551            )
552            .json(&json_payload)
553            .send()
554            .await?;
555
556        info!(
557            target: META_EXECUTOR_TARGET,
558            "Meta transaction sent to relayer. Status: {}, Signer: {:?}, Receiver: {:?}",
559            resp.status(),
560            tr.delegate_action.sender_id,
561            tr.delegate_action.receiver_id
562        );
563        Ok(resp)
564    }
565}