Prompt Contracts
A prompt contract is a first-class primitive that wraps every inference request with a token budget envelope and pre-validation. While prompt contracts use TypeScript interfaces rather than .lode schema files, they follow the same trust-and-verify model.
Contract Structure
interface PromptContract {
id: string; // Unique contract ID (UUID)
userId: string; // Caller identity
nodeKeyId?: string; // Operator key (legacy auth)
stackId?: string; // Stack identifier
ownerId?: string; // Account owner
tokenLimit: number; // Hard ceiling (default: 1M)
estimatedInboundCost: number; // Measured from request
maxPossibleCost: number; // inbound + tokenLimit
callerBalance: number; // Balance at creation time
affordable: boolean; // Can support worst case?
model: string; // Resolved model ID
contentType: string; // "text" | "image" | "code" | "think"
taskType: string; // "ai-prompt" | "coder-session" | etc.
createdAt: number; // Timestamp
}How It Works
1. Build the Contract
import { buildPromptContract } from "@stacknet/prompts";
const contract = await buildPromptContract({
auth: { userId: "u_abc", ownerId: "owner_xyz" },
body: requestBody,
inboundUsage: {
textTokens: 4200,
imageMegapixels: 0,
videoMegabytes: 0,
audioMegabytes: 0,
},
contentType: "code",
taskType: "coder-session",
model: "qwen3-coder:30b",
accountBalanceManager,
});2. Enforce the Contract
import { enforcePromptContract } from "@stacknet/prompts";
const rejection = enforcePromptContract(contract);
if (rejection) return rejection; // HTTP 402 response
// Proceed with inference...3. Rejection Response
If the caller cannot afford the request, enforcePromptContract returns a 402 response:
{
"error": "Insufficient token balance",
"code": "INSUFFICIENT_BALANCE",
"required": 1004200,
"available": 500000,
"tokenLimit": 1000000,
"estimatedInboundCost": 4200,
"suggestion": "Lower token_limit or purchase more tokens"
}Balance Priority
The contract enforcer checks balances in priority order:
- Account balance — For users authenticated with
sk_keys or JWT. Checked viaaccountBalanceManager.getBalance(ownerId). - Node key balance — For operators using
nk_keys directly. Checked vianodeKeyManager.getKeyBalance(nodeKeyId). - Internal auth — Service-to-service calls. Always affordable (balance = Infinity).
Constants
| Constant | Value | Description |
|---|---|---|
DEFAULT_TOKEN_LIMIT | 1,000,000 | Default if token_limit is not specified |
MAX_TOKEN_LIMIT | 10,000,000 | Hard ceiling, cannot be exceeded |
Token Limit Extraction
The token limit is extracted from the request body and clamped:
const rawLimit = body.token_limit ?? body.max_tokens ?? DEFAULT_TOKEN_LIMIT;
const tokenLimit = Math.min(Math.max(1, rawLimit), MAX_TOKEN_LIMIT);Content Types
| Type | Description |
|---|---|
text | Text generation |
image | Image generation |
video | Video generation |
music | Music generation |
code | Code generation |
think | Reasoning/thinking |
Task Types
| Type | Description |
|---|---|
ai-prompt | Standard inference request |
mcp-tool | MCP tool execution |
coder-session | Interactive coding session |
music-pipeline | Multi-step music generation |
Last updated on