overview
Introduction
Writ is a settlement layer for usable work. You post a job, lock a bounty in WRIT, and the bounty settles on Solana when a Pearl proof clears the job — or comes back to you when it expires.
How it works
- Post. You hash your prompt into a
gritand lock WRIT in an escrow account. - Work. A miner on Pearl does the job and produces a proof.
- Settle. Once the writ is marked cleared, 80% goes to the miner and 20% is burned.
- Or expire. If nothing clears it before the expiry slot, 100% is refunded to you.
Principles
- One asset. WRIT, an SPL token on Solana, is the only thing you pay with.
- Private prompts. Only the sha256 hash goes on chain, never the text.
- Chain is the truth. Status changes only after a confirmed transaction, then it is re-read from the RPC.
- Nothing invented. No numbers are shown unless they were read from your endpoint.
Next steps
Read Concepts for the lifecycle of a writ, or jump straight to Getting started to configure the client.
overview
Concepts
The few ideas you need to reason about a writ.
Writ
A writ is one unit of work — one inference job — with a bounty attached. It lives as a 161-byte account on Solana owned by the Writ program, derived from the poster and the grit.
Grit
The grit is sha256(prompt), 32 bytes. It identifies the job without revealing it. The prompt text never leaves your machine through Writ.
Escrow
When you post, the bounty moves from your token account to an escrow token account whose authority is a program address. Nobody, including you, can move it except through the program's settle or refund paths.
Status
| Code | Status | Meaning |
|---|---|---|
0 | open | Bounty locked, waiting for a proof. |
1 | cleared | A proof cleared the job; the miner can claim. |
2 | refunded | Expired uncleared; the poster was refunded. |
Payout
- Cleared: 80% to the miner, 20% burned.
- Expired uncleared: 100% back to the poster.
Pearl proof
When a writ clears, its account stores the 32-byte Pearl transaction id. The client links it to the Pearl explorer only when that field is non-zero. Proof verification itself is not performed in this client.
guides
Getting started
From an empty client to your first posted writ.
1. Configure
Every value is optional and the app boots without any of them. Anything missing is named on screen and the action that needs it stays disabled. Set them as environment variables, or override them at runtime in Settings.
VITE_SOLANA_RPC=https://api.devnet.solana.com
VITE_SOLANA_CLUSTER=devnet
VITE_WRIT_MINT=<mint address>
VITE_WRIT_PROGRAM_ID=<program id>
VITE_WRIT_POOL=<account or https quote endpoint>| Key | Used for |
|---|---|
VITE_SOLANA_RPC | Every read and send. |
VITE_SOLANA_CLUSTER | Explorer links only. |
VITE_WRIT_MINT | The WRIT SPL mint. |
VITE_WRIT_PROGRAM_ID | The deployed Writ program. |
VITE_WRIT_POOL | Bounty quote. If it fails, posting is disabled. |
2. Sign in
Open the app and sign in with your Solana wallet. Jobs, keys and balances belong to your account.
3. Post a writ
Open the app, sign in, and write your prompt in the chat — the client hashes it into the grit and locks WRIT in escrow before the job goes live. You can also post from your own terminal with the API. Every job and its settlement state re-reads straight from chain.
reference
API
Send a prompt, get an answer from a Pearl miner, and pay per token in WRIT from your account balance.
Authentication
Create a key under API keys in the app and send it as a bearer token. Keys look like writ_ followed by 64 hex characters.
Authorization: Bearer writ_…Before your first job
- Link a Solana wallet to your account in the app under Profile.
- Deposit WRIT into your account. Each job reserves its bounty from there.
Create a job
POST /api/public/v1/jobs
{
"prompt": "Explain this stack trace …",
"max_tokens": 800, // optional, default 1024
"expiry_slots": 9000 // optional, default 9000 (~1h)
}The bounty is ceil(max_tokens × price_per_1k_tokens / 1000), with the rate read live from the pool. The job is only stored after its writ is confirmed on Solana.
201 {
"data": {
"id": "8f1c…",
"grit_hex": "…",
"reserved_amount": "…",
"writ_address": "…",
"post_signature": "…"
}
}Read a job
GET /api/public/v1/jobs/:idReturns the stored job plus chain, read live from the writ account: status (open, cleared, refunded), miner and Pearl txid. result is null until a miner delivers.
List jobs
GET /api/public/v1/jobs // latest 50Terminal
A small script is served at /writ.mjs. It needs Node 18 or newer.
curl -o writ.mjs https://writlayer.com/writ.mjs
export WRIT_API_KEY=writ_…
node writ.mjs "Write a haiku about settlement"It creates the job, then polls until a miner answers or the writ expires.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Body failed validation. |
| 401 | unauthorized | Missing, malformed or revoked key. |
| 409 | no_wallet | No wallet linked to the account. |
| 502 | pool_error / chain_error | Rate or transaction failed; raw message included. |
| 503 | not_configured | The server is missing a value, which is named in the message. |
reference
For miners
Pick up open jobs, run them, deliver the answer.
Setup
- Create an account and link the wallet that should receive payouts.
- Create an API key.
Open jobs
GET /api/public/v1/miner/jobs
Authorization: Bearer writ_…Returns up to 20 of the oldest undelivered jobs, including the prompt and max_tokens.
Deliver
POST /api/public/v1/miner/jobs/:id/result
{
"output": "…",
"input_tokens": 312,
"output_tokens": 540
}The first valid delivery wins, and output_tokens cannot exceed max_tokens. Delivery records your linked wallet as the miner.
Payout
You are paid on chain, not by this API. Once the writ is cleared by a Pearl proof, 80% of the bounty goes to the miner and 20% is burned. If nothing clears it before expiry, the poster is refunded.
reference
Program
The Writ program follows Anchor conventions. All integers are little-endian.
Instructions
Instruction data is an 8-byte discriminator — the first 8 bytes of sha256("global:<name>") — followed by Borsh-serialised arguments.
post_writ(grit: [u8; 32], amount: u64, expiry_slot: u64)
claim_writ()
refund_writ()post_writ accounts
| # | Account | Flags |
|---|---|---|
| 0 | writ PDA | writable |
| 1 | escrow PDA | writable |
| 2 | poster | signer, writable |
| 3 | poster token account | writable |
| 4 | mint | — |
| 5 | token program | — |
| 6 | associated token program | — |
| 7 | system program | — |
claim_writ / refund_writ accounts
| # | Account | Flags |
|---|---|---|
| 0 | writ PDA | writable |
| 1 | escrow PDA | writable |
| 2 | signer | signer, writable |
| 3 | destination token account | writable |
| 4 | mint | writable (burn) |
| 5 | token program | — |
| 6 | system program | — |
PDA seeds
writ = ["writ", poster, grit]
escrow = ["escrow", writ]Writ account
161 bytes total.
| Offset | Size | Field | Type |
|---|---|---|---|
0 | 8 | discriminator | Anchor account discriminator |
8 | 32 | poster | pubkey |
40 | 32 | grit | sha256 of the prompt |
72 | 8 | amount | u64, bounty in mint base units |
80 | 1 | status | u8 — 0 open, 1 cleared, 2 refunded |
81 | 32 | miner | pubkey, zeros if none |
113 | 32 | pearl_txid | zeros if none |
145 | 8 | created_slot | u64 |
153 | 8 | expiry_slot | u64 |