Skip to content

Smart Contracts API Reference

The Contract module provides tools for interacting with Midnight Compact smart contracts, including ABI parsing and state management.

Compact Contract

noxipher.contract.compact

Compact smart contract interface.

Compact language
  • TypeScript-like syntax
  • Compiled by compact compile → ZK circuits + ABI JSON
  • compactc examples/counter/counter.compact --output-dir /tmp/out

CompactContract

Compiled Compact contract ready for deployment/interaction. Wraps contract ABI + ZK circuit files.

Source code in src/noxipher/contract/compact.py
class CompactContract:
    """
    Compiled Compact contract ready for deployment/interaction.
    Wraps contract ABI + ZK circuit files.
    """

    def __init__(self, abi: ContractABI, circuit_dir: Path) -> None:
        self._abi = abi
        self._circuit_dir = circuit_dir

    @classmethod
    def from_directory(cls, circuit_dir: Path) -> CompactContract:
        """
        Load contract from compactc output directory.

        Expected files:
          <circuit_dir>/<name>.contract.json  (ABI)
          <circuit_dir>/*.zkey                (proving keys)
          <circuit_dir>/*.wasm               (WASM circuits)
        """
        json_files = list(circuit_dir.glob("*.contract.json"))
        if not json_files:
            raise ContractError(f"No .contract.json found in {circuit_dir}")
        abi = ContractABI.from_json_file(json_files[0])
        return cls(abi=abi, circuit_dir=circuit_dir)

    @property
    def abi(self) -> ContractABI:
        """Contract ABI."""
        return self._abi

    @property
    def name(self) -> str:
        """Contract name."""
        return self._abi.name

    def get_circuit_path(self, circuit_id: str) -> Path:
        """Get path to circuit file."""
        for ext in [".wasm", ".zkey", ".params"]:
            p = self._circuit_dir / f"{circuit_id}{ext}"
            if p.exists():
                return p
        raise ContractError(f"Circuit file not found for: {circuit_id}")

abi property

Contract ABI.

name property

Contract name.

from_directory(circuit_dir) classmethod

Load contract from compactc output directory.

Expected files

/.contract.json (ABI) /.zkey (proving keys) /.wasm (WASM circuits)

Source code in src/noxipher/contract/compact.py
@classmethod
def from_directory(cls, circuit_dir: Path) -> CompactContract:
    """
    Load contract from compactc output directory.

    Expected files:
      <circuit_dir>/<name>.contract.json  (ABI)
      <circuit_dir>/*.zkey                (proving keys)
      <circuit_dir>/*.wasm               (WASM circuits)
    """
    json_files = list(circuit_dir.glob("*.contract.json"))
    if not json_files:
        raise ContractError(f"No .contract.json found in {circuit_dir}")
    abi = ContractABI.from_json_file(json_files[0])
    return cls(abi=abi, circuit_dir=circuit_dir)

get_circuit_path(circuit_id)

Get path to circuit file.

Source code in src/noxipher/contract/compact.py
def get_circuit_path(self, circuit_id: str) -> Path:
    """Get path to circuit file."""
    for ext in [".wasm", ".zkey", ".params"]:
        p = self._circuit_dir / f"{circuit_id}{ext}"
        if p.exists():
            return p
    raise ContractError(f"Circuit file not found for: {circuit_id}")

ContractABI

Bases: BaseModel

Compact contract ABI — parsed from .contract.json.

⚠️ Schema needs verification from compactc output.

Source code in src/noxipher/contract/compact.py
class ContractABI(BaseModel):
    """
    Compact contract ABI — parsed from <contract>.contract.json.

    ⚠️ Schema needs verification from compactc output.
    """

    name: str
    version: str | None = None
    circuits: list[dict[str, Any]] = []

    entry_points: list[ContractEntryPoint] = []

    @classmethod
    def from_json_file(cls, path: Path) -> ContractABI:
        """Load ABI from compactc output JSON file."""
        try:
            raw = json.loads(path.read_text())
        except (json.JSONDecodeError, FileNotFoundError) as e:
            raise ContractError(f"Cannot load ABI from {path}: {e}") from e

        # Parse entry points
        entry_points = []
        for ep in raw.get("entryPoints", raw.get("entry_points", [])):
            entry_points.append(
                ContractEntryPoint(
                    name=ep.get("name", ""),
                    is_impure=ep.get("impure", ep.get("is_impure", False)),
                    param_types=ep.get("params", ep.get("param_types", [])),
                    return_type=ep.get("returns", ep.get("return_type")),
                )
            )

        return cls(
            name=raw.get("name", ""),
            version=raw.get("version"),
            circuits=raw.get("circuits", []),
            entry_points=entry_points,
        )

