diff --git a/VERSION b/VERSION index f1547e6..815e68d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.7 +2.0.8 diff --git a/assets/btc-0/info.json b/assets/btc-0/info.json index 76329b9..fc8d2a0 100644 --- a/assets/btc-0/info.json +++ b/assets/btc-0/info.json @@ -1,5 +1,28 @@ { - "contracts": [], + "contracts": [ + { + "address": "1111111111111111111114oLvT2", + "decimals": 8, + "name": "Bitcoin", + "network": "bitcoin-1", + "symbol": "BTC", + "tags": [ + "mainnet", + "native-coin" + ] + }, + { + "address": "1111111111111111111114oLvT2", + "decimals": 8, + "name": "Bitcoin", + "network": "bitcoin-2", + "symbol": "BTC", + "tags": [ + "native-coin", + "testnet" + ] + } + ], "id": "btc-0", "images": { "png128": true, @@ -19,5 +42,7 @@ "url": "https://www.coingecko.com/en/coins/bitcoin" } ], - "tags": [] + "tags": [ + "bitcoin" + ] } diff --git a/assets/unknown-bitcoin/image-128.png b/assets/unknown-bitcoin/image-128.png new file mode 100644 index 0000000..337897e Binary files /dev/null and b/assets/unknown-bitcoin/image-128.png differ diff --git a/assets/unknown-bitcoin/image-256.png b/assets/unknown-bitcoin/image-256.png new file mode 100644 index 0000000..882c78f Binary files /dev/null and b/assets/unknown-bitcoin/image-256.png differ diff --git a/assets/unknown-bitcoin/image-32.png b/assets/unknown-bitcoin/image-32.png new file mode 100644 index 0000000..66c269a Binary files /dev/null and b/assets/unknown-bitcoin/image-32.png differ diff --git a/assets/unknown-bitcoin/image-64.png b/assets/unknown-bitcoin/image-64.png new file mode 100644 index 0000000..eb3a283 Binary files /dev/null and b/assets/unknown-bitcoin/image-64.png differ diff --git a/assets/unknown-bitcoin/image.svg b/assets/unknown-bitcoin/image.svg new file mode 100644 index 0000000..b648d44 --- /dev/null +++ b/assets/unknown-bitcoin/image.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/assets/unknown-bitcoin/info.json b/assets/unknown-bitcoin/info.json new file mode 100644 index 0000000..75f383d --- /dev/null +++ b/assets/unknown-bitcoin/info.json @@ -0,0 +1,16 @@ +{ + "contracts": [], + "id": "unknown-bitcoin", + "images": { + "png128": true, + "png256": true, + "png32": true, + "png64": true, + "svg": true + }, + "name": "Unknown Bitcoin Asset", + "references": [], + "tags": [ + "bitcoin" + ] +} diff --git a/enums/ids/asset.json b/enums/ids/asset.json index 451754f..4504222 100644 --- a/enums/ids/asset.json +++ b/enums/ids/asset.json @@ -1235,6 +1235,10 @@ "description": "Unknown Bifrost Asset", "value": "unknown-bifrost" }, + { + "description": "Unknown Bitcoin Asset", + "value": "unknown-bitcoin" + }, { "description": "Unknown BNB Asset", "value": "unknown-bnb" diff --git a/enums/ids/network.json b/enums/ids/network.json index 31be78b..597cfa1 100644 --- a/enums/ids/network.json +++ b/enums/ids/network.json @@ -1,4 +1,12 @@ [ + { + "description": "Bitcoin Mainnet", + "value": "bitcoin-1" + }, + { + "description": "Bitcoin Testnet", + "value": "bitcoin-2" + }, { "description": "Ethereum Mainnet", "value": "evm-1" diff --git a/enums/tags/asset.json b/enums/tags/asset.json index 75d34d1..a8ffc8d 100644 --- a/enums/tags/asset.json +++ b/enums/tags/asset.json @@ -15,6 +15,10 @@ "value": "bifrost", "description": "Main network of asset is Bifrost" }, + { + "value": "bitcoin", + "description": "Main network of asset is Bitcoin" + }, { "value": "bnb", "description": "Main network of asset is BNB chain" diff --git a/enums/tags/network.json b/enums/tags/network.json index 8c7fb09..334dd2d 100644 --- a/enums/tags/network.json +++ b/enums/tags/network.json @@ -15,6 +15,10 @@ "value": "bifrost", "description": "Bifrost networks" }, + { + "value": "bitcoin", + "description": "Bitcoin networks" + }, { "value": "bnb", "description": "BNB networks" diff --git a/libraries/models/terminals/address.py b/libraries/models/terminals/address.py index 93231e1..101b4c3 100644 --- a/libraries/models/terminals/address.py +++ b/libraries/models/terminals/address.py @@ -1,16 +1,21 @@ -from typing import Union, Self +from typing import Union, Self, get_args -from pydantic import RootModel +from pydantic import RootModel, model_validator +from libraries.models.terminals.address_bitcoin import AddressBitcoin from libraries.models.terminals.address_evm import AddressEvm +ADDRESS_TYPES = Union[AddressEvm, AddressBitcoin] -class Address(RootModel[Union[AddressEvm]]): + +class Address(RootModel[ADDRESS_TYPES]): """A union of constrained `str` about each address of blockchain networks.""" def __eq__(self, other: Self) -> bool: if self.is_evm_address and other.is_evm_address: return self.root.__eq__(other.root) + elif self.is_bitcoin_address and other.is_bitcoin_address: + return self.root.__eq__(other.root) else: raise ValueError( f"Cannot compare difference addresses {type(self.root)} and {type(other.root)}." @@ -22,6 +27,8 @@ def __ne__(self, other: Self) -> bool: def __lt__(self, other: Self) -> bool: if self.is_evm_address and other.is_evm_address: return self.root.__lt__(other.root) + elif self.is_bitcoin_address and other.is_bitcoin_address: + return self.root.__lt__(other.root) else: raise ValueError( f"Cannot compare difference addresses {type(self.root)} and {type(other.root)}." @@ -50,3 +57,26 @@ def is_evm_address(self) -> bool: Whether the address is an EVM address. """ return isinstance(self.root, AddressEvm) + + @property + def is_bitcoin_address(self) -> bool: + """Check if the address is a Bitcoin address. + + Returns: + Whether the address is a Bitcoin address. + """ + return isinstance(self.root, AddressBitcoin) + + @model_validator(mode="after") + def validator(self) -> Self: + root_types = get_args(ADDRESS_TYPES) + if type(self.root) in root_types: + return self + elif isinstance(self.root, str): + for root_type in root_types: + try: + self.root = root_type(self.root) + return self + except ValueError: + continue + raise ValueError(f"Invalid address: {self.root}") diff --git a/libraries/models/terminals/address_bitcoin.py b/libraries/models/terminals/address_bitcoin.py new file mode 100644 index 0000000..ce807b6 --- /dev/null +++ b/libraries/models/terminals/address_bitcoin.py @@ -0,0 +1,51 @@ +from typing import Self + +from bitcoinlib.encoding import EncodingError +from bitcoinlib.keys import deserialize_address + +from libraries.models.templates.str_model import StrModel + + +class AddressBitcoin(StrModel): + """A constrained `str` for the Bitcoin address.""" + + @property + def __deserialized_result(self) -> dict: + """The result from the deserialization the Bitcoin address.""" + return deserialize_address(self.root) + + @property + def public_key_hash(self) -> str: + """The public key hash of the Bitcoin address.""" + return self.__deserialized_result.get("public_key_hash") + + @property + def encoding_type(self) -> str: + """The encoding type of the Bitcoin address.""" + return self.__deserialized_result.get("encoding") + + @property + def script_type(self) -> str: + """The script type of the Bitcoin address.""" + return self.__deserialized_result.get("script_type") + + def __eq__(self, other: Self | str) -> bool: + match other: + case AddressBitcoin(): + return self.root.lower() == other.root.lower() + case str(): + return self.root.lower() == other.lower() + case _: + raise ValueError(f"Cannot compare {self} with {other}") + + def __lt__(self, other: Self) -> bool: + return self.public_key_hash < other.public_key_hash + + def __hash__(self) -> int: + return hash(self.public_key_hash) + + def validate_str(self) -> Self: + try: + return deserialize_address(self.root).get("address") + except EncodingError: + raise ValueError(f"Invalid Bitcoin address: {self.root}") diff --git a/libraries/models/terminals/engine.py b/libraries/models/terminals/engine.py index 4701759..e093143 100644 --- a/libraries/models/terminals/engine.py +++ b/libraries/models/terminals/engine.py @@ -13,6 +13,7 @@ class _EngineEnum(StrEnum): """ EVM: str = "evm" + BITCOIN: str = "bitcoin" UNKNOWN: str = "unknown" @@ -28,6 +29,15 @@ def is_evm(self) -> bool: """ return self.root == _EngineEnum.EVM + @property + def is_bitcoin(self) -> bool: + """Check if the engine is a Bitcoin engine. + + Returns: + Whether the engine is a Bitcoin engine. + """ + return self.root == _EngineEnum.BITCOIN + @property def is_unknown(self) -> bool: """Check if the engine is an unknown engine. diff --git a/networks/bitcoin-1/image-128.png b/networks/bitcoin-1/image-128.png new file mode 100644 index 0000000..458a5a8 Binary files /dev/null and b/networks/bitcoin-1/image-128.png differ diff --git a/networks/bitcoin-1/image-256.png b/networks/bitcoin-1/image-256.png new file mode 100644 index 0000000..21dbb9f Binary files /dev/null and b/networks/bitcoin-1/image-256.png differ diff --git a/networks/bitcoin-1/image-32.png b/networks/bitcoin-1/image-32.png new file mode 100644 index 0000000..c3acebe Binary files /dev/null and b/networks/bitcoin-1/image-32.png differ diff --git a/networks/bitcoin-1/image-64.png b/networks/bitcoin-1/image-64.png new file mode 100644 index 0000000..62ad717 Binary files /dev/null and b/networks/bitcoin-1/image-64.png differ diff --git a/networks/bitcoin-1/image.svg b/networks/bitcoin-1/image.svg new file mode 100644 index 0000000..d6b71fc --- /dev/null +++ b/networks/bitcoin-1/image.svg @@ -0,0 +1,6 @@ + + + + diff --git a/networks/bitcoin-1/info.json b/networks/bitcoin-1/info.json new file mode 100644 index 0000000..cb7b72e --- /dev/null +++ b/networks/bitcoin-1/info.json @@ -0,0 +1,25 @@ +{ + "currency": { + "address": "1111111111111111111114oLvT2", + "decimals": 8, + "id": "btc-0", + "name": "Bitcoin", + "symbol": "BTC" + }, + "engine": "bitcoin", + "explorers": [], + "id": "bitcoin-1", + "images": { + "png128": true, + "png256": true, + "png32": true, + "png64": true, + "svg": true + }, + "name": "Bitcoin Mainnet", + "network": "mainnet", + "tags": [ + "bitcoin" + ], + "unknownAssetId": "unknown-bitcoin" +} diff --git a/networks/bitcoin-2/image-128.png b/networks/bitcoin-2/image-128.png new file mode 100644 index 0000000..337897e Binary files /dev/null and b/networks/bitcoin-2/image-128.png differ diff --git a/networks/bitcoin-2/image-256.png b/networks/bitcoin-2/image-256.png new file mode 100644 index 0000000..882c78f Binary files /dev/null and b/networks/bitcoin-2/image-256.png differ diff --git a/networks/bitcoin-2/image-32.png b/networks/bitcoin-2/image-32.png new file mode 100644 index 0000000..66c269a Binary files /dev/null and b/networks/bitcoin-2/image-32.png differ diff --git a/networks/bitcoin-2/image-64.png b/networks/bitcoin-2/image-64.png new file mode 100644 index 0000000..eb3a283 Binary files /dev/null and b/networks/bitcoin-2/image-64.png differ diff --git a/networks/bitcoin-2/image.svg b/networks/bitcoin-2/image.svg new file mode 100644 index 0000000..b6682af --- /dev/null +++ b/networks/bitcoin-2/image.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/networks/bitcoin-2/info.json b/networks/bitcoin-2/info.json new file mode 100644 index 0000000..d4ccf7b --- /dev/null +++ b/networks/bitcoin-2/info.json @@ -0,0 +1,25 @@ +{ + "currency": { + "address": "1111111111111111111114oLvT2", + "decimals": 8, + "id": "btc-0", + "name": "Bitcoin", + "symbol": "BTC" + }, + "engine": "bitcoin", + "explorers": [], + "id": "bitcoin-2", + "images": { + "png128": true, + "png256": true, + "png32": true, + "png64": true, + "svg": true + }, + "name": "Bitcoin Testnet", + "network": "testnet", + "tags": [ + "bitcoin" + ], + "unknownAssetId": "unknown-bitcoin" +} diff --git a/requirements/essential.txt b/requirements/essential.txt index 335a9fe..8e48c57 100644 --- a/requirements/essential.txt +++ b/requirements/essential.txt @@ -1,5 +1,6 @@ # Specifies the essential requirements of the project. +bitcoinlib>=0.6.15 +build>=1.2.1 hexbytes>=0.3.1 pydantic>=2.5.3 web3>=6.14.0 -build>=1.2.1