Model Marketplace

The Model Marketplace is a set of SDK types and system contracts that let you discover on-chain ML models, pay for inference, manage rent, and coordinate distributed inference across sharded replicas. It is composed of three pieces:

  • Model SDK type -- A first-class Python object for interacting with any deployed model contract. Working end-to-end today: Model(addr) handles dispatch inference, metadata, rent, and funding to the underlying contract via cross-contract calls.
  • RentCollector -- A system contract that manages model rent lifecycle (active, expired, evicted).
  • StreamingInferenceCoordinator -- A system contract that routes multi-shard inference jobs and records attribution.

This guide covers all three.

What's live vs. coming: Direct Model(addr) handles are fully functional — every example below that constructs a Model from a known address works against the PaidModel and TokenizedModel contracts shipped in contracts/marketplace/. Discovery (Model.find.all() / Model.find.by_kind()) returns an empty list until an on-chain model registry exists (see Model Discovery). Use direct address handles for now.


Model Type Overview

The Model type is imported from panda and wraps any on-chain model contract address. It provides a unified interface for inference, metadata, rent, and funding -- regardless of the underlying model's kind.

from panda import Model

m = Model("0xABC123...")

A Model instance exposes four surfaces:

SurfaceAccessDescription
Top-levelm.infer(), m.predict(), m.kindInference and identity
.metam.meta.accuracy, m.meta.versionModel metadata (read-only, cached)
.rentm.rent.pay(), m.rent.dueRent status and payment
.fundsm.funds.fund(), m.funds.balanceInference credit balance

Kind System

Every model has a kind that determines how inference is dispatched:

KindDispatchDescription
"paid"predict()Model charges per-inference fee from caller's credit balance
"tokenized"inference()Model requires a governance token to invoke
"free"predict()Model is free to call (no credits required)
"empty"n/aNull/zero model (no contract deployed)
"unknown"n/aContract exists but does not conform to a known model interface

The m.kind property is resolved by querying the model contract's on-chain metadata. You do not set it manually.


Using Model in Contracts

The most common use case is calling one model from inside another contract. The Model type uses cross-contract calls under the hood, so inference is fully on-chain and gas-metered.

Basic Inference

from panda import contract, constructor, call, query, event, Model


@contract
class PriceForecast:
    """Calls an external ML model to forecast token prices."""

    class State:
        model_address: str = ""
        owner: str = ""
        last_prediction: float = 0.0

    @constructor
    def deploy(self, ctx, model_address: str):
        self.state.model_address = model_address
        self.state.owner = ctx.sender

    @call
    def forecast(self, ctx, features: list, payment: int = 0) -> float:
        m = Model(self.state.model_address)
        # infer() takes a dict whose keys are splatted as keyword arguments
        # to the model contract's predict()/inference() method. PaidModel and
        # TokenizedModel both expect `x` (the feature rows) and optional `payment`.
        result = m.infer({"x": features, "payment": payment})
        self.state.last_prediction = result
        self.emit(event.Forecasted(
            requester=ctx.sender,
            prediction=result,
        ))
        return result

    @query
    def get_prediction(self) -> float:
        return self.state.last_prediction

    @query
    def model_info(self) -> dict:
        m = Model(self.state.model_address)
        return {
            "address": m.address,
            "kind": m.kind,
            "accuracy": m.meta.accuracy,
            "version": m.meta.version,
            "ready": m.meta.is_ready,
        }

Predict vs Infer

m.infer(inputs) and m.predict(inputs) are aliases -- they call the same underlying dispatch. Use whichever reads better in context:

# These are equivalent:
result = m.infer({"x": [[1.0, 2.0, 3.0]], "payment": 100})
result = m.predict({"x": [[1.0, 2.0, 3.0]], "payment": 100})

inputs is a dict whose keys are passed straight through as keyword arguments to the underlying contract method. The shipped model contracts (PaidModel, TokenizedModel) take x (the feature rows) and an optional integer payment, so a typical call is {"x": rows, "payment": amount}.

Under the hood, the Model type dispatches based on m.kind:

  • "paid" and "free" models route to the contract's predict() method.
  • "tokenized" models route to the contract's inference() method.
  • "empty" and "unknown" models raise ValueError.

