# Copyright (c) Subzero Labs, Inc.
# SPDX-License-Identifier: Apache-2.0
# AUTO-GENERATED FROM spec.wit — DO NOT EDIT

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Optional

# Type aliases
Kelvin = int


# Information about an on-chain account.
@dataclass
class AccountInfo:
    # Account balance in kelvins.
    kelvin: int
    # Owner program public key.
    owner: Any
    # Account data as encoded strings (typically [encoded_data, encoding_format]).
    data: list[str]
    # Whether the account contains executable code.
    executable: bool
    # Rent epoch.
    rent_epoch: int
    # Account data size in bytes.
    space: int

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "AccountInfo":
        return cls(
            kelvin=d["kelvin"],
            owner=d["owner"],
            data=d["data"],
            executable=d["executable"],
            rent_epoch=d["rentEpoch"],
            space=d["space"],
        )

    def to_dict(self) -> dict:
        return {
            "kelvin": self.kelvin,
            "owner": self.owner,
            "data": self.data,
            "executable": self.executable,
            "rentEpoch": self.rent_epoch,
            "space": self.space,
        }


# A compiled instruction within a transaction message.
@dataclass
class CompiledInstruction:
    # Index into the account keys array identifying the program.
    program_id_index: int
    # Indices into the account keys array for this instruction.
    accounts: bytes
    # Instruction data (base58-encoded on the wire).
    data: str

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "CompiledInstruction":
        return cls(
            program_id_index=d["programIdIndex"],
            accounts=bytes(d["accounts"]) if isinstance(d["accounts"], list) else d["accounts"],
            data=d["data"],
        )

    def to_dict(self) -> dict:
        return {
            "programIdIndex": self.program_id_index,
            "accounts": self.accounts,
            "data": self.data,
        }


# Header of a transaction message.
@dataclass
class MessageHeader:
    # Number of signatures required.
    num_required_signatures: int
    # Number of read-only signed accounts.
    num_readonly_signed_accounts: int
    # Number of read-only unsigned accounts.
    num_readonly_unsigned_accounts: int

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "MessageHeader":
        return cls(
            num_required_signatures=d["numRequiredSignatures"],
            num_readonly_signed_accounts=d["numReadonlySignedAccounts"],
            num_readonly_unsigned_accounts=d["numReadonlyUnsignedAccounts"],
        )

    def to_dict(self) -> dict:
        return {
            "numRequiredSignatures": self.num_required_signatures,
            "numReadonlySignedAccounts": self.num_readonly_signed_accounts,
            "numReadonlyUnsignedAccounts": self.num_readonly_unsigned_accounts,
        }


# The message portion of a transaction.
@dataclass
class TransactionMessage:
    header: MessageHeader
    account_keys: list[Any]
    instructions: list[CompiledInstruction]

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "TransactionMessage":
        return cls(
            header=MessageHeader.from_dict(d["header"]),
            account_keys=d["accountKeys"],
            instructions=[CompiledInstruction.from_dict(x) for x in d["instructions"]],
        )

    def to_dict(self) -> dict:
        return {
            "header": self.header.to_dict(),
            "accountKeys": self.account_keys,
            "instructions": [x.to_dict() for x in self.instructions],
        }


# A full transaction (signatures + message).
@dataclass
class TransactionData:
    signatures: list[Any]
    message: TransactionMessage
    # Timestamp from which the transaction is valid.
    valid_from: int

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "TransactionData":
        return cls(
            signatures=d["signatures"],
            message=TransactionMessage.from_dict(d["message"]),
            valid_from=d["validFrom"],
        )

    def to_dict(self) -> dict:
        return {
            "signatures": self.signatures,
            "message": self.message.to_dict(),
            "validFrom": self.valid_from,
        }


# Metadata about transaction execution.
@dataclass
class TransactionStatusMetadata:
    # Fee paid for the transaction in kelvins.
    fee: int
    # Error message if the transaction failed, absent if successful.
    err: Optional[str] = None
    # Log messages emitted during execution (if available).
    log_messages: Optional[list[str]] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "TransactionStatusMetadata":
        return cls(
            fee=d["fee"],
            err=d.get("err"),
            log_messages=d.get("logMessages"),
        )

    def to_dict(self) -> dict:
        return {
            "fee": self.fee,
            "err": self.err,
            "logMessages": self.log_messages,
        }


# Full response for a transaction query.
@dataclass
class TransactionResponse:
    # Block height where this transaction was processed.
    block_height: int
    # Execution metadata (fee, error).
    meta: TransactionStatusMetadata
    # The full transaction content.
    transaction: TransactionData
    # Unix timestamp of the block (if available).
    block_time: Optional[int] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "TransactionResponse":
        return cls(
            block_height=d["blockHeight"],
            block_time=d.get("blockTime"),
            meta=TransactionStatusMetadata.from_dict(d["meta"]),
            transaction=TransactionData.from_dict(d["transaction"]),
        )

    def to_dict(self) -> dict:
        return {
            "blockHeight": self.block_height,
            "blockTime": self.block_time,
            "meta": self.meta.to_dict(),
            "transaction": self.transaction.to_dict(),
        }


# A transaction paired with its metadata (used in block responses).
@dataclass
class TransactionWithMeta:
    # The full transaction content.
    transaction: TransactionData
    # Execution metadata (fee, error), absent if not available.
    meta: Optional[TransactionStatusMetadata] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "TransactionWithMeta":
        return cls(
            transaction=TransactionData.from_dict(d["transaction"]),
            meta=TransactionStatusMetadata.from_dict(d["meta"]) if d.get("meta") is not None else None,
        )

    def to_dict(self) -> dict:
        return {
            "transaction": self.transaction.to_dict(),
            "meta": self.meta.to_dict() if self.meta is not None else None,
        }


# Status of a transaction signature.
@dataclass
class SignatureStatus:
    # Slot in which the transaction was processed.
    slot: int
    # Whether the transaction has been executed.
    executed: bool
    # Error message if the transaction failed.
    err: Optional[str] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "SignatureStatus":
        return cls(
            slot=d["slot"],
            executed=d["executed"],
            err=d.get("err"),
        )

    def to_dict(self) -> dict:
        return {
            "slot": self.slot,
            "executed": self.executed,
            "err": self.err,
        }


# Information about the current epoch.
@dataclass
class EpochInfo:
    # Current epoch number.
    epoch: int
    # Current slot within the epoch.
    slot_index: int
    # Total slots in the epoch.
    slots_in_epoch: int
    # Absolute slot number.
    absolute_slot: int
    # Current block height.
    block_height: int
    # Total transaction count (if available).
    transaction_count: Optional[int] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "EpochInfo":
        return cls(
            epoch=d["epoch"],
            slot_index=d["slotIndex"],
            slots_in_epoch=d["slotsInEpoch"],
            absolute_slot=d["absoluteSlot"],
            block_height=d["blockHeight"],
            transaction_count=d.get("transactionCount"),
        )

    def to_dict(self) -> dict:
        return {
            "epoch": self.epoch,
            "slotIndex": self.slot_index,
            "slotsInEpoch": self.slots_in_epoch,
            "absoluteSlot": self.absolute_slot,
            "blockHeight": self.block_height,
            "transactionCount": self.transaction_count,
        }


# Options for submitting a transaction.
@dataclass
class SendTransactionOptions:
    # Skip preflight simulation.
    skip_preflight: bool
    # Maximum retries by the RPC node.
    max_retries: int
    # Wait until the transaction has been executed before returning.
    wait_for_execution: bool

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "SendTransactionOptions":
        return cls(
            skip_preflight=d["skipPreflight"],
            max_retries=d["maxRetries"],
            wait_for_execution=d["waitForExecution"],
        )

    def to_dict(self) -> dict:
        return {
            "skipPreflight": self.skip_preflight,
            "maxRetries": self.max_retries,
            "waitForExecution": self.wait_for_execution,
        }


# Options for confirming a transaction.
@dataclass
class ConfirmTransactionOptions:
    # Maximum polling attempts (default: 30).
    max_retries: int
    # Delay between polls in milliseconds (default: 1000).
    retry_delay_ms: int

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "ConfirmTransactionOptions":
        return cls(
            max_retries=d["maxRetries"],
            retry_delay_ms=d["retryDelayMs"],
        )

    def to_dict(self) -> dict:
        return {
            "maxRetries": self.max_retries,
            "retryDelayMs": self.retry_delay_ms,
        }


# Combined options for send-and-confirm.
@dataclass
class SendAndConfirmOptions:
    # Send options.
    skip_preflight: bool
    max_retries: int
    # Confirm options.
    confirm_max_retries: int
    confirm_retry_delay_ms: int

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "SendAndConfirmOptions":
        return cls(
            skip_preflight=d["skipPreflight"],
            max_retries=d["maxRetries"],
            confirm_max_retries=d["confirmMaxRetries"],
            confirm_retry_delay_ms=d["confirmRetryDelayMs"],
        )

    def to_dict(self) -> dict:
        return {
            "skipPreflight": self.skip_preflight,
            "maxRetries": self.max_retries,
            "confirmMaxRetries": self.confirm_max_retries,
            "confirmRetryDelayMs": self.confirm_retry_delay_ms,
        }


# Result of a confirmed transaction.
@dataclass
class ConfirmedTransaction:
    # Transaction signature (base58).
    signature: str
    # Whether the transaction was executed.
    executed: bool
    # Error message if failed.
    err: Optional[str] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "ConfirmedTransaction":
        return cls(
            signature=d["signature"],
            executed=d["executed"],
            err=d.get("err"),
        )

    def to_dict(self) -> dict:
        return {
            "signature": self.signature,
            "executed": self.executed,
            "err": self.err,
        }


# Fee calculation response.
@dataclass
class FeeResponse:
    # The fee in kelvins, absent if message is invalid.
    value: Optional[int] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "FeeResponse":
        return cls(
            value=d.get("value"),
        )

    def to_dict(self) -> dict:
        return {
            "value": self.value,
        }


# Response from blockhash validation.
@dataclass
class IsBlockhashValidResponse:
    # Slot from the response context.
    slot: int
    # Whether the blockhash is still valid.
    is_valid: bool

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "IsBlockhashValidResponse":
        return cls(
            slot=d["slot"],
            is_valid=d["isValid"],
        )

    def to_dict(self) -> dict:
        return {
            "slot": self.slot,
            "isValid": self.is_valid,
        }


# Configuration for getSignaturesForAddress.
@dataclass
class GetSignaturesForAddressConfig:
    # Maximum number of signatures to return.
    limit: Optional[int] = None
    # Search backwards from this signature (base58).
    before: Optional[str] = None
    # Search forwards from this signature (base58).
    until: Optional[str] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "GetSignaturesForAddressConfig":
        return cls(
            limit=d.get("limit"),
            before=d.get("before"),
            until=d.get("until"),
        )

    def to_dict(self) -> dict:
        return {
            "limit": self.limit,
            "before": self.before,
            "until": self.until,
        }


# Information about a single signature.
@dataclass
class SignatureInfo:
    # Transaction signature (base58).
    signature: str
    # Block height where the transaction was processed.
    block_height: int
    # Unix timestamp of the block.
    block_time: int
    # Error message if the transaction failed.
    err: Optional[str] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "SignatureInfo":
        return cls(
            signature=d["signature"],
            block_height=d["blockHeight"],
            block_time=d["blockTime"],
            err=d.get("err"),
        )

    def to_dict(self) -> dict:
        return {
            "signature": self.signature,
            "blockHeight": self.block_height,
            "blockTime": self.block_time,
            "err": self.err,
        }


# Kind of subscription.
class SubscriptionKind(Enum):
    PERSISTENT = "persistent"
    ONE_SHOT = "one_shot"


# Metadata about an account used in a subscription instruction.
@dataclass
class SubscriptionAccountMeta:
    # The account's public key (base58).
    pubkey: str
    # Whether the account must sign the transaction.
    is_signer: bool
    # Whether the account can be written to during execution.
    is_writable: bool

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "SubscriptionAccountMeta":
        return cls(
            pubkey=d["pubkey"],
            is_signer=d["is_signer"],
            is_writable=d["is_writable"],
        )

    def to_dict(self) -> dict:
        return {
            "pubkey": self.pubkey,
            "is_signer": self.is_signer,
            "is_writable": self.is_writable,
        }


# A single instruction within a subscription.
@dataclass
class SubscriptionInstruction:
    # The program ID invoked (base58).
    program_id: str
    # Metadata for the accounts involved in the instruction.
    accounts: list[SubscriptionAccountMeta]
    # Opaque binary data passed to the program.
    data: bytes

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "SubscriptionInstruction":
        return cls(
            program_id=d["program_id"],
            accounts=[SubscriptionAccountMeta.from_dict(x) for x in d["accounts"]],
            data=bytes(d["data"]) if isinstance(d["data"], list) else d["data"],
        )

    def to_dict(self) -> dict:
        return {
            "program_id": self.program_id,
            "accounts": [x.to_dict() for x in self.accounts],
            "data": self.data,
        }


# A subscription record.
@dataclass
class Subscription:
    kind: SubscriptionKind
    topic: str
    # Instructions to be executed when triggered.
    instructions: list[SubscriptionInstruction]
    subscriber: str
    # The account pubkey (base58) that stores the event data.
    event_account: Optional[str] = None
    # Timestamp range for triggering (start inclusive, end exclusive).
    timestamp_range: Optional[tuple[int, int]] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "Subscription":
        return cls(
            kind=SubscriptionKind.from_dict(d["kind"]),
            topic=d["topic"],
            instructions=[SubscriptionInstruction.from_dict(x) for x in d["instructions"]],
            subscriber=d["subscriber"],
            event_account=d.get("event_account"),
            timestamp_range=d.get("timestamp_range"),
        )

    def to_dict(self) -> dict:
        return {
            "kind": self.kind.to_dict(),
            "topic": self.topic,
            "instructions": [x.to_dict() for x in self.instructions],
            "subscriber": self.subscriber,
            "event_account": self.event_account,
            "timestamp_range": self.timestamp_range.to_dict() if self.timestamp_range is not None else None,
        }


