|
| 1 | +use bitcoin::ScriptBuf; |
| 2 | +use primitives::{Address as AddressTrait, BitcoinChain, Chain}; |
| 3 | + |
| 4 | +use crate::signer::address::script_for_address; |
| 5 | + |
| 6 | +#[derive(Debug, Clone)] |
| 7 | +pub struct BitcoinAddress { |
| 8 | + chain: BitcoinChain, |
| 9 | + address: String, |
| 10 | + script_pubkey: ScriptBuf, |
| 11 | +} |
| 12 | + |
| 13 | +impl BitcoinAddress { |
| 14 | + pub fn try_parse_for_chain(address: &str, chain: BitcoinChain) -> Option<Self> { |
| 15 | + let script_pubkey = script_for_address(chain, address).ok()?.script_pubkey; |
| 16 | + Some(Self { |
| 17 | + chain, |
| 18 | + address: address.to_string(), |
| 19 | + script_pubkey, |
| 20 | + }) |
| 21 | + } |
| 22 | + |
| 23 | + pub fn is_valid_for_chain(address: &str, chain: Chain) -> bool { |
| 24 | + BitcoinChain::from_chain(chain).is_some_and(|chain| Self::try_parse_for_chain(address, chain).is_some()) |
| 25 | + } |
| 26 | + |
| 27 | + pub fn bitcoin_chain(&self) -> BitcoinChain { |
| 28 | + self.chain |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +impl AddressTrait for BitcoinAddress { |
| 33 | + fn try_parse(address: &str) -> Option<Self> { |
| 34 | + [ |
| 35 | + BitcoinChain::Bitcoin, |
| 36 | + BitcoinChain::BitcoinCash, |
| 37 | + BitcoinChain::Litecoin, |
| 38 | + BitcoinChain::Doge, |
| 39 | + BitcoinChain::Zcash, |
| 40 | + ] |
| 41 | + .into_iter() |
| 42 | + .find_map(|chain| Self::try_parse_for_chain(address, chain)) |
| 43 | + } |
| 44 | + |
| 45 | + fn as_bytes(&self) -> &[u8] { |
| 46 | + self.script_pubkey.as_bytes() |
| 47 | + } |
| 48 | + |
| 49 | + fn encode(&self) -> String { |
| 50 | + self.address.clone() |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +pub fn validate_address(address: &str, chain: Chain) -> bool { |
| 55 | + BitcoinAddress::is_valid_for_chain(address, chain) |
| 56 | +} |
| 57 | + |
| 58 | +#[cfg(test)] |
| 59 | +mod tests { |
| 60 | + use super::*; |
| 61 | + use primitives::Address as AddressTrait; |
| 62 | + |
| 63 | + #[test] |
| 64 | + fn test_validate_address() { |
| 65 | + let bitcoin = BitcoinAddress::mock(); |
| 66 | + let bitcoin_cash = BitcoinAddress::mock_with_chain(BitcoinChain::BitcoinCash); |
| 67 | + let litecoin = BitcoinAddress::mock_with_chain(BitcoinChain::Litecoin); |
| 68 | + let doge = BitcoinAddress::mock_with_chain(BitcoinChain::Doge); |
| 69 | + let zcash = BitcoinAddress::mock_with_chain(BitcoinChain::Zcash); |
| 70 | + |
| 71 | + assert!(validate_address(&bitcoin.encode(), Chain::Bitcoin)); |
| 72 | + assert!(validate_address(&bitcoin_cash.encode(), Chain::BitcoinCash)); |
| 73 | + assert!(validate_address(bitcoin_cash.encode().strip_prefix("bitcoincash:").unwrap(), Chain::BitcoinCash)); |
| 74 | + assert!(validate_address(&litecoin.encode(), Chain::Litecoin)); |
| 75 | + assert!(validate_address(&doge.encode(), Chain::Doge)); |
| 76 | + assert!(validate_address(&zcash.encode(), Chain::Zcash)); |
| 77 | + assert!(!validate_address(&bitcoin.encode(), Chain::Litecoin)); |
| 78 | + assert!(!validate_address("invalid", Chain::Bitcoin)); |
| 79 | + |
| 80 | + let parsed = BitcoinAddress::try_parse_for_chain(&bitcoin.encode(), BitcoinChain::Bitcoin).unwrap(); |
| 81 | + assert_eq!(parsed.bitcoin_chain().get_chain(), Chain::Bitcoin); |
| 82 | + assert_eq!(parsed.encode(), bitcoin.encode()); |
| 83 | + assert_eq!(hex::encode(parsed.as_bytes()), "0014751e76e8199196d454941c45d1b3a323f1433bd6"); |
| 84 | + } |
| 85 | +} |
0 commit comments