1use thiserror::Error;
48
49use crate::types::{AccountId, DelegateDecodeError, PublicKey};
50
51#[derive(Debug, Clone, Error, PartialEq, Eq)]
53pub enum ParseAccountIdError {
54 #[error("Account ID is empty")]
55 Empty,
56
57 #[error("Account ID '{0}' is too long (max 64 characters)")]
58 TooLong(String),
59
60 #[error("Account ID '{0}' is too short (min 2 characters for named accounts)")]
61 TooShort(String),
62
63 #[error("Account ID '{0}' contains invalid character '{1}'")]
64 InvalidChar(String, char),
65
66 #[error("Account ID '{0}' has invalid format")]
67 InvalidFormat(String),
68}
69
70#[derive(Debug, Clone, Error, PartialEq, Eq)]
72pub enum ParseAmountError {
73 #[error("Ambiguous amount '{0}'. Use explicit units like '5 NEAR' or '1000 yocto'")]
74 AmbiguousAmount(String),
75
76 #[error("Invalid amount format: '{0}'")]
77 InvalidFormat(String),
78
79 #[error("Invalid number in amount: '{0}'")]
80 InvalidNumber(String),
81
82 #[error("Amount overflow: value too large")]
83 Overflow,
84}
85
86#[derive(Debug, Clone, Error, PartialEq, Eq)]
88pub enum ParseGasError {
89 #[error("Invalid gas format: '{0}'. Use '30 Tgas', '5 Ggas', or '1000000 gas'")]
90 InvalidFormat(String),
91
92 #[error("Invalid number in gas: '{0}'")]
93 InvalidNumber(String),
94
95 #[error("Gas overflow: value too large")]
96 Overflow,
97}
98
99#[derive(Debug, Clone, Error, PartialEq, Eq)]
101pub enum ParseKeyError {
102 #[error("Invalid key format: expected 'ed25519:...' or 'secp256k1:...'")]
103 InvalidFormat,
104
105 #[error("Unknown key type: '{0}'")]
106 UnknownKeyType(String),
107
108 #[error("Invalid base58 encoding: {0}")]
109 InvalidBase58(String),
110
111 #[error("Invalid key length: expected {expected} bytes, got {actual}")]
112 InvalidLength { expected: usize, actual: usize },
113
114 #[error("Invalid curve point: key bytes do not represent a valid point on the curve")]
115 InvalidCurvePoint,
116}
117
118#[derive(Debug, Clone, Error, PartialEq, Eq)]
120pub enum ParseHashError {
121 #[error("Invalid base58 encoding: {0}")]
122 InvalidBase58(String),
123
124 #[error("Invalid hash length: expected 32 bytes, got {0}")]
125 InvalidLength(usize),
126}
127
128#[derive(Debug, Clone, Error, PartialEq, Eq)]
130pub enum SignerError {
131 #[error("Invalid seed phrase")]
132 InvalidSeedPhrase,
133
134 #[error("Signing failed: {0}")]
135 SigningFailed(String),
136
137 #[error("Key derivation failed: {0}")]
138 KeyDerivationFailed(String),
139}
140
141#[derive(Debug, Error)]
143pub enum KeyStoreError {
144 #[error("Key not found for account: {0}")]
145 KeyNotFound(AccountId),
146
147 #[error("IO error: {0}")]
148 Io(#[from] std::io::Error),
149
150 #[error("JSON error: {0}")]
151 Json(#[from] serde_json::Error),
152
153 #[error("Invalid credential format: {0}")]
154 InvalidFormat(String),
155
156 #[error("Invalid key: {0}")]
157 InvalidKey(#[from] ParseKeyError),
158
159 #[error("Path error: {0}")]
160 PathError(String),
161
162 #[error("Platform keyring error: {0}")]
163 Platform(String),
164}
165
166#[derive(Debug, Error)]
172pub enum RpcError {
173 #[error("HTTP error: {0}")]
175 Http(#[from] reqwest::Error),
176
177 #[error("Network error: {message}")]
178 Network {
179 message: String,
180 status_code: Option<u16>,
181 retryable: bool,
182 },
183
184 #[error("Timeout after {0} retries")]
185 Timeout(u32),
186
187 #[error("JSON parse error: {0}")]
188 Json(#[from] serde_json::Error),
189
190 #[error("Invalid response: {0}")]
191 InvalidResponse(String),
192
193 #[error("RPC error: {message} (code: {code})")]
195 Rpc {
196 code: i64,
197 message: String,
198 data: Option<serde_json::Value>,
199 },
200
201 #[error("Account not found: {0}")]
203 AccountNotFound(AccountId),
204
205 #[error("Invalid account ID: {0}")]
206 InvalidAccount(String),
207
208 #[error("Access key not found: {account_id} / {public_key}")]
209 AccessKeyNotFound {
210 account_id: AccountId,
211 public_key: PublicKey,
212 },
213
214 #[error("Contract not deployed on account: {0}")]
216 ContractNotDeployed(AccountId),
217
218 #[error("Contract state too large for account: {0}")]
219 ContractStateTooLarge(AccountId),
220
221 #[error("Contract execution failed on {contract_id}: {message}")]
222 ContractExecution {
223 contract_id: AccountId,
224 method_name: Option<String>,
225 message: String,
226 },
227
228 #[error("Contract panic: {message}")]
229 ContractPanic { message: String },
230
231 #[error("Function call error on {contract_id}.{method_name}: {}", panic.as_deref().unwrap_or("unknown error"))]
232 FunctionCall {
233 contract_id: AccountId,
234 method_name: String,
235 panic: Option<String>,
236 logs: Vec<String>,
237 },
238
239 #[error(
241 "Block not found: {0}. It may have been garbage-collected. Try an archival node for blocks older than 5 epochs."
242 )]
243 UnknownBlock(String),
244
245 #[error("Chunk not found: {0}. It may have been garbage-collected. Try an archival node.")]
246 UnknownChunk(String),
247
248 #[error(
249 "Epoch not found for block: {0}. The block may be invalid or too old. Try an archival node."
250 )]
251 UnknownEpoch(String),
252
253 #[error("Invalid shard ID: {0}")]
254 InvalidShardId(String),
255
256 #[error("Receipt not found: {0}")]
258 UnknownReceipt(String),
259
260 #[error("Invalid transaction: {message}")]
262 InvalidTransaction {
263 message: String,
264 details: Option<serde_json::Value>,
265 shard_congested: bool,
266 shard_stuck: bool,
267 },
268
269 #[error(
270 "Invalid nonce: transaction nonce {tx_nonce} must be greater than access key nonce {ak_nonce}"
271 )]
272 InvalidNonce { tx_nonce: u64, ak_nonce: u64 },
273
274 #[error("Insufficient balance: required {required}, available {available}")]
275 InsufficientBalance { required: String, available: String },
276
277 #[error("Gas limit exceeded: used {gas_used}, limit {gas_limit}")]
278 GasLimitExceeded { gas_used: String, gas_limit: String },
279
280 #[error("Shard unavailable: {0}")]
282 ShardUnavailable(String),
283
284 #[error("Node not synced: {0}")]
285 NodeNotSynced(String),
286
287 #[error("Internal server error: {0}")]
288 InternalError(String),
289
290 #[error("Parse error: {0}")]
292 ParseError(String),
293
294 #[error("Request timeout: {message}")]
295 RequestTimeout {
296 message: String,
297 transaction_hash: Option<String>,
298 },
299}
300
301impl RpcError {
302 pub fn is_retryable(&self) -> bool {
304 match self {
305 RpcError::Http(e) => e.is_timeout() || e.is_connect(),
306 RpcError::Timeout(_) => true,
307 RpcError::Network { retryable, .. } => *retryable,
308 RpcError::ShardUnavailable(_) => true,
309 RpcError::NodeNotSynced(_) => true,
310 RpcError::InternalError(_) => true,
311 RpcError::RequestTimeout { .. } => true,
312 RpcError::InvalidNonce { .. } => true,
313 RpcError::InvalidTransaction {
314 shard_congested,
315 shard_stuck,
316 ..
317 } => *shard_congested || *shard_stuck,
318 RpcError::Rpc { code, .. } => {
319 *code == -32000 || *code == -32603
321 }
322 _ => false,
323 }
324 }
325
326 pub fn network(message: impl Into<String>, status_code: Option<u16>, retryable: bool) -> Self {
328 RpcError::Network {
329 message: message.into(),
330 status_code,
331 retryable,
332 }
333 }
334
335 pub fn invalid_transaction(
337 message: impl Into<String>,
338 details: Option<serde_json::Value>,
339 ) -> Self {
340 let details_obj = details.as_ref();
341 let shard_congested = details_obj
342 .and_then(|d| d.get("ShardCongested"))
343 .and_then(|v| v.as_bool())
344 .unwrap_or(false);
345 let shard_stuck = details_obj
346 .and_then(|d| d.get("ShardStuck"))
347 .and_then(|v| v.as_bool())
348 .unwrap_or(false);
349
350 RpcError::InvalidTransaction {
351 message: message.into(),
352 details,
353 shard_congested,
354 shard_stuck,
355 }
356 }
357
358 pub fn function_call(
360 contract_id: AccountId,
361 method_name: impl Into<String>,
362 panic: Option<String>,
363 logs: Vec<String>,
364 ) -> Self {
365 RpcError::FunctionCall {
366 contract_id,
367 method_name: method_name.into(),
368 panic,
369 logs,
370 }
371 }
372}
373
374impl RpcError {
380 pub fn is_account_not_found(&self) -> bool {
382 matches!(self, RpcError::AccountNotFound(_))
383 }
384
385 pub fn is_contract_not_deployed(&self) -> bool {
387 matches!(self, RpcError::ContractNotDeployed(_))
388 }
389}
390
391#[derive(Debug, Error)]
392pub enum Error {
393 #[error(
395 "No signer configured. Use .credentials()/.signer() on NearBuilder, .with_signer() on the client, or .sign_with() on the transaction."
396 )]
397 NoSigner,
398
399 #[error(
400 "No signer account ID. Call .default_account() on NearBuilder or use a signer with an account ID."
401 )]
402 NoSignerAccount,
403
404 #[error("Invalid configuration: {0}")]
405 Config(String),
406
407 #[error(transparent)]
409 ParseAccountId(#[from] ParseAccountIdError),
410
411 #[error(transparent)]
412 ParseAmount(#[from] ParseAmountError),
413
414 #[error(transparent)]
415 ParseGas(#[from] ParseGasError),
416
417 #[error(transparent)]
418 ParseKey(#[from] ParseKeyError),
419
420 #[error(transparent)]
422 Rpc(#[from] RpcError),
423
424 #[error("Transaction failed: {0}")]
426 TransactionFailed(String),
427
428 #[error("Invalid transaction: {0}")]
429 InvalidTransaction(String),
430
431 #[error("Contract panic: {0}")]
432 ContractPanic(String),
433
434 #[error("Signing failed: {0}")]
436 Signing(#[from] SignerError),
437
438 #[error(transparent)]
440 KeyStore(#[from] KeyStoreError),
441
442 #[error("JSON error: {0}")]
444 Json(#[from] serde_json::Error),
445
446 #[error("Borsh error: {0}")]
447 Borsh(String),
448
449 #[error("Delegate action decode error: {0}")]
450 DelegateDecode(#[from] DelegateDecodeError),
451
452 #[error("Token {token} is not available on {network}")]
454 TokenNotAvailable { token: String, network: String },
455}
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460
461 #[test]
466 fn test_parse_account_id_error_display() {
467 assert_eq!(
468 ParseAccountIdError::Empty.to_string(),
469 "Account ID is empty"
470 );
471 assert_eq!(
472 ParseAccountIdError::TooLong("a".repeat(65)).to_string(),
473 format!(
474 "Account ID '{}' is too long (max 64 characters)",
475 "a".repeat(65)
476 )
477 );
478 assert_eq!(
479 ParseAccountIdError::TooShort("a".to_string()).to_string(),
480 "Account ID 'a' is too short (min 2 characters for named accounts)"
481 );
482 assert_eq!(
483 ParseAccountIdError::InvalidChar("test@acc".to_string(), '@').to_string(),
484 "Account ID 'test@acc' contains invalid character '@'"
485 );
486 assert_eq!(
487 ParseAccountIdError::InvalidFormat("bad..account".to_string()).to_string(),
488 "Account ID 'bad..account' has invalid format"
489 );
490 }
491
492 #[test]
497 fn test_parse_amount_error_display() {
498 assert_eq!(
499 ParseAmountError::AmbiguousAmount("123".to_string()).to_string(),
500 "Ambiguous amount '123'. Use explicit units like '5 NEAR' or '1000 yocto'"
501 );
502 assert_eq!(
503 ParseAmountError::InvalidFormat("xyz".to_string()).to_string(),
504 "Invalid amount format: 'xyz'"
505 );
506 assert_eq!(
507 ParseAmountError::InvalidNumber("abc".to_string()).to_string(),
508 "Invalid number in amount: 'abc'"
509 );
510 assert_eq!(
511 ParseAmountError::Overflow.to_string(),
512 "Amount overflow: value too large"
513 );
514 }
515
516 #[test]
521 fn test_parse_gas_error_display() {
522 assert_eq!(
523 ParseGasError::InvalidFormat("xyz".to_string()).to_string(),
524 "Invalid gas format: 'xyz'. Use '30 Tgas', '5 Ggas', or '1000000 gas'"
525 );
526 assert_eq!(
527 ParseGasError::InvalidNumber("abc".to_string()).to_string(),
528 "Invalid number in gas: 'abc'"
529 );
530 assert_eq!(
531 ParseGasError::Overflow.to_string(),
532 "Gas overflow: value too large"
533 );
534 }
535
536 #[test]
541 fn test_parse_key_error_display() {
542 assert_eq!(
543 ParseKeyError::InvalidFormat.to_string(),
544 "Invalid key format: expected 'ed25519:...' or 'secp256k1:...'"
545 );
546 assert_eq!(
547 ParseKeyError::UnknownKeyType("rsa".to_string()).to_string(),
548 "Unknown key type: 'rsa'"
549 );
550 assert_eq!(
551 ParseKeyError::InvalidBase58("invalid chars".to_string()).to_string(),
552 "Invalid base58 encoding: invalid chars"
553 );
554 assert_eq!(
555 ParseKeyError::InvalidLength {
556 expected: 32,
557 actual: 16
558 }
559 .to_string(),
560 "Invalid key length: expected 32 bytes, got 16"
561 );
562 assert_eq!(
563 ParseKeyError::InvalidCurvePoint.to_string(),
564 "Invalid curve point: key bytes do not represent a valid point on the curve"
565 );
566 }
567
568 #[test]
573 fn test_parse_hash_error_display() {
574 assert_eq!(
575 ParseHashError::InvalidBase58("bad input".to_string()).to_string(),
576 "Invalid base58 encoding: bad input"
577 );
578 assert_eq!(
579 ParseHashError::InvalidLength(16).to_string(),
580 "Invalid hash length: expected 32 bytes, got 16"
581 );
582 }
583
584 #[test]
589 fn test_signer_error_display() {
590 assert_eq!(
591 SignerError::InvalidSeedPhrase.to_string(),
592 "Invalid seed phrase"
593 );
594 assert_eq!(
595 SignerError::SigningFailed("hardware failure".to_string()).to_string(),
596 "Signing failed: hardware failure"
597 );
598 assert_eq!(
599 SignerError::KeyDerivationFailed("path error".to_string()).to_string(),
600 "Key derivation failed: path error"
601 );
602 }
603
604 #[test]
609 fn test_keystore_error_display() {
610 let account_id: AccountId = "alice.near".parse().unwrap();
611 assert_eq!(
612 KeyStoreError::KeyNotFound(account_id).to_string(),
613 "Key not found for account: alice.near"
614 );
615 assert_eq!(
616 KeyStoreError::InvalidFormat("missing field".to_string()).to_string(),
617 "Invalid credential format: missing field"
618 );
619 assert_eq!(
620 KeyStoreError::PathError("bad path".to_string()).to_string(),
621 "Path error: bad path"
622 );
623 assert_eq!(
624 KeyStoreError::Platform("keyring locked".to_string()).to_string(),
625 "Platform keyring error: keyring locked"
626 );
627 }
628
629 #[test]
634 fn test_rpc_error_display() {
635 let account_id: AccountId = "alice.near".parse().unwrap();
636
637 assert_eq!(RpcError::Timeout(3).to_string(), "Timeout after 3 retries");
638 assert_eq!(
639 RpcError::InvalidResponse("missing result".to_string()).to_string(),
640 "Invalid response: missing result"
641 );
642 assert_eq!(
643 RpcError::AccountNotFound(account_id.clone()).to_string(),
644 "Account not found: alice.near"
645 );
646 assert_eq!(
647 RpcError::InvalidAccount("bad-account".to_string()).to_string(),
648 "Invalid account ID: bad-account"
649 );
650 assert_eq!(
651 RpcError::ContractNotDeployed(account_id.clone()).to_string(),
652 "Contract not deployed on account: alice.near"
653 );
654 assert_eq!(
655 RpcError::ContractStateTooLarge(account_id.clone()).to_string(),
656 "Contract state too large for account: alice.near"
657 );
658 assert_eq!(
659 RpcError::UnknownBlock("12345".to_string()).to_string(),
660 "Block not found: 12345. It may have been garbage-collected. Try an archival node for blocks older than 5 epochs."
661 );
662 assert_eq!(
663 RpcError::UnknownChunk("abc123".to_string()).to_string(),
664 "Chunk not found: abc123. It may have been garbage-collected. Try an archival node."
665 );
666 assert_eq!(
667 RpcError::UnknownEpoch("epoch1".to_string()).to_string(),
668 "Epoch not found for block: epoch1. The block may be invalid or too old. Try an archival node."
669 );
670 assert_eq!(
671 RpcError::UnknownReceipt("receipt123".to_string()).to_string(),
672 "Receipt not found: receipt123"
673 );
674 assert_eq!(
675 RpcError::InvalidShardId("99".to_string()).to_string(),
676 "Invalid shard ID: 99"
677 );
678 assert_eq!(
679 RpcError::ShardUnavailable("shard 0".to_string()).to_string(),
680 "Shard unavailable: shard 0"
681 );
682 assert_eq!(
683 RpcError::NodeNotSynced("syncing...".to_string()).to_string(),
684 "Node not synced: syncing..."
685 );
686 assert_eq!(
687 RpcError::InternalError("database error".to_string()).to_string(),
688 "Internal server error: database error"
689 );
690 assert_eq!(
691 RpcError::ParseError("invalid json".to_string()).to_string(),
692 "Parse error: invalid json"
693 );
694 }
695
696 #[test]
697 fn test_rpc_error_is_retryable() {
698 assert!(RpcError::Timeout(3).is_retryable());
700 assert!(RpcError::ShardUnavailable("shard 0".to_string()).is_retryable());
701 assert!(RpcError::NodeNotSynced("syncing".to_string()).is_retryable());
702 assert!(RpcError::InternalError("db error".to_string()).is_retryable());
703 assert!(
704 RpcError::RequestTimeout {
705 message: "timeout".to_string(),
706 transaction_hash: None,
707 }
708 .is_retryable()
709 );
710 assert!(
711 RpcError::InvalidNonce {
712 tx_nonce: 5,
713 ak_nonce: 10
714 }
715 .is_retryable()
716 );
717 assert!(
718 RpcError::Network {
719 message: "connection reset".to_string(),
720 status_code: Some(503),
721 retryable: true,
722 }
723 .is_retryable()
724 );
725 assert!(
726 RpcError::InvalidTransaction {
727 message: "shard congested".to_string(),
728 details: None,
729 shard_congested: true,
730 shard_stuck: false,
731 }
732 .is_retryable()
733 );
734 assert!(
735 RpcError::Rpc {
736 code: -32000,
737 message: "server error".to_string(),
738 data: None,
739 }
740 .is_retryable()
741 );
742 assert!(
743 RpcError::Rpc {
744 code: -32603,
745 message: "internal error".to_string(),
746 data: None,
747 }
748 .is_retryable()
749 );
750
751 let account_id: AccountId = "alice.near".parse().unwrap();
753 assert!(!RpcError::AccountNotFound(account_id.clone()).is_retryable());
754 assert!(!RpcError::ContractNotDeployed(account_id.clone()).is_retryable());
755 assert!(!RpcError::InvalidAccount("bad".to_string()).is_retryable());
756 assert!(!RpcError::UnknownBlock("12345".to_string()).is_retryable());
757 assert!(!RpcError::ParseError("bad json".to_string()).is_retryable());
758 assert!(
759 !RpcError::Network {
760 message: "not found".to_string(),
761 status_code: Some(404),
762 retryable: false,
763 }
764 .is_retryable()
765 );
766 assert!(
767 !RpcError::InvalidTransaction {
768 message: "invalid".to_string(),
769 details: None,
770 shard_congested: false,
771 shard_stuck: false,
772 }
773 .is_retryable()
774 );
775 assert!(
776 !RpcError::Rpc {
777 code: -32600,
778 message: "invalid request".to_string(),
779 data: None,
780 }
781 .is_retryable()
782 );
783 }
784
785 #[test]
786 fn test_rpc_error_network_constructor() {
787 let err = RpcError::network("connection refused", Some(503), true);
788 match err {
789 RpcError::Network {
790 message,
791 status_code,
792 retryable,
793 } => {
794 assert_eq!(message, "connection refused");
795 assert_eq!(status_code, Some(503));
796 assert!(retryable);
797 }
798 _ => panic!("Expected Network error"),
799 }
800 }
801
802 #[test]
803 fn test_rpc_error_invalid_transaction_constructor() {
804 let err = RpcError::invalid_transaction("invalid nonce", None);
805 match err {
806 RpcError::InvalidTransaction {
807 message,
808 details,
809 shard_congested,
810 shard_stuck,
811 } => {
812 assert_eq!(message, "invalid nonce");
813 assert!(details.is_none());
814 assert!(!shard_congested);
815 assert!(!shard_stuck);
816 }
817 _ => panic!("Expected InvalidTransaction error"),
818 }
819 }
820
821 #[test]
822 fn test_rpc_error_function_call_constructor() {
823 let account_id: AccountId = "contract.near".parse().unwrap();
824 let err = RpcError::function_call(
825 account_id.clone(),
826 "my_method",
827 Some("assertion failed".to_string()),
828 vec!["log1".to_string(), "log2".to_string()],
829 );
830 match err {
831 RpcError::FunctionCall {
832 contract_id,
833 method_name,
834 panic,
835 logs,
836 } => {
837 assert_eq!(contract_id, account_id);
838 assert_eq!(method_name, "my_method");
839 assert_eq!(panic, Some("assertion failed".to_string()));
840 assert_eq!(logs, vec!["log1", "log2"]);
841 }
842 _ => panic!("Expected FunctionCall error"),
843 }
844 }
845
846 #[test]
847 fn test_rpc_error_is_account_not_found() {
848 let account_id: AccountId = "alice.near".parse().unwrap();
849 assert!(RpcError::AccountNotFound(account_id).is_account_not_found());
850 assert!(!RpcError::Timeout(3).is_account_not_found());
851 }
852
853 #[test]
854 fn test_rpc_error_is_contract_not_deployed() {
855 let account_id: AccountId = "alice.near".parse().unwrap();
856 assert!(RpcError::ContractNotDeployed(account_id).is_contract_not_deployed());
857 assert!(!RpcError::Timeout(3).is_contract_not_deployed());
858 }
859
860 #[test]
861 fn test_rpc_error_contract_execution_display() {
862 let account_id: AccountId = "contract.near".parse().unwrap();
863 let err = RpcError::ContractExecution {
864 contract_id: account_id,
865 method_name: Some("my_method".to_string()),
866 message: "execution failed".to_string(),
867 };
868 assert_eq!(
869 err.to_string(),
870 "Contract execution failed on contract.near: execution failed"
871 );
872 }
873
874 #[test]
875 fn test_rpc_error_function_call_display() {
876 let account_id: AccountId = "contract.near".parse().unwrap();
877 let err = RpcError::FunctionCall {
878 contract_id: account_id.clone(),
879 method_name: "my_method".to_string(),
880 panic: Some("assertion failed".to_string()),
881 logs: vec![],
882 };
883 assert_eq!(
884 err.to_string(),
885 "Function call error on contract.near.my_method: assertion failed"
886 );
887
888 let err_no_panic = RpcError::FunctionCall {
889 contract_id: account_id,
890 method_name: "other_method".to_string(),
891 panic: None,
892 logs: vec![],
893 };
894 assert_eq!(
895 err_no_panic.to_string(),
896 "Function call error on contract.near.other_method: unknown error"
897 );
898 }
899
900 #[test]
901 fn test_rpc_error_invalid_nonce_display() {
902 let err = RpcError::InvalidNonce {
903 tx_nonce: 5,
904 ak_nonce: 10,
905 };
906 assert_eq!(
907 err.to_string(),
908 "Invalid nonce: transaction nonce 5 must be greater than access key nonce 10"
909 );
910 }
911
912 #[test]
913 fn test_rpc_error_insufficient_balance_display() {
914 let err = RpcError::InsufficientBalance {
915 required: "100 NEAR".to_string(),
916 available: "50 NEAR".to_string(),
917 };
918 assert_eq!(
919 err.to_string(),
920 "Insufficient balance: required 100 NEAR, available 50 NEAR"
921 );
922 }
923
924 #[test]
925 fn test_rpc_error_gas_limit_exceeded_display() {
926 let err = RpcError::GasLimitExceeded {
927 gas_used: "300 Tgas".to_string(),
928 gas_limit: "200 Tgas".to_string(),
929 };
930 assert_eq!(
931 err.to_string(),
932 "Gas limit exceeded: used 300 Tgas, limit 200 Tgas"
933 );
934 }
935
936 #[test]
937 fn test_rpc_error_access_key_not_found_display() {
938 let account_id: AccountId = "alice.near".parse().unwrap();
939 let public_key: PublicKey = "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp"
940 .parse()
941 .unwrap();
942 let err = RpcError::AccessKeyNotFound {
943 account_id,
944 public_key: public_key.clone(),
945 };
946 assert!(err.to_string().contains("alice.near"));
947 assert!(err.to_string().contains(&public_key.to_string()));
948 }
949
950 #[test]
951 fn test_rpc_error_request_timeout_display() {
952 let err = RpcError::RequestTimeout {
953 message: "request timed out".to_string(),
954 transaction_hash: Some("abc123".to_string()),
955 };
956 assert_eq!(err.to_string(), "Request timeout: request timed out");
957 }
958
959 #[test]
964 fn test_error_no_signer_display() {
965 assert_eq!(
966 Error::NoSigner.to_string(),
967 "No signer configured. Use .credentials()/.signer() on NearBuilder, .with_signer() on the client, or .sign_with() on the transaction."
968 );
969 }
970
971 #[test]
972 fn test_error_no_signer_account_display() {
973 assert_eq!(
974 Error::NoSignerAccount.to_string(),
975 "No signer account ID. Call .default_account() on NearBuilder or use a signer with an account ID."
976 );
977 }
978
979 #[test]
980 fn test_error_config_display() {
981 assert_eq!(
982 Error::Config("invalid url".to_string()).to_string(),
983 "Invalid configuration: invalid url"
984 );
985 }
986
987 #[test]
988 fn test_error_transaction_failed_display() {
989 assert_eq!(
990 Error::TransactionFailed("execution error".to_string()).to_string(),
991 "Transaction failed: execution error"
992 );
993 }
994
995 #[test]
996 fn test_error_borsh_display() {
997 assert_eq!(
998 Error::Borsh("deserialization failed".to_string()).to_string(),
999 "Borsh error: deserialization failed"
1000 );
1001 }
1002
1003 #[test]
1004 fn test_error_from_parse_errors() {
1005 let parse_err = ParseAccountIdError::Empty;
1007 let err: Error = parse_err.into();
1008 assert!(matches!(err, Error::ParseAccountId(_)));
1009
1010 let parse_err = ParseAmountError::Overflow;
1012 let err: Error = parse_err.into();
1013 assert!(matches!(err, Error::ParseAmount(_)));
1014
1015 let parse_err = ParseGasError::Overflow;
1017 let err: Error = parse_err.into();
1018 assert!(matches!(err, Error::ParseGas(_)));
1019
1020 let parse_err = ParseKeyError::InvalidFormat;
1022 let err: Error = parse_err.into();
1023 assert!(matches!(err, Error::ParseKey(_)));
1024 }
1025
1026 #[test]
1027 fn test_error_from_rpc_error() {
1028 let rpc_err = RpcError::Timeout(3);
1029 let err: Error = rpc_err.into();
1030 assert!(matches!(err, Error::Rpc(_)));
1031 }
1032
1033 #[test]
1034 fn test_error_from_signer_error() {
1035 let signer_err = SignerError::InvalidSeedPhrase;
1036 let err: Error = signer_err.into();
1037 assert!(matches!(err, Error::Signing(_)));
1038 }
1039}