Skip to content

Commit f1f6503

Browse files
currency registry — every currency is a URI
The §4 identity lesson applied to money: short codes stay the wire/display token, but each resolves through a registry (registry.json seed + operator extensions, served at GET /api/currencies) to a canonical currency URI with kind + settlement semantics. Kinds: fiat-iou, chain (settled by a publicly verifiable on-chain tx — TBTC4/TBTC3 registered, testnets first), service (serving IS the settlement — LLM inference tokens registered; an LLM agent is a did:nostr key + signed transitions, no payment rails needed), mutual (HRS). Registry is discovery, not permission: unknown codes transact freely. Spec §5.2 rewritten; UI currency picker feeds from the registry; roadmap gains v4 "the machine economy". 39 tests green.
1 parent dffc218 commit f1f6503

6 files changed

Lines changed: 129 additions & 5 deletions

File tree

docs/roadmap.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,17 @@
4646
the recordweb pattern)
4747
- [ ] Agents as participants (autonomous trust management, routing fees?)
4848
- [ ] solidpay.org: site, spec home, and the 2018 → 2026 story
49+
50+
## v4 — the machine economy
51+
52+
- [x] Currency registry: every currency is a URI (registry.json +
53+
GET /api/currencies; codes are aliases; kinds fiat-iou / chain /
54+
service / mutual). TBTC3/TBTC4 registered as chain-settled; LLM
55+
inference tokens registered as service-settled (serving IS the
56+
settlement).
57+
- [ ] Settlement verification for chain currencies (settle carries a txid;
58+
the node checks the testnet3/testnet4 transaction pays the creditor)
59+
- [ ] 402 loop: request → 402 → signed payment over a trustline → retry
60+
(composes with the JSS pay plugin and ollama-proxy)
61+
- [ ] LLM agents as first-class participants (a did:nostr key + a policy:
62+
extend trust, price tokens, settle by serving)

docs/spec/index.html

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,30 @@ <h3 id="amounts"><span class="secno">5.1</span>Amounts<a class="self" href="#amo
267267
<span class="rfc">must</span> be rejected with status 400. Implementations
268268
<span class="rfc">must not</span> perform ledger arithmetic in binary floating point.</p>
269269
<h3 id="currencies"><span class="secno">5.2</span>Currencies<a class="self" href="#currencies">§</a></h3>
270-
<p>A currency code matches <code>[A-Z0-9]{1,12}</code> after upper-casing on input. Codes carry no
271-
built-in semantics: <code>USD</code>, <code>SATS</code>, <code>HRS</code>, and <code>BEER</code> are
272-
equally valid. Currencies are fully isolated: a trustline or balance in one currency confers no
273-
capacity in any other.</p>
270+
<p>A currency code matches <code>[A-Z0-9]{1,12}</code> after upper-casing on input. Currencies are
271+
fully isolated: a trustline or balance in one currency confers no capacity in any other.</p>
272+
<p><strong>Every currency is a URI</strong>; the short code is a display/wire alias. Each node
273+
serves a registry at <code>GET /api/currencies</code> mapping codes to
274+
<code>{uri, name, kind, decimals, settlement}</code>; nodes seed it from the repository registry
275+
and operators <span class="rfc">may</span> extend it. The registry is <em>discovery, not
276+
permission</em>: unknown codes transact freely — but two parties who mean different things by a
277+
code have a dispute the registry exists to prevent. Registered kinds:</p>
278+
<div class="tblwrap"><table>
279+
<tr><th>Kind</th><th>Settled by</th><th>Examples</th></tr>
280+
<tr><td><code>fiat-iou</code></td><td>out-of-band fiat payment</td><td><code>USD</code>, <code>EUR</code></td></tr>
281+
<tr><td><code>chain</code></td><td>an on-chain transaction on the named network — publicly
282+
verifiable</td><td><code>SATS</code>, <code>TBTC4</code>, <code>TBTC3</code></td></tr>
283+
<tr><td><code>service</code></td><td>delivery of the named service — serving IS the
284+
repayment</td><td><code>LLM</code> (inference tokens); provider/model-specific codes minting their
285+
own URIs</td></tr>
286+
<tr><td><code>mutual</code></td><td>agreement between the parties</td><td><code>HRS</code> (time
287+
credit)</td></tr>
288+
</table></div>
289+
<div class="note"><span class="lbl">Note — the machine economy</span><p>Service-settled currencies
290+
make agents natural participants: an inference provider extends a consumer credit denominated in
291+
tokens; consumption draws it down; <em>serving the tokens is the settlement</em>. An LLM agent
292+
needs only a did:nostr key and §&nbsp;9's signed transitions — no payment rails at all for
293+
machine-to-machine credit.</p></div>
274294
<h3 id="trustlines"><span class="secno">5.3</span>Trustlines<a class="self" href="#trustlines">§</a></h3>
275295
<p>A trustline is keyed by <code>(creditor, debtor, currency)</code> and carries a single
276296
<code>limit</code> in micro-units. Rules:</p>

