Skip to main content

near_api/common/
send.rs

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