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
40pub struct Signer {
42 keypair: Keypair,
43 account_id: AccountId,
44 nonce: AtomicNonce,
45}
46
47impl Signer {
48 #[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 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 pub fn sign(&self, data: &[u8]) -> Ed25519Signature {
73 self.keypair.sign(data)
74 }
75
76 pub fn public_key(&self) -> &Ed25519PublicKey {
78 self.keypair.public_key()
79 }
80
81 pub fn secret_key(&self) -> &Ed25519SecretKey {
83 self.keypair.secret_key()
84 }
85
86 pub fn account(&self) -> &AccountId {
88 &self.account_id
89 }
90
91 pub fn nonce(&self) -> Nonce {
93 self.nonce.load(Ordering::Relaxed)
94 }
95
96 pub fn update_nonce(&self, nonce: Nonce) {
98 self.nonce.store(nonce, Ordering::Relaxed);
99 }
100
101 pub fn increment_nonce(&self, value: u64) {
104 self.nonce.fetch_add(value, Ordering::AcqRel);
105 }
106}
107
108#[derive(Clone)]
110pub struct NearClient {
111 pub(crate) rpc_client: RpcClient,
112}
113
114impl NearClient {
115 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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#[derive(Debug)]
515pub struct ViewOutput<T: DeserializeOwned> {
516 logs: Vec<String>,
517 data: T,
518}
519
520impl<T: DeserializeOwned> ViewOutput<T> {
521 pub fn logs(&self) -> Vec<String> {
523 self.logs.clone()
524 }
525
526 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#[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 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 pub const fn id(&self) -> CryptoHash {
565 self.transaction.id
566 }
567
568 pub const fn gas_burnt(&self) -> Gas {
570 self.transaction.outcome.gas_burnt
571 }
572
573 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 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 pub const fn retry(mut self, retry: Retry) -> Self {
635 self.retry = retry;
636 self
637 }
638
639 pub async fn commit(self, finality: Finality) -> Result<Output> {
646 let call = self.build()?;
647 call.commit(finality).await
648 }
649
650 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#[repr(usize)]
669#[derive(Debug, Default, Clone, Copy)]
670pub enum Retry {
671 #[default]
673 NONE = 1,
674 ONCE = 2,
676 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 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 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 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
780fn 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}