Pre-flight Checks

Before calling inference, you may want to verify the model is ready and the caller has sufficient funds:

@call
def safe_forecast(self, ctx, features: list) -> float:
    m = Model(self.state.model_address)

    if not m.meta.is_ready:
        raise ValueError("Model is not ready for inference")

    if m.kind == "paid" and m.funds.balance <= 0:
        raise ValueError("No inference credits -- call m.funds.fund() first")

    if m.rent.due:
        raise ValueError("Model rent is past due -- inference disabled")

    return m.infer({"x": features, "payment": 100})

Model Sub-types

Metadata: m.meta

The .meta sub-type provides read-only access to the model's on-chain metadata. Metadata is cached after the first access and can be explicitly invalidated.

m = Model("0xABC...")

m.meta.accuracy     # float, 0.0 - 1.0 (e.g. 0.94)
m.meta.version      # int (e.g. 3)
m.meta.is_ready     # bool -- True if the model has been trained and is serving
m.meta.raw()        # dict -- full metadata as stored on-chain
m.meta.invalidate() # force re-fetch on next access
PropertyTypeDescription
accuracyfloatModel accuracy score, range 0.0 to 1.0
versionintModel version number (increments on retrain)
is_readyboolWhether the model is trained and accepting inference
MethodReturnsDescription
raw()dictFull metadata dict as stored in the model contract's state
invalidate()NoneClears the local cache so the next property access re-fetches from chain

Caching behavior: The first access to any .meta property triggers a cross-contract query to the model. Subsequent accesses within the same transaction return the cached value. Call invalidate() if the model may have been retrained between accesses.

Rent: m.rent

The .rent sub-type lets you check and pay rent for a model. Rent is managed by the RentCollector system contract.

m = Model("0xABC...")

m.rent.due          # bool -- True if rent is past due
m.rent.pay(1000)    # buy 1000 blocks of rent (extends paid_through_block)
m.rent.status()     # dict with full rent state
PropertyTypeDescription
dueboolTrue if the model's rent has expired (past paid_through_block)
MethodParametersReturnsDescription
pay(amount)amount: intNoneBuy amount blocks of rent. The model's paid_through_block advances by amount from whichever is later: its current paid-through block or the current height (so lapsed rent restarts the clock now). amount must be positive
status()(none)dictRent status reported by the model contract's rent_status query (the SDK reads blocks_remaining from it)

status() returns whatever the model contract's rent_status query reports. The SDK's due property reads the blocks_remaining field from that dict and treats the model as past due when blocks_remaining <= 0:

{
    "paid_through_block": 95000,  # block up to which rent is funded
    "current_block": 93580,       # the model's last-active block
    "blocks_remaining": 1420,     # blocks of rent still funded; <= 0 means past due
    "status": "active",           # "active" while blocks_remaining > 0, else "due"
}

(This is the exact dict the shipped PaidModel.rent_status query returns; TokenizedModel exposes the same metadata/funding surface but does not implement rent.)

Funding: m.funds

The .funds sub-type manages prepaid inference credits. The dispatch is kind-aware: for "paid" models it calls fund / reads get_balance; for "tokenized" models it calls buy (the bonding-curve purchase) / reads get_holdings. For "free" and "empty" models funding is unsupported (fund raises NotImplementedError, balance returns 0).

m = Model("0xABC...")

m.funds.balance       # int -- current prepaid credit balance (paid) or token holdings (tokenized)
m.funds.fund(5000)    # paid: add 5000 prepaid credits; tokenized: buy 5000 tokens
PropertyTypeDescription
balanceintFor "paid" models, the model's prepaid credit balance (get_balance().balance); for "tokenized" models, the caller's token holdings (get_holdings().balance). 0 for kinds without a funding surface
MethodParametersReturnsDescription
fund(amount)amount: int (positive)NoneFor "paid" models, add amount prepaid credits (fund); for "tokenized" models, buy amount tokens (buy). Raises NotImplementedError for kinds without funding

Model Discovery

The Model class exposes class-level discovery methods via Model.find, plus a Model.empty() helper.

