near_fetch/
ops.rs

1//! All operation types that are generated/used when commiting transactions to the network.
2
3use std::fmt;
4use std::task::Poll;
5use std::time::Duration;
6
7use near_account_id::AccountId;
8use near_crypto::PublicKey;
9use near_gas::NearGas;
10use near_jsonrpc_client::errors::{JsonRpcError, JsonRpcServerError};
11use near_jsonrpc_primitives::types::transactions::RpcTransactionError;
12use near_primitives::account::AccessKey;
13use near_primitives::borsh;
14use near_primitives::hash::CryptoHash;
15use near_primitives::transaction::{
16    Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction,
17    DeployContractAction, FunctionCallAction, StakeAction, TransferAction,
18};
19use near_primitives::views::{FinalExecutionOutcomeView, TxExecutionStatus};
20use near_token::NearToken;
21use tokio_retry::strategy::{jitter, ExponentialBackoff};
22use tokio_retry::Retry;
23
24use crate::query::BoxFuture;
25use crate::result::ExecutionFinalResult;
26use crate::signer::SignerExt;
27use crate::{Client, Error, Result};
28
29/// Maximum amount of gas that can be used in a single transaction.
30pub const MAX_GAS: NearGas = NearGas::from_tgas(300);
31
32/// Default amount of gas to be used when calling into a function on a contract.
33/// This is set to 10 TGas as a default for convenience.
34pub const DEFAULT_CALL_FN_GAS: NearGas = NearGas::from_tgas(10);
35
36/// Default amount of deposit to be used when calling into a function on a contract.
37/// This is set to 0 NEAR as a default for convenience. Note, that some contracts
38/// will require 1 yoctoNEAR to be deposited in order to perform a function.
39pub const DEFAULT_CALL_DEPOSIT: NearToken = NearToken::from_near(0);
40
41/// A set of arguments we can provide to a transaction, containing
42/// the function name, arguments, the amount of gas to use and deposit.
43#[derive(Debug)]
44pub struct Function {
45    pub(crate) name: String,
46    pub(crate) args: Result<Vec<u8>>,
47    pub(crate) deposit: NearToken,
48    pub(crate) gas: NearGas,
49}
50
51impl Function {
52    /// Initialize a new instance of [`Function`], tied to a specific function on a
53    /// contract that lives directly on a contract we've specified in [`Transaction`].
54    pub fn new(name: &str) -> Self {
55        Self {
56            name: name.into(),
57            args: Ok(vec![]),
58            deposit: DEFAULT_CALL_DEPOSIT,
59            gas: DEFAULT_CALL_FN_GAS,
60        }
61    }
62
63    /// Provide the arguments for the call. These args are serialized bytes from either
64    /// a JSON or Borsh serializable set of arguments. To use the more specific versions
65    /// with better quality of life, use `args_json` or `args_borsh`.
66    pub fn args(mut self, args: Vec<u8>) -> Self {
67        if self.args.is_err() {
68            return self;
69        }
70        self.args = Ok(args);
71        self
72    }
73
74    /// Similar to `args`, specify an argument that is JSON serializable and can be
75    /// accepted by the equivalent contract. Recommend to use something like
76    /// `serde_json::json!` macro to easily serialize the arguments.
77    pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
78        match serde_json::to_vec(&args) {
79            Ok(args) => self.args = Ok(args),
80            Err(e) => self.args = Err(Error::Serialization(e)),
81        }
82        self
83    }
84
85    /// Similar to `args`, specify an argument that is borsh serializable and can be
86    /// accepted by the equivalent contract.
87    pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
88        match borsh::to_vec(&args) {
89            Ok(args) => self.args = Ok(args),
90            Err(e) => self.args = Err(Error::Io(e)),
91        }
92        self
93    }
94
95    /// Specify the amount of tokens to be deposited where `deposit` is the amount of
96    /// tokens in yocto near.
97    pub fn deposit(mut self, deposit: NearToken) -> Self {
98        self.deposit = deposit;
99        self
100    }
101
102    /// Specify the amount of gas to be used.
103    pub fn gas(mut self, gas: NearGas) -> Self {
104        self.gas = gas;
105        self
106    }
107
108    /// Use the maximum amount of gas possible to perform this function call into the contract.
109    pub fn max_gas(self) -> Self {
110        self.gas(MAX_GAS)
111    }
112
113    pub(crate) fn into_action(self) -> Result<FunctionCallAction> {
114        Ok(FunctionCallAction {
115            args: self.args?,
116            method_name: self.name,
117            gas: self.gas.as_gas(),
118            deposit: self.deposit.as_yoctonear(),
119        })
120    }
121}
122
123pub struct FunctionCallTransaction<'a> {
124    pub(crate) client: Client,
125    pub(crate) signer: &'a dyn SignerExt,
126    pub(crate) receiver_id: AccountId,
127    pub(crate) function: Function,
128    pub(crate) retry_strategy: Option<Box<dyn Iterator<Item = Duration> + Send + Sync>>,
129    pub(crate) wait_until: TxExecutionStatus,
130}
131
132impl FunctionCallTransaction<'_> {
133    /// Provide the arguments for the call. These args are serialized bytes from either
134    /// a JSON or Borsh serializable set of arguments. To use the more specific versions
135    /// with better quality of life, use `args_json` or `args_borsh`.
136    pub fn args(mut self, args: Vec<u8>) -> Self {
137        self.function = self.function.args(args);
138        self
139    }
140
141    /// Similar to `args`, specify an argument that is JSON serializable and can be
142    /// accepted by the equivalent contract. Recommend to use something like
143    /// `serde_json::json!` macro to easily serialize the arguments.
144    pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
145        self.function = self.function.args_json(args);
146        self
147    }
148
149    /// Similar to `args`, specify an argument that is borsh serializable and can be
150    /// accepted by the equivalent contract.
151    pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
152        self.function = self.function.args_borsh(args);
153        self
154    }
155
156    /// Specify the amount of tokens to be deposited where `deposit` is the amount of
157    /// tokens in yocto near.
158    pub fn deposit(mut self, deposit: NearToken) -> Self {
159        self.function = self.function.deposit(deposit);
160        self
161    }
162
163    /// Specify the amount of gas to be used.
164    pub fn gas(mut self, gas: NearGas) -> Self {
165        self.function = self.function.gas(gas);
166        self
167    }
168
169    /// Use the maximum amount of gas possible to perform this function call into the contract.
170    pub fn max_gas(self) -> Self {
171        self.gas(MAX_GAS)
172    }
173}
174
175impl FunctionCallTransaction<'_> {
176    /// Process the transaction, and return the result of the execution.
177    pub async fn transact(self) -> Result<ExecutionFinalResult> {
178        RetryableTransaction {
179            client: self.client.clone(),
180            signer: self.signer,
181            receiver_id: self.receiver_id,
182            actions: self
183                .function
184                .into_action()
185                .map(|action| vec![action.into()]),
186            strategy: self.retry_strategy,
187            wait_until: self.wait_until,
188        }
189        .await
190        .map(ExecutionFinalResult::from_view)
191    }
192
193    /// Send the transaction to the network to be processed. This will be done asynchronously
194    /// without waiting for the transaction to complete. This returns us a [`TransactionStatus`]
195    /// for which we can call into [`status`] and/or `.await` to retrieve info about whether
196    /// the transaction has been completed or not. Note that `.await` will wait till completion
197    /// of the transaction.
198    pub async fn transact_async(self) -> Result<AsyncTransactionStatus> {
199        let hash = self
200            .client
201            .send_tx_async(
202                self.signer,
203                &self.receiver_id,
204                vec![self.function.into_action()?.into()],
205            )
206            .await?;
207
208        Ok(AsyncTransactionStatus::new(
209            self.client,
210            self.receiver_id,
211            hash,
212        ))
213    }
214
215    /// Retry this transactions if it fails. This will retry the transaction with exponential
216    /// backoff. This cannot be used in combination with
217    pub fn retry_exponential(self, base_millis: u64, max_retries: usize) -> Self {
218        self.retry(
219            ExponentialBackoff::from_millis(base_millis)
220                .map(jitter)
221                .take(max_retries),
222        )
223    }
224
225    /// Retry this transactions if it fails. This will retry the transaction with the provided
226    /// retry strategy.
227    pub fn retry(
228        mut self,
229        strategy: impl Iterator<Item = Duration> + Send + Sync + 'static,
230    ) -> Self {
231        self.retry_strategy = Some(Box::new(strategy));
232        self
233    }
234
235    /// Specifies the status to wait until the transaction reaches in the network. The default
236    /// value is [`TxExecutionStatus::ExecutedOptimistic`] if not specified by this function.
237    pub fn wait_until(mut self, wait_until: TxExecutionStatus) -> Self {
238        self.wait_until = wait_until;
239        self
240    }
241}
242
243/// A builder-like object that will allow specifying various actions to be performed
244/// in a single transaction. For details on each of the actions, find them in
245/// [NEAR transactions](https://docs.near.org/docs/concepts/transaction).
246///
247/// All actions are performed on the account specified by `receiver_id`.
248pub struct Transaction<'a> {
249    client: Client,
250    signer: &'a dyn SignerExt,
251    receiver_id: AccountId,
252    // Result used to defer errors in argument parsing to later when calling into transact
253    actions: Result<Vec<Action>>,
254    retry_strategy: Option<Box<dyn Iterator<Item = Duration> + Send + Sync>>,
255    wait_until: TxExecutionStatus,
256}
257
258impl<'a> Transaction<'a> {
259    pub(crate) fn new(client: &Client, signer: &'a dyn SignerExt, receiver_id: AccountId) -> Self {
260        Self {
261            client: client.clone(),
262            signer,
263            receiver_id,
264            actions: Ok(Vec::new()),
265            retry_strategy: None,
266            wait_until: TxExecutionStatus::default(),
267        }
268    }
269
270    /// Process the transaction, and return the result of the execution.
271    pub async fn transact(self) -> Result<FinalExecutionOutcomeView> {
272        RetryableTransaction {
273            client: self.client.clone(),
274            signer: self.signer,
275            receiver_id: self.receiver_id,
276            actions: self.actions,
277            strategy: self.retry_strategy,
278            wait_until: self.wait_until,
279        }
280        .await
281    }
282
283    /// Send the transaction to the network to be processed. This will be done asynchronously
284    /// without waiting for the transaction to complete.
285    pub async fn transact_async(self) -> Result<AsyncTransactionStatus> {
286        let hash = self
287            .client
288            .send_tx_async(self.signer, &self.receiver_id, self.actions?)
289            .await?;
290
291        Ok(AsyncTransactionStatus::new(
292            self.client,
293            self.receiver_id,
294            hash,
295        ))
296    }
297}
298
299impl Transaction<'_> {
300    /// Adds a key to the `receiver_id`'s account, where the public key can be used
301    /// later to delete the same key.
302    pub fn add_key(mut self, pk: PublicKey, ak: AccessKey) -> Self {
303        if let Ok(actions) = &mut self.actions {
304            actions.push(
305                AddKeyAction {
306                    public_key: pk,
307                    access_key: ak,
308                }
309                .into(),
310            );
311        }
312
313        self
314    }
315
316    /// Call into the `receiver_id`'s contract with the specific function arguments.
317    pub fn call(mut self, function: Function) -> Self {
318        let args = match function.args {
319            Ok(args) => args,
320            Err(err) => {
321                self.actions = Err(err);
322                return self;
323            }
324        };
325
326        if let Ok(actions) = &mut self.actions {
327            actions.push(Action::FunctionCall(Box::new(FunctionCallAction {
328                method_name: function.name.to_string(),
329                args,
330                deposit: function.deposit.as_yoctonear(),
331                gas: function.gas.as_gas(),
332            })));
333        }
334
335        self
336    }
337
338    /// Create a new account with the account id being `receiver_id`.
339    pub fn create_account(mut self) -> Self {
340        if let Ok(actions) = &mut self.actions {
341            actions.push(CreateAccountAction {}.into());
342        }
343        self
344    }
345
346    /// Deletes the `receiver_id`'s account. The beneficiary specified by
347    /// `beneficiary_id` will receive the funds of the account deleted.
348    pub fn delete_account(mut self, beneficiary_id: &AccountId) -> Self {
349        if let Ok(actions) = &mut self.actions {
350            actions.push(
351                DeleteAccountAction {
352                    beneficiary_id: beneficiary_id.clone(),
353                }
354                .into(),
355            );
356        }
357        self
358    }
359
360    /// Deletes a key from the `receiver_id`'s account, where the public key is
361    /// associated with the access key to be deleted.
362    pub fn delete_key(mut self, pk: PublicKey) -> Self {
363        if let Ok(actions) = &mut self.actions {
364            actions.push(DeleteKeyAction { public_key: pk }.into());
365        }
366        self
367    }
368
369    /// Deploy contract code or WASM bytes to the `receiver_id`'s account.
370    pub fn deploy(mut self, code: &[u8]) -> Self {
371        if let Ok(actions) = &mut self.actions {
372            actions.push(DeployContractAction { code: code.into() }.into());
373        }
374        self
375    }
376
377    /// An action which stakes the signer's tokens and setups a validator public key.
378    pub fn stake(mut self, stake: NearToken, pk: PublicKey) -> Self {
379        if let Ok(actions) = &mut self.actions {
380            actions.push(
381                StakeAction {
382                    stake: stake.as_yoctonear(),
383                    public_key: pk,
384                }
385                .into(),
386            );
387        }
388        self
389    }
390
391    /// Transfer `deposit` amount from `signer`'s account into `receiver_id`'s account.
392    pub fn transfer(mut self, deposit: NearToken) -> Self {
393        if let Ok(actions) = &mut self.actions {
394            actions.push(
395                TransferAction {
396                    deposit: deposit.as_yoctonear(),
397                }
398                .into(),
399            );
400        }
401        self
402    }
403
404    /// Retry this transactions if it fails. This will retry the transaction with exponential
405    /// backoff.
406    pub fn retry_exponential(self, base_millis: u64, max_retries: usize) -> Self {
407        self.retry(
408            ExponentialBackoff::from_millis(base_millis)
409                .map(jitter)
410                .take(max_retries),
411        )
412    }
413
414    /// Retry this transactions if it fails. This will retry the transaction with the provided
415    /// retry strategy.
416    pub fn retry(
417        mut self,
418        strategy: impl Iterator<Item = Duration> + Send + Sync + 'static,
419    ) -> Self {
420        self.retry_strategy = Some(Box::new(strategy));
421        self
422    }
423
424    /// Specifies the status to wait until the transaction reaches in the network. The default
425    /// value is [`TxExecutionStatus::ExecutedOptimistic`] if not specified by this function.
426    pub fn wait_until(mut self, wait_until: TxExecutionStatus) -> Self {
427        self.wait_until = wait_until;
428        self
429    }
430}
431
432pub struct RetryableTransaction<'a> {
433    pub(crate) client: Client,
434    pub(crate) signer: &'a dyn SignerExt,
435    pub(crate) receiver_id: AccountId,
436    pub(crate) actions: Result<Vec<Action>>,
437    pub(crate) strategy: Option<Box<dyn Iterator<Item = Duration> + Send + Sync>>,
438    pub(crate) wait_until: TxExecutionStatus,
439}
440
441impl RetryableTransaction<'_> {
442    /// Retry this transactions if it fails. This will retry the transaction with exponential
443    /// backoff.
444    pub fn retry_exponential(self, base_millis: u64, max_retries: usize) -> Self {
445        self.retry(
446            ExponentialBackoff::from_millis(base_millis)
447                .map(jitter)
448                .take(max_retries),
449        )
450    }
451
452    /// Retry this transactions if it fails. This will retry the transaction with the provided
453    /// retry strategy.
454    pub fn retry(
455        mut self,
456        strategy: impl Iterator<Item = Duration> + Send + Sync + 'static,
457    ) -> Self {
458        self.strategy = Some(Box::new(strategy));
459        self
460    }
461
462    /// Specifies the status to wait until the transaction reaches in the network. The default
463    /// value is [`TxExecutionStatus::ExecutedOptimistic`] if not specified by this function.
464    pub fn wait_until(mut self, wait_until: TxExecutionStatus) -> Self {
465        self.wait_until = wait_until;
466        self
467    }
468}
469
470impl<'a> std::future::IntoFuture for RetryableTransaction<'a> {
471    type Output = Result<FinalExecutionOutcomeView>;
472    type IntoFuture = BoxFuture<'a, Self::Output>;
473
474    fn into_future(self) -> Self::IntoFuture {
475        Box::pin(async move {
476            let actions = self.actions?;
477            let action = || async {
478                self.client
479                    .send_tx_once(
480                        self.signer,
481                        &self.receiver_id,
482                        actions.clone(),
483                        self.wait_until.clone(),
484                    )
485                    .await
486            };
487
488            if let Some(strategy) = self.strategy {
489                Retry::spawn(strategy, action).await
490            } else {
491                action().await
492            }
493        })
494    }
495}
496
497impl Client {
498    /// Start calling into a contract on a specific function. Returns a [`FunctionCallTransaction`]
499    /// object where we can use to add more parameters such as the arguments, deposit, and gas.
500    pub fn call<'a>(
501        &self,
502        signer: &'a dyn SignerExt,
503        contract_id: &AccountId,
504        function: &str,
505    ) -> FunctionCallTransaction<'a> {
506        FunctionCallTransaction {
507            client: self.clone(),
508            signer,
509            receiver_id: contract_id.clone(),
510            function: Function::new(function),
511            retry_strategy: None,
512            wait_until: TxExecutionStatus::default(),
513        }
514    }
515
516    /// Start a batch transaction. Returns a [`Transaction`] object that we can
517    /// use to add Actions to the batched transaction. Call `transact` to send
518    /// the batched transaction to the network.
519    pub fn batch<'a>(&self, signer: &'a dyn SignerExt, receiver_id: &AccountId) -> Transaction<'a> {
520        Transaction::new(self, signer, receiver_id.clone())
521    }
522}
523
524/// `TransactionStatus` object relating to an [`asynchronous transaction`] on the network.
525/// Used to query into the status of the Transaction for whether it has completed or not.
526///
527/// [`asynchronous transaction`]: https://docs.near.org/api/rpc/transactions#send-transaction-async
528#[derive(Clone)]
529#[must_use]
530pub struct AsyncTransactionStatus {
531    client: Client,
532    sender_id: AccountId,
533    hash: CryptoHash,
534}
535
536impl AsyncTransactionStatus {
537    pub(crate) fn new(client: Client, sender_id: AccountId, hash: CryptoHash) -> Self {
538        Self {
539            client,
540            sender_id,
541            hash,
542        }
543    }
544
545    /// Query the status of the transaction. This will return a [`TransactionStatus`]
546    /// object that we can use to query into the status of the transaction.
547    pub async fn status(&self) -> Result<Poll<ExecutionFinalResult>> {
548        let result = self
549            .client
550            .status_tx_async(&self.sender_id, self.hash, TxExecutionStatus::Included)
551            .await
552            .map(ExecutionFinalResult::from_view);
553
554        match result {
555            Ok(result) => Ok(Poll::Ready(result)),
556            Err(err) => match err {
557                Error::RpcTransactionError(err) => match &*err {
558                    JsonRpcError::ServerError(JsonRpcServerError::HandlerError(
559                        RpcTransactionError::UnknownTransaction { .. },
560                    )) => Ok(Poll::Pending),
561
562                    JsonRpcError::ServerError(JsonRpcServerError::HandlerError(
563                        RpcTransactionError::TimeoutError,
564                    )) => Ok(Poll::Pending),
565                    _ => Err(Error::RpcTransactionError(err)),
566                },
567                Error::RpcTransactionPending => Ok(Poll::Pending),
568                other => Err(other),
569            },
570        }
571    }
572
573    /// Wait until the completion of the transaction by polling [`AsyncTransactionStatus::status`].
574    pub(crate) async fn wait_default(self) -> Result<ExecutionFinalResult> {
575        self.wait(Duration::from_millis(300)).await
576    }
577
578    /// Wait until the transaction completes with a given time interval. This will poll the
579    /// [`AsyncTransactionStatus::status`] every interval until the transaction completes.
580    pub async fn wait(self, interval: Duration) -> Result<ExecutionFinalResult> {
581        loop {
582            match self.status().await? {
583                Poll::Ready(val) => break Ok(val),
584                Poll::Pending => (),
585            }
586
587            tokio::time::sleep(interval).await;
588        }
589    }
590
591    /// Waits until a sepcific transaction status is reached.
592    pub async fn wait_until(self, wait_until: TxExecutionStatus) -> Result<ExecutionFinalResult> {
593        self.client
594            .status_tx_async(&self.sender_id, self.hash, wait_until)
595            .await
596            .map(ExecutionFinalResult::from_view)
597    }
598
599    /// Get the [`AccountId`] of the account that initiated this transaction.
600    pub fn sender_id(&self) -> &AccountId {
601        &self.sender_id
602    }
603
604    /// Reference [`CryptoHash`] to the submitted transaction, pending completion.
605    pub fn hash(&self) -> &CryptoHash {
606        &self.hash
607    }
608}
609
610impl fmt::Debug for AsyncTransactionStatus {
611    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
612        f.debug_struct("TransactionStatus")
613            .field("sender_id", &self.sender_id)
614            .field("hash", &self.hash)
615            .finish()
616    }
617}
618
619impl std::future::IntoFuture for AsyncTransactionStatus {
620    type Output = Result<ExecutionFinalResult>;
621    type IntoFuture = BoxFuture<'static, Self::Output>;
622
623    fn into_future(self) -> Self::IntoFuture {
624        Box::pin(async { self.wait_default().await })
625    }
626}