Source code for cometa.common.datum

"""
Copyright 2025 Biglup Labs.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

from __future__ import annotations
from typing import Optional, Union

from .._ffi import ffi, lib
from ..errors import CardanoError
from .datum_type import DatumType
from ..cryptography.blake2b_hash import Blake2bHash
from ..cbor.cbor_reader import CborReader
from ..cbor.cbor_writer import CborWriter
from ..plutus_data.plutus_data import PlutusData


[docs] class Datum: """ Represents a piece of data attached to a UTxO for Plutus script interaction. Datums act as state for UTxOs, allowing Plutus scripts to perform complex logic based on stored data when the UTxO is being spent. There are two types: - DATA_HASH: A hash reference to off-chain datum data - INLINE_DATA: The actual Plutus data stored directly on-chain Example: >>> datum = Datum.from_data_hash_hex("00" * 32) >>> datum.datum_type DatumType.DATA_HASH """
[docs] def __init__(self, ptr) -> None: if ptr == ffi.NULL: raise CardanoError("Datum: invalid handle") self._ptr = ptr
def __del__(self) -> None: if getattr(self, "_ptr", ffi.NULL) not in (None, ffi.NULL): ptr_ptr = ffi.new("cardano_datum_t**", self._ptr) lib.cardano_datum_unref(ptr_ptr) self._ptr = ffi.NULL
[docs] def __enter__(self) -> Datum: return self
[docs] def __exit__(self, exc_type, exc_val, exc_tb) -> None: pass
[docs] def __repr__(self) -> str: return f"Datum(type={self.datum_type.name})"
[docs] @classmethod def from_data_hash(cls, hash_value: Blake2bHash) -> Datum: """ Creates a datum from a Blake2b hash reference. Args: hash_value: The Blake2b hash of the datum data. Returns: A new Datum instance with type DATA_HASH. Raises: CardanoError: If creation fails. Example: >>> hash_val = Blake2bHash.from_hex("00" * 32) >>> datum = Datum.from_data_hash(hash_val) """ out = ffi.new("cardano_datum_t**") err = lib.cardano_datum_new_data_hash(hash_value._ptr, out) if err != 0: raise CardanoError(f"Failed to create Datum from hash (error code: {err})") return cls(out[0])
[docs] @classmethod def from_data_hash_hex(cls, hex_string: str) -> Datum: """ Creates a datum from a hexadecimal hash string. Args: hex_string: The hash as a hexadecimal string. Returns: A new Datum instance with type DATA_HASH. Raises: CardanoError: If creation fails or hash is invalid. Example: >>> datum = Datum.from_data_hash_hex("abcd1234" * 8) """ out = ffi.new("cardano_datum_t**") hex_bytes = hex_string.encode("utf-8") err = lib.cardano_datum_new_data_hash_hex(hex_bytes, len(hex_bytes), out) if err != 0: raise CardanoError(f"Failed to create Datum from hex (error code: {err})") return cls(out[0])
[docs] @classmethod def from_data_hash_bytes(cls, data: Union[bytes, bytearray]) -> Datum: """ Creates a datum from raw hash bytes. Args: data: The hash as raw bytes. Returns: A new Datum instance with type DATA_HASH. Raises: CardanoError: If creation fails. Example: >>> datum = Datum.from_data_hash_bytes(bytes(32)) """ out = ffi.new("cardano_datum_t**") c_data = ffi.from_buffer("byte_t[]", data) err = lib.cardano_datum_new_data_hash_bytes(c_data, len(data), out) if err != 0: raise CardanoError(f"Failed to create Datum from bytes (error code: {err})") return cls(out[0])
[docs] @classmethod def from_inline_data(cls, data) -> Datum: """ Creates a datum from inline Plutus data. Args: data: The Plutus data to store inline (PlutusData instance). Returns: A new Datum instance with type INLINE_DATA. Raises: CardanoError: If creation fails. Example: >>> plutus_data = PlutusData.from_int(42) >>> datum = Datum.from_inline_data(plutus_data) """ out = ffi.new("cardano_datum_t**") err = lib.cardano_datum_new_inline_data(data._ptr, out) if err != 0: raise CardanoError(f"Failed to create Datum from inline data (error code: {err})") return cls(out[0])
[docs] @classmethod def from_cbor(cls, reader: CborReader) -> Datum: """ Deserializes a Datum from CBOR data. Args: reader: A CborReader positioned at the datum data. Returns: A new Datum deserialized from the CBOR data. Raises: CardanoError: If deserialization fails. """ out = ffi.new("cardano_datum_t**") err = lib.cardano_datum_from_cbor(reader._ptr, out) if err != 0: raise CardanoError(f"Failed to deserialize Datum from CBOR (error code: {err})") return cls(out[0])
@property def datum_type(self) -> DatumType: """Returns the type of this datum (DATA_HASH or INLINE_DATA).""" type_out = ffi.new("cardano_datum_type_t*") err = lib.cardano_datum_get_type(self._ptr, type_out) if err != 0: raise CardanoError(f"Failed to get datum type (error code: {err})") return DatumType(type_out[0]) @property def data_hash(self) -> Optional[Blake2bHash]: """ Returns the hash associated with this datum. Returns None if this is an inline datum without a hash. """ ptr = lib.cardano_datum_get_data_hash(self._ptr) if ptr == ffi.NULL: return None return Blake2bHash(ptr) @data_hash.setter def data_hash(self, value: Blake2bHash) -> None: """Sets the data hash for this datum.""" err = lib.cardano_datum_set_data_hash(self._ptr, value._ptr) if err != 0: raise CardanoError(f"Failed to set data hash (error code: {err})") @property def data_hash_hex(self) -> str: """Returns the data hash as a hexadecimal string.""" hex_ptr = lib.cardano_datum_get_data_hash_hex(self._ptr) if hex_ptr == ffi.NULL: return "" return ffi.string(hex_ptr).decode("utf-8") @property def data_hash_bytes(self) -> bytes: """Returns the data hash as raw bytes.""" size = lib.cardano_datum_get_data_hash_bytes_size(self._ptr) if size == 0: return b"" data = lib.cardano_datum_get_data_hash_bytes(self._ptr) if data == ffi.NULL: return b"" return bytes(ffi.buffer(data, size))
[docs] def get_inline_data(self): """ Returns the inline Plutus data if this is an inline datum. Returns None if this is a data hash datum. Note: This method requires the PlutusData module to be available. Import PlutusData from cometa.plutus_data before calling this method. Raises: ImportError: If PlutusData module is not available. """ ptr = lib.cardano_datum_get_inline_data(self._ptr) if ptr == ffi.NULL: return None return PlutusData(ptr)
[docs] def to_cbor(self, writer: CborWriter) -> None: """ Serializes the datum to CBOR format. Args: writer: A CborWriter to write the serialized data to. Raises: CardanoError: If serialization fails. """ err = lib.cardano_datum_to_cbor(self._ptr, writer._ptr) if err != 0: raise CardanoError(f"Failed to serialize Datum to CBOR (error code: {err})")
[docs] def to_cip116_json(self, writer: "JsonWriter") -> None: """ Converts this object to CIP-116 compliant JSON representation. CIP-116 defines a standard JSON format for Cardano data structures. Args: writer: A JsonWriter to write the serialized data to. Raises: CardanoError: If conversion fails. """ from ..json.json_writer import JsonWriter if not isinstance(writer, JsonWriter): raise TypeError("writer must be a JsonWriter instance") err = lib.cardano_datum_to_cip116_json(self._ptr, writer._ptr) if err != 0: raise CardanoError(f"Failed to convert to CIP-116 JSON (error code: {err})")
[docs] def __eq__(self, other: object) -> bool: """Checks equality with another Datum.""" if not isinstance(other, Datum): return False return lib.cardano_datum_equals(self._ptr, other._ptr)
[docs] def __hash__(self) -> int: """Returns a Python hash for use in sets and dicts.""" return hash((self.datum_type, self.data_hash_bytes))
[docs] def __str__(self) -> str: """Returns a string representation of the datum.""" if self.datum_type == DatumType.DATA_HASH: hash_hex = self.data_hash_hex if hash_hex: return f"DatumHash({hash_hex[:16]}...)" return "DatumHash()" return f"InlineDatum(type={self.datum_type.name})"