"""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]