lib/ui.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ input.mono{font-family:var(--mono);font-size:13px}
231231
232232
</main>
233233
<datalist id="peers"></datalist>
234-
<datalist id="curs"><option value="USD"><option value="EUR"><option value="GBP"><option value="SATS"><option value="HRS"></datalist>
234+
<datalist id="curs"><option value="USD"><option value="EUR"><option value="GBP"><option value="SATS"><option value="TBTC4"><option value="HRS"><option value="LLM"></datalist>
235235
<div class="toast" id="toast"></div>
236236
237237
<script>
@@ -551,6 +551,10 @@ function bindLineActions(scope){
551551
}
552552
553553
refresh();
554+
fetch('/api/currencies').then(function(r){return r.json()}).then(function(d){
555+
var ks=Object.keys(d.currencies||{});
556+
if(ks.length)$('curs').innerHTML=ks.map(function(c){return '<option value="'+esc(c)+'">'+esc((d.currencies[c]||{}).name||c)+'</option>'}).join('');
557+
}).catch(function(){});
554558
setInterval(function(){if(document.visibilityState==='visible')refresh()},6000);
555559
})();
556560
</script>

registry.json

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"$comment": "SolidPay currency registry — every currency is a URI; short codes are display/wire aliases that resolve here. Served by every node at GET /api/currencies (operators may extend via <data>/currencies.json). Kinds: fiat-iou (settled out-of-band in the named fiat), chain (settled by an on-chain transaction on the named network), service (settled by delivery of the named service — e.g. LLM inference tokens), mutual (pure mutual credit, settlement by agreement).",
3+
"currencies": {
4+
"USD": {
5+
"uri": "https://jss.live/solidpay/currency/usd",
6+
"name": "US Dollar (IOU)",
7+
"kind": "fiat-iou",
8+
"decimals": 2
9+
},
10+
"EUR": {
11+
"uri": "https://jss.live/solidpay/currency/eur",
12+
"name": "Euro (IOU)",
13+
"kind": "fiat-iou",
14+
"decimals": 2
15+
},
16+
"GBP": {
17+
"uri": "https://jss.live/solidpay/currency/gbp",
18+
"name": "Pound Sterling (IOU)",
19+
"kind": "fiat-iou",
20+
"decimals": 2
21+
},
22+
"SATS": {
23+
"uri": "https://jss.live/solidpay/currency/sats",
24+
"name": "Bitcoin satoshis (IOU)",
25+
"kind": "chain",
26+
"network": "btc",
27+
"decimals": 0,
28+
"settlement": "on-chain bitcoin transaction; the creditor records it with settle"
29+
},
30+
"TBTC4": {
31+
"uri": "https://jss.live/solidpay/currency/tbtc4",
32+
"name": "Bitcoin testnet4 satoshis",
33+
"kind": "chain",
34+
"network": "tbtc4",
35+
"decimals": 0,
36+
"explorer": "https://mempool.guide/testnet4",
37+
"settlement": "a testnet4 transaction paying the creditor; verifiable by anyone via the explorer"
38+
},
39+
"TBTC3": {
40+
"uri": "https://jss.live/solidpay/currency/tbtc3",
41+
"name": "Bitcoin testnet3 satoshis",
42+
"kind": "chain",
43+
"network": "tbtc3",
44+
"decimals": 0,
45+
"settlement": "a testnet3 transaction paying the creditor"
46+
},
47+
"HRS": {
48+
"uri": "https://jss.live/solidpay/currency/hrs",
49+
"name": "Hours (time credit)",
50+
"kind": "mutual",
51+
"decimals": 2,
52+
"settlement": "an hour of work; the creditor records it with settle"
53+
},
54+
"LLM": {
55+
"uri": "https://jss.live/solidpay/currency/llm",
56+
"name": "LLM inference tokens (generic)",
57+
"kind": "service",
58+
"decimals": 0,
59+
"settlement": "delivery of inference: the provider serving tokens IS the repayment — the creditor (consumer) records served usage with settle",
60+
"$comment": "Provider/model-specific token currencies SHOULD mint their own code + URI (e.g. LLAMA70B -> <provider-agent-uri>/currency/llama-3-70b). An LLM agent is a did:nostr key + buildTxEvent; the 402 loop composes: request -> 402 -> signed payment over the trustline -> retry."
61+
}
62+
}
63+
}

