Documentation
Everything needed to run the network from either side, and enough detail to work out what is wrong without asking anybody.
What is actually sold
Rendered seconds. Not impressions, not views. A Claude Code status-line command receives JSON on stdin, prints, and exits โ it is never told when its output left the screen, whether the terminal was focused, or whether the window was even visible.
So the only claim this network can honestly make is that a line was rendered, and for how long. Everything downstream is priced on that, and every inference is biased low: under-counting costs a publisher a fraction of a cent, while over-counting bills an advertiser for delivery that never happened.
How the time is measured
Each render appends one line to a local file. Duration is inferred from the gap to the next render, with three rules:
- An interval is capped at 15 seconds, 1.5ร the heartbeat. A closed laptop leaves an unbounded final gap; anything longer than the cap is a missing observation, not a longer view.
- The final render of a session credits nothing. There is no closing observation, so its length is genuinely unknown.
- A backwards clock credits zero, never a negative number.
Timestamps are whole seconds. The status line stamps with date +%s, so a render in the same second as an ad change reads as slightly earlier than the change and is dropped. That costs a fraction of a cent per ad rotation and is deliberate: the alternative would charge an advertiser for a second that may not have shown their line.
The activity gate
An interval only counts if the session did real model work across it. Claude Code supplies two monotonic integers โ total API duration and total input tokens โ and if neither moved, nothing happened and the interval is worth zero.
Practically: leaving a window open earns nothing. Farming requires actually spending money on model calls. This is not proof of a human, and anyone who controls their own machine controls the payload it sends โ it raises the floor, it does not close the door.
Which ad gets chosen
Highest bid with budget remaining, first price. Ties break deterministically by campaign id so the result is auditable. When no paid campaign is eligible, house inventory fills the slot โ it bids zero, bills nobody and credits nobody, and rotates. Any real bid displaces it immediately.
Install
Register a wallet at sigil.bond/#earn, then:
# write the daemon config and patch Claude Code's settings node bin/install.mjs pk_yourtoken --server https://sigil.bond # start the daemon โ the only part that touches the network node bin/daemon.mjs
The status line it installs is three POSIX tools:
{ printf '%s ' "$(date +%s)"; cat; echo; } >> ~/.sigil/ticks 2>/dev/null; cat ~/.sigil/line 2>/dev/null; true
Claude Code has exactly one status-line slot. Installing replaces whatever is there. install.mjs refuses if you already have one unless you pass --force, and writes settings.json.bak either way.
To test without touching your global config, use an isolated project: node bin/testbed.mjs pk_yourtoken creates a folder whose .claude/settings.json applies to that folder only.
What goes where
| Path | What it is |
|---|---|
~/.sigil/config.json | Your token and server. Read by the daemon. |
~/.sigil/ticks | Append-only render log. The daemon claims it by rename. |
~/.sigil/ticks.out | Claimed batch awaiting acknowledgement. Its presence means an upload is unconfirmed. |
~/.sigil/line | The cached ad. This is what the status line prints. |
~/.sigil/state.json | Ad history and per-session sequence numbers. |
~/.claude/settings.json | Yours. We add statusLine and back it up first. |
Verify it works
Paste your token at sigil.bond/#earn. The status pill tells you which of three states you are in:
| State | Means | Do |
|---|---|---|
connected | An upload arrived within the last 10 minutes. | Nothing. It is working. |
idle | Registered and seen before, but nothing recently. | Check the daemon is still running. |
waiting | Registered, never heard from. | Start the daemon. First upload lands within about a minute. |
Earnings and payouts
- You receive 70% of gross for every second your machine rendered a paid line.
- Payouts are batched direct transfers. There is no claim transaction and no account rent for you to pay.
- The threshold is $1.00 in SOL. Below it, the balance carries forward โ nothing is lost.
- A first payout to a wallet that has never held SOL must clear the network's rent-exempt minimum, currently 890,880 lamports. Below that the transfer would fail, so it waits.
Rendered time going up while earnings stay flat is usually house inventory. House ads render but bill nobody. Your dashboard shows unsold time separately for exactly this reason.
Payout assets
Advertisers pay USDC. You choose which asset that debt is settled in โ SOL, $PACT, or a split. This is a denomination election, not a liquidity position: nothing is pooled, no fee accrues, and there is no impermanent loss.
curl -X PATCH https://sigil.bond/v1/publishers/me \
-H "Authorization: Bearer pk_yourtoken" \
-H 'Content-Type: application/json' \
-d '{"election":{"SOL":7000,"PACT":3000}}'
- Basis points, and they must sum to exactly 10,000. A split that sums to less would silently discard the remainder.
- Applied at the moment earnings are credited, so changing it never redenominates what you already earned.
- Each asset has its own threshold: $1.00 for SOL, $25.00 for $PACT. A thin market makes a small token payout unrealisable, so the halves clear independently.
$PACT payouts are not live yet. The rail is built and tested; the mint does not exist. $PACT balances accrue and are honoured, but nothing can send them until the token launches and passes its safety checks.
Buying placements
No account. Ask for a campaign, get a 402 with payment requirements, pay, retry.
# 1. ask โ this returns 402, which is the intended flow, not an error
curl -X POST https://sigil.bond/v1/campaigns \
-H 'Content-Type: application/json' \
-d '{"text":"your line, 72 chars max","cprmMicros":"3000000","budgetMicros":"250000000"}'
# 2. the response names the amount, mint, recipient and network
# 3. pay it, then retry with the signed payload
curl -X POST https://sigil.bond/v1/campaigns \
-H 'Content-Type: application/json' \
-H 'X-Payment: <base64 x402 payload>' \
-d '{"text":"your line, 72 chars max","cprmMicros":"3000000","budgetMicros":"250000000"}'
The response contains a manage key, shown once. Only a hash is stored and there is no recovery. sigil.bond runs no accounts, so that key is the only way back to your campaign.
Creative is plain text, 72 characters. Terminal control sequences are refused โ the line renders inside somebody else's terminal, and an advertiser must not be able to move their cursor or clear their screen.
Budget and pacing
- You bid CPRM โ micro-USD per 1,000 rendered minutes. Minimum $1.00.
- Your deposit is a hard ceiling. Delivery past it is given away, never billed.
pacing: "asap"spends as fast as delivery allows."even"releases the budget linearly acrosswindowSeconds, so at any moment you can only have spentbudget ร elapsed / window.- A top-up adds to the deposit and never resets what has been spent.
Delivery reporting
GET /v1/campaigns/{id}/delivery returns hourly buckets and a
per-publisher breakdown.
The two views measure different things and will not sum equal. Buckets are the gross you were charged. The publisher spread is what each publisher earned from it. They differ by exactly the operator's 30%.
HTTP API
| Method | Path | Auth | Does |
|---|---|---|---|
| GET | /.well-known/x402 | โ | Discovery document. |
| GET | /v1/campaigns | โ | 402 with payment requirements. |
| POST | /v1/campaigns | X-Payment | Create. Returns a manage key once. |
| GET | /v1/campaigns/{id} | manage key | State, spend, pacing, servability. |
| PATCH | /v1/campaigns/{id} | manage key | Text, bid, pacing, window, pause/resume/cancel. |
| POST | /v1/campaigns/{id}/topup | manage key + X-Payment | Add budget. |
| GET | /v1/campaigns/{id}/delivery | manage key | Hourly spend, publisher spread. |
| POST | /v1/publishers | โ | Register a wallet. Free. |
| GET | /v1/publishers/me | token | Balances, rendered time, liveness. |
| PATCH | /v1/publishers/me | token | Set payout election. |
| POST | /v1/exchange | token | Upload renders, receive an ad. |
Money crosses the wire as decimal strings, never JSON numbers. Amounts are integers in micro-USD (1e-6 USD) or token base units. Parse them as big integers โ a float silently loses precision above 253.
Limits and constants
| Thing | Value |
|---|---|
| Publisher share of gross | 70% |
| Minimum bid | $1.00 CPRM |
| Creative length | 72 characters, plain text |
| Status-line heartbeat | 10 seconds |
| Interval cap | 15 seconds |
| Renders per upload | 5,000 |
| Payout threshold | $1.00 SOL ยท $25.00 $PACT |
| Rent-exempt minimum | 890,880 lamports |
| Ad cache lifetime | 300 seconds |
Error codes
| Code | Means |
|---|---|
unauthorized | Missing or wrong Bearer token, or a key that belongs to a different campaign. |
not_base58 | The wallet contains 0, O, I or l, which base58 excludes. |
not_32_bytes | Valid base58, wrong length. Not a Solana address. |
off_curve_no_private_key | A valid 32-byte value with no private key โ a program address. Payouts to it could never be spent. |
unpayable_address | The System Program, the incinerator, or a mint. Funds sent there are destroyed. |
control_characters_refused | Creative contained escape or control bytes. |
bid_below_floor | CPRM under the $1.00 minimum. |
must_sum_to_10000:got_N | A payout election that does not add to 10,000 basis points. |
cancelled_is_terminal | A cancelled campaign cannot be revived. Create a new one. |
status_not_settable | exhausted is a fact about the deposit, not a state you can write. |
payments_not_configured | The exchange has no payment recipient set. Nothing is broken on your end. |
too_many_ticks | More than 5,000 renders in one upload. |
Troubleshooting
My status line is blank
Expected on a fresh install โ the daemon has not fetched an ad yet. Check
~/.sigil/line exists. If it does not:
- Is the daemon running? It logs to stderr on every cycle.
- Is there inventory? If nothing is live, the daemon deletes
linerather than letting a stale ad ride. - Run the command by hand:
echo '{"session_id":"x"}' | sh -c "$(your statusLine command)". It should exit 0 and print nothing.
The status line never changed from what I had before
Claude Code has one slot and something else holds it. Check
~/.claude/settings.json, and remember project settings override
user settings โ a project's .claude/settings.json wins inside
that folder.
Rendered time is climbing but earnings are zero
Three possible reasons, in order of likelihood:
- House inventory. It renders and bills nobody. Your dashboard shows unsold time separately.
- The activity gate. Intervals where the agent did no model work are worth zero. An open window is not activity.
- Below the payout threshold. Earnings exist but are under $1.00. They carry forward.
The dashboard says "waiting" and never changes
The daemon has never successfully uploaded. Its stderr will say why โ a
failed cycle is logged, and the outbox is retained rather than dropped.
Check the token in ~/.sigil/config.json matches the one you
pasted.
A ticks.out file has been sitting there
An upload was not acknowledged. It is retained on purpose and re-sent on the next cycle; the server discards anything already settled, so a retry cannot pay twice. It clears itself once a cycle succeeds.
My payout has not arrived
- Below the threshold for that asset โ the dashboard says how far.
- A never-funded wallet below the rent-exempt minimum. The transfer would fail, so it waits until the balance clears it.
- Payouts run in batches, not per-earning.
How do I check my earnings later?
Open sigil.bond/#dashboard. Your token is kept in your browser, so it loads straight away โ bookmark it. If the browser has forgotten it, or you are on another machine, click I already have a token and paste it.
Do not go through registration again to check a balance. It would work, but it is not what it is for.
I lost my publisher token
Register the same wallet again. Earnings are held against the wallet, not the token, so the new token sees the same balance โ including everything earned before it existed. Your old tokens keep working too; a wallet may hold several, one per machine, and issuing one never revokes another.
This is the opposite of the campaign manage key, which genuinely cannot be recovered. The difference is that a publisher's identity is a wallet you already control, and a campaign's is not.
I lost my manage key
There is no recovery. Only a hash is stored, deliberately โ the alternative is an account system with a password reset, which means an email address, a support queue, and a new thing to breach. A campaign without its key runs to the end of its budget and stops.
Creating a campaign returns 402
That is the payment flow working. The body carries the amount, mint, recipient and network. Pay it and retry with the X-Payment header.
Creating a campaign returns 503
payments_not_configured means the exchange has no payment
recipient set. It refuses rather than quoting a price it cannot honour โ
a 402 naming a placeholder would take money nobody could recover.
What leaves your machine
Claude Code hands the status line a rich payload. This is everything we upload from it:
{
"sessionId": "abc123", // opaque, from the agent
"seq": 47, // ordering
"at": 1788340193, // whole seconds
"adId": "cmp_9f2a71", // which line was on screen
"apiMs": 91240, // monotonic, from the agent
"inTokens": 155300 // monotonic, from the agent
}
No path, repository, branch, prompt, transcript or code. The status line never parses the payload โ it appends bytes and prints a file โ so there is no parser in your editor to leak anything, and no network call to leak it over. See Privacy for the policy version.