A web contract separates logic from data. The contract defines rules in any language. The state, ledger, and trail are always JSON — universal, language-agnostic, verifiable.
A web contract cleanly separates concerns:
| Layer | File | Mutable? | Role |
|---|---|---|---|
| Contract | contract.json | No | Rules & transition logic (immutable once deployed) |
| State | state.json | Yes | Current values, updated each transition |
| Ledger | ledger.json | Yes | Balance tracking (webledgers) |
| Trail | blocktrail | Append | Bitcoin-anchored audit log (blocktrails) |
The contract logic is pluggable. State, ledger, and trail are always the same format regardless of what language the contract is written in.
The contract declares its profile, language, and source. The logic lives in a separate file written in any supported language.
{
"@context": "https://webcontracts.org/v1",
"type": "WebContract",
"profile": "amm.v1",
"id": "pool-tbtc3-tbtc4",
"language": "javascript",
"source": "amm.js",
"created": 1741737600
}
The source file exports transition functions. Each receives the current state and params, and returns the new state plus ledger effects:
export function swap(state, params, ledger) {
const { sender, sell, amount } = params
const buy = state.pair.find(u => u !== sell)
const rIn = state.reserves[sell], rOut = state.reserves[buy]
const amtAfterFee = amount * (1 - state.fee)
const out = Math.floor((rOut * amtAfterFee) / (rIn + amtAfterFee))
return {
state: {
...state,
reserves: {
[sell]: rIn + amount,
[buy]: rOut - out,
},
k: (rIn + amount) * (rOut - out),
},
effects: [
{ op: 'debit', who: sender, currency: sell, amount },
{ op: 'credit', who: sender, currency: buy, amount: out },
]
}
}
The contract logic is swappable. The pod just needs an executor for the language.
The contract interface is always the same: a function that takes (state, params, ledger) and returns { state, effects }. The language is an implementation detail.
The state is pure data — no logic. It holds the current values and a hash chain for verification.
{
"contract": "pool-tbtc3-tbtc4",
"seq": 5,
"prev": "a1b2c3d4e5f6...",
"updated": 1741737600,
"pair": ["tbtc3", "tbtc4"],
"reserves": { "tbtc3": 5000, "tbtc4": 10000 },
"k": 50000000,
"fee": 0.003,
"totalShares": 1000,
"lpShares": {
"did:nostr:abc123...": 1000
}
}
| Field | Type | Description |
|---|---|---|
contract | string | References the contract id |
seq | number | Transition sequence number, starts at 0 |
prev | string|null | SHA-256 of previous JCS-canonicalized state |
updated | number | Unix timestamp of last transition |
| ...rest | any | Profile-specific state data |
Each transition increments seq and sets prev to the SHA-256 hash of the previous state (JCS-canonicalized). This creates a hash chain that can be verified independently and anchored to Bitcoin via blocktrails.
Profiles define the shape of state and the expected transitions. A contract declares its profile; the source file implements it.
Transition functions return an effects array. The executor applies these against the webledger atomically with the state update.
The contract never touches the ledger directly. It declares intent; the executor enforces it.
Web contracts use a trust but verify model. There are three roles:
prev hash, verify the blocktrail against Bitcoin. If the executor cheated, the chain breaks.No consensus mechanism. No global blockchain. The executor is efficient (single process), and the hash chain + Bitcoin trail provides cryptographic tamper evidence. Anyone with the contract source can independently verify the full history.
Parties are identified by did:nostr:<pubkey>. Authentication uses NIP-98 HTTP auth — a signed Nostr event in the Authorization header.
This makes contracts accessible to both humans (via NIP-07 browser extensions) and AI agents (via stored keys). Any HTTP client with a signing key can participate.
Web contracts are transport-agnostic. The four files can live anywhere:
| Host | Example |
|---|---|
| Solid pod | Full integration — auth, ledger, and trail built in |
| Web server | Any HTTP server with a contract executor |
| Git repo | State as commits, trail as tags |
| Local directory | Agent runs executor locally, syncs state |
When served over HTTP, the convention is:
PUT /contracts/{id}/contract.json — deploy contract
GET /contracts/{id}/state.json — read current state
POST /contracts/{id}/ — submit a transition
GET /contracts/{id}/trail.json — read audit trail
Transitions are submitted as JSON:
{
"transition": "swap",
"params": { "sell": "tbtc3", "amount": 1000 }
}
Web contracts are designed for autonomous agents:
contract.json + source to understand the rulesstate.json to see current valuesNo browser required. No human approval. Compatible with OpenClaw, nanoclaw, and any agent framework that can make HTTP requests.
An agent can even write a contract — deploy contract.json + source, and other agents or humans can interact with it.
Web contracts make a deliberate tradeoff: give up trustless global consensus, gain speed, zero cost, language freedom, and simplicity.
| Ethereum | Web Contracts | |
|---|---|---|
| Consensus | Global PoS, thousands of nodes | None — single executor, verify via trail |
| Language | Solidity (EVM bytecode) | Any (JS, Solidity, Python…) |
| Cost | Gas fees per operation | No gas — execution is free, verification is on Bitcoin |
| Speed | ~12s block time | Instant — state updates in a single HTTP round-trip |
| State | On-chain, global | JSON — portable, readable, hosted anywhere |
| Proof | Blockchain consensus | Bitcoin-anchored hash chain — deterministic replay |
| Access | Wallet + RPC | HTTP + keypair — humans and AI agents alike |
For most real-world contracts — escrow between two parties, a token sale, a subscription, an AMM — you don’t need 10,000 nodes agreeing. You need tamper evidence and verifiability. That’s what the blocktrail provides.
| Project | Role |
|---|---|
| JavaScript Solid Server | Implements payment and AMM profiles (JavaScript) |
| Web Ledgers | Balance tracking spec |
| Blocktrails | Bitcoin anchoring spec |
| NIP-98 | HTTP authentication |