from_json_file(path) classmethod

Load ABI from compactc output JSON file.

Source code in src/noxipher/contract/compact.py
@classmethod
def from_json_file(cls, path: Path) -> ContractABI:
    """Load ABI from compactc output JSON file."""
    try:
        raw = json.loads(path.read_text())
    except (json.JSONDecodeError, FileNotFoundError) as e:
        raise ContractError(f"Cannot load ABI from {path}: {e}") from e

    # Parse entry points
    entry_points = []
    for ep in raw.get("entryPoints", raw.get("entry_points", [])):
        entry_points.append(
            ContractEntryPoint(
                name=ep.get("name", ""),
                is_impure=ep.get("impure", ep.get("is_impure", False)),
                param_types=ep.get("params", ep.get("param_types", [])),
                return_type=ep.get("returns", ep.get("return_type")),
            )
        )

    return cls(
        name=raw.get("name", ""),
        version=raw.get("version"),
        circuits=raw.get("circuits", []),
        entry_points=entry_points,
    )

ContractEntryPoint

Bases: BaseModel

Contract entry point (function).

Source code in src/noxipher/contract/compact.py
class ContractEntryPoint(BaseModel):
    """Contract entry point (function)."""

    name: str
    is_impure: bool = False  # Impure = modifies state
    param_types: list[str] = []
    return_type: str | None = None

Contract Instance

noxipher.contract.instance

ContractInstance — deployed contract interaction.

ContractInstance

Deployed contract on Midnight network. Allows calling contract entry points.

Source code in src/noxipher/contract/instance.py
class ContractInstance:
    """
    Deployed contract on Midnight network.
    Allows calling contract entry points.
    """

    def __init__(
        self,
        address: str,
        contract: CompactContract,
        client: NoxipherClient,
    ) -> None:
        self._address = address
        self._contract = contract
        self._client = client

    @property
    def address(self) -> str:
        """Contract address."""
        return self._address

    async def call(
        self,
        entry_point: str,
        args: dict[str, Any] | None = None,
        wallet: MidnightWallet | None = None,
    ) -> dict[str, Any]:
        """
        Call contract entry point.

        Impure calls → create transaction (requires wallet).
        Pure calls → query state (no wallet needed).
        """
        ep = next(
            (e for e in self._contract.abi.entry_points if e.name == entry_point),
            None,
        )
        if ep is None:
            raise ContractError(f"Entry point not found: {entry_point}")

        if ep.is_impure:
            if wallet is None:
                raise ContractError(f"Impure entry point '{entry_point}' requires wallet")
            return await self._call_impure(entry_point, args or {}, wallet)
        else:
            return await self._call_pure(entry_point, args or {})

    async def _call_pure(self, entry_point: str, args: dict[str, Any]) -> dict[str, Any]:
        """Query contract state (no transaction)."""
        state = await self._client.indexer.get_transactions(address=self._address, limit=1)
        return {"result": state, "tx_hash": None}

    async def _call_impure(
        self, entry_point: str, args: dict[str, Any], wallet: MidnightWallet
    ) -> dict[str, Any]:
        """Call impure entry point → transaction."""
        tx_receipt = await self._client.tx.call_contract(
            wallet=wallet,
            contract_address=self._address,
            entry_point=entry_point,
            args=args,
        )
        return {"result": None, "tx_hash": tx_receipt.hash}

address property

Contract address.

call(entry_point, args=None, wallet=None) async

Call contract entry point.

Impure calls → create transaction (requires wallet). Pure calls → query state (no wallet needed).

Source code in src/noxipher/contract/instance.py
async def call(
    self,
    entry_point: str,
    args: dict[str, Any] | None = None,
    wallet: MidnightWallet | None = None,
) -> dict[str, Any]:
    """
    Call contract entry point.

    Impure calls → create transaction (requires wallet).
    Pure calls → query state (no wallet needed).
    """
    ep = next(
        (e for e in self._contract.abi.entry_points if e.name == entry_point),
        None,
    )
    if ep is None:
        raise ContractError(f"Entry point not found: {entry_point}")

    if ep.is_impure:
        if wallet is None:
            raise ContractError(f"Impure entry point '{entry_point}' requires wallet")
        return await self._call_impure(entry_point, args or {}, wallet)
    else:
        return await self._call_pure(entry_point, args or {})

Contract Service

noxipher.contract.service

ContractService — deploy and manage Compact contracts.

ContractService

Service for deploying and interacting with Compact contracts.

Source code in src/noxipher/contract/service.py
class ContractService:
    """Service for deploying and interacting with Compact contracts."""

    def __init__(self, client: NoxipherClient) -> None:
        self._client = client

    def load_contract(self, circuit_dir: Path) -> CompactContract:
        """Load a compiled Compact contract from directory."""
        return CompactContract.from_directory(circuit_dir)

    async def deploy(
        self,
        contract: CompactContract,
        wallet: MidnightWallet,
        initial_state: dict[str, Any] | None = None,
    ) -> ContractInstance:
        """
        Deploy a contract to the network.

        Returns: ContractInstance with deployed address.
        """
        # For now, we assume bytecode is passed as a string/bytes in initial_state or similar
        # Real compact contracts have a .wasm circuit that acts as bytecode
        bytecode = contract.get_circuit_path(contract.name).read_bytes()

        receipt = await self._client.tx.deploy_contract(
            wallet=wallet,
            bytecode=bytecode,
            initial_state=initial_state or {},
        )

        # Address is extracted from receipt events (ContractDeployed)
        address = receipt.contract_address
        if not address:
            # Fallback for older indexers or testnets
            log.warning("contract_address_not_found_in_receipt", tx_hash=receipt.hash)
            address = f"0x{receipt.hash[:64]}"
        return self.at_address(address, contract)

    def at_address(self, address: str, contract: CompactContract) -> ContractInstance:
        """Create ContractInstance for an already-deployed contract."""
        return ContractInstance(address=address, contract=contract, client=self._client)

at_address(address, contract)

Create ContractInstance for an already-deployed contract.

Source code in src/noxipher/contract/service.py
def at_address(self, address: str, contract: CompactContract) -> ContractInstance:
    """Create ContractInstance for an already-deployed contract."""
    return ContractInstance(address=address, contract=contract, client=self._client)

deploy(contract, wallet, initial_state=None) async

Deploy a contract to the network.

Returns: ContractInstance with deployed address.

Source code in src/noxipher/contract/service.py
async def deploy(
    self,
    contract: CompactContract,
    wallet: MidnightWallet,
    initial_state: dict[str, Any] | None = None,
) -> ContractInstance:
    """
    Deploy a contract to the network.

    Returns: ContractInstance with deployed address.
    """
    # For now, we assume bytecode is passed as a string/bytes in initial_state or similar
    # Real compact contracts have a .wasm circuit that acts as bytecode
    bytecode = contract.get_circuit_path(contract.name).read_bytes()

    receipt = await self._client.tx.deploy_contract(
        wallet=wallet,
        bytecode=bytecode,
        initial_state=initial_state or {},
    )

    # Address is extracted from receipt events (ContractDeployed)
    address = receipt.contract_address
    if not address:
        # Fallback for older indexers or testnets
        log.warning("contract_address_not_found_in_receipt", tx_hash=receipt.hash)
        address = f"0x{receipt.hash[:64]}"
    return self.at_address(address, contract)

load_contract(circuit_dir)

Load a compiled Compact contract from directory.

Source code in src/noxipher/contract/service.py
def load_contract(self, circuit_dir: Path) -> CompactContract:
    """Load a compiled Compact contract from directory."""
    return CompactContract.from_directory(circuit_dir)