1use 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
29pub const MAX_GAS: NearGas = NearGas::from_tgas(300);
31
32pub const DEFAULT_CALL_FN_GAS: NearGas = NearGas::from_tgas(10);
35
36pub const DEFAULT_CALL_DEPOSIT: NearToken = NearToken::from_near(0);
40
41#[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 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 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 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 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 pub fn deposit(mut self, deposit: NearToken) -> Self {
98 self.deposit = deposit;
99 self
100 }
101
102 pub fn gas(mut self, gas: NearGas) -> Self {
104 self.gas = gas;
105 self
106 }
107
108 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 pub fn args(mut self, args: Vec<u8>) -> Self {
137 self.function = self.function.args(args);
138 self
139 }
140
141 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 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 pub fn deposit(mut self, deposit: NearToken) -> Self {
159 self.function = self.function.deposit(deposit);
160 self
161 }
162
163 pub fn gas(mut self, gas: NearGas) -> Self {
165 self.function = self.function.gas(gas);
166 self
167 }
168
169 pub fn max_gas(self) -> Self {
171 self.gas(MAX_GAS)
172 }
173}
174
175impl FunctionCallTransaction<'_> {
176 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 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 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 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 pub fn wait_until(mut self, wait_until: TxExecutionStatus) -> Self {
238 self.wait_until = wait_until;
239 self
240 }
241}
242
243pub struct Transaction<'a> {
249 client: Client,
250 signer: &'a dyn SignerExt,
251 receiver_id: AccountId,
252 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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#[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 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 pub(crate) async fn wait_default(self) -> Result<ExecutionFinalResult> {
575 self.wait(Duration::from_millis(300)).await
576 }
577
578 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 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 pub fn sender_id(&self) -> &AccountId {
601 &self.sender_id
602 }
603
604 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}