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

  1. Post. You hash your prompt into a grit and lock WRIT in an escrow account.
  2. Work. A miner on Pearl does the job and produces a proof.
  3. Settle. Once the writ is marked cleared, 80% goes to the miner and 20% is burned.
  4. 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

CodeStatusMeaning
0openBounty locked, waiting for a proof.
1clearedA proof cleared the job; the miner can claim.
2refundedExpired 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>
KeyUsed for
VITE_SOLANA_RPCEvery read and send.
VITE_SOLANA_CLUSTERExplorer links only.
VITE_WRIT_MINTThe WRIT SPL mint.
VITE_WRIT_PROGRAM_IDThe deployed Writ program.
VITE_WRIT_POOLBounty 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

  1. Link a Solana wallet to your account in the app under Profile.
  2. 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/:id

Returns 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 50

Terminal

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

StatusCodeMeaning
400invalid_requestBody failed validation.
401unauthorizedMissing, malformed or revoked key.
409no_walletNo wallet linked to the account.
502pool_error / chain_errorRate or transaction failed; raw message included.
503not_configuredThe server is missing a value, which is named in the message.

reference

For miners

Pick up open jobs, run them, deliver the answer.

Setup

  1. Create an account and link the wallet that should receive payouts.
  2. 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

#AccountFlags
0writ PDAwritable
1escrow PDAwritable
2postersigner, writable
3poster token accountwritable
4mint—
5token program—
6associated token program—
7system program—

claim_writ / refund_writ accounts

#AccountFlags
0writ PDAwritable
1escrow PDAwritable
2signersigner, writable
3destination token accountwritable
4mintwritable (burn)
5token program—
6system program—

PDA seeds

writ   = ["writ",   poster, grit]
escrow = ["escrow", writ]

Writ account

161 bytes total.

OffsetSizeFieldType
08discriminatorAnchor account discriminator
832posterpubkey
4032gritsha256 of the prompt
728amountu64, bounty in mint base units
801statusu8 — 0 open, 1 cleared, 2 refunded
8132minerpubkey, zeros if none
11332pearl_txidzeros if none
1458created_slotu64
1538expiry_slotu64