1use crate::detectors::detect_address;
11use crate::input::{
12 classify_input, extract_characteristics, match_input_with_metadata, InputCharacteristics,
13 InputPossibility,
14};
15use crate::pipelines::addresses::execute_pipeline;
16use crate::registry::{PublicKeyType, Registry};
17use crate::shared::derivation::decode_public_key;
18use crate::Error;
19use serde_json::json;
20
21#[derive(Debug, Clone)]
23pub struct IdentificationCandidate {
24 pub input_type: InputType,
26 pub chain: String,
28 pub encoding: crate::registry::EncodingType,
30 pub normalized: String,
32 pub confidence: f64,
34 pub reasoning: String,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum InputType {
41 Address,
43 PublicKey,
45 }
47
48pub fn identify(input: &str) -> Result<Vec<IdentificationCandidate>, Error> {
59 let chars = extract_characteristics(input);
61
62 let possibilities = classify_input(input, &chars)?;
64
65 let registry = Registry::get();
67 let chain_matches = match_input_with_metadata(input, &chars, &possibilities, registry);
68
69 let results: Vec<IdentificationCandidate> = chain_matches
71 .into_iter()
72 .flat_map(|chain_match| match chain_match.possibility {
73 InputPossibility::Address => {
74 try_address_detection_for_chain(input, &chars, &chain_match.chain_id)
76 }
77 InputPossibility::PublicKey { key_type } => {
78 try_public_key_derivation_for_chain(input, &chars, key_type, &chain_match.chain_id)
80 }
81 })
82 .collect();
83
84 let mut sorted_results = results;
87 sorted_results.sort_by(|a, b| {
88 b.confidence
89 .partial_cmp(&a.confidence)
90 .unwrap_or(std::cmp::Ordering::Equal)
91 });
92
93 if sorted_results.is_empty() {
94 Err(Error::InvalidInput(format!(
95 "Unable to identify address format: {}",
96 input
97 )))
98 } else {
99 Ok(sorted_results)
100 }
101}
102
103fn try_address_detection_for_chain(
105 input: &str,
106 chars: &InputCharacteristics,
107 chain_id: &str,
108) -> Vec<IdentificationCandidate> {
109 let registry = Registry::get();
110
111 let chain_metadata = match registry.chains.iter().find(|c| c.id == chain_id) {
113 Some(chain) => chain,
114 None => return Vec::new(),
115 };
116
117 chain_metadata
118 .address_formats
119 .iter()
120 .filter_map(|addr_format| {
121 detect_address(input, chars, addr_format, chain_id.to_string())
123 .ok()
124 .flatten()
125 })
126 .map(|result| IdentificationCandidate {
127 input_type: InputType::Address,
128 chain: result.chain,
129 encoding: result.encoding,
130 normalized: result.normalized,
131 confidence: result.confidence,
132 reasoning: result.reasoning,
133 })
134 .collect()
135}
136
137fn try_public_key_derivation_for_chain(
139 input: &str,
140 chars: &InputCharacteristics,
141 key_type: crate::input::DetectedKeyType,
142 chain_id: &str,
143) -> Vec<IdentificationCandidate> {
144 let key_bytes = match decode_public_key(input, chars, key_type) {
146 Ok(bytes) => bytes,
147 Err(_) => return Vec::new(),
148 };
149
150 let registry = Registry::get();
151
152 let chain_config = match registry.get_chain_config(chain_id) {
154 Some(config) => config,
155 None => return Vec::new(),
156 };
157
158 if chain_config.requires_stake_key {
160 return Vec::new();
161 }
162
163 let params = json!(chain_config.address_params);
165
166 match execute_pipeline(&chain_config.address_pipeline, &key_bytes, ¶ms) {
168 Ok(derived_address) => {
169 let derived_chars = extract_characteristics(&derived_address);
171 let chain_metadata = match registry.chains.iter().find(|c| c.id == chain_id) {
172 Some(chain) => chain,
173 None => return Vec::new(),
174 };
175
176 let matches = chain_metadata
177 .address_formats
178 .iter()
179 .any(|addr_format| addr_format.validate_raw(&derived_address, &derived_chars));
180
181 if matches {
182 let curve = match key_type {
183 crate::input::DetectedKeyType::Secp256k1 { .. } => PublicKeyType::Secp256k1,
184 crate::input::DetectedKeyType::Ed25519 => PublicKeyType::Ed25519,
185 crate::input::DetectedKeyType::Sr25519 => PublicKeyType::Sr25519,
186 };
187
188 vec![IdentificationCandidate {
189 input_type: InputType::PublicKey,
190 chain: chain_id.to_string(),
191 encoding: chain_metadata.address_formats[0].encoding,
192 normalized: derived_address,
193 confidence: 0.8, reasoning: format!(
195 "Derived from {} public key using {} pipeline",
196 curve_name(curve),
197 chain_config.address_pipeline
198 ),
199 }]
200 } else {
201 Vec::new()
202 }
203 }
204 Err(_) => Vec::new(),
205 }
206}
207
208fn curve_name(key_type: PublicKeyType) -> &'static str {
210 match key_type {
211 PublicKeyType::Secp256k1 => "secp256k1",
212 PublicKeyType::Ed25519 => "ed25519",
213 PublicKeyType::Sr25519 => "sr25519",
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_identify_empty_input() {
223 let result = identify("");
224 assert!(result.is_err());
225 }
226
227 #[test]
228 fn test_identify_invalid_input() {
229 let result = identify("not-an-address");
230 assert!(result.is_err());
231 }
232
233 #[test]
234 fn test_identify_evm_address_full_pipeline() {
235 let input = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
237 let result = identify(input);
238
239 if result.is_ok() {
241 let candidates = result.unwrap();
242 assert!(!candidates.is_empty());
243 assert!(candidates.iter().any(|c| c.chain == "ethereum"));
245 for i in 1..candidates.len() {
247 assert!(candidates[i - 1].confidence >= candidates[i].confidence);
248 }
249 assert!(candidates[0].confidence > 0.0);
251 assert_ne!(candidates[0].normalized, input);
253 assert!(candidates[0].normalized.starts_with("0x"));
254 assert_eq!(candidates[0].normalized.len(), 42);
255 } else {
256 assert!(result.is_err());
258 }
259 }
260
261 #[test]
262 fn test_identify_evm_address_mixed_case_full_pipeline() {
263 let input = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e";
265 let result = identify(input);
266
267 if result.is_ok() {
269 let candidates = result.unwrap();
270 assert!(!candidates.is_empty());
271 assert!(candidates.len() >= 1);
273 let evm_chains = [
275 "ethereum",
276 "polygon",
277 "bsc",
278 "avalanche",
279 "arbitrum",
280 "optimism",
281 "base",
282 "fantom",
283 "celo",
284 "gnosis",
285 ];
286 assert!(candidates
287 .iter()
288 .all(|c| evm_chains.contains(&c.chain.as_str())));
289 for i in 1..candidates.len() {
291 assert!(candidates[i - 1].confidence >= candidates[i].confidence);
292 }
293 } else {
294 assert!(result.is_err());
296 }
297 }
298
299 #[test]
300 fn test_identify_tron_full_pipeline() {
301 use base58::ToBase58;
303 use sha2::{Digest, Sha256};
304
305 let version = 0x41u8;
306 let address_bytes = vec![0u8; 20];
307 let payload = [&[version], address_bytes.as_slice()].concat();
308 let hash1 = Sha256::digest(&payload);
309 let hash2 = Sha256::digest(hash1);
310 let checksum = &hash2[..4];
311 let full_bytes = [payload, checksum.to_vec()].concat();
312 let tron_addr = full_bytes.to_base58();
313
314 let result = identify(&tron_addr);
315
316 if result.is_ok() {
318 let candidates = result.unwrap();
319 assert!(!candidates.is_empty());
320 if candidates.iter().any(|c| c.chain == "tron") {
322 let tron_candidate = candidates.iter().find(|c| c.chain == "tron").unwrap();
324 assert!(!tron_candidate.normalized.is_empty());
325 assert!(tron_candidate.confidence > 0.0);
326 }
327 for i in 1..candidates.len() {
329 assert!(candidates[i - 1].confidence >= candidates[i].confidence);
330 }
331 } else {
332 assert!(result.is_err());
334 }
335 }
336
337 #[test]
338 fn test_identify_full_pipeline_structure() {
339 let input = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
341 let result = identify(input);
342
343 match result {
345 Ok(candidates) => {
346 for candidate in &candidates {
348 assert!(!candidate.chain.is_empty());
349 assert!(!candidate.normalized.is_empty());
350 assert!(candidate.confidence >= 0.0 && candidate.confidence <= 1.0);
351 assert!(!candidate.reasoning.is_empty());
352 match candidate.encoding {
354 crate::registry::EncodingType::Hex => {
355 assert!(candidate.normalized.starts_with("0x"))
356 }
357 _ => {}
358 }
359 }
360 for i in 1..candidates.len() {
362 assert!(candidates[i - 1].confidence >= candidates[i].confidence);
363 }
364 }
365 Err(e) => {
366 match e {
368 Error::InvalidInput(msg) => {
369 assert!(!msg.is_empty());
370 assert!(msg.contains(input) || msg.contains("Unable to"));
371 }
372 Error::NotImplemented => {}
373 }
374 }
375 }
376 }
377
378 #[test]
384 fn test_identify_evm_burn_address() {
385 let input = "0x000000000000000000000000000000000000dEaD";
387 let result = identify(input).unwrap();
388
389 assert!(!result.is_empty());
390 let evm_chains = [
392 "ethereum",
393 "polygon",
394 "bsc",
395 "avalanche",
396 "arbitrum",
397 "optimism",
398 "base",
399 "fantom",
400 "celo",
401 "gnosis",
402 ];
403 let matched_chains: Vec<_> = result.iter().map(|c| c.chain.as_str()).collect();
404 assert!(evm_chains
405 .iter()
406 .any(|&chain| matched_chains.contains(&chain)));
407
408 assert!(result.iter().all(|c| c.input_type == InputType::Address));
410 assert!(result[0].normalized.starts_with("0x"));
412 assert_eq!(result[0].normalized.len(), 42);
413 }
414
415 #[test]
416 fn test_identify_evm_vitalik_address() {
417 let input = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
419 let result = identify(input).unwrap();
420
421 assert!(!result.is_empty());
422 let evm_chains = [
424 "ethereum",
425 "polygon",
426 "bsc",
427 "avalanche",
428 "arbitrum",
429 "optimism",
430 "base",
431 "fantom",
432 "celo",
433 "gnosis",
434 ];
435 let matched_chains: Vec<_> = result.iter().map(|c| c.chain.as_str()).collect();
436 assert!(evm_chains
437 .iter()
438 .any(|&chain| matched_chains.contains(&chain)));
439
440 assert!(result[0].normalized.starts_with("0x"));
442 assert_eq!(result[0].normalized.len(), 42);
443 }
444
445 #[test]
446 fn test_identify_evm_usdt_contract() {
447 let input = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
449 let result = identify(input).unwrap();
450
451 assert!(!result.is_empty());
452 let evm_chains = [
454 "ethereum",
455 "polygon",
456 "bsc",
457 "avalanche",
458 "arbitrum",
459 "optimism",
460 "base",
461 "fantom",
462 "celo",
463 "gnosis",
464 ];
465 let matched_chains: Vec<_> = result.iter().map(|c| c.chain.as_str()).collect();
466 assert!(evm_chains
467 .iter()
468 .any(|&chain| matched_chains.contains(&chain)));
469 }
470
471 #[test]
472 fn test_identify_evm_lowercase() {
473 let input = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
475 let result = identify(input).unwrap();
476
477 assert!(!result.is_empty());
478 assert!(result[0].normalized.starts_with("0x"));
480 assert_eq!(result[0].normalized.len(), 42);
481 }
482
483 #[test]
484 fn test_identify_evm_uppercase() {
485 let input = "0xD8DA6BF26964AF9D7EED9E03E53415D37AA96045";
487 let result = identify(input).unwrap();
488
489 assert!(!result.is_empty());
490 assert!(result[0].normalized.starts_with("0x"));
492 assert_eq!(result[0].normalized.len(), 42);
493 }
494
495 #[test]
497 fn test_identify_bitcoin_p2pkh() {
498 let input = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
500 let result = identify(input).unwrap();
501
502 assert!(!result.is_empty());
503 assert!(result.iter().any(|c| c.chain == "bitcoin"));
505 assert!(result.iter().all(|c| c.input_type == InputType::Address));
507 assert!(result[0].confidence > 0.0);
508 }
509
510 #[test]
511 fn test_identify_bitcoin_p2sh() {
512 let input = "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy";
514 let result = identify(input).unwrap();
515
516 assert!(!result.is_empty());
517 assert!(result.iter().any(|c| c.chain == "bitcoin"));
519 }
520
521 #[test]
522 fn test_identify_bitcoin_bech32() {
523 let input = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
525 let result = identify(input).unwrap();
526
527 assert!(!result.is_empty());
528 assert!(result.iter().any(|c| c.chain == "bitcoin"));
530 assert_eq!(result[0].normalized, input.to_lowercase());
532 }
533
534 #[test]
535 fn test_identify_litecoin() {
536 let input = "LcNS6c8RddAMjewDrUAAi8BzecKoosnkN3";
538 let result = identify(input).unwrap();
539
540 assert!(!result.is_empty());
541 assert!(result.iter().any(|c| c.chain == "litecoin"));
543 }
544
545 #[test]
546 fn test_identify_dogecoin() {
547 let input = "DH5yaieqoZN36fDVciNyRueRGvGLR3mr7L";
549 let result = identify(input).unwrap();
550
551 assert!(!result.is_empty());
552 assert!(result.iter().any(|c| c.chain == "dogecoin"));
554 }
555
556 #[test]
558 fn test_identify_cosmos_hub() {
559 let input = "cosmos1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
563 let result = identify(input);
564
565 match result {
567 Ok(candidates) => {
568 assert!(!candidates.is_empty());
569 if candidates.iter().any(|c| c.chain == "cosmos_hub") {
571 let cosmos_match = candidates.iter().find(|c| c.chain == "cosmos_hub").unwrap();
572 assert_eq!(cosmos_match.input_type, InputType::Address);
573 assert_eq!(cosmos_match.normalized, input.to_lowercase());
574 }
575 }
576 Err(_) => {
577 }
581 }
582 }
583
584 #[test]
585 fn test_identify_osmosis() {
586 let input = "osmo1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
588 let result = identify(input);
589
590 if let Ok(candidates) = result {
591 if candidates.iter().any(|c| c.chain == "osmosis") {
592 let osmosis_match = candidates.iter().find(|c| c.chain == "osmosis").unwrap();
593 assert_eq!(osmosis_match.input_type, InputType::Address);
594 }
595 }
596 }
597
598 #[test]
599 fn test_identify_juno() {
600 let input = "juno1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
602 let result = identify(input);
603
604 if let Ok(candidates) = result {
605 if candidates.iter().any(|c| c.chain == "juno") {
606 let juno_match = candidates.iter().find(|c| c.chain == "juno").unwrap();
607 assert_eq!(juno_match.input_type, InputType::Address);
608 }
609 }
610 }
611
612 #[test]
613 fn test_identify_akash() {
614 let input = "akash1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
616 let result = identify(input);
617
618 if let Ok(candidates) = result {
619 if candidates.iter().any(|c| c.chain == "akash") {
620 let akash_match = candidates.iter().find(|c| c.chain == "akash").unwrap();
621 assert_eq!(akash_match.input_type, InputType::Address);
622 }
623 }
624 }
625
626 #[test]
627 fn test_identify_stargaze() {
628 let input = "stars1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
630 let result = identify(input);
631
632 if let Ok(candidates) = result {
633 if candidates.iter().any(|c| c.chain == "stargaze") {
634 let stargaze_match = candidates.iter().find(|c| c.chain == "stargaze").unwrap();
635 assert_eq!(stargaze_match.input_type, InputType::Address);
636 }
637 }
638 }
639
640 #[test]
641 fn test_identify_secret_network() {
642 let input = "secret1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
644 let result = identify(input);
645
646 if let Ok(candidates) = result {
647 if candidates.iter().any(|c| c.chain == "secret_network") {
648 let secret_match = candidates
649 .iter()
650 .find(|c| c.chain == "secret_network")
651 .unwrap();
652 assert_eq!(secret_match.input_type, InputType::Address);
653 }
654 }
655 }
656
657 #[test]
658 fn test_identify_terra() {
659 let input = "terra1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
661 let result = identify(input);
662
663 if let Ok(candidates) = result {
664 if candidates.iter().any(|c| c.chain == "terra") {
665 let terra_match = candidates.iter().find(|c| c.chain == "terra").unwrap();
666 assert_eq!(terra_match.input_type, InputType::Address);
667 }
668 }
669 }
670
671 #[test]
672 fn test_identify_kava() {
673 let input = "kava1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
675 let result = identify(input);
676
677 if let Ok(candidates) = result {
678 if candidates.iter().any(|c| c.chain == "kava") {
679 let kava_match = candidates.iter().find(|c| c.chain == "kava").unwrap();
680 assert_eq!(kava_match.input_type, InputType::Address);
681 }
682 }
683 }
684
685 #[test]
686 fn test_identify_regen() {
687 let input = "regen1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
689 let result = identify(input);
690
691 if let Ok(candidates) = result {
692 if candidates.iter().any(|c| c.chain == "regen") {
693 let regen_match = candidates.iter().find(|c| c.chain == "regen").unwrap();
694 assert_eq!(regen_match.input_type, InputType::Address);
695 }
696 }
697 }
698
699 #[test]
700 fn test_identify_sentinel() {
701 let input = "sent1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
703 let result = identify(input);
704
705 if let Ok(candidates) = result {
706 if candidates.iter().any(|c| c.chain == "sentinel") {
707 let sentinel_match = candidates.iter().find(|c| c.chain == "sentinel").unwrap();
708 assert_eq!(sentinel_match.input_type, InputType::Address);
709 }
710 }
711 }
712
713 #[test]
715 fn test_identify_polkadot() {
716 let input = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
718 let result = identify(input).unwrap();
719
720 assert!(!result.is_empty());
721 assert!(result.iter().any(|c| c.chain == "polkadot"));
723 }
724
725 #[test]
726 fn test_identify_kusama() {
727 let input = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty";
729 let result = identify(input).unwrap();
730
731 assert!(!result.is_empty());
732 assert!(result.iter().any(|c| c.chain == "kusama"));
734 }
735
736 #[test]
737 fn test_identify_substrate() {
738 let input = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
741 let result = identify(input).unwrap();
742
743 assert!(!result.is_empty());
744 let substrate_chains = ["polkadot", "kusama", "substrate"];
746 assert!(result
747 .iter()
748 .any(|c| substrate_chains.contains(&c.chain.as_str())));
749 }
750
751 #[test]
753 fn test_identify_solana() {
754 let input = "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM";
756 let result = identify(input).unwrap();
757
758 assert!(!result.is_empty());
759 assert!(result.iter().any(|c| c.chain == "solana"));
761 }
762
763 #[test]
764 fn test_identify_solana_usdc_mint() {
765 let input = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
767 let result = identify(input).unwrap();
768
769 assert!(!result.is_empty());
770 assert!(result.iter().any(|c| c.chain == "solana"));
772 }
773
774 #[test]
775 fn test_identify_tron() {
776 let input = "T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb";
778 let result = identify(input).unwrap();
779
780 assert!(!result.is_empty());
781 assert!(result.iter().any(|c| c.chain == "tron"));
783 }
784
785 #[test]
786 fn test_identify_cardano() {
787 let input = "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnjhl2zqwpg7h3vj6";
790 let result = identify(input);
791
792 if let Ok(candidates) = result {
794 assert!(!candidates.is_empty());
795 if candidates.iter().any(|c| c.chain == "cardano") {
797 let cardano_match = candidates.iter().find(|c| c.chain == "cardano").unwrap();
798 assert_eq!(cardano_match.input_type, InputType::Address);
799 assert_eq!(cardano_match.normalized, input.to_lowercase());
801 }
802 }
803 }
806
807 #[test]
813 fn test_identify_secp256k1_compressed_evm() {
814 let input = "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
817 let result = identify(input).unwrap();
818
819 assert!(!result.is_empty());
820 let evm_chains = [
822 "ethereum",
823 "polygon",
824 "bsc",
825 "avalanche",
826 "arbitrum",
827 "optimism",
828 "base",
829 "fantom",
830 "celo",
831 "gnosis",
832 ];
833 let matched_chains: Vec<_> = result.iter().map(|c| c.chain.as_str()).collect();
834 assert!(evm_chains
835 .iter()
836 .any(|&chain| matched_chains.contains(&chain)));
837 assert!(result
839 .iter()
840 .all(|c| c.input_type == InputType::PublicKey || c.input_type == InputType::Address));
841 }
842
843 #[test]
844 fn test_identify_secp256k1_uncompressed_evm() {
845 let input = "0x0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8";
847 let result = identify(input).unwrap();
848
849 assert!(!result.is_empty());
850 let evm_chains = [
852 "ethereum",
853 "polygon",
854 "bsc",
855 "avalanche",
856 "arbitrum",
857 "optimism",
858 "base",
859 "fantom",
860 "celo",
861 "gnosis",
862 ];
863 let matched_chains: Vec<_> = result.iter().map(|c| c.chain.as_str()).collect();
864 assert!(evm_chains
865 .iter()
866 .any(|&chain| matched_chains.contains(&chain)));
867 }
868
869 #[test]
870 fn test_identify_secp256k1_bitcoin() {
871 let input = "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
873 let result = identify(input);
874
875 if let Ok(candidates) = result {
876 if candidates.iter().any(|c| c.chain == "bitcoin") {
877 let bitcoin_match = candidates.iter().find(|c| c.chain == "bitcoin").unwrap();
878 assert!(
879 bitcoin_match.input_type == InputType::PublicKey
880 || bitcoin_match.input_type == InputType::Address
881 );
882 }
883 }
884 }
885
886 #[test]
887 fn test_identify_secp256k1_tron() {
888 let input = "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
890 let result = identify(input);
891
892 if let Ok(candidates) = result {
893 if candidates.iter().any(|c| c.chain == "tron") {
894 let tron_match = candidates.iter().find(|c| c.chain == "tron").unwrap();
895 assert!(
896 tron_match.input_type == InputType::PublicKey
897 || tron_match.input_type == InputType::Address
898 );
899 }
900 }
901 }
902
903 #[test]
905 fn test_identify_ed25519_solana() {
906 let input = "0x9f7f8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9";
909 let result = identify(input);
910
911 if let Ok(candidates) = result {
912 if candidates.iter().any(|c| {
913 c.chain == "solana"
914 || c.chain == "cardano"
915 || c.chain.starts_with("cosmos")
916 || c.chain == "polkadot"
917 || c.chain == "kusama"
918 }) {
919 let ed25519_match = candidates
921 .iter()
922 .find(|c| {
923 c.chain == "solana"
924 || c.chain == "cardano"
925 || c.chain.starts_with("cosmos")
926 || c.chain == "polkadot"
927 || c.chain == "kusama"
928 })
929 .unwrap();
930 assert!(
931 ed25519_match.input_type == InputType::PublicKey
932 || ed25519_match.input_type == InputType::Address
933 );
934 }
935 }
936 }
937
938 #[test]
939 fn test_identify_ed25519_cardano() {
940 let input = "0x9f7f8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9";
942 let result = identify(input);
943
944 if let Ok(candidates) = result {
946 if candidates.iter().any(|c| c.chain == "cardano") {
947 let cardano_match = candidates.iter().find(|c| c.chain == "cardano").unwrap();
948 assert!(cardano_match.confidence > 0.0);
949 }
950 }
951 }
952
953 #[test]
954 fn test_identify_ed25519_cosmos() {
955 let input = "0x9f7f8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9";
957 let result = identify(input);
958
959 if let Ok(candidates) = result {
960 let cosmos_chains = [
961 "cosmos_hub",
962 "osmosis",
963 "juno",
964 "akash",
965 "stargaze",
966 "secret_network",
967 "terra",
968 "kava",
969 "regen",
970 "sentinel",
971 ];
972 let matched_chains: Vec<_> = candidates.iter().map(|c| c.chain.as_str()).collect();
973 if cosmos_chains
974 .iter()
975 .any(|&chain| matched_chains.contains(&chain))
976 {
977 let cosmos_match = candidates
979 .iter()
980 .find(|c| cosmos_chains.contains(&c.chain.as_str()))
981 .unwrap();
982 assert!(
983 cosmos_match.input_type == InputType::PublicKey
984 || cosmos_match.input_type == InputType::Address
985 );
986 }
987 }
988 }
989
990 #[test]
991 fn test_identify_ed25519_substrate() {
992 let input = "0x9f7f8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9";
994 let result = identify(input);
995
996 if let Ok(candidates) = result {
997 let substrate_chains = ["polkadot", "kusama", "substrate"];
998 let matched_chains: Vec<_> = candidates.iter().map(|c| c.chain.as_str()).collect();
999 if substrate_chains
1000 .iter()
1001 .any(|&chain| matched_chains.contains(&chain))
1002 {
1003 let substrate_match = candidates
1005 .iter()
1006 .find(|c| substrate_chains.contains(&c.chain.as_str()))
1007 .unwrap();
1008 assert!(
1009 substrate_match.input_type == InputType::PublicKey
1010 || substrate_match.input_type == InputType::Address
1011 );
1012 }
1013 }
1014 }
1015
1016 #[test]
1018 fn test_identify_sr25519_substrate() {
1019 let input = "0x9f7f8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9";
1021 let result = identify(input);
1022
1023 if let Ok(candidates) = result {
1024 let substrate_chains = ["polkadot", "kusama", "substrate"];
1025 let matched_chains: Vec<_> = candidates.iter().map(|c| c.chain.as_str()).collect();
1026 if substrate_chains
1027 .iter()
1028 .any(|&chain| matched_chains.contains(&chain))
1029 {
1030 let substrate_match = candidates
1032 .iter()
1033 .find(|c| substrate_chains.contains(&c.chain.as_str()))
1034 .unwrap();
1035 assert!(
1036 substrate_match.input_type == InputType::PublicKey
1037 || substrate_match.input_type == InputType::Address
1038 );
1039 }
1040 }
1041 }
1042
1043 #[test]
1044 fn test_try_address_detection_evm() {
1045 let input = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
1047 let chars = extract_characteristics(input);
1048 let chain_id = "ethereum";
1049
1050 let candidates = try_address_detection_for_chain(input, &chars, chain_id);
1051
1052 if !candidates.is_empty() {
1054 assert!(candidates
1056 .iter()
1057 .all(|c| c.input_type == InputType::Address));
1058 assert!(candidates.iter().all(|c| c.chain == "ethereum"));
1060 assert!(candidates[0].normalized.starts_with("0x"));
1062 assert_eq!(candidates[0].normalized.len(), 42);
1063 assert!(candidates[0].confidence > 0.0);
1065 assert!(!candidates[0].reasoning.is_empty());
1067 }
1068 for candidate in &candidates {
1070 assert_eq!(candidate.input_type, InputType::Address);
1071 assert!(!candidate.chain.is_empty());
1072 assert!(!candidate.normalized.is_empty());
1073 assert!(candidate.confidence >= 0.0 && candidate.confidence <= 1.0);
1074 }
1075 }
1076
1077 #[test]
1078 fn test_try_address_detection_evm_mixed_case() {
1079 let input = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e";
1081 let chars = extract_characteristics(input);
1082 let chain_id = "ethereum";
1083
1084 let candidates = try_address_detection_for_chain(input, &chars, chain_id);
1085
1086 for candidate in &candidates {
1088 assert_eq!(candidate.input_type, InputType::Address);
1089 assert_eq!(candidate.chain, "ethereum");
1090 if !candidates.is_empty() {
1091 assert!(candidate.normalized.starts_with("0x"));
1093 assert_eq!(candidate.normalized.len(), 42);
1094 }
1095 }
1096 }
1097
1098 #[test]
1099 fn test_try_address_detection_bitcoin() {
1100 let input = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
1102 let chars = extract_characteristics(input);
1103 let chain_id = "bitcoin";
1104
1105 let candidates = try_address_detection_for_chain(input, &chars, chain_id);
1106
1107 for candidate in &candidates {
1109 assert_eq!(candidate.input_type, InputType::Address);
1110 assert_eq!(candidate.chain, "bitcoin");
1111 if !candidates.is_empty() {
1112 assert!(!candidate.normalized.is_empty());
1114 assert!(candidate.confidence > 0.0);
1115 }
1116 }
1117 }
1118
1119 #[test]
1120 fn test_try_address_detection_bitcoin_bech32() {
1121 let input = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
1123 let chars = extract_characteristics(input);
1124 let chain_id = "bitcoin";
1125
1126 let candidates = try_address_detection_for_chain(input, &chars, chain_id);
1127
1128 for candidate in &candidates {
1130 assert_eq!(candidate.input_type, InputType::Address);
1131 assert_eq!(candidate.chain, "bitcoin");
1132 if !candidates.is_empty() {
1133 assert!(!candidate.normalized.is_empty());
1135 assert!(candidate.confidence > 0.0);
1136 }
1137 }
1138 }
1139
1140 #[test]
1141 fn test_try_address_detection_invalid_chain() {
1142 let input = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
1144 let chars = extract_characteristics(input);
1145 let chain_id = "nonexistent_chain";
1146
1147 let candidates = try_address_detection_for_chain(input, &chars, chain_id);
1148
1149 assert!(candidates.is_empty());
1151 }
1152
1153 #[test]
1154 fn test_try_address_detection_wrong_chain() {
1155 let input = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
1157 let chars = extract_characteristics(input);
1158 let chain_id = "bitcoin";
1159
1160 let candidates = try_address_detection_for_chain(input, &chars, chain_id);
1161
1162 assert!(candidates.is_empty());
1165 }
1166
1167 #[test]
1168 fn test_try_address_detection_multiple_formats() {
1169 let input = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
1172 let chars = extract_characteristics(input);
1173 let chain_id = "bitcoin";
1174
1175 let candidates = try_address_detection_for_chain(input, &chars, chain_id);
1176
1177 for candidate in &candidates {
1180 assert_eq!(candidate.input_type, InputType::Address);
1181 assert_eq!(candidate.chain, "bitcoin");
1182 assert!(!candidate.normalized.is_empty());
1183 assert!(candidate.confidence >= 0.0 && candidate.confidence <= 1.0);
1184 assert!(!candidate.reasoning.is_empty());
1185 }
1186 }
1187
1188 #[test]
1194 fn test_try_address_detection_all_evm_chains() {
1195 let input = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
1197 let chars = extract_characteristics(input);
1198 let evm_chains = [
1199 "ethereum",
1200 "polygon",
1201 "bsc",
1202 "avalanche",
1203 "arbitrum",
1204 "optimism",
1205 "base",
1206 "fantom",
1207 "celo",
1208 "gnosis",
1209 ];
1210
1211 for chain_id in &evm_chains {
1212 let candidates = try_address_detection_for_chain(input, &chars, chain_id);
1213 if !candidates.is_empty() {
1215 assert_eq!(candidates[0].chain, *chain_id);
1216 assert_eq!(candidates[0].input_type, InputType::Address);
1217 assert!(candidates[0].confidence > 0.0);
1218 }
1219 }
1220 }
1221
1222 #[test]
1223 fn test_try_address_detection_cosmos_chains() {
1224 let cosmos_tests = vec![
1226 (
1227 "cosmos_hub",
1228 "cosmos1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
1229 ),
1230 ("osmosis", "osmo1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"),
1231 ("juno", "juno1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"),
1232 ];
1233
1234 for (chain_id, address) in cosmos_tests {
1235 let chars = extract_characteristics(address);
1236 let candidates = try_address_detection_for_chain(address, &chars, chain_id);
1237
1238 if !candidates.is_empty() {
1239 assert_eq!(candidates[0].chain, chain_id);
1240 assert_eq!(candidates[0].input_type, InputType::Address);
1241 }
1242 }
1243 }
1244
1245 #[test]
1246 fn test_try_address_detection_substrate_chains() {
1247 let substrate_tests = vec![
1249 (
1250 "polkadot",
1251 "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
1252 ),
1253 ("kusama", "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"),
1254 ];
1255
1256 for (chain_id, address) in substrate_tests {
1257 let chars = extract_characteristics(address);
1258 let candidates = try_address_detection_for_chain(address, &chars, chain_id);
1259
1260 if !candidates.is_empty() {
1261 assert_eq!(candidates[0].chain, chain_id);
1262 assert_eq!(candidates[0].input_type, InputType::Address);
1263 }
1264 }
1265 }
1266
1267 #[test]
1269 fn test_try_public_key_derivation_secp256k1_evm() {
1270 let input = "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
1272 let chars = extract_characteristics(input);
1273 let key_type = crate::input::DetectedKeyType::Secp256k1 { compressed: true };
1274 let chain_id = "ethereum";
1275
1276 let candidates = try_public_key_derivation_for_chain(input, &chars, key_type, chain_id);
1277
1278 if !candidates.is_empty() {
1280 assert_eq!(candidates[0].chain, "ethereum");
1281 assert_eq!(candidates[0].input_type, InputType::PublicKey);
1282 assert!(candidates[0].confidence > 0.0);
1283 assert!(candidates[0].normalized.starts_with("0x"));
1284 assert_eq!(candidates[0].normalized.len(), 42);
1285 }
1286 }
1287
1288 #[test]
1289 fn test_try_public_key_derivation_secp256k1_bitcoin() {
1290 let input = "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
1292 let chars = extract_characteristics(input);
1293 let key_type = crate::input::DetectedKeyType::Secp256k1 { compressed: true };
1294 let chain_id = "bitcoin";
1295
1296 let candidates = try_public_key_derivation_for_chain(input, &chars, key_type, chain_id);
1297
1298 if !candidates.is_empty() {
1300 assert_eq!(candidates[0].chain, "bitcoin");
1301 assert_eq!(candidates[0].input_type, InputType::PublicKey);
1302 assert!(candidates[0].confidence > 0.0);
1303 }
1304 }
1305
1306 #[test]
1307 fn test_try_public_key_derivation_ed25519_solana() {
1308 let input = "0x9f7f8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9";
1311 let chars = extract_characteristics(input);
1312 let key_type = crate::input::DetectedKeyType::Ed25519;
1313 let chain_id = "solana";
1314
1315 let candidates = try_public_key_derivation_for_chain(input, &chars, key_type, chain_id);
1316
1317 if !candidates.is_empty() {
1319 assert_eq!(candidates[0].chain, "solana");
1320 assert_eq!(candidates[0].input_type, InputType::PublicKey);
1321 assert!(candidates[0].confidence > 0.0);
1322 }
1323 }
1324
1325 #[test]
1326 fn test_try_public_key_derivation_ed25519_cosmos() {
1327 let input = "0x9f7f8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9";
1329 let chars = extract_characteristics(input);
1330 let key_type = crate::input::DetectedKeyType::Ed25519;
1331 let chain_id = "cosmos_hub";
1332
1333 let candidates = try_public_key_derivation_for_chain(input, &chars, key_type, chain_id);
1334
1335 if !candidates.is_empty() {
1337 assert_eq!(candidates[0].chain, "cosmos_hub");
1338 assert_eq!(candidates[0].input_type, InputType::PublicKey);
1339 assert!(candidates[0].normalized.starts_with("cosmos1"));
1340 }
1341 }
1342
1343 #[test]
1344 fn test_try_public_key_derivation_ed25519_ss58() {
1345 let input = "0x9f7f8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9";
1347 let chars = extract_characteristics(input);
1348 let key_type = crate::input::DetectedKeyType::Ed25519;
1349 let chain_id = "polkadot";
1350
1351 let candidates = try_public_key_derivation_for_chain(input, &chars, key_type, chain_id);
1352
1353 if !candidates.is_empty() {
1355 assert_eq!(candidates[0].chain, "polkadot");
1356 assert_eq!(candidates[0].input_type, InputType::PublicKey);
1357 assert!(candidates[0].confidence > 0.0);
1358 }
1359 }
1360
1361 #[test]
1362 fn test_try_public_key_derivation_cardano_requires_stake_key() {
1363 let input = "0x9f7f8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9";
1365 let chars = extract_characteristics(input);
1366 let key_type = crate::input::DetectedKeyType::Ed25519;
1367 let chain_id = "cardano";
1368
1369 let candidates = try_public_key_derivation_for_chain(input, &chars, key_type, chain_id);
1370
1371 assert!(candidates.is_empty());
1373 }
1374
1375 #[test]
1376 fn test_try_public_key_derivation_invalid_chain() {
1377 let input = "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
1379 let chars = extract_characteristics(input);
1380 let key_type = crate::input::DetectedKeyType::Secp256k1 { compressed: true };
1381 let chain_id = "nonexistent_chain";
1382
1383 let candidates = try_public_key_derivation_for_chain(input, &chars, key_type, chain_id);
1384
1385 assert!(candidates.is_empty());
1387 }
1388
1389 #[test]
1395 fn test_edge_case_evm_lowercase_normalize() {
1396 let input = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
1398 let result = identify(input).unwrap();
1399
1400 assert!(!result.is_empty());
1401 assert!(result[0].normalized.starts_with("0x"));
1403 assert_eq!(result[0].normalized.len(), 42);
1404 assert_ne!(result[0].normalized, input);
1406 }
1407
1408 #[test]
1409 fn test_edge_case_evm_uppercase_normalize() {
1410 let input = "0xD8DA6BF26964AF9D7EED9E03E53415D37AA96045";
1412 let result = identify(input).unwrap();
1413
1414 assert!(!result.is_empty());
1415 assert!(result[0].normalized.starts_with("0x"));
1417 assert_eq!(result[0].normalized.len(), 42);
1418 }
1419
1420 #[test]
1421 fn test_edge_case_evm_mixed_case_incorrect_checksum() {
1422 let input = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e";
1424 let result = identify(input).unwrap();
1425
1426 assert!(!result.is_empty());
1427 assert!(result[0].normalized.starts_with("0x"));
1429 assert_eq!(result[0].normalized.len(), 42);
1430 }
1431
1432 #[test]
1433 fn test_edge_case_address_length_boundaries() {
1434 let input = "0x0000000000000000000000000000000000000000";
1437 let result = identify(input).unwrap();
1438
1439 assert!(!result.is_empty());
1440 assert_eq!(result[0].normalized.len(), 42);
1441 }
1442
1443 #[test]
1444 fn test_edge_case_address_valid_structure_wrong_chain() {
1445 let input = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
1448 let chars = extract_characteristics(input);
1449 let chain_id = "bitcoin";
1450
1451 let candidates = try_address_detection_for_chain(input, &chars, chain_id);
1452
1453 assert!(candidates.is_empty());
1455 }
1456
1457 #[test]
1458 fn test_edge_case_ambiguous_32byte_base58() {
1459 let input = "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM";
1461 let result = identify(input).unwrap();
1462
1463 assert!(!result.is_empty());
1464 let has_address = result.iter().any(|c| c.input_type == InputType::Address);
1466 let has_pk = result.iter().any(|c| c.input_type == InputType::PublicKey);
1467 assert!(has_address || has_pk);
1469 }
1470
1471 #[test]
1473 fn test_edge_case_compressed_vs_uncompressed_secp256k1() {
1474 let compressed = "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
1476 let uncompressed = "0x0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8";
1477
1478 let result_compressed = identify(compressed).unwrap();
1479 let result_uncompressed = identify(uncompressed).unwrap();
1480
1481 assert!(!result_compressed.is_empty());
1483 assert!(!result_uncompressed.is_empty());
1484 }
1485
1486 #[test]
1487 fn test_edge_case_32byte_hex_ed25519_vs_sr25519() {
1488 let input = "0x9f7f8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9";
1491 let result = identify(input).unwrap();
1492
1493 assert!(!result.is_empty());
1494 let has_ed25519 = result.iter().any(|c| {
1496 matches!(c.input_type, InputType::PublicKey)
1497 && (c.chain == "solana" || c.chain == "cardano" || c.chain.starts_with("cosmos"))
1498 });
1499 let has_sr25519 = result.iter().any(|c| {
1500 matches!(c.input_type, InputType::PublicKey)
1501 && (c.chain == "polkadot" || c.chain == "kusama")
1502 });
1503 assert!(has_ed25519 || has_sr25519);
1505 }
1506
1507 #[test]
1508 fn test_edge_case_public_key_no_matching_curve() {
1509 let input = "0x1234"; let result = identify(input);
1513
1514 assert!(result.is_err());
1516 }
1517
1518 #[test]
1519 fn test_edge_case_cardano_single_pk_excluded() {
1520 let input = "0x9f7f8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9";
1522 let chars = extract_characteristics(input);
1523 let key_type = crate::input::DetectedKeyType::Ed25519;
1524 let chain_id = "cardano";
1525
1526 let candidates = try_public_key_derivation_for_chain(input, &chars, key_type, chain_id);
1527
1528 assert!(candidates.is_empty());
1530 }
1531
1532 #[test]
1533 fn test_edge_case_invalid_key_encoding() {
1534 let input = "not-a-valid-key-encoding";
1536 let result = identify(input);
1537
1538 assert!(result.is_err());
1540 }
1541
1542 #[test]
1544 fn test_edge_case_empty_input_string() {
1545 let result = identify("");
1547
1548 assert!(result.is_err());
1550 }
1551
1552 #[test]
1553 fn test_edge_case_invalid_encoding() {
1554 let input = "!!!invalid!!!";
1556 let result = identify(input);
1557
1558 assert!(result.is_err());
1560 }
1561
1562 #[test]
1563 fn test_edge_case_wrong_hrp_bech32() {
1564 let input = "cosmos1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
1566 let chars = extract_characteristics(input);
1567 let chain_id = "osmosis"; let candidates = try_address_detection_for_chain(input, &chars, chain_id);
1570
1571 assert!(candidates.is_empty());
1573 }
1574
1575 #[test]
1577 fn test_edge_case_same_address_multiple_evm_chains() {
1578 let input = "0x000000000000000000000000000000000000dEaD";
1580 let result = identify(input).unwrap();
1581
1582 assert!(!result.is_empty());
1583 let evm_chains = [
1585 "ethereum",
1586 "polygon",
1587 "bsc",
1588 "avalanche",
1589 "arbitrum",
1590 "optimism",
1591 "base",
1592 "fantom",
1593 "celo",
1594 "gnosis",
1595 ];
1596 let matched_chains: Vec<_> = result.iter().map(|c| c.chain.as_str()).collect();
1597 let matched_evm_count = evm_chains
1598 .iter()
1599 .filter(|&chain| matched_chains.contains(&chain))
1600 .count();
1601 assert!(matched_evm_count >= 1); }
1603
1604 #[test]
1605 fn test_edge_case_ambiguous_input_address_and_pk() {
1606 let input = "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM";
1608 let result = identify(input).unwrap();
1609
1610 assert!(!result.is_empty());
1611 let has_address = result.iter().any(|c| c.input_type == InputType::Address);
1613 let has_pk = result.iter().any(|c| c.input_type == InputType::PublicKey);
1614 assert!(has_address || has_pk);
1616 }
1617
1618 #[test]
1619 fn test_edge_case_chain_multiple_address_formats() {
1620 let p2pkh = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
1622 let bech32 = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
1623
1624 let result_p2pkh = identify(p2pkh).unwrap();
1625 let result_bech32 = identify(bech32).unwrap();
1626
1627 assert!(result_p2pkh.iter().any(|c| c.chain == "bitcoin"));
1629 assert!(result_bech32.iter().any(|c| c.chain == "bitcoin"));
1630 }
1631
1632 #[test]
1633 fn test_edge_case_public_key_derives_multiple_chains() {
1634 let input = "0x0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
1636 let result = identify(input).unwrap();
1637
1638 assert!(!result.is_empty());
1639 let secp256k1_chains = ["ethereum", "bitcoin", "tron"];
1641 let matched_chains: Vec<_> = result.iter().map(|c| c.chain.as_str()).collect();
1642 let matched_count = secp256k1_chains
1643 .iter()
1644 .filter(|&chain| matched_chains.contains(&chain))
1645 .count();
1646 assert!(matched_count >= 1); }
1648}