Status: Discovery is not live yet. Model.find.all() and Model.find.by_kind() resolve their results through an on-chain system-contract registry (ModelExchange / PaidModelRegistry). That registry does not exist on-chain today — get_system_contract returns None — so both methods return an empty list and degrade gracefully rather than erroring. Until the registry lands, construct Model handles directly from a known address, which is the fully working path:

m = Model("0xABC123...")   # works today
from panda import Model

# Discovery (returns [] today -- no on-chain registry yet):
all_models = Model.find.all()
paid_models = Model.find.by_kind("paid")

# Working today -- a direct handle to a known model address:
m = Model("0xABC123...")

# Create a null/zero model (useful as a default):
null_model = Model.empty()
MethodParametersReturnsDescription
Model.find.all()(none)list[Model]Every model the chain knows about. Returns [] until an on-chain model registry exists
Model.find.by_kind(k)k: strlist[Model]Models filtered by kind ("paid", "free", "tokenized"). Returns [] until an on-chain model registry exists
Model.empty()(none)ModelA null model with kind "empty" (works today)

Discovery in a Contract

This pattern shows how discovery is intended to be used once the registry lands. Today Model.find.by_kind("paid") returns [], so select_best would raise "No ready paid models found" — pass an explicit address (or maintain your own allowlist of model addresses) until discovery is live.

from panda import contract, call, query, Model


@contract
class ModelAggregator:
    """Finds the best model on-chain and uses it."""

    class State:
        selected_model: str = ""

    @call
    def select_best(self, ctx):
        """Pick the model with the highest accuracy."""
        models = Model.find.by_kind("paid")
        best = None
        best_acc = 0.0
        for m in models:
            if m.meta.is_ready and m.meta.accuracy > best_acc:
                best = m
                best_acc = m.meta.accuracy
        if best is None:
            raise ValueError("No ready paid models found")
        self.state.selected_model = best.address

    @query
    def get_selected(self) -> dict:
        if not self.state.selected_model:
            return {"selected": False}
        m = Model(self.state.selected_model)
        return {
            "selected": True,
            "address": m.address,
            "kind": m.kind,
            "accuracy": m.meta.accuracy,
        }

Rent Economics: RentCollector

The RentCollector is a system contract that enforces rent on deployed models. Models that do not pay rent are first marked expired, then evicted after a grace period. Anyone can trigger expiration and eviction -- the eviction caller receives a small bounty as incentive.

Lifecycle

active ──[block > paid_through]──> expired
   ^                                  |
   | pay_rent                         | [block > expired + grace]
   |                                  v
active <── reinstate <── evicted

A model starts in the active state when rent is paid. When the current block exceeds the paid_through_block, anyone can call mark_expired to transition it to expired. If rent remains unpaid past the grace window, anyone can call evict to move it to evicted and claim a bounty. The owner can reinstate an evicted model by paying all back rent.

Default Parameters

These are constructor parameters of the RentCollector contract:

ParameterDefaultDescription
sweeper_bounty_bps50 (0.5%)Percentage of a model's residual balance paid to the account that triggers evict. Must be 0..1000 (0..10%)
rent_grace_blocks7200 blocks (~6 hours)Window after expiration before eviction is allowed
max_per_call100Rate-limit on how many models one sweep transaction may touch

Per-block rent itself is tracked on each model contract (via its own rent_paid_through_block metadata), not on the RentCollector.

Methods

