Paid Inference
Introduction
The PaidModel pattern lets you deploy a trained machine learning model as a Panda smart contract and charge users for every prediction. Revenue accumulates on-chain and the model owner can withdraw earnings at any time. This is the simplest way to monetize ML on PandaChain.
How It Works
- Deploy a PaidModel contract with your model type and pricing
- Train the model on-chain with your dataset
- Users pay the per-call price to submit features and receive predictions
- Revenue accumulates in the contract balance
- Withdraw your earnings whenever you want
Creating a PaidModel Contract
Here is a complete PaidModel contract that trains a linear regression model and charges per prediction:
"""
PaidModel -- Deploy an ML model and charge per prediction.
Demonstrates:
- Training a model and persisting weights on-chain
- Charging a per-call fee for inference
- Tracking usage statistics (calls, revenue, unique users)
- Owner-only withdraw and price management
"""
from panda import contract, constructor, call, query, event
from panda.ml import LinearRegression, save_model, load_model, r2_score
@contract
class PaidModel:
"""ML model that charges per prediction."""
class State:
owner: str = ""
name: str = ""
description: str = ""
category: str = "ML"
model: dict = {}
is_trained: bool = False
accuracy: float = 0.0
price_per_call: float = 0.01
total_calls: int = 0
total_revenue: float = 0.0
unique_users: dict = {}
balance: float = 0.0
@constructor
def deploy(self, ctx, name: str, description: str = "", category: str = "ML", price: float = 0.01):
"""Deploy the paid model contract.
Args:
name: Human-readable model name.
description: What the model does.
category: Model category (ML, NLP, Vision, Audio, RL).
price: Price per prediction call in PANDA.
"""
self.state.owner = ctx.sender
self.state.name = name
self.state.description = description
self.state.category = category
self.state.price_per_call = price
self.emit(event.ModelDeployed(name=name, owner=ctx.sender, price=price))
@call
def train(self, ctx, features: list, labels: list):
"""Train or retrain the model. Owner only.
Args:
features: 2D feature matrix [[f1, f2, ...], ...]
labels: Target values [y1, y2, ...]
"""
if ctx.sender != self.state.owner:
raise ValueError("Only the owner can train the model")
if len(features) != len(labels):
raise ValueError("features and labels must have same length")
model = LinearRegression()
model.fit(features, labels)
preds = model.predict(features)
r2 = r2_score(labels, preds)
self.state.model = save_model(model)
self.state.is_trained = True
self.state.accuracy = round(r2 * 100, 2)
self.emit(event.ModelTrained(
samples=len(features),
accuracy=self.state.accuracy,
trainer=ctx.sender,
))
@call
def predict(self, ctx, features: list) -> list:
"""Run inference. Caller must pay price_per_call.
Args:
features: Input feature vectors [[f1, f2, ...], ...]
Returns:
List of predictions.
"""
if not self.state.is_trained:
raise ValueError("Model not trained yet")
# Charge the caller
if ctx.value < self.state.price_per_call:
raise ValueError(f"Insufficient payment: need {self.state.price_per_call}, got {ctx.value}")
model = load_model(self.state.model)
predictions = model.predict(features)
# Update stats
self.state.total_calls += 1
self.state.total_revenue += ctx.value
self.state.balance += ctx.value
self.state.unique_users[ctx.sender] = True
self.emit(event.Prediction(
caller=ctx.sender,
num_features=len(features),
payment=ctx.value,
))
return [round(p, 4) for p in predictions]
@call
def set_price(self, ctx, price: float):
"""Update the price per call. Owner only."""
if ctx.sender != self.state.owner:
raise ValueError("Only the owner can update the price")
old_price = self.state.price_per_call
self.state.price_per_call = price
self.emit(event.PriceUpdated(old=old_price, new=price))
@call
def withdraw(self, ctx):
"""Withdraw accumulated earnings. Owner only."""
if ctx.sender != self.state.owner:
raise ValueError("Only the owner can withdraw")
amount = self.state.balance
if amount <= 0:
raise ValueError("Nothing to withdraw")
self.state.balance = 0
ctx.transfer(ctx.sender, amount)
self.emit(event.Withdrawal(amount=amount, to=ctx.sender))
@query
def get_stats(self) -> dict:
"""Return model statistics."""
return {
"name": self.state.name,
"description": self.state.description,
"creator": self.state.owner,
"category": self.state.category,
"accuracy": self.state.accuracy,
"pricePerCall": self.state.price_per_call,
"totalCalls": self.state.total_calls,
"totalRevenue": self.state.total_revenue,
"uniqueUsers": len(self.state.unique_users),
"isTrained": self.state.is_trained,
"withdrawableBalance": self.state.balance,
}
How Pricing Works
The PaidModel uses a simple pay-per-call model:
- The owner sets a
price_per_callat deploy time (or updates it later withset_price) - Every call to
predict()checks thatctx.value >= price_per_call - The payment is added to the contract's internal
balance - The owner can call
withdraw()at any time to claim accumulated earnings
Pricing is denominated in PANDA tokens. A typical range is:
| Model Type | Typical Price | Use Case |
|---|---|---|
| Simple regression | 0.001 - 0.01 | Price predictions, simple forecasts |
| Classification | 0.005 - 0.05 | Fraud detection, spam filtering |
| NLP models | 0.01 - 0.1 | Sentiment analysis, text classification |
| Vision models | 0.02 - 0.2 | Image classification, object detection |
Withdrawing Earnings
The owner can call withdraw() at any time. This transfers the entire accumulated balance to the owner's address. The balance resets to zero after withdrawal.
# From the SDK:
from panda_sdk import PandaClient
client = PandaClient("http://localhost:9650")
result = client.call_contract(
from_address="0xYourAddress",
contract_address="0xModelAddress",
method="withdraw",
args={},
)
print(f"Withdrawn: {result}")
Using from the SDK
Deploy a PaidModel
from panda_sdk import PandaClient
client = PandaClient("http://localhost:9650")
# Deploy with constructor args
address = client.deploy_contract(
from_address="0xYourAddress",
source=open("paid_model.py").read(),
constructor_args={
"name": "FraudDetector",
"description": "Detects fraudulent transactions",
"category": "ML",
"price": 0.01,
},
)
print(f"Deployed at: {address}")
Train the Model
# Training data
features = [[100, 5000], [200, 8000], [150, 6000], [300, 12000]]
labels = [0, 1, 0, 1] # 0 = legit, 1 = fraud
client.call_contract(
from_address="0xYourAddress",
contract_address=address,
method="train",
args={"features": features, "labels": labels},
)
Run a Prediction
result = client.call_contract(
from_address="0xCallerAddress",
contract_address=address,
method="predict",
args={"features": [[250, 10000]]},
value=0.01, # Pay the per-call price
)
print(f"Prediction: {result}")
Check Stats
stats = client.query_contract(
contract_address=address,
method="get_stats",
args={},
)
print(f"Total calls: {stats['totalCalls']}")
print(f"Revenue: {stats['totalRevenue']}")
print(f"Accuracy: {stats['accuracy']}%")