# A transaction triggered by a subscription.
@dataclass
class TriggeredTransaction:
    # Transaction signature (base58).
    signature: str
    # Block number where executed.
    block_number: int

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "TriggeredTransaction":
        return cls(
            signature=d["signature"],
            block_number=d["blockNumber"],
        )

    def to_dict(self) -> dict:
        return {
            "signature": self.signature,
            "blockNumber": self.block_number,
        }


# Event data from a workflow trigger.
@dataclass
class EventData:
    topic: str

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "EventData":
        return cls(
            topic=d["topic"],
        )

    def to_dict(self) -> dict:
        return {
            "topic": self.topic,
        }


# Timestamp range.
@dataclass
class TimestampRange:
    start: int
    end: int

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "TimestampRange":
        return cls(
            start=d["start"],
            end=d["end"],
        )

    def to_dict(self) -> dict:
        return {
            "start": self.start,
            "end": self.end,
        }


# Information about what triggered a workflow transaction.
@dataclass
class TriggerInfo:
    event: EventData
    subscription_pubkey: str
    timestamp_range: Optional[TimestampRange] = None
    event_account: Optional[str] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "TriggerInfo":
        return cls(
            event=EventData.from_dict(d["event"]),
            subscription_pubkey=d["subscriptionPubkey"],
            timestamp_range=TimestampRange.from_dict(d["timestampRange"]) if d.get("timestampRange") is not None else None,
            event_account=d.get("eventAccount"),
        )

    def to_dict(self) -> dict:
        return {
            "event": self.event.to_dict(),
            "subscriptionPubkey": self.subscription_pubkey,
            "timestampRange": self.timestamp_range.to_dict() if self.timestamp_range is not None else None,
            "eventAccount": self.event_account,
        }


# Transaction data within a workflow node.
@dataclass
class TransactionNodeData:
    block_height: int
    timestamp: int
    success: bool
    instruction_count: int
    instruction_program_ids: list[str]

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "TransactionNodeData":
        return cls(
            block_height=d["blockHeight"],
            timestamp=d["timestamp"],
            success=d["success"],
            instruction_count=d["instructionCount"],
            instruction_program_ids=d["instructionProgramIds"],
        )

    def to_dict(self) -> dict:
        return {
            "blockHeight": self.block_height,
            "timestamp": self.timestamp,
            "success": self.success,
            "instructionCount": self.instruction_count,
            "instructionProgramIds": self.instruction_program_ids,
        }


# A node in the workflow lineage tree.
@dataclass
class WorkflowNode:
    # Transaction signature (base58).
    id: str
    # Distance from the root transaction.
    depth: int
    # Transaction data.
    data: TransactionNodeData
    # Subscriptions associated with this transaction.
    subscriptions: list[Subscription]
    # Child transaction signatures.
    workflow_children: list[str]
    # Whether more children exist beyond what is shown.
    has_more_children: bool
    # Trigger information if this node was triggered by a subscription event.
    triggered_by: Optional[TriggerInfo] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "WorkflowNode":
        return cls(
            id=d["id"],
            depth=d["depth"],
            data=TransactionNodeData.from_dict(d["data"]),
            subscriptions=[Subscription.from_dict(x) for x in d["subscriptions"]],
            triggered_by=TriggerInfo.from_dict(d["triggeredBy"]) if d.get("triggeredBy") is not None else None,
            workflow_children=d["workflowChildren"],
            has_more_children=d["hasMoreChildren"],
        )

    def to_dict(self) -> dict:
        return {
            "id": self.id,
            "depth": self.depth,
            "data": self.data.to_dict(),
            "subscriptions": [x.to_dict() for x in self.subscriptions],
            "triggeredBy": self.triggered_by.to_dict() if self.triggered_by is not None else None,
            "workflowChildren": self.workflow_children,
            "hasMoreChildren": self.has_more_children,
        }


# Reason for truncating workflow traversal.
class TruncationReason(Enum):
    MAX_DEPTH = "max_depth"
    MAX_NODES = "max_nodes"
    NONE = "none"


# Workflow lineage request parameters.
@dataclass
class GetWorkflowLineageRequest:
    # Root transaction signature (base58).
    signature: str
    # Maximum traversal depth (1-100, default 3).
    max_depth: Optional[int] = None
    # Whether to include event details.
    include_events: Optional[bool] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "GetWorkflowLineageRequest":
        return cls(
            signature=d["signature"],
            max_depth=d.get("maxDepth"),
            include_events=d.get("includeEvents"),
        )

    def to_dict(self) -> dict:
        return {
            "signature": self.signature,
            "maxDepth": self.max_depth,
            "includeEvents": self.include_events,
        }


