1use blake2::Blake2b512;
32use serde::{Deserialize, Serialize};
33use sha3::{Digest, Keccak256};
34use thiserror::Error;
35
36#[derive(Error, Debug, Clone, PartialEq, Eq)]
38pub enum ValidationError {
39 #[error("Invalid EVM address format: {0}")]
41 InvalidEvmAddress(String),
42
43 #[error("EIP-55 checksum validation failed for address: {0}")]
45 InvalidChecksum(String),
46
47 #[error("Invalid chain ID: expected {expected} for {chain}, got {actual}")]
49 InvalidChainId {
50 chain: String,
51 expected: u64,
52 actual: u64,
53 },
54
55 #[error("Chain ID not available for chain: {0}")]
57 ChainIdNotFound(String),
58
59 #[error("Invalid Substrate SS58 address format: {0}")]
61 InvalidSubstrateAddress(String),
62
63 #[error("SS58 checksum validation failed for address: {0}")]
65 InvalidSs58Checksum(String),
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
70pub enum ChainType {
71 Substrate,
73 Evm,
75 Hybrid,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
81pub enum Chain {
82 Polkadot,
85 Kusama,
87
88 Moonbeam,
91 Astar,
93 Acala,
95 Phala,
97 Bifrost,
99 Westend,
101 Paseo,
103
104 Ethereum,
107 BinanceSmartChain,
109 Polygon,
111 Avalanche,
113
114 Arbitrum,
117 Optimism,
119 ZkSync,
121 Base,
123}
124
125impl Chain {
126 pub fn chain_type(&self) -> ChainType {
127 match self {
128 Chain::Polkadot
130 | Chain::Kusama
131 | Chain::Acala
132 | Chain::Phala
133 | Chain::Bifrost
134 | Chain::Westend
135 | Chain::Paseo => ChainType::Substrate,
136
137 Chain::Ethereum
139 | Chain::BinanceSmartChain
140 | Chain::Polygon
141 | Chain::Avalanche
142 | Chain::Arbitrum
143 | Chain::Optimism
144 | Chain::ZkSync
145 | Chain::Base => ChainType::Evm,
146
147 Chain::Moonbeam | Chain::Astar => ChainType::Hybrid,
149 }
150 }
151
152 pub fn name(&self) -> &str {
154 match self {
155 Chain::Polkadot => "Polkadot",
157 Chain::Kusama => "Kusama",
158 Chain::Acala => "Acala",
159 Chain::Phala => "Phala",
160 Chain::Bifrost => "Bifrost",
161 Chain::Westend => "Westend",
162 Chain::Paseo => "Paseo",
163
164 Chain::Ethereum => "Ethereum",
166 Chain::BinanceSmartChain => "Binance Smart Chain",
167 Chain::Polygon => "Polygon",
168 Chain::Avalanche => "Avalanche",
169
170 Chain::Arbitrum => "Arbitrum",
172 Chain::Optimism => "Optimism",
173 Chain::ZkSync => "zkSync",
174 Chain::Base => "Base",
175
176 Chain::Moonbeam => "Moonbeam",
178 Chain::Astar => "Astar",
179 }
180 }
181
182 pub fn default_endpoint(&self) -> &str {
184 match self {
185 Chain::Polkadot => "wss://polkadot.api.onfinality.io/public-ws",
187 Chain::Kusama => "wss://kusama.api.onfinality.io/public-ws",
188 Chain::Acala => "wss://acala.api.onfinality.io/public-ws",
189 Chain::Phala => "wss://phala.api.onfinality.io/public-ws",
190 Chain::Bifrost => "wss://bifrost-polkadot.api.onfinality.io/public-ws",
191 Chain::Westend => "wss://westend-rpc.polkadot.io",
192 Chain::Paseo => "wss://paseo.rpc.amforc.com",
193
194 Chain::Ethereum => "https://eth.llamarpc.com",
196 Chain::BinanceSmartChain => "https://bsc.publicnode.com",
197 Chain::Polygon => "https://polygon-rpc.com",
198 Chain::Avalanche => "https://api.avax.network/ext/bc/C/rpc",
199
200 Chain::Arbitrum => "https://arb1.arbitrum.io/rpc",
202 Chain::Optimism => "https://mainnet.optimism.io",
203 Chain::ZkSync => "https://mainnet.era.zksync.io",
204 Chain::Base => "https://mainnet.base.org",
205
206 Chain::Moonbeam => "wss://moonbeam.api.onfinality.io/public-ws",
208 Chain::Astar => "wss://astar.api.onfinality.io/public-ws",
209 }
210 }
211
212 pub fn rpc_endpoints(&self) -> Vec<&str> {
214 match self {
215 Chain::Polkadot => vec![
217 "wss://polkadot.api.onfinality.io/public-ws",
218 "wss://rpc.ibp.network/polkadot",
219 "wss://polkadot.dotters.network",
220 ],
221 Chain::Kusama => vec![
222 "wss://kusama.api.onfinality.io/public-ws",
223 "wss://rpc.ibp.network/kusama",
224 "wss://kusama.dotters.network",
225 ],
226 Chain::Westend => vec![
227 "wss://westend-rpc.polkadot.io",
228 "wss://rpc.ibp.network/westend",
229 "wss://westend.dotters.network",
230 ],
231 _ => vec![self.default_endpoint()],
233 }
234 }
235
236 pub fn is_layer2(&self) -> bool {
238 matches!(
239 self,
240 Chain::Arbitrum | Chain::Optimism | Chain::ZkSync | Chain::Base
241 )
242 }
243
244 pub fn supports_smart_contracts(&self) -> bool {
246 match self.chain_type() {
247 ChainType::Evm => true,
248 ChainType::Hybrid => true,
249 ChainType::Substrate => matches!(
250 self,
251 Chain::Acala | Chain::Phala | Chain::Moonbeam | Chain::Astar
252 ),
253 }
254 }
255
256 pub fn is_testnet(&self) -> bool {
258 matches!(self, Chain::Westend | Chain::Paseo)
259 }
260
261 pub fn from_str_case_insensitive(s: &str) -> Option<Self> {
263 match s.to_lowercase().as_str() {
264 "polkadot" => Some(Chain::Polkadot),
266 "kusama" => Some(Chain::Kusama),
267 "acala" => Some(Chain::Acala),
268 "phala" => Some(Chain::Phala),
269 "bifrost" => Some(Chain::Bifrost),
270 "westend" => Some(Chain::Westend),
271 "paseo" => Some(Chain::Paseo),
272
273 "ethereum" | "eth" => Some(Chain::Ethereum),
275 "binance" | "bsc" | "binancesmartchain" => Some(Chain::BinanceSmartChain),
276 "polygon" | "matic" => Some(Chain::Polygon),
277 "avalanche" | "avax" => Some(Chain::Avalanche),
278
279 "arbitrum" | "arb" => Some(Chain::Arbitrum),
281 "optimism" | "op" => Some(Chain::Optimism),
282 "zksync" => Some(Chain::ZkSync),
283 "base" => Some(Chain::Base),
284
285 "moonbeam" => Some(Chain::Moonbeam),
287 "astar" => Some(Chain::Astar),
288
289 _ => None,
290 }
291 }
292
293 pub fn is_substrate_endpoint(endpoint: &str) -> bool {
295 endpoint.starts_with("ws://") || endpoint.starts_with("wss://")
296 }
297
298 pub fn is_evm_endpoint(endpoint: &str) -> bool {
300 endpoint.starts_with("http://") || endpoint.starts_with("https://")
301 }
302
303 pub fn chain_id(&self) -> Option<u64> {
308 match self {
309 Chain::Polkadot
311 | Chain::Kusama
312 | Chain::Acala
313 | Chain::Phala
314 | Chain::Bifrost
315 | Chain::Westend
316 | Chain::Paseo => None,
317
318 Chain::Ethereum => Some(1),
320 Chain::BinanceSmartChain => Some(56),
321 Chain::Polygon => Some(137),
322 Chain::Avalanche => Some(43114),
323
324 Chain::Arbitrum => Some(42161),
326 Chain::Optimism => Some(10),
327 Chain::ZkSync => Some(324),
328 Chain::Base => Some(8453),
329
330 Chain::Moonbeam => Some(1284),
332 Chain::Astar => Some(592),
333 }
334 }
335
336 pub fn validate_chain_id(&self, chain_id: u64) -> Result<(), ValidationError> {
341 match self.chain_id() {
342 None => Err(ValidationError::ChainIdNotFound(self.name().to_string())),
343 Some(expected) => {
344 if expected == chain_id {
345 Ok(())
346 } else {
347 Err(ValidationError::InvalidChainId {
348 chain: self.name().to_string(),
349 expected,
350 actual: chain_id,
351 })
352 }
353 }
354 }
355 }
356}
357
358fn is_valid_evm_format(addr: &str) -> bool {
360 if !addr.starts_with("0x") {
361 return false;
362 }
363
364 let hex_part = &addr[2..];
365 hex_part.len() == 40 && hex_part.chars().all(|c| c.is_ascii_hexdigit())
366}
367
368fn to_checksum_address(addr: &str) -> String {
373 let addr_lower = addr.trim_start_matches("0x").to_lowercase();
375
376 let mut hasher = Keccak256::new();
378 hasher.update(addr_lower.as_bytes());
379 let hash = hasher.finalize();
380
381 let mut result = String::from("0x");
383 for (i, ch) in addr_lower.chars().enumerate() {
384 if ch.is_ascii_digit() {
385 result.push(ch);
386 } else {
387 let hash_byte = hash[i / 2];
389 let nibble = if i % 2 == 0 {
390 hash_byte >> 4
391 } else {
392 hash_byte & 0x0f
393 };
394
395 if nibble >= 8 {
396 result.push(ch.to_ascii_uppercase());
397 } else {
398 result.push(ch);
399 }
400 }
401 }
402
403 result
404}
405
406fn validate_eip55_checksum(addr: &str) -> bool {
411 let hex_part = &addr[2..];
412
413 let all_lower = hex_part.chars().all(|c| !c.is_ascii_uppercase());
416 let all_upper = hex_part.chars().all(|c| !c.is_ascii_lowercase());
417
418 if all_lower || all_upper {
419 return true;
420 }
421
422 let checksummed = to_checksum_address(addr);
424 addr == checksummed
425}
426
427fn validate_ss58_address(addr: &str) -> bool {
434 let decoded = match bs58::decode(addr).into_vec() {
436 Ok(d) => d,
437 Err(_) => return false,
438 };
439
440 if decoded.len() < 3 {
442 return false;
443 }
444
445 let checksum_len =
447 if decoded.len() == 3 || decoded.len() == 4 || decoded.len() == 6 || decoded.len() == 10 {
448 1
449 } else {
450 2
451 };
452
453 let body_len = decoded.len() - checksum_len;
454 let body = &decoded[..body_len];
455 let checksum = &decoded[body_len..];
456
457 let mut hasher = Blake2b512::new();
459 hasher.update(b"SS58PRE");
460 hasher.update(body);
461 let hash = hasher.finalize();
462
463 &hash[..checksum_len] == checksum
465}
466
467fn validate_ss58_for_network(addr: &str, expected_ss58_format: u16) -> bool {
469 if !validate_ss58_address(addr) {
470 return false;
471 }
472
473 let decoded = match bs58::decode(addr).into_vec() {
475 Ok(d) => d,
476 Err(_) => return false,
477 };
478
479 if decoded.is_empty() {
480 return false;
481 }
482
483 let network_id = if decoded[0] & 0b01000000 == 0 {
485 u16::from(decoded[0] & 0b00111111)
487 } else {
488 if decoded.len() < 2 {
493 return false;
494 }
495 let lower = u16::from(decoded[0] & 0b00111111);
496 let upper = u16::from(decoded[1]);
497 ((lower << 8) | upper) | 0x0040
499 };
500
501 network_id == expected_ss58_format
502}
503
504pub fn extract_ss58_prefix(address: &str) -> Option<u16> {
507 use bs58;
508
509 let decoded = match bs58::decode(address).into_vec() {
511 Ok(bytes) => bytes,
512 Err(_) => return None,
513 };
514
515 if decoded.is_empty() {
516 return None;
517 }
518
519 let network_id = if decoded[0] & 0b01000000 == 0 {
521 u16::from(decoded[0] & 0b00111111)
523 } else {
524 if decoded.len() < 2 {
526 return None;
527 }
528 let lower = u16::from(decoded[0] & 0b00111111);
529 let upper = u16::from(decoded[1]);
530 ((lower << 8) | upper) | 0x0040
531 };
532
533 Some(network_id)
534}
535
536#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
538pub enum Address {
539 Substrate(String),
541 Evm(String),
543}
544
545impl Address {
546 pub fn substrate_checked(addr: impl Into<String>) -> Result<Self, ValidationError> {
569 let addr_str = addr.into();
570
571 if !validate_ss58_address(&addr_str) {
573 return Err(ValidationError::InvalidSubstrateAddress(addr_str));
574 }
575
576 Ok(Address::Substrate(addr_str))
577 }
578
579 pub fn substrate(addr: impl Into<String>) -> Self {
587 Address::Substrate(addr.into())
588 }
589
590 pub fn evm_checked(addr: impl Into<String>) -> Result<Self, ValidationError> {
616 let addr_str = addr.into();
617
618 if !is_valid_evm_format(&addr_str) {
620 return Err(ValidationError::InvalidEvmAddress(addr_str));
621 }
622
623 if !validate_eip55_checksum(&addr_str) {
625 return Err(ValidationError::InvalidChecksum(addr_str));
626 }
627
628 Ok(Address::Evm(addr_str))
629 }
630
631 pub fn evm(addr: impl Into<String>) -> Self {
639 Address::Evm(addr.into())
640 }
641
642 pub fn substrate_for_chain(
651 addr: impl Into<String>,
652 chain: &Chain,
653 ) -> Result<Self, ValidationError> {
654 let addr_str = addr.into();
655
656 let expected_ss58_format = match chain {
657 Chain::Polkadot => 0,
658 Chain::Kusama => 2,
659 Chain::Westend => 42,
660 Chain::Paseo => 42, Chain::Moonbeam => 1284,
662 Chain::Astar => 5,
663 Chain::Acala => 10,
664 Chain::Phala => 30,
665 Chain::Bifrost => 6,
666 _ => return Err(ValidationError::ChainIdNotFound(chain.name().to_string())),
667 };
668
669 if !validate_ss58_for_network(&addr_str, expected_ss58_format) {
670 return Err(ValidationError::InvalidSs58Checksum(format!(
671 "Address {} is not valid for network {} (expected SS58 format {})",
672 addr_str,
673 chain.name(),
674 expected_ss58_format
675 )));
676 }
677
678 Ok(Address::Substrate(addr_str))
679 }
680
681 pub fn to_checksum(&self) -> String {
686 match self {
687 Address::Evm(addr) => to_checksum_address(addr),
688 Address::Substrate(addr) => addr.clone(),
689 }
690 }
691
692 pub fn as_str(&self) -> &str {
693 match self {
694 Address::Substrate(s) | Address::Evm(s) => s,
695 }
696 }
697
698 pub fn validate(&self) -> Result<(), ValidationError> {
703 match self {
704 Address::Evm(addr) => {
705 if !is_valid_evm_format(addr) {
706 return Err(ValidationError::InvalidEvmAddress(addr.clone()));
707 }
708 if !validate_eip55_checksum(addr) {
709 return Err(ValidationError::InvalidChecksum(addr.clone()));
710 }
711 Ok(())
712 }
713 Address::Substrate(addr) => {
714 if !validate_ss58_address(addr) {
715 return Err(ValidationError::InvalidSubstrateAddress(addr.clone()));
716 }
717 Ok(())
718 }
719 }
720 }
721}
722
723impl std::fmt::Display for Address {
724 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
725 match self {
726 Address::Substrate(addr) => write!(f, "{}", addr),
727 Address::Evm(addr) => write!(f, "{}", addr),
728 }
729 }
730}
731
732#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
734pub enum TxStatus {
735 Pending,
737 InMempool,
739 Confirmed,
741 Finalized,
743 Failed,
745 Unknown,
747}
748
749#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
751pub struct TransactionStatus {
752 pub hash: String,
754 pub status: TxStatus,
756 pub block_number: Option<u64>,
758 pub block_hash: Option<String>,
760 pub gas_used: Option<u64>,
762 pub effective_gas_price: Option<u128>,
764 pub confirmations: Option<u32>,
766 pub error: Option<String>,
768}
769
770impl TransactionStatus {
771 pub fn pending(hash: String) -> Self {
772 Self {
773 hash,
774 status: TxStatus::Pending,
775 block_number: None,
776 block_hash: None,
777 gas_used: None,
778 effective_gas_price: None,
779 confirmations: None,
780 error: None,
781 }
782 }
783
784 pub fn confirmed(
786 hash: String,
787 block_number: u64,
788 block_hash: String,
789 gas_used: Option<u64>,
790 effective_gas_price: Option<u128>,
791 confirmations: Option<u32>,
792 ) -> Self {
793 Self {
794 hash,
795 status: TxStatus::Confirmed,
796 block_number: Some(block_number),
797 block_hash: Some(block_hash),
798 gas_used,
799 effective_gas_price,
800 confirmations,
801 error: None,
802 }
803 }
804
805 pub fn finalized(
807 hash: String,
808 block_number: u64,
809 block_hash: String,
810 gas_used: Option<u64>,
811 effective_gas_price: Option<u128>,
812 confirmations: Option<u32>,
813 ) -> Self {
814 Self {
815 hash,
816 status: TxStatus::Finalized,
817 block_number: Some(block_number),
818 block_hash: Some(block_hash),
819 gas_used,
820 effective_gas_price,
821 confirmations,
822 error: None,
823 }
824 }
825
826 pub fn failed(hash: String, error: String) -> Self {
828 Self {
829 hash,
830 status: TxStatus::Failed,
831 block_number: None,
832 block_hash: None,
833 gas_used: None,
834 effective_gas_price: None,
835 confirmations: None,
836 error: Some(error),
837 }
838 }
839
840 pub fn unknown(hash: String) -> Self {
841 Self {
842 hash,
843 status: TxStatus::Unknown,
844 block_number: None,
845 block_hash: None,
846 gas_used: None,
847 effective_gas_price: None,
848 confirmations: None,
849 error: None,
850 }
851 }
852
853 pub fn is_confirmed(&self) -> bool {
855 matches!(self.status, TxStatus::Confirmed | TxStatus::Finalized)
856 }
857
858 pub fn is_finalized(&self) -> bool {
860 matches!(self.status, TxStatus::Finalized)
861 }
862}
863
864impl Default for TransactionStatus {
865 fn default() -> Self {
866 Self {
867 hash: String::new(),
868 status: TxStatus::Unknown,
869 block_number: None,
870 block_hash: None,
871 gas_used: None,
872 effective_gas_price: None,
873 confirmations: None,
874 error: None,
875 }
876 }
877}
878
879pub use TransactionStatus as OldTransactionStatus;
881
882#[derive(Debug, Clone, Serialize, Deserialize)]
913pub struct Event {
914 pub name: String,
916 pub data: serde_json::Value,
918 pub block_number: Option<u64>,
920 pub tx_hash: Option<String>,
922 pub index: Option<u32>,
924}
925
926#[derive(Debug, Clone, Serialize, Deserialize)]
932pub struct EventFilter {
933 pub event_names: Option<Vec<String>>,
938 pub addresses: Option<Vec<Address>>,
943 pub from_block: Option<u64>,
948 pub to_block: Option<u64>,
953}
954
955#[derive(Debug, Clone, Serialize, Deserialize)]
957pub struct CrossChainTransaction {
958 pub id: String,
960 pub source_chain: Chain,
962 pub destination_chain: Chain,
964 pub source_tx_hash: Option<String>,
966 pub destination_tx_hash: Option<String>,
968 pub status: TransactionStatus,
970 pub timestamp: u64,
972}
973
974#[cfg(test)]
975mod tests {
976 use super::*;
977
978 #[test]
979 fn test_chain_type() {
980 assert_eq!(Chain::Polkadot.chain_type(), ChainType::Substrate);
981 assert_eq!(Chain::Ethereum.chain_type(), ChainType::Evm);
982 assert_eq!(Chain::Moonbeam.chain_type(), ChainType::Hybrid);
983 }
984
985 #[test]
986 fn test_address_creation() {
987 let sub_addr = Address::substrate("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY");
988 assert!(matches!(sub_addr, Address::Substrate(_)));
989
990 let evm_addr = Address::evm("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7");
991 assert!(matches!(evm_addr, Address::Evm(_)));
992 }
993
994 #[test]
995 fn test_chain_id() {
996 assert_eq!(Chain::Ethereum.chain_id(), Some(1));
998 assert_eq!(Chain::BinanceSmartChain.chain_id(), Some(56));
999 assert_eq!(Chain::Polygon.chain_id(), Some(137));
1000 assert_eq!(Chain::Avalanche.chain_id(), Some(43114));
1001 assert_eq!(Chain::Arbitrum.chain_id(), Some(42161));
1002 assert_eq!(Chain::Optimism.chain_id(), Some(10));
1003 assert_eq!(Chain::ZkSync.chain_id(), Some(324));
1004 assert_eq!(Chain::Base.chain_id(), Some(8453));
1005
1006 assert_eq!(Chain::Moonbeam.chain_id(), Some(1284));
1008 assert_eq!(Chain::Astar.chain_id(), Some(592));
1009
1010 assert_eq!(Chain::Polkadot.chain_id(), None);
1012 assert_eq!(Chain::Kusama.chain_id(), None);
1013 assert_eq!(Chain::Westend.chain_id(), None);
1014 assert_eq!(Chain::Paseo.chain_id(), None);
1015 }
1016
1017 #[test]
1018 fn test_chain_id_validation() {
1019 assert!(Chain::Ethereum.validate_chain_id(1).is_ok());
1021 assert!(Chain::BinanceSmartChain.validate_chain_id(56).is_ok());
1022 assert!(Chain::Polygon.validate_chain_id(137).is_ok());
1023
1024 assert!(Chain::Ethereum.validate_chain_id(56).is_err());
1026 assert!(Chain::Polygon.validate_chain_id(1).is_err());
1027
1028 assert!(Chain::Polkadot.validate_chain_id(1).is_err());
1030 }
1031
1032 #[test]
1033 fn test_eip55_valid_checksummed_addresses() {
1034 let valid_addresses = vec![
1035 "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
1036 "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
1037 "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
1038 "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
1039 ];
1040
1041 for addr in valid_addresses {
1042 let result = Address::evm_checked(addr);
1043 assert!(
1044 result.is_ok(),
1045 "Address {} should be valid, got error: {:?}",
1046 addr,
1047 result.err()
1048 );
1049 }
1050 }
1051
1052 #[test]
1053 fn test_eip55_lowercase_addresses() {
1054 let lowercase_addr = "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed";
1056 assert!(Address::evm_checked(lowercase_addr).is_ok());
1057
1058 let lowercase_addr2 = "0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359";
1059 assert!(Address::evm_checked(lowercase_addr2).is_ok());
1060 }
1061
1062 #[test]
1063 fn test_eip55_uppercase_addresses() {
1064 let uppercase_addr = "0x5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED";
1066 assert!(Address::evm_checked(uppercase_addr).is_ok());
1067 }
1068
1069 #[test]
1070 fn test_eip55_invalid_checksum() {
1071 let invalid_addr = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAeD";
1073 let result = Address::evm_checked(invalid_addr);
1074 assert!(result.is_err());
1075 assert!(matches!(
1076 result.unwrap_err(),
1077 ValidationError::InvalidChecksum(_)
1078 ));
1079 }
1080
1081 #[test]
1082 fn test_eip55_invalid_format() {
1083 let no_prefix = "5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed";
1085 let result = Address::evm_checked(no_prefix);
1086 assert!(result.is_err());
1087 assert!(matches!(
1088 result.unwrap_err(),
1089 ValidationError::InvalidEvmAddress(_)
1090 ));
1091
1092 let too_short = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeA";
1094 let result = Address::evm_checked(too_short);
1095 assert!(result.is_err());
1096
1097 let too_long = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAedAA";
1099 let result = Address::evm_checked(too_long);
1100 assert!(result.is_err());
1101
1102 let invalid_hex = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAeG";
1104 let result = Address::evm_checked(invalid_hex);
1105 assert!(result.is_err());
1106 }
1107
1108 #[test]
1109 fn test_to_checksum_address() {
1110 let lowercase = "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed";
1111 let checksummed = to_checksum_address(lowercase);
1112 assert_eq!(checksummed, "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed");
1113
1114 let lowercase2 = "0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359";
1115 let checksummed2 = to_checksum_address(lowercase2);
1116 assert_eq!(checksummed2, "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359");
1117 }
1118
1119 #[test]
1120 fn test_address_to_checksum_method() {
1121 let addr = Address::evm("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed");
1122 assert_eq!(
1123 addr.to_checksum(),
1124 "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
1125 );
1126
1127 let sub_addr = Address::substrate("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY");
1129 assert_eq!(
1130 sub_addr.to_checksum(),
1131 "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
1132 );
1133 }
1134
1135 #[test]
1136 fn test_address_validate() {
1137 let addr = Address::evm("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed");
1139 assert!(addr.validate().is_ok());
1140
1141 let addr = Address::evm("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed");
1143 assert!(addr.validate().is_ok());
1144
1145 let addr = Address::evm("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAeD");
1147 assert!(addr.validate().is_err());
1148
1149 let addr = Address::evm("invalid");
1151 assert!(addr.validate().is_err());
1152
1153 let addr = Address::substrate("15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5");
1155 assert!(addr.validate().is_ok());
1156
1157 let addr = Address::substrate("invalid");
1159 assert!(addr.validate().is_err());
1160 }
1161
1162 #[test]
1163 fn test_is_testnet() {
1164 assert!(Chain::Westend.is_testnet());
1165 assert!(Chain::Paseo.is_testnet());
1166 assert!(!Chain::Polkadot.is_testnet());
1167 assert!(!Chain::Ethereum.is_testnet());
1168 }
1169
1170 #[test]
1171 fn test_substrate_ss58_validation_valid_addresses() {
1172 let valid_addresses = vec![
1174 "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
1175 "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
1176 "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM",
1177 ];
1178
1179 for addr in valid_addresses {
1180 let result = Address::substrate_checked(addr);
1181 assert!(
1182 result.is_ok(),
1183 "Address {} should be valid, got error: {:?}",
1184 addr,
1185 result.err()
1186 );
1187 }
1188 }
1189
1190 #[test]
1191 fn test_substrate_ss58_validation_invalid_addresses() {
1192 let invalid_addresses = vec![
1194 "invalid", "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQ", "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY123", "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQX", ];
1199
1200 for addr in invalid_addresses {
1201 let result = Address::substrate_checked(addr);
1202 assert!(
1203 result.is_err(),
1204 "Address {} should be invalid but was accepted",
1205 addr
1206 );
1207 }
1208 }
1209
1210 #[test]
1211 fn test_substrate_ss58_validation_error_types() {
1212 let invalid_format = "not-base58!@#";
1213 let result = Address::substrate_checked(invalid_format);
1214 assert!(result.is_err());
1215 match result.unwrap_err() {
1216 ValidationError::InvalidSubstrateAddress(_) => (),
1217 _ => panic!("Expected InvalidSubstrateAddress error"),
1218 }
1219
1220 let invalid_checksum = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQX";
1221 let result = Address::substrate_checked(invalid_checksum);
1222 assert!(result.is_err());
1223 }
1225
1226 #[test]
1227 fn test_validation_error_messages() {
1228 let invalid_evm = "0xinvalid";
1229 let result = Address::evm_checked(invalid_evm);
1230 assert!(result.is_err());
1231 let err = result.unwrap_err();
1232 assert!(err.to_string().contains("Invalid EVM address"));
1233
1234 let invalid_checksum = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAeD";
1235 let result = Address::evm_checked(invalid_checksum);
1236 assert!(result.is_err());
1237 let err = result.unwrap_err();
1238 assert!(err.to_string().contains("checksum"));
1239 }
1240
1241 #[test]
1242 fn test_overflow_protection_in_validation() {
1243 let long_string = "a".repeat(1000);
1244 let result = Address::evm_checked(&long_string);
1245 assert!(result.is_err());
1246
1247 let result = Address::substrate_checked(&long_string);
1248 assert!(result.is_err());
1249 }
1250}