MethodTypeParametersDescription
mark_expired@callmodel_addr: strTransition from active to expired (anyone can call when overdue)
evict@callmodel_addr: strEvict after grace period; caller receives the sweeper_bounty_bps bounty
reinstate@callmodel_addr: strOwner pays back rent (extends the model's own rent_paid_through_block) to restore an evicted model
register_listing@callmodel_addr: str, registry_addr: str, listing_id: intRecord where a model is listed so evict can un-list it
get_status@querymodel_addr: strReturn { "status", "expired_at_block" }
stats@query(none)Return aggregate sweep statistics
list_expired@querylimit: int = 100Return up to limit model addresses currently expired (ready to evict)

Paying Rent

From a contract that manages its own model:

from panda import contract, constructor, call, query, event, Model


@contract
class ManagedModel:
    """A model contract that pays its own rent."""

    class State:
        model: dict = {}
        owner: str = ""
        self_address: str = ""

    @constructor
    def deploy(self, ctx):
        self.state.owner = ctx.sender
        # Stash our own address so @query methods (which receive no ctx) can
        # build a Model handle to this contract.
        self.state.self_address = ctx.contract_address

    @call
    def top_up_rent(self, ctx, blocks: int):
        """Owner tops up rent for the specified number of blocks."""
        if ctx.sender != self.state.owner:
            raise ValueError("Only owner can pay rent")
        m = Model(ctx.contract_address)
        m.rent.pay(blocks)  # amount is the number of blocks of rent to buy
        self.emit(event.RentPaid(
            blocks=blocks,
            paid_by=ctx.sender,
        ))

    @query
    def rent_status(self) -> dict:
        m = Model(self.state.self_address)
        return m.rent.status()

Eviction Bounty

When a model is evicted, the caller who triggers evict receives 0.5% of the model's accumulated rent balance as a bounty. This incentivizes network participants to clean up expired models.

# Anyone can call this -- no special permissions needed
from panda import call_contract

# Mark expired first (required before eviction)
call_contract("0xRentCollector", "mark_expired", model_addr="0xDeadModel")

# Wait for grace period (rent_grace_blocks, default 7200)...

# Evict and claim bounty
call_contract("0xRentCollector", "evict", model_addr="0xDeadModel")
# Caller receives the sweeper_bounty_bps share (default 0.5%) of the model's residual balance

Sweeping the Registry

Keeper bots can discover models that are ready to evict with the list_expired query, then call evict on each. Use get_status to check an individual model.

from panda import call_contract, query_contract

# Discover models already in the expired state (up to `limit`)
expired = query_contract("0xRentCollector", "list_expired", limit=100)

for addr in expired:
    # Each evict is rate-limited by the contract's max_per_call guard
    call_contract("0xRentCollector", "evict", model_addr=addr)

Distributed Inference: StreamingInferenceCoordinator

The StreamingInferenceCoordinator is a system contract that manages multi-shard inference jobs. Large models that are split across multiple replicas use this coordinator to route inference through each layer, track attribution, and emit a final receipt.

How It Works

  1. A caller requests inference, specifying the model, an inputs hash, and the number of shards.
  2. The coordinator creates a job and assigns it a unique job_id.
  3. Each shard processes its layer and calls advance_layer with the result hash and attribution data.
  4. When all layers are complete, the caller (or the final shard) calls complete to finalize the job.
  5. The coordinator emits an InferenceReceipt event with the full route and attribution chain.

Methods

MethodTypeParametersReturnsDescription
request_inference@callmodel_address: str, inputs_hash: str, num_shards: intint (job_id)Create a new inference job
advance_layer@calljob_id: int, shard_id: int, replica_address: str, outputs_hash: str, attribution_contributor: str = "", attribution_weight_bps: int = 0NoneRecord a shard's contribution to the job (requester or owner only)
complete@calljob_id: int, final_output_hash: strdict (the receipt)Finalize the job and emit receipt (requester or owner only)
register_replica_set@callshard_id: int, replica_set_address: strNoneBind a replica set to a shard (owner only)
get_job@queryjob_id: intdictReturn current job state
get_receipt@queryjob_id: intdictReturn the finalized receipt (after complete)

Requesting Inference

from panda import contract, call, query, event, call_contract


@contract
class InferenceClient:
    """Requests distributed inference and tracks job status."""

    class State:
        coordinator: str = ""
        pending_jobs: dict = {}
        owner: str = ""

    @call
    def request(self, ctx, model_address: str, inputs_hash: str, num_shards: int):
        """Submit an inference request to the coordinator."""
        job_id = call_contract(
            self.state.coordinator,
            "request_inference",
            model_address=model_address,
            inputs_hash=inputs_hash,
            num_shards=num_shards,
        )
        jobs = dict(self.state.pending_jobs)
        jobs[job_id] = {
            "model": model_address,
            "status": "pending",
            "shards_total": num_shards,
            "shards_done": 0,
        }
        self.state.pending_jobs = jobs
        self.emit(event.InferenceRequested(
            job_id=job_id,
            model=model_address,
            num_shards=num_shards,
        ))
        return job_id

    @query
    def job_status(self, job_id: str) -> dict:
        job = call_contract(
            self.state.coordinator,
            "get_job",
            job_id=job_id,
        )
        return job

Shard Processing

Each shard is a separate contract (or off-chain worker with on-chain settlement) that processes one layer of the model and reports back to the coordinator.

from panda import contract, call, event, call_contract


@contract
class ShardWorker:
    """Processes one layer of a sharded model."""

    class State:
        coordinator: str = ""
        shard_id: int = 0
        owner: str = ""

    @call
    def process_layer(self, ctx, job_id: int, input_hash: str):
        """Run inference on this shard's layer and report the result."""
        # Perform actual computation (model-specific)
        result_hash = self._compute(input_hash)

        # Report result to coordinator. shard_id must equal the job's next
        # expected layer (the coordinator enforces monotonic shard order).
        call_contract(
            self.state.coordinator,
            "advance_layer",
            job_id=job_id,
            shard_id=self.state.shard_id,
            replica_address=ctx.contract_address,
            outputs_hash=result_hash,
            attribution_contributor=ctx.contract_address,
            attribution_weight_bps=10000,
        )
        self.emit(event.LayerProcessed(
            job_id=job_id,
            shard_id=self.state.shard_id,
            result_hash=result_hash,
        ))
        return result_hash

    def _compute(self, input_hash: str) -> str:
        # Model-specific inference logic
        return "0xresult..."

Replica Sets

For high availability, each shard can have multiple replicas. Use register_replica_set to associate a shard ID with a replica set address (typically a contract that manages failover).

from panda import call_contract

# Register a replica set for shard 0 (shard IDs are integers)
call_contract(
    "0xCoordinator",
    "register_replica_set",
    shard_id=0,
    replica_set_address="0xReplicaSet0",
)

InferenceReceipt

When a distributed inference job completes, the coordinator emits an InferenceReceipt event. This event contains the full routing and attribution chain, providing an auditable record of which shards contributed to the final output.

Event Shape

event.InferenceReceipt(
    job_id=42,
    model_address="0xModelAddress",
    inputs_hash="0xinputs...",
    outputs_hash="0xoutput...",
    # Each route entry is [shard_id, replica_address, outputs_hash]
    route=[
        [0, "0xReplica0", "0xhash0..."],
        [1, "0xReplica1", "0xhash1..."],
        [2, "0xReplica2", "0xhash2..."],
    ],
    # Each attribution entry is [shard_id, contributor, weight_bps]
    attributions=[
        [0, "0xWorker0", 3300],
        [1, "0xWorker1", 3300],
        [2, "0xWorker2", 3400],
    ],
    shard_count=3,
    start_block=100,
    end_block=102,
)

Fields

FieldTypeDescription
job_idintUnique identifier for the inference job
model_addressstrAddress of the model contract
inputs_hashstrHash of the original inputs
outputs_hashstrHash of the final output
routelist[list]Ordered route entries, each [shard_id, replica_address, outputs_hash]
attributionslist[list]Attribution records, each [shard_id, contributor, weight_bps] (only present when attribution was supplied)
shard_countintNumber of shards (num_shards) for the job
start_blockintBlock number when the job was created
end_blockintBlock number when the job was finalized

Querying Receipts

After a job completes, use get_receipt to retrieve the full receipt:

from panda import query_contract

receipt = query_contract("0xCoordinator", "get_receipt", job_id=42)

print(receipt["model_address"])  # "0xModelAddress"
print(receipt["route"])          # [[0, "0xReplica0", "0xhash0..."], ...]
print(receipt["outputs_hash"])   # "0xoutput..."
print(len(receipt["attributions"]))  # number of attributed layers

Receipts are also indexed by the block explorer and visible on the model's detail page.


ModelRegistry

The ModelRegistry is the discovery layer for the marketplace -- the "app store" for on-chain models. Where Model.find gives you a direct handle to a known address, the registry is where creators publish listings so consumers can discover models they didn't already know about.

A creator registers their deployed PaidModel (or TokenizedModel) contract address along with a name, description, category, task type, a self-reported accuracy, and an informational price. Users then browse, filter, search, and rate listings, so a consumer can find a model by category or task type and rank candidates by community rating. Each listing points to a real deployed contract where inference actually happens; the registry only holds metadata and reverse indexes.

Registering a Model

from panda import call_contract

# A creator lists their deployed PaidModel contract
listing_id = call_contract(
    "0xRegistry",
    "register_model",
    model_address="0xMyPaidModel",
    model_name="Fraud Classifier v2",
    description="Logistic regression fraud detector trained on 1M txns",
    category="finance",
    task_type="classification",
    accuracy=0.94,
    price_per_call=1000,
)

Duplicate addresses are rejected, so a given contract appears at most once in the registry.

Discovering Models

from panda import query_contract

# Browse a category
listings = query_contract("0xRegistry", "list_models", category="finance", active_only=True)

# Search by task type, capped at a price, sorted by rating (desc)
best = query_contract("0xRegistry", "search_models", task_type="classification", max_price=2000)

# Reverse lookup: which listing points at this contract?
listing = query_contract("0xRegistry", "get_listing_by_address", model_address="0xMyPaidModel")

Consumers can also rate_model(listing_id, score) (1--5, one rating per address) to build the running avg_rating that search_models sorts by.

State highlights

owner, next_listing_id, listings (listing_id -> metadata), categories (category -> listing ids), address_to_listing (reverse index), ratings (listing_id -> {rater -> score}), creator_listings (creator -> listing ids).

Methods

MethodKindDescription
deploy()constructorSets the registry owner.
register_model(model_address, model_name, description, category, task_type, accuracy, price_per_call)callRegisters a model contract; indexes it by category and creator. Rejects duplicate addresses. Returns the listing id. Emits ModelRegistered.
deactivate_model(listing_id)callCreator or registry owner marks a listing inactive. Emits ModelDeactivated.
rate_model(listing_id, score)callRate a listing 1--5 (one rating per address); recomputes the running average. Emits ModelRated.
update_accuracy(listing_id, accuracy)callCreator updates the listing's accuracy metric. Emits AccuracyUpdated.
get_listing(listing_id)queryFull listing metadata.
list_models(category, active_only)queryList listings, optionally filtered by category and active flag.
list_by_creator(creator)queryAll listings registered by a given creator.
search_models(task_type, max_price)queryFilter active listings by task type and max price, sorted by avg_rating descending.
get_listing_by_address(model_address)queryReverse lookup: find a listing by its model contract address.
listing_count()queryTotal number of listings ever registered.

ShardReplicaSet

When a shard is served by more than one worker, a ShardReplicaSet contract sits behind that shard and picks which replica handles the next layer. It is the health-aware routing layer that the register_replica_set binding above points to: one ShardReplicaSet per shard.

Replicas register themselves (owner-gated in v1). The set records each replica's success/fail counts and rolling latency, and exposes select_replica() to return the next healthy replica. Selection is round-robin -- the cursor is advanced by report_completion so select_replica stays a pure read -- and skips any replica below the configured success-rate floor (min_success_rate_bps) or above the latency ceiling (max_latency_ms). A reporter whitelist prevents an unrelated account from flooding fail-reports to knock a target replica out of rotation (selection DoS).

from panda import call_contract, query_contract

# Deployed with health thresholds for shard 0
# (min_success_rate_bps, max_latency_ms, owner set at deploy time)

# Owner registers replicas for this shard
call_contract("0xReplicaSet0", "register_replica", replica_address="0xReplicaA")
call_contract("0xReplicaSet0", "register_replica", replica_address="0xReplicaB")

# Pick the next healthy replica (round-robin, skips unhealthy)
replica = query_contract("0xReplicaSet0", "select_replica")

# A whitelisted reporter (or the replica itself) records how a layer went;
# this also advances the round-robin cursor
call_contract(
    "0xReplicaSet0",
    "report_completion",
    replica_address=replica,
    latency_ms=42,
    success=True,
)

Bind the set to a shard on the coordinator with register_replica_set(shard_id, replica_set_address) (see Replica Sets above), then let the coordinator route layers through the healthy replica that select_replica() returns.

State highlights

owner, shard_id, replicas (address -> {success, fail, total_latency_ms, registered_at}), rr_cursor, min_success_rate_bps, max_latency_ms, reporters.

Methods

MethodKindDescription
deploy(shard_id, min_success_rate_bps, max_latency_ms, owner)constructorConfigures the shard's health thresholds. Emits ShardReplicaSetDeployed.
register_replica(replica_address)callOwner registers a replica for this shard. Emits ReplicaRegistered.
deregister_replica(replica_address)callOwner removes a replica. Emits ReplicaDeregistered.
add_reporter(reporter_address) / remove_reporter(reporter_address)callOwner manages the whitelist allowed to call report_completion.
report_completion(replica_address, latency_ms, success)callOwner, a whitelisted reporter, or the replica itself records a layer completion; advances the round-robin cursor.
advance_cursor()callMoves the round-robin pointer forward without recording stats (no-report path).
select_replica()queryReturns the next healthy replica (round-robin, skipping unhealthy) or "" if none.
list_replicas()queryPer-replica success/fail, success-rate bps, avg latency, and health flag.
stats()queryShard id, replica count, and configured thresholds.

API Reference

Model

Property / MethodTypeReturnsDescription
Model(address)constructorModelCreate a Model handle from an on-chain address
.addresspropertystrThe on-chain contract address
.kindpropertystrModel kind: "paid", "tokenized", "free", "empty", "unknown"
.infer(inputs)methodvariesRun inference (dispatches by kind)
.predict(inputs)methodvariesAlias for .infer()
.metasub-typeModelMetaMetadata accessor
.rentsub-typeModelRentRent accessor
.fundssub-typeModelFundsFunding accessor

ModelMeta

Property / MethodTypeReturnsDescription
.accuracypropertyfloatModel accuracy (0.0 -- 1.0)
.versionpropertyintModel version number
.is_readypropertyboolWhether the model is trained and serving
.raw()methoddictFull metadata dict from chain
.invalidate()methodNoneClear local metadata cache

ModelRent

Property / MethodTypeReturnsDescription
.duepropertyboolTrue if rent is past due
.pay(amount)methodNoneBuy amount blocks of rent (extends paid_through_block)
.status()methoddictFull rent status dict (paid_through_block, current_block, blocks_remaining, status)

ModelFunds

Property / MethodTypeReturnsDescription
.balancepropertyintPrepaid credits (paid) or token holdings (tokenized); 0 for kinds without funding
.fund(amount)methodNoneAdd credits (paid: fund) or buy tokens (tokenized: buy); raises NotImplementedError otherwise

Model.find (Discovery)

MethodParametersReturnsDescription
Model.find.all()(none)list[Model]All registered models. Returns [] until an on-chain registry exists
Model.find.by_kind(k)k: strlist[Model]Models filtered by kind. Returns [] until an on-chain registry exists
Model.empty()(none)ModelNull model with kind "empty" (works today)

RentCollector

MethodTypeParametersReturnsDescription
pay_rent@callmodel_address: str, blocks: intNonePay rent for N blocks
mark_expired@callmodel_address: strNoneMark overdue model as expired
evict@callmodel_address: strNoneEvict after grace; caller gets 0.5% bounty
reinstate@callmodel_address: strNoneOwner restores evicted model
batch_mark_expired@calladdresses: listNoneBulk expire overdue models
get_status@querymodel_address: strdictRent status for a model

StreamingInferenceCoordinator

MethodTypeParametersReturnsDescription
request_inference@callmodel_address: str, inputs_hash: str, num_shards: intintCreate job, returns job_id
advance_layer@calljob_id: int, shard_id: int, replica_address: str, outputs_hash: str, attribution_contributor: str = "", attribution_weight_bps: int = 0NoneRecord shard result
complete@calljob_id: int, final_output_hash: strdictFinalize job, emit receipt, return receipt
register_replica_set@callshard_id: int, replica_set_address: strNoneAssociate shard with replicas (owner only)
get_job@queryjob_id: intdictCurrent job state
get_receipt@queryjob_id: intdictFinalized receipt

Related Guides