server.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ export function createNode({ dataDir = './data', publicUrl = null } = {}) {
5050
// served byte-identical at /xlogin.js. Immutable for a running server.
5151
const xloginSrc = fs.readFileSync(path.join(__dirname, 'lib', 'xlogin.js'), 'utf8');
5252

53+
// Currency registry: every currency is a URI; short codes are aliases that
54+
// resolve here (registry.json seed + optional operator extensions in
55+
// <data>/currencies.json). Unknown codes still transact — the registry is
56+
// discovery, not permission.
57+
let currencies = {};
58+
try { currencies = JSON.parse(fs.readFileSync(path.join(__dirname, 'registry.json'), 'utf8')).currencies || {}; } catch { /* none */ }
59+
try {
60+
const extra = JSON.parse(fs.readFileSync(path.join(dataDir, 'currencies.json'), 'utf8'));
61+
currencies = { ...currencies, ...(extra.currencies || extra) };
62+
} catch { /* none */ }
63+
5364
// ---- persistent bits ---------------------------------------------------
5465
const stateFile = path.join(dataDir, 'state.json');
5566
const accountsFile = path.join(dataDir, 'accounts.json');
@@ -308,6 +319,9 @@ export function createNode({ dataDir = './data', publicUrl = null } = {}) {
308319
// ---- reads ----
309320
if (req.method === 'GET' && p === '/api/whoami') return send(res, 200, { agent: agentOf(req, url) });
310321
if (req.method === 'GET' && p === '/api/graph') return send(res, 200, ledger.graph());
322+
if (req.method === 'GET' && p === '/api/currencies') {
323+
return send(res, 200, { currencies, note: 'codes are aliases; the uri is the currency\'s canonical identity. Unknown codes transact freely — the registry is discovery, not permission.' });
324+
}
311325
if (req.method === 'GET' && p === '/api/balances') {
312326
return send(res, 200, ledger.balancesFor(url.searchParams.get('agent')));
313327
}

test/server.test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ describe('solidpay node', () => {
101101
assert.strictEqual(log.tip, log.entries[log.entries.length - 1].hash);
102102
});
103103

104+
it('serves the currency registry — every currency is a URI', async () => {
105+
const res = await fetch(`${base}/api/currencies`);
106+
assert.strictEqual(res.status, 200);
107+
const { currencies } = await res.json();
108+
assert.ok(currencies.USD?.uri, 'USD resolves to a URI');
109+
assert.strictEqual(currencies.TBTC4?.kind, 'chain');
110+
assert.strictEqual(currencies.LLM?.kind, 'service', 'LLM tokens are service-settled');
111+
});
112+
104113
it('serves the app UI at /', async () => {
105114
const res = await fetch(`${base}/`);
106115
assert.strictEqual(res.status, 200);

0 commit comments

Comments
 (0)