1use crate::error::{ErrorKind, RpcErrorCode};
4use crate::result::{Execution, ExecutionFinalResult, Result, ViewResultDetails};
5use crate::rpc::client::{
6 send_batch_tx_and_retry, send_batch_tx_async_and_retry, DEFAULT_CALL_DEPOSIT,
7 DEFAULT_CALL_FN_GAS,
8};
9use crate::rpc::query::{Query, ViewFunction};
10use crate::types::{
11 AccessKey, AccountId, Gas, InMemorySigner, KeyType, NearToken, PublicKey, SecretKey,
12};
13use crate::worker::Worker;
14use crate::{Account, CryptoHash, Network};
15
16use near_account_id::ParseAccountError;
17use near_gas::NearGas;
18use near_jsonrpc_client::errors::{JsonRpcError, JsonRpcServerError};
19use near_jsonrpc_client::methods::tx::RpcTransactionError;
20use near_primitives::borsh;
21use near_primitives::transaction::{
22 Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction,
23 DeployContractAction, FunctionCallAction, StakeAction, TransferAction,
24};
25use near_primitives::views::{FinalExecutionOutcomeView, TxExecutionStatus};
26use std::convert::TryInto;
27use std::fmt;
28use std::future::IntoFuture;
29use std::pin::Pin;
30use std::task::Poll;
31
32const MAX_GAS: NearGas = NearGas::from_tgas(300);
33
34#[derive(Debug)]
37pub struct Function {
38 pub(crate) name: String,
39 pub(crate) args: Result<Vec<u8>>,
40 pub(crate) deposit: NearToken,
41 pub(crate) gas: Gas,
42}
43
44impl Function {
45 pub fn new(name: &str) -> Self {
48 Self {
49 name: name.into(),
50 args: Ok(vec![]),
51 deposit: DEFAULT_CALL_DEPOSIT,
52 gas: DEFAULT_CALL_FN_GAS,
53 }
54 }
55
56 pub fn args(mut self, args: Vec<u8>) -> Self {
60 if self.args.is_err() {
61 return self;
62 }
63 self.args = Ok(args);
64 self
65 }
66
67 pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
71 match serde_json::to_vec(&args) {
72 Ok(args) => self.args = Ok(args),
73 Err(e) => self.args = Err(ErrorKind::DataConversion.custom(e)),
74 }
75 self
76 }
77
78 pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
81 match borsh::to_vec(&args) {
82 Ok(args) => self.args = Ok(args),
83 Err(e) => self.args = Err(ErrorKind::DataConversion.custom(e)),
84 }
85 self
86 }
87
88 pub fn deposit(mut self, deposit: NearToken) -> Self {
91 self.deposit = deposit;
92 self
93 }
94
95 pub fn gas(mut self, gas: Gas) -> Self {
97 self.gas = gas;
98 self
99 }
100
101 pub fn max_gas(self) -> Self {
103 self.gas(MAX_GAS)
104 }
105}
106
107pub struct Transaction {
120 worker: Worker<dyn Network>,
121 signer: InMemorySigner,
122 receiver_id: AccountId,
123 actions: Result<Vec<Action>>,
125}
126
127impl Transaction {
128 pub(crate) fn new(
129 worker: Worker<dyn Network>,
130 signer: InMemorySigner,
131 receiver_id: AccountId,
132 ) -> Self {
133 Self {
134 worker,
135 signer,
136 receiver_id,
137 actions: Ok(Vec::new()),
138 }
139 }
140
141 pub fn add_key(mut self, pk: PublicKey, ak: AccessKey) -> Self {
144 if let Ok(actions) = &mut self.actions {
145 actions.push(
146 AddKeyAction {
147 public_key: pk.into(),
148 access_key: ak.into(),
149 }
150 .into(),
151 );
152 }
153
154 self
155 }
156
157 pub fn call(mut self, function: Function) -> Self {
159 let args = match function.args {
160 Ok(args) => args,
161 Err(err) => {
162 self.actions = Err(err);
163 return self;
164 }
165 };
166
167 if let Ok(actions) = &mut self.actions {
168 actions.push(Action::FunctionCall(Box::new(FunctionCallAction {
169 method_name: function.name.to_string(),
170 args,
171 deposit: function.deposit.as_yoctonear(),
172 gas: function.gas.as_gas(),
173 })));
174 }
175
176 self
177 }
178
179 pub fn create_account(mut self) -> Self {
181 if let Ok(actions) = &mut self.actions {
182 actions.push(CreateAccountAction {}.into());
183 }
184 self
185 }
186
187 pub fn delete_account(mut self, beneficiary_id: &AccountId) -> Self {
190 if let Ok(actions) = &mut self.actions {
191 actions.push(
192 DeleteAccountAction {
193 beneficiary_id: beneficiary_id.clone(),
194 }
195 .into(),
196 );
197 }
198 self
199 }
200
201 pub fn delete_key(mut self, pk: PublicKey) -> Self {
204 if let Ok(actions) = &mut self.actions {
205 actions.push(DeleteKeyAction { public_key: pk.0 }.into());
206 }
207 self
208 }
209
210 pub fn deploy(mut self, code: &[u8]) -> Self {
212 if let Ok(actions) = &mut self.actions {
213 actions.push(DeployContractAction { code: code.into() }.into());
214 }
215 self
216 }
217
218 pub fn stake(mut self, stake: NearToken, pk: PublicKey) -> Self {
220 if let Ok(actions) = &mut self.actions {
221 actions.push(
222 StakeAction {
223 stake: stake.as_yoctonear(),
224 public_key: pk.0,
225 }
226 .into(),
227 );
228 }
229 self
230 }
231
232 pub fn transfer(mut self, deposit: NearToken) -> Self {
234 if let Ok(actions) = &mut self.actions {
235 actions.push(
236 TransferAction {
237 deposit: deposit.as_yoctonear(),
238 }
239 .into(),
240 );
241 }
242 self
243 }
244
245 async fn transact_raw(self) -> Result<FinalExecutionOutcomeView> {
246 let view = send_batch_tx_and_retry(
247 self.worker.client(),
248 &self.signer,
249 &self.receiver_id,
250 self.actions?,
251 )
252 .await?;
253
254 if !self.worker.tx_callbacks.is_empty() {
255 let total_gas_burnt = view.transaction_outcome.outcome.gas_burnt
256 + view
257 .receipts_outcome
258 .iter()
259 .map(|t| t.outcome.gas_burnt)
260 .sum::<u64>();
261
262 for callback in self.worker.tx_callbacks {
263 callback(Gas::from_gas(total_gas_burnt))?;
264 }
265 }
266
267 Ok(view)
268 }
269
270 pub async fn transact(self) -> Result<ExecutionFinalResult> {
272 self.transact_raw()
273 .await
274 .map(ExecutionFinalResult::from_view)
275 }
276
277 pub async fn transact_async(self) -> Result<TransactionStatus> {
285 send_batch_tx_async_and_retry(self.worker, &self.signer, &self.receiver_id, self.actions?)
286 .await
287 }
288}
289
290pub struct CallTransaction {
293 worker: Worker<dyn Network>,
294 signer: InMemorySigner,
295 contract_id: AccountId,
296 function: Function,
297}
298
299impl CallTransaction {
300 pub(crate) fn new(
301 worker: Worker<dyn Network>,
302 contract_id: AccountId,
303 signer: InMemorySigner,
304 function: &str,
305 ) -> Self {
306 Self {
307 worker,
308 signer,
309 contract_id,
310 function: Function::new(function),
311 }
312 }
313
314 pub fn args(mut self, args: Vec<u8>) -> Self {
318 self.function = self.function.args(args);
319 self
320 }
321
322 pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
326 self.function = self.function.args_json(args);
327 self
328 }
329
330 pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
333 self.function = self.function.args_borsh(args);
334 self
335 }
336
337 pub fn deposit(mut self, deposit: NearToken) -> Self {
340 self.function = self.function.deposit(deposit);
341 self
342 }
343
344 pub fn gas(mut self, gas: NearGas) -> Self {
346 self.function = self.function.gas(gas);
347 self
348 }
349
350 pub fn max_gas(self) -> Self {
352 self.gas(MAX_GAS)
353 }
354
355 pub async fn transact(self) -> Result<ExecutionFinalResult> {
359 let txn = self
360 .worker
361 .client()
362 .call(
363 &self.signer,
364 &self.contract_id,
365 self.function.name.to_string(),
366 self.function.args?,
367 self.function.gas.as_gas(),
368 self.function.deposit,
369 )
370 .await
371 .map(ExecutionFinalResult::from_view)?;
372
373 for callback in self.worker.tx_callbacks.iter() {
374 callback(txn.total_gas_burnt)?;
375 }
376 Ok(txn)
377 }
378
379 pub async fn transact_async(self) -> Result<TransactionStatus> {
387 send_batch_tx_async_and_retry(
388 self.worker,
389 &self.signer,
390 &self.contract_id,
391 vec![FunctionCallAction {
392 args: self.function.args?,
393 method_name: self.function.name,
394 gas: self.function.gas.as_gas(),
395 deposit: self.function.deposit.as_yoctonear(),
396 }
397 .into()],
398 )
399 .await
400 }
401
402 pub async fn view(self) -> Result<ViewResultDetails> {
404 Query::new(
405 self.worker.client(),
406 ViewFunction {
407 account_id: self.contract_id.clone(),
408 function: self.function,
409 },
410 )
411 .await
412 }
413}
414
415pub struct CreateAccountTransaction<'a, 'b> {
418 worker: &'a Worker<dyn Network>,
419 signer: InMemorySigner,
420 parent_id: AccountId,
421 new_account_id: &'b str,
422
423 initial_balance: NearToken,
424 secret_key: Option<SecretKey>,
425}
426
427impl<'a, 'b> CreateAccountTransaction<'a, 'b> {
428 pub(crate) fn new(
429 worker: &'a Worker<dyn Network>,
430 signer: InMemorySigner,
431 parent_id: AccountId,
432 new_account_id: &'b str,
433 ) -> Self {
434 Self {
435 worker,
436 signer,
437 parent_id,
438 new_account_id,
439 initial_balance: NearToken::from_yoctonear(100000000000000000000000u128),
440 secret_key: None,
441 }
442 }
443
444 pub fn initial_balance(mut self, initial_balance: NearToken) -> Self {
447 self.initial_balance = initial_balance;
448 self
449 }
450
451 pub fn keys(mut self, secret_key: SecretKey) -> Self {
453 self.secret_key = Some(secret_key);
454 self
455 }
456
457 pub async fn transact(self) -> Result<Execution<Account>> {
460 let sk = self
461 .secret_key
462 .unwrap_or_else(|| SecretKey::from_seed(KeyType::ED25519, "subaccount.seed"));
463 let id: AccountId = format!("{}.{}", self.new_account_id, self.parent_id)
464 .try_into()
465 .map_err(|e: ParseAccountError| ErrorKind::DataConversion.custom(e))?;
466
467 let outcome = self
468 .worker
469 .client()
470 .create_account(&self.signer, &id, sk.public_key(), self.initial_balance)
471 .await?;
472
473 let signer = InMemorySigner::from_secret_key(id, sk);
474 let account = Account::new(signer, self.worker.clone());
475 let details = ExecutionFinalResult::from_view(outcome);
476
477 for callback in self.worker.tx_callbacks.iter() {
478 callback(details.total_gas_burnt)?;
479 }
480
481 Ok(Execution {
482 result: account,
483 details,
484 })
485 }
486}
487
488#[must_use]
493pub struct TransactionStatus {
494 worker: Worker<dyn Network>,
495 sender_id: AccountId,
496 hash: CryptoHash,
497}
498
499impl TransactionStatus {
500 pub(crate) fn new(
501 worker: Worker<dyn Network>,
502 id: AccountId,
503 hash: near_primitives::hash::CryptoHash,
504 ) -> Self {
505 Self {
506 worker,
507 sender_id: id,
508 hash: CryptoHash(hash.0),
509 }
510 }
511
512 pub async fn status(&self) -> Result<Poll<ExecutionFinalResult>> {
516 let rpc_resp = self
517 .worker
518 .client()
519 .tx_async_status(
520 &self.sender_id,
521 near_primitives::hash::CryptoHash(self.hash.0),
522 TxExecutionStatus::Included,
523 )
524 .await;
525
526 let rpc_resp = match rpc_resp {
527 Ok(rpc_resp) => rpc_resp,
528 Err(err) => match err {
529 JsonRpcError::ServerError(JsonRpcServerError::HandlerError(
530 RpcTransactionError::UnknownTransaction { .. },
531 )) => return Ok(Poll::Pending),
532 other => return Err(RpcErrorCode::BroadcastTxFailure.custom(other)),
533 },
534 };
535
536 if matches!(rpc_resp.final_execution_status, TxExecutionStatus::Included) {
537 return Ok(Poll::Pending);
538 }
539
540 let Some(final_outcome) = rpc_resp.final_execution_outcome else {
541 return Ok(Poll::Pending);
543 };
544
545 let outcome = final_outcome.into_outcome();
546
547 match outcome.status {
548 near_primitives::views::FinalExecutionStatus::NotStarted => return Ok(Poll::Pending),
549 near_primitives::views::FinalExecutionStatus::Started => return Ok(Poll::Pending),
550 _ => (),
551 }
552
553 Ok(Poll::Ready(ExecutionFinalResult::from_view(outcome)))
554 }
555
556 pub(crate) async fn wait(self) -> Result<ExecutionFinalResult> {
558 loop {
559 match self.status().await? {
560 Poll::Ready(val) => break Ok(val),
561 Poll::Pending => (),
562 }
563
564 tokio::time::sleep(std::time::Duration::from_millis(300)).await;
565 }
566 }
567
568 pub fn sender_id(&self) -> &AccountId {
570 &self.sender_id
571 }
572
573 pub fn hash(&self) -> &CryptoHash {
575 &self.hash
576 }
577}
578
579impl fmt::Debug for TransactionStatus {
580 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
581 f.debug_struct("TransactionStatus")
582 .field("sender_id", &self.sender_id)
583 .field("hash", &self.hash)
584 .finish()
585 }
586}
587
588impl IntoFuture for TransactionStatus {
589 type Output = Result<ExecutionFinalResult>;
590 type IntoFuture = Pin<Box<dyn std::future::Future<Output = Self::Output>>>;
591
592 fn into_future(self) -> Self::IntoFuture {
593 Box::pin(async { self.wait().await })
594 }
595}