# Workflow lineage response.
# The lineage tree structure.
@dataclass
class WorkflowLineage:
    # All nodes (transactions) in the lineage tree.
    workflow_nodes: list[WorkflowNode]

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "WorkflowLineage":
        return cls(
            workflow_nodes=[WorkflowNode.from_dict(x) for x in d["workflowNodes"]],
        )

    def to_dict(self) -> dict:
        return {
            "workflowNodes": [x.to_dict() for x in self.workflow_nodes],
        }


# Workflow lineage response.
@dataclass
class GetWorkflowLineageResponse:
    # The lineage graph data.
    lineage: WorkflowLineage
    # Leaf transaction signatures (base58-encoded).
    leaves: list[str]
    # Whether the traversal was truncated due to max-depth.
    truncated: bool
    # The reason for truncation.
    truncation_reason: TruncationReason
    # Hints for continuation (base58-encoded).
    continuation_hints: list[str]

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "GetWorkflowLineageResponse":
        return cls(
            lineage=WorkflowLineage.from_dict(d["lineage"]),
            leaves=d["leaves"],
            truncated=d["truncated"],
            truncation_reason=TruncationReason.from_dict(d["truncationReason"]),
            continuation_hints=d["continuationHints"],
        )

    def to_dict(self) -> dict:
        return {
            "lineage": self.lineage.to_dict(),
            "leaves": self.leaves,
            "truncated": self.truncated,
            "truncationReason": self.truncation_reason.to_dict(),
            "continuationHints": self.continuation_hints,
        }


# Configuration for paginated transaction queries.
@dataclass
class GetTransactionsConfig:
    # Maximum number of transactions to return.
    limit: Optional[int] = None
    # Cursor for pagination.
    before: Optional[str] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "GetTransactionsConfig":
        return cls(
            limit=d.get("limit"),
            before=d.get("before"),
        )

    def to_dict(self) -> dict:
        return {
            "limit": self.limit,
            "before": self.before,
        }


# Brief info about a transaction in a paginated list.
@dataclass
class TransactionInfo:
    # Transaction signature (base58).
    signature: str
    # Slot where the transaction was processed.
    slot: int
    # Block time (unix timestamp).
    block_time: Optional[int] = None
    # Error message if failed.
    err: Optional[str] = None
    # Transaction version.
    version: Optional[str] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "TransactionInfo":
        return cls(
            signature=d["signature"],
            slot=d["slot"],
            block_time=d.get("blockTime"),
            err=d.get("err"),
            version=d.get("version"),
        )

    def to_dict(self) -> dict:
        return {
            "signature": self.signature,
            "slot": self.slot,
            "blockTime": self.block_time,
            "err": self.err,
            "version": self.version,
        }


# Filter for what kind of accounts to return.
@dataclass
class AccountFilter:
    tag: str
    value: Any = None


# Configuration for getAccountsByOwner pagination.
@dataclass
class GetAccountsByOwnerConfig:
    # Maximum number of accounts to return.
    limit: Optional[int] = None
    # Cursor for pagination: accounts after this pubkey (base58).
    after: Optional[str] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "GetAccountsByOwnerConfig":
        return cls(
            limit=d.get("limit"),
            after=d.get("after"),
        )

    def to_dict(self) -> dict:
        return {
            "limit": self.limit,
            "after": self.after,
        }


# An account returned by getAccountsByOwner.
@dataclass
class OwnerAccount:
    # Account public key.
    pubkey: Any
    # Account information.
    account: AccountInfo

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "OwnerAccount":
        return cls(
            pubkey=d["pubkey"],
            account=AccountInfo.from_dict(d["account"]),
        )

    def to_dict(self) -> dict:
        return {
            "pubkey": self.pubkey,
            "account": self.account.to_dict(),
        }


# Pagination metadata.
@dataclass
class PaginationInfo:
    has_more: bool
    next_cursor: Optional[str] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "PaginationInfo":
        return cls(
            has_more=d["hasMore"],
            next_cursor=d.get("nextCursor"),
        )

    def to_dict(self) -> dict:
        return {
            "hasMore": self.has_more,
            "nextCursor": self.next_cursor,
        }

OptionalAccountInfo = Optional[AccountInfo]


# Validator health status.
@dataclass
class ValidatorHealth:
    status: str

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "ValidatorHealth":
        return cls(
            status=d["status"],
        )

    def to_dict(self) -> dict:
        return {
            "status": self.status,
        }


# Connected full node info.
@dataclass
class ConnectedNode:
    # Network public key (hex or base58).
    public_key: str
    # Connection duration in milliseconds.
    connected_ms: int

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "ConnectedNode":
        return cls(
            public_key=d["publicKey"],
            connected_ms=d["connectedMs"],
        )

    def to_dict(self) -> dict:
        return {
            "publicKey": self.public_key,
            "connectedMs": self.connected_ms,
        }


