From 78f5139afab7da9f76dc8761efa482ed9a4e9988 Mon Sep 17 00:00:00 2001 From: hantyrram Date: Fri, 24 Apr 2026 17:55:48 +0800 Subject: [PATCH 1/4] Add BitcoinCashAddressType enum and token support - Add BitcoinCashAddressType IntEnum with P2PKH, P2SH, and token variants - Support prefix kwarg in encode method for address prefix override - Add token_support kwarg to enable token address encoding (version byte 0x10) - Enhance decode method to return address type info when decode_type=True - Support multiple HRPs (mainnet/testnet) in decode validation --- hdwallet/addresses/bitcoincash.py | 41 +++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/hdwallet/addresses/bitcoincash.py b/hdwallet/addresses/bitcoincash.py index c550c2e..a20ce06 100644 --- a/hdwallet/addresses/bitcoincash.py +++ b/hdwallet/addresses/bitcoincash.py @@ -22,6 +22,24 @@ from .iaddress import IAddress +from enum import IntEnum + +class BitcoinCashAddressType(IntEnum): + P2PKH = 0b0000 + P2SH = 0b0001 + P2PKH_WITH_TOKENS = 0b0010 + P2SH_WITH_TOKENS = 0b0011 + + @property + def label(self): + mapping = { + BitcoinCashAddressType.P2PKH: "P2PKH", + BitcoinCashAddressType.P2SH: "P2SH", + BitcoinCashAddressType.P2PKH_WITH_TOKENS: "P2PKH_WITH_TOKENS", + BitcoinCashAddressType.P2SH_WITH_TOKENS: "P2SH_WITH_TOKENS" + } + return mapping.get(self, "Unknown Type") + class BitcoinCashAddress(IAddress): hrp: str = BitcoinCash.NETWORKS.MAINNET.HRP @@ -55,7 +73,7 @@ def encode(cls, public_key: Union[bytes, str, IPublicKey], **kwargs: Any) -> str :return: The encoded CashAddr address. :rtype: str """ - hrp = kwargs.get("hrp", cls.hrp) + hrp = kwargs.get("prefix") or kwargs.get("hrp", cls.hrp) public_key_address_prefix = kwargs.get("public_key_address_prefix", cls.public_key_address_prefix) public_key: IPublicKey = validate_and_get_public_key( @@ -69,7 +87,8 @@ def encode(cls, public_key: Union[bytes, str, IPublicKey], **kwargs: Any) -> str # CashAddr version byte: 0 for P2PKH, 1 for P2SH version_byte = 0x00 # P2PKH with 160-bit hash - + if kwargs.get('token_support'): + version_byte = 0x10 # Pack version and hash payload = bytes([version_byte]) + public_key_hash @@ -97,7 +116,7 @@ def encode(cls, public_key: Union[bytes, str, IPublicKey], **kwargs: Any) -> str return ensure_string(hrp + ':' + ''.join([CHARSET[d] for d in combined])) @classmethod - def decode(cls, address: str, **kwargs: Any) -> str: + def decode(cls, address: str, **kwargs: Any) -> str or dict: """ Decode a Bitcoin Cash CashAddr address. @@ -110,7 +129,7 @@ def decode(cls, address: str, **kwargs: Any) -> str: :return: The decoded address as a string. :rtype: str """ - hrp_expected = kwargs.get("hrp", cls.hrp) + hrp_expected = [BitcoinCash.NETWORKS.MAINNET.HRP, BitcoinCash.NETWORKS.TESTNET.HRP, BitcoinCash.NETWORKS.MAINNET.HRP] # Parse address if ':' in address: @@ -142,7 +161,7 @@ def decode(cls, address: str, **kwargs: Any) -> str: if polymod != 0: raise ValueError("Invalid CashAddr checksum") - if hrp and hrp != hrp_expected: + if hrp and hrp not in hrp_expected: raise ValueError(f"Invalid HRP (expected: {hrp_expected}, got: {hrp})") # Remove 8-byte checksum @@ -156,5 +175,13 @@ def decode(cls, address: str, **kwargs: Any) -> str: # First byte is version, rest is hash version = decoded[0] address_hash = bytes(decoded[1:]) - - return bytes_to_string(address_hash) + address_type = None + if kwargs.get('decode_type'): + type_bits = (version & 0b01111000) >> 3 + print('typebits', type_bits) + address_type = BitcoinCashAddressType(type_bits) + return { + 'payload': bytes_to_string(address_hash), + 'type': address_type.label + } + return bytes_to_string(address_hash) \ No newline at end of file From 542989ec6a8e9e6e9a9e8268108f32b5d154fd58 Mon Sep 17 00:00:00 2001 From: hantyrram Date: Fri, 24 Apr 2026 18:22:17 +0800 Subject: [PATCH 2/4] cleanup remove print log --- hdwallet/addresses/bitcoincash.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hdwallet/addresses/bitcoincash.py b/hdwallet/addresses/bitcoincash.py index a20ce06..01bad7e 100644 --- a/hdwallet/addresses/bitcoincash.py +++ b/hdwallet/addresses/bitcoincash.py @@ -178,7 +178,6 @@ def decode(cls, address: str, **kwargs: Any) -> str or dict: address_type = None if kwargs.get('decode_type'): type_bits = (version & 0b01111000) >> 3 - print('typebits', type_bits) address_type = BitcoinCashAddressType(type_bits) return { 'payload': bytes_to_string(address_hash), From 24102bffcb6bdf6ad0221d6e6d317d9e91e9fa07 Mon Sep 17 00:00:00 2001 From: hantyrram Date: Fri, 24 Apr 2026 19:03:55 +0800 Subject: [PATCH 3/4] cleanup, remove unnecessary init of address_type --- hdwallet/addresses/bitcoincash.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hdwallet/addresses/bitcoincash.py b/hdwallet/addresses/bitcoincash.py index 01bad7e..6d48c0d 100644 --- a/hdwallet/addresses/bitcoincash.py +++ b/hdwallet/addresses/bitcoincash.py @@ -175,7 +175,6 @@ def decode(cls, address: str, **kwargs: Any) -> str or dict: # First byte is version, rest is hash version = decoded[0] address_hash = bytes(decoded[1:]) - address_type = None if kwargs.get('decode_type'): type_bits = (version & 0b01111000) >> 3 address_type = BitcoinCashAddressType(type_bits) From dd622cf5937f6289f4c8c6467ee65f5f9c4fbea4 Mon Sep 17 00:00:00 2001 From: hantyrram Date: Fri, 24 Apr 2026 23:01:20 +0800 Subject: [PATCH 4/4] fix dup hrp expected, add regtest --- hdwallet/addresses/bitcoincash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hdwallet/addresses/bitcoincash.py b/hdwallet/addresses/bitcoincash.py index 6d48c0d..74addf4 100644 --- a/hdwallet/addresses/bitcoincash.py +++ b/hdwallet/addresses/bitcoincash.py @@ -129,7 +129,7 @@ def decode(cls, address: str, **kwargs: Any) -> str or dict: :return: The decoded address as a string. :rtype: str """ - hrp_expected = [BitcoinCash.NETWORKS.MAINNET.HRP, BitcoinCash.NETWORKS.TESTNET.HRP, BitcoinCash.NETWORKS.MAINNET.HRP] + hrp_expected = [BitcoinCash.NETWORKS.MAINNET.HRP, BitcoinCash.NETWORKS.TESTNET.HRP, BitcoinCash.NETWORKS.REGTEST.HRP] # Parse address if ':' in address: