How to write something that runs on the Vulpine desktop. Everything here is checked against wallet 0.98.0. Where this and gekker.ts disagree, gekker.ts is right.
.tod) is an application. One JSON file holding a manifest and a page. It installs onto the Vulpine desktop rather than onto Windows or Linux, and is meant to run the same wherever the wallet runs — desktop, phone, and a headset's browser. On desktop it currently does not; see the known gaps.fwetch-post, fwetch-vote, fwetch-name and the rest. The protocol's vocabulary. A tod does not invent records; it writes the ones the network already understands.The full vocabulary — 52 types — is at <https://fwetch.lol/protocol>, and how to write one is at <https://fwetch.lol/building>. Read them before inventing anything. Almost everything you would want to say already has a type, and fwetch-assert {type, parent?, payload} is the general escape hatch for anything that does not.
Verify a record against what the indexer reads, not against its name. Three verbs shipped writing records the indexer filed and the apps could not read, and all three looked like they worked. fwetch-article needs a Library coordinate rather than text, and fwetch-listing needs kind and category; the ones the index recognizes carry them. A fwetch-name claim is { name } and nothing else. The index recognizes claims with no un, and the protocol reads un: 1 as a release, so leave un off a claim.
{
"tod": 1,
"name": "fortune",
"title": "Fortune",
"version": "1.0.0",
"author": "#1",
"about": "prints something a block once said",
"glyph": "🜚",
"needs": ["read"],
"page": "<!doctype html><html>…</html>"
}
name — lowercase, hyphens, 2–31 characters. How it is invoked.needs — the capabilities, declared up front. A capability not declared is refused at runtime, and the refusal names it, so an author learns what they forgot rather than watching something fail quietly.page — the whole application: markup, styles and scripts, in one string. No external files. Anything loaded from elsewhere will not be there when somebody runs it from a chain.The bridge was called Gekker until the social site at gekker.lol took that name. Nothing a tod calls changed: it is still vulpine.read() and the rest, and the runtime file is still gekker.ts.
The wallet injects this before any of your code runs. Every call returns a promise.
await vulpine.list('/boards') // what is there
await vulpine.read('/boards/pol/a1b2c3d4') // what it says
await vulpine.fetch('/api/tartary/map') // this network's API, read-only
await vulpine.who() // { number, numbers } — never a key
await vulpine.sign({ p: 'fwetch-post', b: 'hello', board: 'FG' })
await vulpine.pay('1Abc…', 5000) // satoshis
await vulpine.pay('#4', 5000) // to whoever holds #4 right now; the index resolves it
await vulpine.put('score', '41') // your own corner of this machine
await vulpine.get('score')
await vulpine.open('chat') // one of the wallet's applications
| capability | what it allows | asks the person? |
|---|---|---|
read | the filesystem and this network's API | no, including /numbers, /names and /pictures, which describe the person |
who | the numbers they hold, lowest first; no name, key or address | once per run |
sign | writing a record to the chain as them | every time, showing the record |
pay | sending satoshis | every time, showing who and how much |
store | keeping notes between runs | no |
open | opening the wallet's applications | no |
What a tod cannot do, and will not be able to: see a private key, see a seed phrase, read another tod's notes, reach the wallet's own state, or sign anything without the person seeing exactly what it is. The frame has no same-origin access to the wallet at all; the bridge is its only channel to the wallet. The frame can still reach the network on its own: see the known gaps below.
Paths are questions to the network, not a tree anybody stores:
/numbers the user numbers this key holds
/pictures the library pieces it holds
/names the .fox names and $handles it holds
/boards every board; dig into one for its last 40 posts, each
named by the first 8 characters of its txid
/chain districts, newest first; dig into one for its transactions
/apps what this machine can run
A tod that replies to a post or reacts to one needs the full txid, which the filesystem does not give. Read the feed instead: vulpine.fetch('/api/feed?board=FG&limit=40').
There is no toolchain to install. A tod is a page.
# 1. write index.html — markup, styles and script in one file
# 2. wrap it
node -e "
const fs = require('fs')
fs.writeFileSync('fortune.tod', JSON.stringify({
tod: 1, name: 'fortune', title: 'Fortune', version: '1.0.0',
author: '#1', about: 'prints something a block once said',
glyph: '🜚', needs: ['read'],
page: fs.readFileSync('index.html', 'utf8'),
}))
"
# 3. install it
# in the wallet's terminal:
# foxhole install https://your-site/fortune.tod
For anything with a build step, bundle to a single file first. Tartaria's make-tod.mjs does exactly this with Vite: build, inline every asset back into the page, refuse to continue if anything is still loaded from outside. Copy it.
A tod is one JSON file, which is why it can be an inscription. Inscribe it, and foxhole install <outpoint> fetches it from the chain rather than from anybody's server. It is then transferable and sellable like everything else here.
The Den — a listing of tods claimed on chain, first inscription wins a name — is designed but not built. See VULPNET.md in the wallet project.
who, sign and pay each ask when they are used, so a tod that needs read alone never prompts anybody.Checked against the runtime. Build around these until the wallet closes them.
sign goes through the house and pay sends a transaction of its own. Orders, ad bookings and sales are recognized only with the payment in the same transaction, so no tod can write them.sign stamps t and sets app to the tod's name, over whatever the tod sent. A reader that filters by app, such as Breddit's strict feed, will not show what a tod writes.fetch gets nothing back; use vulpine.fetch and declare read. Tartaria's current tod still reads directly.Closed in 0.99.0: tods now run on desktop; /numbers, /pictures and /names ask the who question; /numbers/#N no longer shows an address; who returns the name; reactions, votes and reveals sign as they should; the sign prompt shows the whole record; open refuses an unknown application; removing a tod removes its notes; /chain lists the newest blocks.
author today. A tod inscribed on chain is attributable by its inscription; one served from a web address is not.