# The TEE's X25519 public key for HPKE encryption.
@dataclass
class SecretSharingPubkey:
    # Hex-encoded public key.
    public_key: str

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "SecretSharingPubkey":
        return cls(
            public_key=d["publicKey"],
        )

    def to_dict(self) -> dict:
        return {
            "publicKey": self.public_key,
        }


# Request to submit an epoch change (admin-only).
# Validator information for epoch change requests.
@dataclass
class ValidatorInfoRequest:
    # Validator stake amount.
    stake: int
    # Consensus network address (Multiaddr string).
    consensus_address: str
    # State sync network address (Multiaddr string).
    state_sync_address: str
    # Validator hostname.
    hostname: str
    # Authority public key.
    authority_key: str
    # Protocol public key.
    protocol_key: str
    # Network public key.
    network_key: str
    # Signing public key.
    signing_key: str

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "ValidatorInfoRequest":
        return cls(
            stake=d["stake"],
            consensus_address=d["consensusAddress"],
            state_sync_address=d["stateSyncAddress"],
            hostname=d["hostname"],
            authority_key=d["authorityKey"],
            protocol_key=d["protocolKey"],
            network_key=d["networkKey"],
            signing_key=d["signingKey"],
        )

    def to_dict(self) -> dict:
        return {
            "stake": self.stake,
            "consensusAddress": self.consensus_address,
            "stateSyncAddress": self.state_sync_address,
            "hostname": self.hostname,
            "authorityKey": self.authority_key,
            "protocolKey": self.protocol_key,
            "networkKey": self.network_key,
            "signingKey": self.signing_key,
        }


# Consensus configuration parameters for an epoch change.
@dataclass
class EpochConsensusConfigRequest:
    # Number of leaders per round.
    num_leaders_per_round: Optional[int] = None
    # Maximum transaction bytes per block.
    max_transactions_in_block_bytes: Optional[int] = None
    # Maximum number of transactions per block.
    max_num_transactions_in_block: Optional[int] = None
    # Maximum size of a single transaction in bytes.
    max_transaction_size_bytes: Optional[int] = None
    # Garbage collection depth.
    gc_depth: Optional[int] = None
    # Whether to enable zstd compression.
    zstd_compression: Optional[bool] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "EpochConsensusConfigRequest":
        return cls(
            num_leaders_per_round=d.get("numLeadersPerRound"),
            max_transactions_in_block_bytes=d.get("maxTransactionsInBlockBytes"),
            max_num_transactions_in_block=d.get("maxNumTransactionsInBlock"),
            max_transaction_size_bytes=d.get("maxTransactionSizeBytes"),
            gc_depth=d.get("gcDepth"),
            zstd_compression=d.get("zstdCompression"),
        )

    def to_dict(self) -> dict:
        return {
            "numLeadersPerRound": self.num_leaders_per_round,
            "maxTransactionsInBlockBytes": self.max_transactions_in_block_bytes,
            "maxNumTransactionsInBlock": self.max_num_transactions_in_block,
            "maxTransactionSizeBytes": self.max_transaction_size_bytes,
            "gcDepth": self.gc_depth,
            "zstdCompression": self.zstd_compression,
        }


# Request to submit an epoch change (admin-only).
@dataclass
class SubmitEpochChangeRequest:
    # The new epoch number.
    new_epoch: int
    # List of validators for the new epoch.
    validators: list[ValidatorInfoRequest]
    # Optional consensus configuration overrides.
    consensus_config: Optional[EpochConsensusConfigRequest] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "SubmitEpochChangeRequest":
        return cls(
            new_epoch=d["newEpoch"],
            validators=[ValidatorInfoRequest.from_dict(x) for x in d["validators"]],
            consensus_config=EpochConsensusConfigRequest.from_dict(d["consensusConfig"]) if d.get("consensusConfig") is not None else None,
        )

    def to_dict(self) -> dict:
        return {
            "newEpoch": self.new_epoch,
            "validators": [x.to_dict() for x in self.validators],
            "consensusConfig": self.consensus_config.to_dict() if self.consensus_config is not None else None,
        }


# Response from submitting an epoch change.
@dataclass
class SubmitEpochChangeResponse:
    # Whether the submission was accepted.
    success: bool

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "SubmitEpochChangeResponse":
        return cls(
            success=d["success"],
        )

    def to_dict(self) -> dict:
        return {
            "success": self.success,
        }


# Configuration for block queries.
@dataclass
class GetBlockConfig:
    # Transaction detail level: "full", "signatures", or "none".
    # Default: "full".
    transaction_details: Optional[str] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "GetBlockConfig":
        return cls(
            transaction_details=d.get("transactionDetails"),
        )

    def to_dict(self) -> dict:
        return {
            "transactionDetails": self.transaction_details,
        }


