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,
172 gas: near_primitives::gas::Gas::from_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,
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(TransferAction { deposit }.into());
236 }
237 self
238 }
239
240 async fn transact_raw(self) -> Result<FinalExecutionOutcomeView> {
241 let view = send_batch_tx_and_retry(
242 self.worker.client(),
243 &self.signer,
244 &self.receiver_id,
245 self.actions?,
246 )
247 .await?;
248
249 if !self.worker.tx_callbacks.is_empty() {
250 let total_gas_burnt = view.transaction_outcome.outcome.gas_burnt.as_gas()
251 + view
252 .receipts_outcome
253 .iter()
254 .map(|t| t.outcome.gas_burnt.as_gas())
255 .sum::<u64>();
256
257 for callback in self.worker.tx_callbacks {
258 callback(Gas::from_gas(total_gas_burnt))?;
259 }
260 }
261
262 Ok(view)
263 }
264
265 pub async fn transact(self) -> Result<ExecutionFinalResult> {
267 self.transact_raw()
268 .await
269 .map(ExecutionFinalResult::from_view)
270 }
271
272 pub async fn transact_async(self) -> Result<TransactionStatus> {
280 send_batch_tx_async_and_retry(self.worker, &self.signer, &self.receiver_id, self.actions?)
281 .await
282 }
283}
284
285pub struct CallTransaction {
288 worker: Worker<dyn Network>,
289 signer: InMemorySigner,
290 contract_id: AccountId,
291 function: Function,
292}
293
294impl CallTransaction {
295 pub(crate) fn new(
296 worker: Worker<dyn Network>,
297 contract_id: AccountId,
298 signer: InMemorySigner,
299 function: &str,
300 ) -> Self {
301 Self {
302 worker,
303 signer,
304 contract_id,
305 function: Function::new(function),
306 }
307 }
308
309 pub fn args(mut self, args: Vec<u8>) -> Self {
313 self.function = self.function.args(args);
314 self
315 }
316
317 pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
321 self.function = self.function.args_json(args);
322 self
323 }
324
325 pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
328 self.function = self.function.args_borsh(args);
329 self
330 }
331
332 pub fn deposit(mut self, deposit: NearToken) -> Self {
335 self.function = self.function.deposit(deposit);
336 self
337 }
338
339 pub fn gas(mut self, gas: NearGas) -> Self {
341 self.function = self.function.gas(gas);
342 self
343 }
344
345 pub fn max_gas(self) -> Self {
347 self.gas(MAX_GAS)
348 }
349
350 pub async fn transact(self) -> Result<ExecutionFinalResult> {
354 let txn = self
355 .worker
356 .client()
357 .call(
358 &self.signer,
359 &self.contract_id,
360 self.function.name.to_string(),
361 self.function.args?,
362 near_primitives::gas::Gas::from_gas(self.function.gas.as_gas()),
363 self.function.deposit,
364 )
365 .await
366 .map(ExecutionFinalResult::from_view)?;
367
368 for callback in self.worker.tx_callbacks.iter() {
369 callback(txn.total_gas_burnt)?;
370 }
371 Ok(txn)
372 }
373
374 pub async fn transact_async(self) -> Result<TransactionStatus> {
382 send_batch_tx_async_and_retry(
383 self.worker,
384 &self.signer,
385 &self.contract_id,
386 vec![FunctionCallAction {
387 args: self.function.args?,
388 method_name: self.function.name,
389 gas: near_primitives::gas::Gas::from_gas(self.function.gas.as_gas()),
390 deposit: self.function.deposit,
391 }
392 .into()],
393 )
394 .await
395 }
396
397 pub async fn view(self) -> Result<ViewResultDetails> {
399 Query::new(
400 self.worker.client(),
401 ViewFunction {
402 account_id: self.contract_id.clone(),
403 function: self.function,
404 },
405 )
406 .await
407 }
408}
409
410pub struct CreateAccountTransaction<'a, 'b> {
413 worker: &'a Worker<dyn Network>,
414 signer: InMemorySigner,
415 parent_id: AccountId,
416 new_account_id: &'b str,
417
418 initial_balance: NearToken,
419 secret_key: Option<SecretKey>,
420}
421
422impl<'a, 'b> CreateAccountTransaction<'a, 'b> {
423 pub(crate) fn new(
424 worker: &'a Worker<dyn Network>,
425 signer: InMemorySigner,
426 parent_id: AccountId,
427 new_account_id: &'b str,
428 ) -> Self {
429 Self {
430 worker,
431 signer,
432 parent_id,
433 new_account_id,
434 initial_balance: NearToken::from_yoctonear(100000000000000000000000u128),
435 secret_key: None,
436 }
437 }
438
439 pub fn initial_balance(mut self, initial_balance: NearToken) -> Self {
442 self.initial_balance = initial_balance;
443 self
444 }
445
446 pub fn keys(mut self, secret_key: SecretKey) -> Self {
448 self.secret_key = Some(secret_key);
449 self
450 }
451
452 pub async fn transact(self) -> Result<Execution<Account>> {
455 let sk = self
456 .secret_key
457 .unwrap_or_else(|| SecretKey::from_seed(KeyType::ED25519, "subaccount.seed"));
458 let id: AccountId = format!("{}.{}", self.new_account_id, self.parent_id)
459 .try_into()
460 .map_err(|e: ParseAccountError| ErrorKind::DataConversion.custom(e))?;
461
462 let outcome = self
463 .worker
464 .client()
465 .create_account(&self.signer, &id, sk.public_key(), self.initial_balance)
466 .await?;
467
468 let signer = InMemorySigner::from_secret_key(id, sk);
469 let account = Account::new(signer, self.worker.clone());
470 let details = ExecutionFinalResult::from_view(outcome);
471
472 for callback in self.worker.tx_callbacks.iter() {
473 callback(details.total_gas_burnt)?;
474 }
475
476 Ok(Execution {
477 result: account,
478 details,
479 })
480 }
481}
482
483#[must_use]
488pub struct TransactionStatus {
489 worker: Worker<dyn Network>,
490 sender_id: AccountId,
491 hash: CryptoHash,
492}
493
494impl TransactionStatus {
495 pub(crate) fn new(
496 worker: Worker<dyn Network>,
497 id: AccountId,
498 hash: near_primitives::hash::CryptoHash,
499 ) -> Self {
500 Self {
501 worker,
502 sender_id: id,
503 hash: CryptoHash(hash.0),
504 }
505 }
506
507 pub async fn status(&self) -> Result<Poll<ExecutionFinalResult>> {
511 let rpc_resp = self
512 .worker
513 .client()
514 .tx_async_status(
515 &self.sender_id,
516 near_primitives::hash::CryptoHash(self.hash.0),
517 TxExecutionStatus::Included,
518 )
519 .await;
520
521 let rpc_resp = match rpc_resp {
522 Ok(rpc_resp) => rpc_resp,
523 Err(err) => match err {
524 JsonRpcError::ServerError(JsonRpcServerError::HandlerError(
525 RpcTransactionError::UnknownTransaction { .. },
526 )) => return Ok(Poll::Pending),
527 other => return Err(RpcErrorCode::BroadcastTxFailure.custom(other)),
528 },
529 };
530
531 if matches!(rpc_resp.final_execution_status, TxExecutionStatus::Included) {
532 return Ok(Poll::Pending);
533 }
534
535 let Some(final_outcome) = rpc_resp.final_execution_outcome else {
536 return Ok(Poll::Pending);
538 };
539
540 let outcome = final_outcome.into_outcome();
541
542 match outcome.status {
543 near_primitives::views::FinalExecutionStatus::NotStarted => return Ok(Poll::Pending),
544 near_primitives::views::FinalExecutionStatus::Started => return Ok(Poll::Pending),
545 _ => (),
546 }
547
548 Ok(Poll::Ready(ExecutionFinalResult::from_view(outcome)))
549 }
550
551 pub(crate) async fn wait(self) -> Result<ExecutionFinalResult> {
553 loop {
554 match self.status().await? {
555 Poll::Ready(val) => break Ok(val),
556 Poll::Pending => (),
557 }
558
559 tokio::time::sleep(std::time::Duration::from_millis(300)).await;
560 }
561 }
562
563 pub fn sender_id(&self) -> &AccountId {
565 &self.sender_id
566 }
567
568 pub fn hash(&self) -> &CryptoHash {
570 &self.hash
571 }
572}
573
574impl fmt::Debug for TransactionStatus {
575 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
576 f.debug_struct("TransactionStatus")
577 .field("sender_id", &self.sender_id)
578 .field("hash", &self.hash)
579 .finish()
580 }
581}
582
583impl IntoFuture for TransactionStatus {
584 type Output = Result<ExecutionFinalResult>;
585 type IntoFuture = Pin<Box<dyn std::future::Future<Output = Self::Output>>>;
586
587 fn into_future(self) -> Self::IntoFuture {
588 Box::pin(async { self.wait().await })
589 }
590}