Source code for cashscript_py.interfaces

"""Shared interfaces and simple data models used across the public API."""

from collections.abc import Callable
from enum import Flag, StrEnum
from typing import TYPE_CHECKING, Any

from bitcash.network.meta import Unspent

if TYPE_CHECKING:
    import cashscript_py

    from cashscript_py.helpers.argument_encoding import FunctionArgument


[docs] class SignatureAlgorithm(Flag): """Supported signature algorithms.""" ECDSA = 0x00 SCHNORR = 0x01
[docs] class HashType(Flag): """Supported SIGHASH flags.""" SIGHASH_ALL = 0x01 SIGHASH_NONE = 0x02 SIGHASH_SINGLE = 0x03 SIGHASH_UTXOS = 0x20 SIGHASH_FORKID = 0x40 SIGHASH_ANYONECANPAY = 0x80
[docs] class NftCapability(StrEnum): """NFT CashTokens capabilities.""" NONE = "none" MUTABLE = "mutable" MINTING = "minting"
[docs] class TokenDetails: """CashToken details: fungible amount and optional NFT."""
[docs] class Nft: """Nested NFT metadata.""" def __init__(self, capability: NftCapability, commitment: str | None): """Create an NFT description with capability and optional commitment.""" self.capability = capability self.commitment = "" if commitment is None else commitment def __repr__(self) -> str: """Return a concise string representation.""" return f"Nft(capability={self.capability}, commitment={self.commitment})"
def __init__(self, amount: int | None, category: str, nft: "TokenDetails.Nft | None"): """Create token details with optional NFT (amount defaults to 0).""" self.amount = 0 if amount is None else amount self.category = category self.nft = nft def __repr__(self) -> str: """Return a concise string representation.""" nft_str = str(self.nft) if self.nft is not None else "None" return f"TokenDetails(amount={self.amount}, category={self.category}, nft={nft_str})"
[docs] class Output: """Transaction output description. Attributes: to: Recipient locking bytecode (bytes) or CashAddress (str). amount: BCH amount in satoshis. token: Optional CashToken details. """ def __init__(self, to: bytes | str, amount: int, token: TokenDetails | None = None): """Create a new Output. Args: to: Locking script (bytes) or CashAddress (str). amount: Amount in satoshis. token: Optional CashToken details. """ self.to = to self.amount = amount self.token = token def __repr__(self) -> str: """Return a concise string representation.""" token_str = str(self.token) if self.token is not None else "None" to = self.to if isinstance(self.to, str) else self.to.hex() return f"Output(to={to}, amount={self.amount}, token={token_str})"
[docs] class Transaction: """Simple transaction template used for signing and serialization.""" def __init__(self, inputs: list["UnlockableUtxo"], locktime: int, outputs: list[Output], version: int): """Create a transaction template. Args: inputs: Unlockable inputs to include. locktime: Transaction locktime. outputs: Outputs to include. version: Transaction version (typically 2). """ self.inputs = inputs self.locktime = locktime self.outputs = outputs self.version = version
[docs] class TransactionDetails(Transaction): """Transaction including txid and hex representation.""" txid: str hex: str
[docs] class Unlocker: """Unlocker providing locking/unlocking bytecode generators.""" def __init__( self, generate_locking_bytecode: Callable[[], bytes], generate_unlocking_bytecode: Callable[[Transaction, list[Output], int], bytes], ): """Create an Unlocker. Args: generate_locking_bytecode: Function that returns locking bytecode. generate_unlocking_bytecode: Function that returns unlocking bytecode for an input. """ self.generate_locking_bytecode = generate_locking_bytecode self.generate_unlocking_bytecode = generate_unlocking_bytecode
[docs] class ContractUnlocker(Unlocker): """Unlocker associated with a Contract ABI function and its parameters.""" def __init__( self, generate_locking_bytecode: Callable[[], bytes], generate_unlocking_bytecode: Callable[[Transaction, list[Output], int], bytes], contract: "cashscript_py.Contract", params: list["FunctionArgument"], abi_function: dict[str, Any], ): """Create a ContractUnlocker. Args: generate_locking_bytecode: Locking bytecode generator. generate_unlocking_bytecode: Unlocking bytecode generator. contract: Contract instance that created this unlocker. params: ABI parameters used to construct the unlocker. abi_function: ABI function metadata. """ super().__init__(generate_locking_bytecode, generate_unlocking_bytecode) self.contract = contract self.params = params self.abi_function = abi_function
[docs] class Utxo: """Spendable output reference with optional CashToken details.""" def __init__(self, txid: str, vout: int, satoshis: int, token: TokenDetails | None = None): """Create a UTXO reference. Args: txid: Transaction ID (hex). vout: Output index. satoshis: Output amount (satoshis). token: Optional CashToken details. """ self.txid = txid self.vout = vout self.satoshis = satoshis self.token = token def __repr__(self) -> str: """Return a concise string representation.""" token_str = str(self.token) if self.token is not None else "None" return f"Utxo(txid={self.txid}, vout={self.vout}, satoshis={self.satoshis}, token={token_str})"
[docs] @classmethod def from_unspent(cls, unspent: Unspent) -> "Utxo": """Convert a bitcash Unspent to a Utxo, mapping CashToken/NFT details. Args: unspent: bitcash Unspent instance. Returns: Utxo: Converted UTXO. """ token_details = nft = None if getattr(unspent, "has_cashtoken", False): if getattr(unspent, "has_nft", False): nft_capability = NftCapability(unspent.nft_capability) nft = TokenDetails.Nft( nft_capability, unspent.nft_commitment.hex() if unspent.nft_commitment else "", ) token_details = TokenDetails(unspent.token_amount, unspent.category_id, nft) return cls(unspent.txid, unspent.txindex, unspent.amount, token_details)
[docs] class UnlockableUtxo(Utxo): """Utxo extended with an unlocker and optional input options.""" def __init__( self, txid: str, vout: int, satoshis: int, token: TokenDetails | None, unlocker: Unlocker, sequence: int | None, ): """Create an UnlockableUtxo from UTXO details and an Unlocker.""" super().__init__(txid, vout, satoshis, token) self.unlocker: Unlocker = unlocker self.sequence: int | None = sequence self.unlocking_bytecode = b""
[docs] @classmethod def from_utxo(cls, utxo: Utxo, unlocker: Unlocker, sequence: int | None) -> "UnlockableUtxo": """Construct an UnlockableUtxo from a plain Utxo and an Unlocker.""" return cls(utxo.txid, utxo.vout, utxo.satoshis, utxo.token, unlocker, sequence)