sigil.bond

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:

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

PathWhat it is
~/.sigil/config.jsonYour token and server. Read by the daemon.
~/.sigil/ticksAppend-only render log. The daemon claims it by rename.
~/.sigil/ticks.outClaimed batch awaiting acknowledgement. Its presence means an upload is unconfirmed.
~/.sigil/lineThe cached ad. This is what the status line prints.
~/.sigil/state.jsonAd history and per-session sequence numbers.
~/.claude/settings.jsonYours. 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:

StateMeansDo
connectedAn upload arrived within the last 10 minutes.Nothing. It is working.
idleRegistered and seen before, but nothing recently.Check the daemon is still running.
waitingRegistered, never heard from.Start the daemon. First upload lands within about a minute.

Earnings and payouts

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}}'

$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

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

MethodPathAuthDoes
GET/.well-known/x402โ€”Discovery document.
GET/v1/campaignsโ€”402 with payment requirements.
POST/v1/campaignsX-PaymentCreate. Returns a manage key once.
GET/v1/campaigns/{id}manage keyState, spend, pacing, servability.
PATCH/v1/campaigns/{id}manage keyText, bid, pacing, window, pause/resume/cancel.
POST/v1/campaigns/{id}/topupmanage key + X-PaymentAdd budget.
GET/v1/campaigns/{id}/deliverymanage keyHourly spend, publisher spread.
POST/v1/publishersโ€”Register a wallet. Free.
GET/v1/publishers/metokenBalances, rendered time, liveness.
PATCH/v1/publishers/metokenSet payout election.
POST/v1/exchangetokenUpload 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

ThingValue
Publisher share of gross70%
Minimum bid$1.00 CPRM
Creative length72 characters, plain text
Status-line heartbeat10 seconds
Interval cap15 seconds
Renders per upload5,000
Payout threshold$1.00 SOL ยท $25.00 $PACT
Rent-exempt minimum890,880 lamports
Ad cache lifetime300 seconds

Error codes

CodeMeans
unauthorizedMissing or wrong Bearer token, or a key that belongs to a different campaign.
not_base58The wallet contains 0, O, I or l, which base58 excludes.
not_32_bytesValid base58, wrong length. Not a Solana address.
off_curve_no_private_keyA valid 32-byte value with no private key โ€” a program address. Payouts to it could never be spent.
unpayable_addressThe System Program, the incinerator, or a mint. Funds sent there are destroyed.
control_characters_refusedCreative contained escape or control bytes.
bid_below_floorCPRM under the $1.00 minimum.
must_sum_to_10000:got_NA payout election that does not add to 10,000 basis points.
cancelled_is_terminalA cancelled campaign cannot be revived. Create a new one.
status_not_settableexhausted is a fact about the deposit, not a state you can write.
payments_not_configuredThe exchange has no payment recipient set. Nothing is broken on your end.
too_many_ticksMore 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:

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:

  1. House inventory. It renders and bills nobody. Your dashboard shows unsold time separately.
  2. The activity gate. Intervals where the agent did no model work are worth zero. An open window is not activity.
  3. 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

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.