# Information about a block.
@dataclass
class BlockInfo:
    # The blockhash (base58).
    blockhash: str
    # Block height.
    block_height: int
    # Unix timestamp of the block.
    block_time: int
    # Full transactions with metadata (present when transaction-details = "full").
    transactions: Optional[list[TransactionWithMeta]] = None
    # Transaction signatures only (present when transaction-details = "signatures").
    signatures: Optional[list[str]] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "BlockInfo":
        return cls(
            blockhash=d["blockhash"],
            block_height=d["blockHeight"],
            block_time=d["blockTime"],
            transactions=[TransactionWithMeta.from_dict(x) for x in d["transactions"]] if d.get("transactions") is not None else None,
            signatures=d.get("signatures"),
        )

    def to_dict(self) -> dict:
        return {
            "blockhash": self.blockhash,
            "blockHeight": self.block_height,
            "blockTime": self.block_time,
            "transactions": [x.to_dict() for x in self.transactions] if self.transactions is not None else None,
            "signatures": self.signatures,
        }


# Configuration for getAllAccounts.
@dataclass
class GetAllAccountsConfig:
    # Encoding for account data (e.g. "base64").
    encoding: Optional[str] = None
    # Whether to include account data in the response.
    include_data: Optional[bool] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "GetAllAccountsConfig":
        return cls(
            encoding=d.get("encoding"),
            include_data=d.get("includeData"),
        )

    def to_dict(self) -> dict:
        return {
            "encoding": self.encoding,
            "includeData": self.include_data,
        }


# An entry in the getAllAccounts response.
@dataclass
class AllAccountsEntry:
    # Account public key (base58).
    pubkey: str
    # Account information.
    account: AccountInfo

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "AllAccountsEntry":
        return cls(
            pubkey=d["pubkey"],
            account=AccountInfo.from_dict(d["account"]),
        )

    def to_dict(self) -> dict:
        return {
            "pubkey": self.pubkey,
            "account": self.account.to_dict(),
        }


# State of a stake account.
class StakeState(Enum):
    INACTIVE = "inactive"
    ACTIVATING = "activating"
    ACTIVE = "active"
    DEACTIVATING = "deactivating"
    UNBONDING = "unbonding"


# Information about a stake account.
@dataclass
class StakeAccountInfo:
    # Current state of the stake account.
    state: StakeState
    # Total kelvins in the account.
    kelvins: int
    # Amount delegated to validator (0 when inactive).
    delegated_balance: int
    # Undelegated amount.
    undelegated_balance: int
    # Admin authority (hot wallet, base58).
    admin_authority: str
    # Withdraw authority (cold wallet, base58).
    withdraw_authority: str
    # Unix timestamp (ms) when ActivateStake was called.
    activation_requested: Optional[int] = None
    # Unix timestamp (ms) when DeactivateStake was called.
    deactivation_requested: Optional[int] = None
    # Validator info account (base58), absent if not delegated.
    validator: Optional[str] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "StakeAccountInfo":
        return cls(
            state=StakeState.from_dict(d["state"]),
            kelvins=d["kelvins"],
            delegated_balance=d["delegatedBalance"],
            undelegated_balance=d["undelegatedBalance"],
            activation_requested=d.get("activationRequested"),
            deactivation_requested=d.get("deactivationRequested"),
            validator=d.get("validator"),
            admin_authority=d["adminAuthority"],
            withdraw_authority=d["withdrawAuthority"],
        )

    def to_dict(self) -> dict:
        return {
            "state": self.state.to_dict(),
            "kelvins": self.kelvins,
            "delegatedBalance": self.delegated_balance,
            "undelegatedBalance": self.undelegated_balance,
            "activationRequested": self.activation_requested,
            "deactivationRequested": self.deactivation_requested,
            "validator": self.validator,
            "adminAuthority": self.admin_authority,
            "withdrawAuthority": self.withdraw_authority,
        }


# Request parameters for getValidatorAccounts.
@dataclass
class GetValidatorAccountsRequest:
    # If true, return from last frozen epoch; if false, return from pending.
    use_frozen: bool

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "GetValidatorAccountsRequest":
        return cls(
            use_frozen=d["useFrozen"],
        )

    def to_dict(self) -> dict:
        return {
            "useFrozen": self.use_frozen,
        }


