Source code for cashscript_py.helpers.cashtoken

"""Utility functions for CashTokens operations (token-aware outputs and address detection)."""

from cashscript_py.helpers.cashaddress import (
    TOKEN_PUBKEY_TYPE,
    TOKEN_SCRIPT_TYPE,
    decode_cash_address_format,
    split_version_byte,
)
from cashscript_py.helpers.data_encoding import int_to_bytes, var_int_bytes
from cashscript_py.interfaces import NftCapability, TokenDetails

PREFIX_TOKEN = b"\xef"


[docs] def serialize_token_prefix(token: TokenDetails | None) -> bytes: """Serialize a CashTokens token prefix. Layout (when present): - 0xef (PREFIX_TOKEN) - token category (32 bytes), encoded in OP_HASH256 byte order (reversed txid) - token bitfield (1 byte): high nibble (structure): 0x40 -> HAS_COMMITMENT_LENGTH (commitment length + commitment follow) 0x20 -> HAS_NFT (low nibble encodes capability: 0x00 none, 0x01 mutable, 0x02 minting) 0x10 -> HAS_AMOUNT (fungible amount follows as varint) 0x80 -> RESERVED (must be 0) low nibble (nft_capability): 0x00 none (immutable), 0x01 mutable, 0x02 minting - [NFT commitment length (varint) + commitment (bytes)] if 0x40 - [fungible amount (varint)] if 0x10 Returns: bytes: the token prefix bytes, or empty bytes if token is None. """ if token is None: return b"" # Category must be 32 bytes; encode in OP_HASH256 byte order (reversed txid hex) category_bytes = bytes.fromhex(token.category)[::-1] if len(category_bytes) != 32: raise ValueError(f"Token category must be 32 bytes, got {len(category_bytes)}") # NFT details (optional) commitment_bytes = b"" nft_cap_bits = 0x00 has_commitment = False if token.nft: cap = token.nft.capability if cap == NftCapability.NONE: nft_cap_bits = 0x00 elif cap == NftCapability.MUTABLE: nft_cap_bits = 0x01 elif cap == NftCapability.MINTING: nft_cap_bits = 0x02 else: raise ValueError(f"Unsupported NFT capability: {cap}") commitment_hex = token.nft.commitment or "" commitment_bytes = bytes.fromhex(commitment_hex) has_commitment = len(commitment_bytes) > 0 has_amount = token.amount > 0 # Build token bitfield # High nibble: structure bits; Low nibble: capability (only if HAS_NFT) bitfield = 0x00 if has_commitment: bitfield |= 0x40 if token.nft: bitfield |= 0x20 bitfield |= nft_cap_bits # low nibble if has_amount: bitfield |= 0x10 # Assemble prefix parts = bytearray() parts += PREFIX_TOKEN parts += category_bytes parts += int_to_bytes(bitfield, 1) if has_commitment: parts += var_int_bytes(len(commitment_bytes)) parts += commitment_bytes if has_amount: parts += var_int_bytes(token.amount) return bytes(parts)
[docs] def is_token_address(address: str) -> bool: """Detect whether a CashAddr is token-aware by inspecting its version byte. Args: address: CashAddr string with prefix. Returns: True if the address indicates a token-aware version; False otherwise. """ _payload, _prefix, version = decode_cash_address_format(address) address_type, _size = split_version_byte(version) return address_type in [TOKEN_PUBKEY_TYPE, TOKEN_SCRIPT_TYPE]