import binascii
from enum import IntEnum
from typing import Union
from . import xdr as stellar_xdr
from .strkey import StrKey
__all__ = ["Address", "AddressType"]
[docs]
class AddressType(IntEnum):
"""Represents an Address type."""
ACCOUNT = 0
"""An account address, address looks like ``GBJCHUKZMTFSLOMNC7P4TS4VJJBTCYL3XKSOLXAUJSD56C4LHND5T...``."""
CONTRACT = 1
"""An contract address, address looks like ``CCJZ5DGASBWQXR5MPFCJXMBI333XE5U3FSJTNQU7RIKE3P5GN2K2W...``."""
MUXED_ACCOUNT = 2
"""A muxed account address, address looks like ``MAQAA5L65LSYH7CQ3VTJ7F3HHLGCL3DSLAR2Y47263D56MNNGHSQSAAAAAAAAAAE2L...``."""
CLAIMABLE_BALANCE = 3
"""A claimable balance address, address looks like ``BAAD6DBUX6J22DMZOHIEZTEQ64CVCHEDRKWZONFEUL5Q26QD7R76RGR...``."""
LIQUIDITY_POOL = 4
"""A liquidity pool address, address looks like ``LA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJU...``."""
[docs]
class Address:
"""Represents a single address in the Stellar network.
An address can represent an account or a contract.
:param address: ID of the account, contract, muxed account, claimable balance, or liquidity pool.
"""
def __init__(self, address: str):
if StrKey.is_valid_ed25519_public_key(address):
self.type = AddressType.ACCOUNT
self.key = StrKey.decode_ed25519_public_key(address)
elif StrKey.is_valid_contract(address):
self.type = AddressType.CONTRACT
self.key = StrKey.decode_contract(address)
elif StrKey.is_valid_med25519_public_key(address):
self.type = AddressType.MUXED_ACCOUNT
self.key = StrKey.decode_med25519_public_key(address)
elif StrKey.is_valid_claimable_balance(address):
self.type = AddressType.CLAIMABLE_BALANCE
self.key = StrKey.decode_claimable_balance(address)
elif StrKey.is_valid_liquidity_pool(address):
self.type = AddressType.LIQUIDITY_POOL
self.key = StrKey.decode_liquidity_pool(address)
else:
raise ValueError("Unsupported address type.")
@property
def address(self) -> str:
"""Returns the encoded address.
:return: The encoded address.
"""
if self.type == AddressType.ACCOUNT:
return StrKey.encode_ed25519_public_key(self.key)
elif self.type == AddressType.CONTRACT:
return StrKey.encode_contract(self.key)
elif self.type == AddressType.MUXED_ACCOUNT:
return StrKey.encode_med25519_public_key(self.key)
elif self.type == AddressType.CLAIMABLE_BALANCE:
return StrKey.encode_claimable_balance(self.key)
elif self.type == AddressType.LIQUIDITY_POOL:
return StrKey.encode_liquidity_pool(self.key)
else:
raise ValueError("Unsupported address type.")
[docs]
@staticmethod
def from_raw_account(account: Union[bytes, str]) -> "Address":
"""Creates a new account Address object from raw bytes.
:param account: The raw bytes of the account, it can be a byte array or a hex encoded string.
:return: A new Address object.
"""
if isinstance(account, str):
account = binascii.unhexlify(account)
return Address(StrKey.encode_ed25519_public_key(account))
[docs]
@staticmethod
def from_raw_contract(contract: Union[bytes, str]) -> "Address":
"""Creates a new contract Address object from a buffer of raw bytes.
:param contract: The raw bytes of the contract.
:return: A new Address object.
"""
if isinstance(contract, str):
contract = binascii.unhexlify(contract)
return Address(StrKey.encode_contract(contract))
[docs]
@staticmethod
def from_raw_muxed_account(muxed_account: Union[bytes, str]) -> "Address":
"""Creates a new muxed account Address object from raw bytes.
:param muxed_account: The raw bytes of the muxed account.
:return: A new Address object.
"""
if isinstance(muxed_account, str):
muxed_account = binascii.unhexlify(muxed_account)
return Address(StrKey.encode_med25519_public_key(muxed_account))
[docs]
@staticmethod
def from_raw_claimable_balance(claimable_balance: Union[bytes, str]) -> "Address":
"""Creates a new claimable balance Address object from raw bytes.
:param claimable_balance: The raw bytes of the claimable balance.
:return: A new Address object.
"""
if isinstance(claimable_balance, str):
claimable_balance = binascii.unhexlify(claimable_balance)
return Address(StrKey.encode_claimable_balance(claimable_balance))
[docs]
@staticmethod
def from_raw_liquidity_pool(liquidity_pool: Union[bytes, str]) -> "Address":
"""Creates a new liquidity pool Address object from raw bytes.
:param liquidity_pool: The raw bytes of the liquidity pool.
:return: A new Address object.
"""
if isinstance(liquidity_pool, str):
liquidity_pool = binascii.unhexlify(liquidity_pool)
return Address(StrKey.encode_liquidity_pool(liquidity_pool))
[docs]
def to_xdr_sc_address(self) -> stellar_xdr.SCAddress:
"""Converts the Address object to a :class:`stellar_sdk.xdr.SCAddress` XDR object.
:return: A :class:`stellar_sdk.xdr.SCAddress` XDR object.
"""
if self.type == AddressType.ACCOUNT:
account = stellar_xdr.AccountID(
stellar_xdr.PublicKey(
stellar_xdr.PublicKeyType.PUBLIC_KEY_TYPE_ED25519,
stellar_xdr.Uint256(self.key),
)
)
return stellar_xdr.SCAddress(
stellar_xdr.SCAddressType.SC_ADDRESS_TYPE_ACCOUNT, account_id=account
)
elif self.type == AddressType.CONTRACT:
contract = stellar_xdr.ContractID.from_xdr_bytes(self.key)
return stellar_xdr.SCAddress(
stellar_xdr.SCAddressType.SC_ADDRESS_TYPE_CONTRACT, contract_id=contract
)
elif self.type == AddressType.MUXED_ACCOUNT:
muxed_account = stellar_xdr.MuxedEd25519Account.from_xdr_bytes(self.key)
return stellar_xdr.SCAddress(
stellar_xdr.SCAddressType.SC_ADDRESS_TYPE_MUXED_ACCOUNT,
muxed_account=muxed_account,
)
elif self.type == AddressType.CLAIMABLE_BALANCE:
# See https://github.com/stellar/stellar-protocol/pull/1646/files#r1974431825
if self.key[:1] != b"\x00":
raise ValueError(
"The claimable balance ID type is not supported, it must be `CLAIMABLE_BALANCE_ID_TYPE_V0`."
)
claimable_balance_id = stellar_xdr.ClaimableBalanceID(
stellar_xdr.ClaimableBalanceIDType.CLAIMABLE_BALANCE_ID_TYPE_V0,
v0=stellar_xdr.Hash.from_xdr_bytes(self.key[1:]),
)
return stellar_xdr.SCAddress(
stellar_xdr.SCAddressType.SC_ADDRESS_TYPE_CLAIMABLE_BALANCE,
claimable_balance_id=claimable_balance_id,
)
elif self.type == AddressType.LIQUIDITY_POOL:
liquidity_pool_id = stellar_xdr.PoolID.from_xdr_bytes(self.key)
return stellar_xdr.SCAddress(
stellar_xdr.SCAddressType.SC_ADDRESS_TYPE_LIQUIDITY_POOL,
liquidity_pool_id=liquidity_pool_id,
)
else:
raise ValueError("Unsupported address type.")
[docs]
@classmethod
def from_xdr_sc_address(cls, sc_address: stellar_xdr.SCAddress) -> "Address":
"""Creates a new Address object from a :class:`stellar_sdk.xdr.SCAddress` XDR object.
:param sc_address: The :class:`stellar_sdk.xdr.SCAddress` XDR object.
:return: A new Address object.
"""
if sc_address.type == stellar_xdr.SCAddressType.SC_ADDRESS_TYPE_ACCOUNT:
assert sc_address.account_id is not None
assert sc_address.account_id.account_id.ed25519 is not None
return cls.from_raw_account(
sc_address.account_id.account_id.ed25519.uint256
)
elif sc_address.type == stellar_xdr.SCAddressType.SC_ADDRESS_TYPE_CONTRACT:
assert sc_address.contract_id is not None
return cls.from_raw_contract(sc_address.contract_id.to_xdr_bytes())
elif sc_address.type == stellar_xdr.SCAddressType.SC_ADDRESS_TYPE_MUXED_ACCOUNT:
assert sc_address.muxed_account is not None
return cls.from_raw_muxed_account(sc_address.muxed_account.to_xdr_bytes())
elif (
sc_address.type
== stellar_xdr.SCAddressType.SC_ADDRESS_TYPE_CLAIMABLE_BALANCE
):
assert sc_address.claimable_balance_id is not None
# See https://github.com/stellar/stellar-protocol/pull/1646/files#r1974431825
if (
sc_address.claimable_balance_id.type
!= stellar_xdr.ClaimableBalanceIDType.CLAIMABLE_BALANCE_ID_TYPE_V0
):
raise ValueError(
f"The claimable balance ID type is not supported: {sc_address.claimable_balance_id.type.name}"
) # This is a safeguard.
return cls.from_raw_claimable_balance(
sc_address.claimable_balance_id.to_xdr_bytes()[3:]
)
elif (
sc_address.type == stellar_xdr.SCAddressType.SC_ADDRESS_TYPE_LIQUIDITY_POOL
):
assert sc_address.liquidity_pool_id is not None
return cls.from_raw_liquidity_pool(
sc_address.liquidity_pool_id.to_xdr_bytes()
)
else:
raise ValueError("Unsupported address type.")
def to_xdr_sc_val(self) -> stellar_xdr.SCVal:
return stellar_xdr.SCVal(
stellar_xdr.SCValType.SCV_ADDRESS, address=self.to_xdr_sc_address()
)
@classmethod
def from_xdr_sc_val(cls, sc_val: stellar_xdr.SCVal) -> "Address":
if sc_val.type != stellar_xdr.SCValType.SCV_ADDRESS:
raise ValueError("Unsupported SCVal type.")
assert sc_val.address is not None
return cls.from_xdr_sc_address(sc_val.address)
def __hash__(self):
return hash((self.key, self.type))
def __eq__(self, other: object) -> bool:
if not isinstance(other, self.__class__):
return NotImplemented
return self.key == other.key and self.type == other.type
def __repr__(self):
return f"<Address [type={self.type.name}, address={self.address}]>"