# Information about a validator account.
@dataclass
class ValidatorAccountInfo:
    # Accumulated commission (kelvins) in the validator account.
    commission: int
    # Public key of the validator info account (base58).
    pubkey: str
    # Node's public key / initial authorized withdrawer (base58).
    node_identity: str
    # Authority who can withdraw rewards (base58).
    authorized_withdrawer: str
    # Amount of token staked for next epoch.
    stake_next: int
    # Network address for communicating with the validator.
    address: str
    # Amount of token staked for current epoch (from last frozen snapshot).
    stake_current: Optional[int] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "ValidatorAccountInfo":
        return cls(
            commission=d["commission"],
            pubkey=d["pubkey"],
            node_identity=d["nodeIdentity"],
            authorized_withdrawer=d["authorizedWithdrawer"],
            stake_next=d["stakeNext"],
            stake_current=d.get("stakeCurrent"),
            address=d["address"],
        )

    def to_dict(self) -> dict:
        return {
            "commission": self.commission,
            "pubkey": self.pubkey,
            "nodeIdentity": self.node_identity,
            "authorizedWithdrawer": self.authorized_withdrawer,
            "stakeNext": self.stake_next,
            "stakeCurrent": self.stake_current,
            "address": self.address,
        }


# SPL Token account balance information.
@dataclass
class TokenBalance:
    # Raw token amount as string.
    amount: str
    # Number of decimals for the token.
    decimals: int
    # Human-readable amount string.
    ui_amount_string: str

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "TokenBalance":
        return cls(
            amount=d["amount"],
            decimals=d["decimals"],
            ui_amount_string=d["uiAmountString"],
        )

    def to_dict(self) -> dict:
        return {
            "amount": self.amount,
            "decimals": self.decimals,
            "uiAmountString": self.ui_amount_string,
        }


# Information about a node in the cluster.
@dataclass
class ClusterNodeInfo:
    # Stake amount in kelvins.
    stake: int
    # Network address.
    address: str
    # Hostname of the node.
    hostname: str
    # Authority public key (base58).
    authority_pubkey: str
    # Protocol public key (base58).
    protocol_pubkey: str
    # Network public key (base58).
    network_pubkey: str
    # Last committed consensus round (if available).
    last_committed_round: Optional[int] = None

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "ClusterNodeInfo":
        return cls(
            stake=d["stake"],
            address=d["address"],
            hostname=d["hostname"],
            authority_pubkey=d["authorityPubkey"],
            protocol_pubkey=d["protocolPubkey"],
            network_pubkey=d["networkPubkey"],
            last_committed_round=d.get("lastCommittedRound"),
        )

    def to_dict(self) -> dict:
        return {
            "stake": self.stake,
            "address": self.address,
            "hostname": self.hostname,
            "authorityPubkey": self.authority_pubkey,
            "protocolPubkey": self.protocol_pubkey,
            "networkPubkey": self.network_pubkey,
            "lastCommittedRound": self.last_committed_round,
        }


# A single REX duty assignment.
@dataclass
class RexDuty:
    # Target timestamp for the duty.
    target_timestamp: str
    # Validators assigned to this duty (base58 pubkeys).
    assigned_validators: list[str]

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "RexDuty":
        return cls(
            target_timestamp=d["targetTimestamp"],
            assigned_validators=d["assignedValidators"],
        )

    def to_dict(self) -> dict:
        return {
            "targetTimestamp": self.target_timestamp,
            "assignedValidators": self.assigned_validators,
        }


# Information about a REX request and its duties.
@dataclass
class RexInfoAndDuties:
    # Pubkey of account that created the REX (base58).
    creator: str
    # Hex representation of the REX's nonce.
    nonce: str
    # Whether the REX is currently active.
    is_active: bool
    # Description of the REX request.
    description: str
    # Update frequency.
    update_frequency: str
    # Starting timestamp.
    starting_timestamp: str
    # Creation timestamp.
    created_at: str
    # Number of validators per duty.
    validators_per_duty: int
    # Delay in milliseconds for REX request processing.
    rex_request_delay_ms: int
    # List of duties expecting updates or commitment.
    duties: list[RexDuty]

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def __contains__(self, key: str) -> bool:
        return hasattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        return getattr(self, key, default)

    @classmethod
    def from_dict(cls, d: dict) -> "RexInfoAndDuties":
        return cls(
            creator=d["creator"],
            nonce=d["nonce"],
            is_active=d["isActive"],
            description=d["description"],
            update_frequency=d["updateFrequency"],
            starting_timestamp=d["startingTimestamp"],
            created_at=d["createdAt"],
            validators_per_duty=d["validatorsPerDuty"],
            rex_request_delay_ms=d["rexRequestDelayMs"],
            duties=[RexDuty.from_dict(x) for x in d["duties"]],
        )

    def to_dict(self) -> dict:
        return {
            "creator": self.creator,
            "nonce": self.nonce,
            "isActive": self.is_active,
            "description": self.description,
            "updateFrequency": self.update_frequency,
            "startingTimestamp": self.starting_timestamp,
            "createdAt": self.created_at,
            "validatorsPerDuty": self.validators_per_duty,
            "rexRequestDelayMs": self.rex_request_delay_ms,
            "duties": [x.to_dict() for x in self.duties],
        }

