The Tenor TypeScript SDK provides TenorEvaluator for local contract evaluation via WASM. No network calls, no API key, no platform dependency.
Installation
npm install @tenor/sdkThe package includes the WASM evaluator binary. No additional native dependencies are required. Works in Node.js 18+, Deno, Bun, and modern browsers.
TenorEvaluator — Local WASM Evaluation
TenorEvaluator loads a compiled contract (.tenor.wasm) and evaluates it locally in your process. No network calls, no API key, no platform dependency. The evaluator is a pure function: same inputs always produce same outputs.
Loading a Contract
import { TenorEvaluator } from "@tenor/sdk";
// Load from file path (Node.js)
const evaluator = await TenorEvaluator.load("./contracts/escrow.tenor.wasm");
// Load from URL (browser)
const evaluator = await TenorEvaluator.load(
"https://cdn.example.com/contracts/escrow.tenor.wasm"
);
// Load from buffer (any runtime)
const wasmBytes = await fs.readFile("./contracts/escrow.tenor.wasm");
const evaluator = await TenorEvaluator.fromBytes(wasmBytes);The compiled WASM artifact is produced by tenor compile:
tenor compile escrow.tenor --output escrow.tenor.wasmEvaluating a Contract
const result = evaluator.evaluate({
facts: {
payment_received: true,
payment_amount: "5000.00",
customer_tier: "premium",
},
entities: {
Order: { order_001: "pending" },
Payment: { pay_001: "cleared" },
},
});
console.log(result.verdicts);
// {
// payment_ok: { value: true, type: "Bool", stratum: 0 },
// eligible_for_release: { value: true, type: "Bool", stratum: 1 }
// }
console.log(result.evaluationTimeMs);
// 0.4Computing the Action Space
const actions = evaluator.computeActionSpace({
facts: {
payment_received: true,
payment_amount: "5000.00",
customer_tier: "premium",
},
entities: {
Order: { order_001: "pending" },
Payment: { pay_001: "cleared" },
},
});
console.log(actions);
// {
// escrow_agent: [
// {
// operation: "release_funds",
// preconditionMet: true,
// entitiesAffected: [
// { entity: "Order", instance: "order_001", from: "pending", to: "released" }
// ]
// }
// ],
// buyer: [
// {
// operation: "dispute_transaction",
// preconditionMet: true,
// entitiesAffected: [
// { entity: "Order", instance: "order_001", from: "pending", to: "disputed" }
// ]
// }
// ],
// seller: []
// }Action Space for a Single Persona
const buyerActions = evaluator.computeActionSpace({
facts: { payment_received: true, payment_amount: "5000.00" },
entities: { Order: { order_001: "pending" } },
persona: "buyer",
});
console.log(buyerActions);
// [
// {
// operation: "dispute_transaction",
// preconditionMet: true,
// entitiesAffected: [...]
// }
// ]Querying Contract Metadata
const meta = evaluator.metadata();
console.log(meta.entities);
// [
// { name: "Order", states: ["pending", "approved", "disputed", "released"], initial: "pending" },
// { name: "Payment", states: ["held", "released", "refunded"], initial: "held" }
// ]
console.log(meta.personas);
// ["escrow_agent", "buyer", "seller"]
console.log(meta.facts);
// [
// { name: "payment_received", type: "Bool" },
// { name: "payment_amount", type: "Money" },
// { name: "customer_tier", type: "Text" }
// ]
console.log(meta.contractHash);
// "sha256:9f4a2b..."Error Handling
try {
evaluator.evaluate({
facts: {
payment_received: "yes", // Wrong type: expected Bool, got Text
},
entities: {},
});
} catch (err) {
if (err instanceof TenorTypeError) {
console.error(err.fact); // "payment_received"
console.error(err.expectedType); // "Bool"
console.error(err.receivedType); // "Text"
console.error(err.message);
// "Fact 'payment_received' expected type Bool, got Text"
}
}
try {
evaluator.evaluate({
facts: {
nonexistent_fact: true, // Undeclared fact
},
entities: {},
});
} catch (err) {
if (err instanceof TenorUndeclaredFactError) {
console.error(err.fact); // "nonexistent_fact"
console.error(err.message);
// "Fact 'nonexistent_fact' is not declared in this contract"
}
}Generated Types
Use tenor generate typescript to produce typed bindings from your contract:
tenor generate typescript escrow.tenor --output ./src/generated/escrow.tsThis produces typed interfaces you can use with the evaluator:
// src/generated/escrow.ts (generated — do not edit)
export interface EscrowFacts {
payment_received: boolean;
payment_amount: string; // Money type is string in TS
customer_tier: string;
release_approved?: boolean;
dispute_reason?: string;
}
export type OrderState =
| "pending"
| "approved"
| "disputed"
| "released"
| "cancelled";
export type PaymentState = "held" | "released" | "refunded";
export type EscrowPersona = "escrow_agent" | "buyer" | "seller";
export type EscrowOperation =
| "release_funds"
| "dispute_transaction"
| "cancel_order"
| "verify_release"
| "escalate_dispute"
| "refund_payment";
export interface EscrowVerdicts {
payment_ok?: { value: boolean; stratum: 0 };
eligible_for_release?: { value: boolean; stratum: 1 };
release_approval_ok?: { value: boolean; stratum: 1 };
}
export interface EscrowEntities {
Order: Record<string, OrderState>;
Payment: Record<string, PaymentState>;
}Using the generated types:
import { TenorEvaluator } from "@tenor/sdk";
import type { EscrowFacts, EscrowEntities, EscrowVerdicts } from "./generated/escrow";
const evaluator = await TenorEvaluator.load("./escrow.tenor.wasm");
const facts: EscrowFacts = {
payment_received: true,
payment_amount: "5000.00",
customer_tier: "premium",
};
const entities: EscrowEntities = {
Order: { order_001: "pending" },
Payment: { pay_001: "cleared" },
// Compile error: unknown entity type
};
const result = evaluator.evaluate({ facts, entities });
const verdicts = result.verdicts as EscrowVerdicts;
if (verdicts.payment_ok?.value) {
console.log("Payment verified at stratum", verdicts.payment_ok.stratum);
}Full Working Example: Browser Action Space Preview
Using the WASM evaluator in a React component to show available actions:
import { useState, useEffect } from "react";
import { TenorEvaluator } from "@tenor/sdk";
import type { EscrowPersona } from "./generated/escrow";
interface ActionPreviewProps {
persona: EscrowPersona;
orderId: string;
orderState: string;
facts: Record<string, unknown>;
}
export function ActionPreview({ persona, orderId, orderState, facts }: ActionPreviewProps) {
const [actions, setActions] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
async function evaluate() {
const evaluator = await TenorEvaluator.load("/contracts/escrow.tenor.wasm");
const result = evaluator.computeActionSpace({
facts,
entities: { Order: { [orderId]: orderState } },
persona,
});
if (!cancelled) {
setActions(result);
setLoading(false);
}
}
evaluate();
return () => { cancelled = true; };
}, [persona, orderId, orderState, facts]);
if (loading) return <div>Evaluating contract...</div>;
return (
<div>
<h3>Available Actions for {persona}</h3>
{actions.length === 0 ? (
<p>No actions available in current state.</p>
) : (
<ul>
{actions.map((action) => (
<li key={action.operation}>
<strong>{action.operation}</strong>
{action.preconditionMet ? " (ready)" : " (precondition not met)"}
</li>
))}
</ul>
)}
</div>
);
}