near_client/
client.rs

1use crate::{
2    components::{
3        CallResult, TransactionInfo, ViewAccessKey, ViewAccessKeyList, ViewAccessKeyListResult,
4        ViewAccessKeyResult, ViewResult, ViewStateResult,
5    },
6    near_primitives_light::{
7        transaction::{
8            Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction,
9            DeployContractAction, FunctionCallAction, TransferAction,
10        },
11        types::Finality,
12        views::{
13            AccessKeyListView, AccessKeyView, BlockView, ExecutionOutcomeWithIdView,
14            FinalExecutionOutcomeView, FinalExecutionStatus, StatusResponse,
15        },
16    },
17    prelude::{transaction_errors::TxExecutionErrorContainer, InvalidTxError, TxExecutionError},
18    rpc::{client::RpcClient, CauseKind, Error as RpcError, NearError, NearErrorVariant},
19    utils::{extract_logs, serialize_arguments, serialize_transaction},
20    Error, Result, ViewAccessKeyCall,
21};
22use near_primitives_core::{
23    account::{id::AccountId, AccessKey, AccessKeyPermission, Account},
24    hash::CryptoHash,
25    types::{Balance, Gas, Nonce},
26};
27use std::{
28    ops::{Deref, DerefMut},
29    sync::atomic::{AtomicU64, Ordering},
30};
31
32use crate::crypto::prelude::*;
33use base64::prelude::*;
34use serde::de::DeserializeOwned;
35use serde_json::{json, Value};
36use url::Url;
37
38type AtomicNonce = AtomicU64;
39
40/// Used for signing a transactions
41pub struct Signer {
42    keypair: Keypair,
43    account_id: AccountId,
44    nonce: AtomicNonce,
45}
46
47impl Signer {
48    /// Creates a [`Signer`] from [`str`]
49    #[allow(clippy::result_large_err)]
50    pub fn from_secret_str(secret_key: &str, account_id: AccountId, nonce: Nonce) -> Result<Self> {
51        Ok(Self {
52            keypair: Keypair::from_expanded_secret(secret_key).map_err(Error::CreateSigner)?,
53            account_id,
54            nonce: AtomicU64::new(nonce),
55        })
56    }
57
58    /// Creates a [`Signer`] from [`Ed25519SecretKey`]
59    pub fn from_secret(secret_key: Ed25519SecretKey, account_id: AccountId, nonce: Nonce) -> Self {
60        Self {
61            keypair: Keypair::new(secret_key),
62            account_id,
63            nonce: AtomicU64::new(nonce),
64        }
65    }
66
67    /// Sign a transaction
68    ///
69    /// Arguments
70    ///
71    /// - data - Serialized transaction with a [Borsh](https://borsh.io/)
72    pub fn sign(&self, data: &[u8]) -> Ed25519Signature {
73        self.keypair.sign(data)
74    }
75
76    /// Returns the [public key](Ed25519PublicKey) of a [`Signer`]
77    pub fn public_key(&self) -> &Ed25519PublicKey {
78        self.keypair.public_key()
79    }
80
81    /// Returns the [secret key](Ed25519SecretKey) of a [`Signer`]
82    pub fn secret_key(&self) -> &Ed25519SecretKey {
83        self.keypair.secret_key()
84    }
85
86    /// Returns an [account](AccountId) of a [`Signer`]
87    pub fn account(&self) -> &AccountId {
88        &self.account_id
89    }
90
91    /// Returns the key nonce
92    pub fn nonce(&self) -> Nonce {
93        self.nonce.load(Ordering::Relaxed)
94    }
95
96    /// Update the key nonce
97    pub fn update_nonce(&self, nonce: Nonce) {
98        self.nonce.store(nonce, Ordering::Relaxed);
99    }
100
101    /// Increment the key nonce.
102    /// Function is thread safe
103    pub fn increment_nonce(&self, value: u64) {
104        self.nonce.fetch_add(value, Ordering::AcqRel);
105    }
106}
107
108/// Near RPC client
109#[derive(Clone)]
110pub struct NearClient {
111    pub(crate) rpc_client: RpcClient,
112}
113
114impl NearClient {
115    /// Creates a new client
116    ///
117    /// ## Arguments
118    ///
119    /// - url - A RPC Endpoint [Url](https://docs.near.org/api/rpc/providers)
120    #[allow(clippy::result_large_err)]
121    pub fn new(url: Url) -> Result<Self> {
122        Ok(Self {
123            rpc_client: RpcClient::new(url).map_err(Error::CreateClient)?,
124        })
125    }
126
127    /// Queries network and returns block for given height or hash
128    pub async fn block(&self, finality: Finality) -> Result<CryptoHash> {
129        self.rpc_client
130            .request("block", Some(json!({ "finality": finality })))
131            .await
132            .map_err(Error::BlockCall)
133            .and_then(|block_res| {
134                serde_json::from_value::<BlockView>(block_res).map_err(Error::DeserializeBlock)
135            })
136            .map(|block_view| block_view.header.hash)
137    }
138
139    /// Allows you to call a contract method as a view function.
140    ///
141    /// Arguments
142    ///
143    /// - contract_id - The [`AccountId`] where smart contract is located
144    /// - finality - [`Finality`]
145    /// - method - Function that is declared in a smart contract
146    /// - args - Function arguments, could be empty
147    pub async fn view<'a, T: DeserializeOwned>(
148        &'a self,
149        contract_id: &'a AccountId,
150        finality: Finality,
151        method: &'static str,
152        args: Option<Value>,
153    ) -> Result<ViewOutput<T>> {
154        let args = BASE64_STANDARD_NO_PAD.encode(serialize_arguments(args)?);
155        self.rpc_client
156            .request(
157                "query",
158                Some(json!({
159                    "request_type": "call_function",
160                    "finality": finality,
161                    "account_id": contract_id,
162                    "method_name": method,
163                    "args_base64": args
164                })),
165            )
166            .await
167            .map_err(Error::ViewCall)
168            .and_then(|it| {
169                serde_json::from_value::<ViewResult>(it).map_err(Error::DeserializeViewCall)
170            })
171            .and_then(|view_res| match view_res.result {
172                CallResult::Ok(data) => Ok(ViewOutput {
173                    logs: view_res.logs,
174                    data: serde_json::from_slice(&data).map_err(Error::DeserializeResponseView)?,
175                }),
176                CallResult::Err(cause) => Err(Error::ViewCall(RpcError::NearProtocol(
177                    NearError::handler(cause),
178                ))),
179            })
180    }
181
182    /// Returns information about a single access key for given account
183    ///
184    /// Arguments
185    ///
186    /// - account_id - The user [`AccountId`] in a Near network
187    /// - public_key - The user [`Ed25519PublicKey`] in a Near network
188    pub async fn view_access_key(
189        &self,
190        account_id: &AccountId,
191        public_key: &Ed25519PublicKey,
192        finality: Finality,
193    ) -> Result<AccessKeyView> {
194        self.rpc_client
195            .request(
196                "query",
197                Some(json!({
198                    "request_type": "view_access_key",
199                    "finality": finality,
200                    "account_id": account_id,
201                    "public_key": public_key,
202                })),
203            )
204            .await
205            .map_err(|err| Error::ViewAccessKeyCall(ViewAccessKeyCall::Rpc(err)))
206            .and_then(|it| {
207                serde_json::from_value::<ViewAccessKey>(it)
208                    .map_err(Error::DeserializeAccessKeyViewCall)
209            })
210            .and_then(|view_access_key| match view_access_key.result {
211                ViewAccessKeyResult::Ok(access_key_view) => Ok(access_key_view),
212                ViewAccessKeyResult::Err { error, logs } => {
213                    Err(Error::ViewAccessKeyCall(ViewAccessKeyCall::ParseError {
214                        error,
215                        logs,
216                    }))
217                }
218            })
219    }
220
221    /// Returns list of all access keys for the given account
222    ///
223    /// Arguments
224    /// - account_id - The user [`AccountId`] in a Near network
225    pub async fn view_access_key_list(
226        &self,
227        account_id: &AccountId,
228        finality: Finality,
229    ) -> Result<AccessKeyListView> {
230        self.rpc_client
231            .request(
232                "query",
233                Some(json!({
234                    "request_type": "view_access_key_list",
235                    "finality": finality,
236                    "account_id": account_id
237                })),
238            )
239            .await
240            .map_err(|err| Error::ViewAccessKeyListCall(ViewAccessKeyCall::Rpc(err)))
241            .and_then(|it| {
242                serde_json::from_value::<ViewAccessKeyList>(it)
243                    .map_err(Error::DeserializeAccessKeyListViewCall)
244            })
245            .and_then(|view_access_key_list| match view_access_key_list.result {
246                ViewAccessKeyListResult::Ok(access_key_list_view) => Ok(access_key_list_view),
247                ViewAccessKeyListResult::Err { error, logs } => Err(Error::ViewAccessKeyListCall(
248                    ViewAccessKeyCall::ParseError { error, logs },
249                )),
250            })
251    }
252
253    /// Returns information regarding contract state
254    /// in a key-value sequence representation
255    ///
256    /// Arguments
257    ///
258    /// - account_id - The contract [`AccountId`] in a Near network
259    pub async fn view_contract_state(&self, account_id: &AccountId) -> Result<ViewStateResult> {
260        self.rpc_client
261            .request(
262                "query",
263                Some(json!({
264                    "request_type": "view_state",
265                    "finality": Finality::Final,
266                    "account_id": account_id,
267                    "prefix_base64": ""
268                })),
269            )
270            .await
271            .map_err(Error::ViewCall)
272            .and_then(|it| {
273                serde_json::from_value::<ViewStateResult>(it).map_err(Error::DeserializeViewCall)
274            })
275    }
276
277    /// Returns general status of a given node
278    /// (sync status, nearcore node version, protocol version, etc),
279    /// and the current set of validators.
280    pub async fn network_status(&self) -> Result<StatusResponse> {
281        self.rpc_client
282            .request("status", None)
283            .await
284            .map_err(Error::RpcError)
285            .and_then(|it| {
286                serde_json::from_value::<StatusResponse>(it).map_err(Error::DeserializeResponseView)
287            })
288    }
289
290    /// Queries status of a transaction by hash,
291    /// returning the final transaction result and details of all receipts.
292    ///
293    /// Arguments
294    ///
295    /// - transaction_id - Transaction [`CryptoHash`]
296    /// - signer - [`Signer`] that contain information regarding user [`Keypair`]
297    ///
298    /// Return
299    ///
300    /// If a transaction still processing will be returned an error [`Error::ViewTransaction`],
301    /// in this case can try to execute [`view_transaction`](NearClient::view_transaction()) one more time, or a several times.
302    /// If an error differs from [`Error::ViewTransaction`] that something goes totally wrong and
303    /// you should stop to try executing [`view_transaction`](NearClient::view_transaction()) with the same arguments/signer
304    pub async fn view_transaction<'a>(
305        &'a self,
306        transaction_id: &'a CryptoHash,
307        signer: &'a Signer,
308    ) -> Result<Output> {
309        let params = Value::Array(vec![
310            serde_json::to_value(transaction_id)
311                .map_err(|err| Error::SerializeTxViewArg("transaction_id", err))?,
312            serde_json::to_value(signer.account())
313                .map_err(|err| Error::SerializeTxViewArg("signer_acc_id", err))?,
314        ]);
315
316        let execution_outcome = self
317            .rpc_client
318            .request("EXPERIMENTAL_tx_status", Some(params))
319            .await
320            .map_err(Error::ViewTransaction)
321            .and_then(|execution_outcome| {
322                serde_json::from_value::<FinalExecutionOutcomeView>(execution_outcome)
323                    .map_err(Error::DeserializeExecutionOutcome)
324            })?;
325
326        proceed_outcome(signer, execution_outcome)
327    }
328
329    /// Returns basic account information.
330    /// ## Arguments
331    ///
332    /// - `account_id` - The account ID [`AccountId`] for which to retrieve information.
333    ///
334    /// ## Returns
335    ///
336    /// Returns a struct [`Account`] containing basic information about the specified Near account.
337    pub async fn view_account(&self, account_id: &AccountId) -> Result<Account> {
338        self.rpc_client
339            .request(
340                "query",
341                Some(json!({
342                    "request_type": "view_account",
343                    "finality": Finality::Final,
344                    "account_id": account_id,
345                })),
346            )
347            .await
348            .map_err(Error::ViewCall)
349            .and_then(|it| {
350                serde_json::from_value::<Account>(it).map_err(Error::DeserializeViewCall)
351            })
352    }
353
354    /// Creates new access key on the specified account
355    ///
356    /// Arguments
357    /// - signer - Transaction [`Signer`]
358    /// - account_id - The user [`AccountId`] in a Near network
359    /// - new_account_pk - The new [`Ed25519PublicKey`]
360    /// - permission - Granted permissions level for the new access key
361    pub fn add_access_key<'a>(
362        &'a self,
363        signer: &'a Signer,
364        account_id: &'a AccountId,
365        new_account_pk: Ed25519PublicKey,
366        permission: AccessKeyPermission,
367    ) -> FunctionCall {
368        let info = TransactionInfo::new(self, signer, account_id);
369        let actions = vec![AddKeyAction {
370            public_key: new_account_pk,
371            access_key: AccessKey {
372                nonce: rand::random::<u64>(),
373                permission,
374            },
375        }
376        .into()];
377        FunctionCall::new(info, actions)
378    }
379
380    /// Deletes an access key on the specified account
381    ///
382    /// Arguments
383    /// - signer - Transaction [`Signer`]
384    /// - account_id - The user [`AccountId`] in a Near network
385    /// - public_key - The [`Ed25519PublicKey`] to be deleted from users access keys
386    pub fn delete_access_key<'a>(
387        &'a self,
388        signer: &'a Signer,
389        account_id: &'a AccountId,
390        public_key: Ed25519PublicKey,
391    ) -> FunctionCall {
392        let info = TransactionInfo::new(self, signer, account_id);
393        let actions = vec![DeleteKeyAction { public_key }.into()];
394        FunctionCall::new(info, actions)
395    }
396
397    /// Execute a transaction with a function call to the smart contract
398    ///
399    /// Arguments
400    ///
401    /// - signer - Transaction [`Signer`]
402    /// - contract_id - The [`AccountId`] where smart contract is located
403    /// - method - Function that is declared in a smart contract (Arguments fir function call provided later in a [`FunctionCallBuilder`])
404    pub fn function_call<'a>(
405        &'a self,
406        signer: &'a Signer,
407        contract_id: &'a AccountId,
408        method: &'static str,
409    ) -> FunctionCallBuilder {
410        let transaction_info = TransactionInfo::new(self, signer, contract_id);
411        FunctionCallBuilder::new(transaction_info, method)
412    }
413
414    /// Deploys contract code to the chain
415    ///
416    /// ## Arguments
417    ///
418    /// - signer - Transaction [`Signer`]
419    /// - contract_id - The [`AccountId`] where smart contract is located
420    /// - wasm - Actually a compiled code
421    pub fn deploy_contract<'a>(
422        &'a self,
423        signer: &'a Signer,
424        contract_id: &'a AccountId,
425        wasm: Vec<u8>,
426    ) -> FunctionCall {
427        FunctionCall::new(
428            TransactionInfo::new(self, signer, contract_id),
429            vec![Action::from(DeployContractAction { code: wasm })],
430        )
431    }
432
433    /// Creates account
434    ///
435    /// ## Arguments
436    ///
437    /// - signer - Transaction [`Signer`]
438    /// - new_account_id - The new [`AccountId`]
439    /// - new_account_pk - The new [`Ed25519PublicKey`]
440    /// - amount - Initial balance of that account, could be zero
441    pub fn create_account<'a>(
442        &'a self,
443        signer: &'a Signer,
444        new_account_id: &'a AccountId,
445        new_account_pk: Ed25519PublicKey,
446        amount: Balance,
447    ) -> FunctionCall {
448        let info = TransactionInfo::new(self, signer, new_account_id);
449        let actions = vec![
450            CreateAccountAction {}.into(),
451            AddKeyAction {
452                public_key: new_account_pk,
453                access_key: AccessKey {
454                    nonce: 0,
455                    permission: AccessKeyPermission::FullAccess,
456                },
457            }
458            .into(),
459            TransferAction { deposit: amount }.into(),
460        ];
461
462        FunctionCall::new(info, actions)
463    }
464
465    /// Deletes account
466    ///
467    /// ## Arguments
468    ///
469    /// - signer - Transaction [`Signer`]
470    /// - account_id - The [`AccountId`] that we own and want to delete
471    /// - beneficiary_acc_id - Where to return a founds from the deleted account
472    pub fn delete_account<'a>(
473        &'a self,
474        signer: &'a Signer,
475        account_id: &'a AccountId,
476        beneficiary_acc_id: &'a AccountId,
477    ) -> FunctionCall {
478        let info = TransactionInfo::new(self, signer, account_id);
479        let actions = vec![DeleteAccountAction {
480            beneficiary_id: beneficiary_acc_id.clone(),
481        }
482        .into()];
483
484        FunctionCall::new(info, actions)
485    }
486
487    /// Sends Near tokens from one account to another.
488    ///
489    /// ## Arguments
490    ///
491    /// - `signer` - The account ID of the sender and transaction [`Signer`]
492    /// - `receiver_id` - The account ID of the receiver.
493    /// - `deposit` - The amount of Near tokens to send.
494    ///
495    /// ## Errors
496    ///
497    /// Possible error conditions include insufficient funds, invalid account IDs, or
498    /// other issues preventing the successful execution of the transaction.
499    pub fn send<'a>(
500        &'a self,
501        signer: &'a Signer,
502        receiver_id: &'a AccountId,
503        deposit: Balance,
504    ) -> FunctionCall {
505        let info = TransactionInfo::new(self, signer, receiver_id);
506        let actions = vec![TransferAction { deposit }.into()];
507
508        FunctionCall::new(info, actions)
509    }
510}
511
512/// Output of a view contract call
513/// Contains the return data and logs
514#[derive(Debug)]
515pub struct ViewOutput<T: DeserializeOwned> {
516    logs: Vec<String>,
517    data: T,
518}
519
520impl<T: DeserializeOwned> ViewOutput<T> {
521    /// Logs from view call
522    pub fn logs(&self) -> Vec<String> {
523        self.logs.clone()
524    }
525
526    /// Return a view call result
527    pub fn data(self) -> T {
528        self.data
529    }
530}
531
532impl<T: DeserializeOwned> Deref for ViewOutput<T> {
533    type Target = T;
534
535    fn deref(&self) -> &Self::Target {
536        &self.data
537    }
538}
539
540impl<T: DeserializeOwned> DerefMut for ViewOutput<T> {
541    fn deref_mut(&mut self) -> &mut Self::Target {
542        &mut self.data
543    }
544}
545
546/// Function call output.
547#[derive(Debug)]
548pub struct Output {
549    transaction: ExecutionOutcomeWithIdView,
550    logs: Vec<String>,
551    data: Vec<u8>,
552}
553
554impl Output {
555    #[allow(clippy::result_large_err)]
556    /// If function don't return anything it will return [`Error::DeserializeTransactionOutput`]
557    /// Or if you miss matching a return type
558    pub fn output<T: DeserializeOwned>(&self) -> Result<T> {
559        serde_json::from_slice::<T>(&self.data).map_err(Error::DeserializeTransactionOutput)
560    }
561
562    #[allow(clippy::misnamed_getters)]
563    /// Returns a transaction id
564    pub const fn id(&self) -> CryptoHash {
565        self.transaction.id
566    }
567
568    /// Amount of gas that was burnt during transaction execution
569    pub const fn gas_burnt(&self) -> Gas {
570        self.transaction.outcome.gas_burnt
571    }
572
573    /// Logs that smart contract produced
574    pub fn logs(&self) -> Vec<String> {
575        self.logs.clone()
576    }
577}
578
579#[doc(hidden)]
580pub struct FunctionCallBuilder<'a> {
581    info: TransactionInfo<'a>,
582    deposit: Balance,
583    gas: Gas,
584    args: Option<Value>,
585    retry: Retry,
586    method_name: &'a str,
587}
588
589impl<'a> FunctionCallBuilder<'a> {
590    fn new(info: TransactionInfo<'a>, method_name: &'a str) -> Self {
591        Self {
592            info,
593            method_name,
594            gas: Default::default(),
595            args: Default::default(),
596            deposit: Default::default(),
597            retry: Default::default(),
598        }
599    }
600
601    pub const fn deposit(mut self, deposit: Balance) -> Self {
602        self.deposit = deposit;
603        self
604    }
605
606    /// Amount of gas that will be hold for function execution
607    pub const fn gas(mut self, gas: Gas) -> Self {
608        self.gas = gas;
609        self
610    }
611
612    pub fn args(mut self, args: Value) -> Self {
613        self.args = Some(args);
614        self
615    }
616
617    #[allow(clippy::result_large_err)]
618    pub fn build(self) -> Result<FunctionCall<'a>> {
619        let action = Action::from(FunctionCallAction {
620            method_name: self.method_name.to_string(),
621            args: serialize_arguments(self.args)?,
622            gas: self.gas,
623            deposit: self.deposit,
624        });
625
626        Ok(FunctionCall {
627            info: self.info,
628            actions: vec![action],
629            retry: self.retry,
630        })
631    }
632
633    /// Set [`Retry`] strategy
634    pub const fn retry(mut self, retry: Retry) -> Self {
635        self.retry = retry;
636        self
637    }
638
639    /// Sends a transaction and waits until transaction is fully complete. (Has a 10 second timeout)
640    /// Also, possible that an output data will be empty if the transaction is still executing
641    ///
642    /// ## Arguments
643    ///
644    /// - **finality** - Block [`Finality`]
645    pub async fn commit(self, finality: Finality) -> Result<Output> {
646        let call = self.build()?;
647        call.commit(finality).await
648    }
649
650    /// Sends a transaction and immediately returns transaction hash.
651    ///
652    /// ## Arguments
653    ///
654    /// - **finality** - Block [`Finality`]
655    pub async fn commit_async(self, finality: Finality) -> Result<CryptoHash> {
656        let call = self.build()?;
657        call.commit_async(finality).await
658    }
659}
660
661/// Tells the **client** to execute transaction one more time if it's failed.
662/// > It's only happens during **InvalidNonce** error.
663///
664/// - NONE - default value, transaction executes once
665/// - ONCE - retry once
666/// - TWICE - retry two times
667///
668#[repr(usize)]
669#[derive(Debug, Default, Clone, Copy)]
670pub enum Retry {
671    /// Executes once, basically no retry
672    #[default]
673    NONE = 1,
674    /// If **InvalidNonce** error received try to execute one more time
675    ONCE = 2,
676    /// If **InvalidNonce** error received try to execute two times
677    TWICE = 3,
678}
679
680#[doc(hidden)]
681pub struct FunctionCall<'a> {
682    info: TransactionInfo<'a>,
683    actions: Vec<Action>,
684    retry: Retry,
685}
686
687impl<'a> FunctionCall<'a> {
688    /// Sends a transaction and waits until transaction is fully complete. (Has a 10 second timeout)
689    /// Also, possible that an output data will be empty if the transaction is still executing
690    ///
691    /// ## Arguments
692    ///
693    /// - **finality** - Block [`Finality`]
694    pub async fn commit(self, finality: Finality) -> Result<Output> {
695        let execution_outcome =
696            commit_with_retry(&self, finality, "broadcast_tx_commit", self.retry)
697                .await
698                .and_then(|execution_outcome| {
699                    serde_json::from_value::<FinalExecutionOutcomeView>(execution_outcome)
700                        .map_err(Error::DeserializeExecutionOutcome)
701                })?;
702
703        proceed_outcome(self.info.signer(), execution_outcome)
704    }
705
706    /// Sends a transaction and immediately returns transaction hash.
707    ///
708    /// ## Arguments
709    ///
710    /// - **finality** - Block [`Finality`]
711    pub async fn commit_async(self, finality: Finality) -> Result<CryptoHash> {
712        commit_with_retry(&self, finality, "broadcast_tx_async", self.retry)
713            .await
714            .and_then(|id| {
715                serde_json::from_value::<CryptoHash>(id).map_err(Error::DeserializeTransactionId)
716            })
717    }
718
719    /// Set [`Retry`] strategy
720    pub const fn retry(mut self, retry: Retry) -> Self {
721        self.retry = retry;
722        self
723    }
724
725    const fn info(&self) -> &TransactionInfo {
726        &self.info
727    }
728
729    fn actions(&self) -> &[Action] {
730        &self.actions
731    }
732
733    const fn new(info: TransactionInfo<'a>, actions: Vec<Action>) -> Self {
734        Self {
735            info,
736            actions,
737            retry: Retry::NONE,
738        }
739    }
740}
741
742async fn commit_with_retry<'a>(
743    call: &FunctionCall<'a>,
744    finality: Finality,
745    transaction_type: &'static str,
746    retry: Retry,
747) -> Result<Value> {
748    let mut execution_count = 0;
749    let retry_count = retry as usize;
750
751    loop {
752        execution_count += 1;
753
754        let transaction = BASE64_STANDARD_NO_PAD.encode(
755            serialize_transaction(call.info(), call.actions().to_vec(), finality.clone()).await?,
756        );
757
758        let resp = call
759            .info()
760            .rpc()
761            .request(transaction_type, Some(json!(vec![transaction])))
762            .await
763            .map_err(transaction_error);
764
765        if let Err(Error::TxExecution(
766            TxExecutionError::InvalidTxError(InvalidTxError::InvalidNonce { ak_nonce, .. }),
767            ..,
768        )) = resp
769        {
770            if retry_count > 1 && execution_count <= retry_count {
771                call.info().signer().update_nonce(ak_nonce + 1);
772                continue;
773            }
774        }
775
776        return resp;
777    }
778}
779
780// Try to parse the error that may be located in the node response
781fn transaction_error(err: RpcError) -> Error {
782    let RpcError::NearProtocol(near_err) = &err else {
783        return Error::RpcError(err);
784    };
785
786    let (NearErrorVariant::RequestValidation(CauseKind::ParseError(cause))
787    | NearErrorVariant::Handler(CauseKind::InvalidTransaction(cause))) = near_err.error()
788    else {
789        return Error::RpcError(err);
790    };
791
792    serde_json::from_value::<TxExecutionErrorContainer>(cause.to_owned())
793        .or_else(|err| {
794            near_err.data().ok_or(err).and_then(|cause| {
795                serde_json::from_value::<TxExecutionErrorContainer>(cause.to_owned())
796            })
797        })
798        .map(|exec_err| Error::TxExecution(exec_err.tx_execution_error, Default::default()))
799        .unwrap_or(Error::RpcError(err))
800}
801
802#[allow(clippy::result_large_err)]
803pub(crate) fn proceed_outcome(
804    signer: &Signer,
805    execution_outcome: FinalExecutionOutcomeView,
806) -> Result<Output> {
807    signer.update_nonce(execution_outcome.transaction.nonce);
808    let transaction = execution_outcome.transaction_outcome;
809    let logs = extract_logs(execution_outcome.receipts_outcome);
810
811    match execution_outcome.status {
812        FinalExecutionStatus::Failure(err) => Err(Error::TxExecution(err, Box::new(logs))),
813        FinalExecutionStatus::SuccessValue(data) => Ok(Output {
814            transaction,
815            logs,
816            data,
817        }),
818        FinalExecutionStatus::NotStarted => Err(Error::TxNotStarted(Box::new(logs))),
819        FinalExecutionStatus::Started => Ok(Output {
820            transaction,
821            logs,
822            data: vec![],
823        }),
824    }
825}