Files
orgchain/tests/belts.test.js
T
teahubxyzandClaude Opus 5.5 5a7f11e49d Orgchain v1.14.1: a belt counts only on its seat's own sat
Placement is now a rule of lineage/v0, not just how Orgchain inscribes:
an award counts only if it is inscribed on its recipient's own sat, so any
indexer can check that a belt is soulbound. The reducer compares sats
(satOf): the lab's sat numbers, ord's, or, without ord's sat index, its
satpoints, which two inscriptions share exactly when they share a sat. ord
events now carry the sat number when ord has one. Belts inscribed anywhere
else, as before v1.14, are rejected.

A promotion is another reinscription on the same sat, over the belt before
it: each badge keeps its skill's earlier belts, and Inspect shows the
ladder ("re-inscribed over white (beat 5)"). The sample's sol now goes
white, then green. An award and its recipient's own close no longer
conflict within a block, since both spend the recipient's inscription.

Rehearsed again on Bitcoin Core regtest from height 178: sixteen beats and
133 transactions; ord shows sol's sat carrying sol, its white belt and its
green belt, in order, each belt charmed reinscription and vindicated, and
the compare matches. The fixture is re-recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4upF5LVwmPDDqrhdUfnXh
2026-10-15 16:10:18 -07:00

120 lines
9.3 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { MetadataIndex } from '../src/adapters/metadata-index.js';
import { holderCoin } from '../src/adapters/handoff.js';
import { offsetOf } from '../src/adapters/sats.js';
import { emptyLab, setRule } from '../src/orgchain/store.js';
import { act, deposit, gasCost, lineageState, offer, sell, send, take } from '../src/orgchain/actions.js';
import { SAT, tankOf } from '../src/orgchain/money.js';
import { keyOf, scriptHex } from '../src/orgchain/keys.js';
import { askId } from '../src/orgchain/offers.js';
import { BELT_GAS, charmsOf } from '../src/orgchain/belts.js';
import { PROTOCOL } from '../src/orgchain/lineage.js';
import { InscriptionAdapter } from '../src/adapters/inscription.js';
import { Script, Tap, Tx } from '@cmdcode/tapscript';
import { FEE, envelope, tapleafOf } from '../src/adapters/inscription.js';
import { getVin, getVout } from '../src/upstream/primitives.js';
// Belts are soulbound (v1.14): the reducer binds a belt to the node it names, and the lab inscribes it on that node's own
// sat, so it goes wherever the node goes and never sells apart from it.
function twoHoldings() {
const lab = emptyLab();
setRule(lab, 'none');
const acme = act(lab, 'found', null, { name: 'acme' }), beta = act(lab, 'found', null, { name: 'beta' });
deposit(lab, acme, 10 * SAT);
deposit(lab, beta, 10 * SAT);
return { lab, acme, beta };
}
// Where an inscription sits: its outpoint and its sat's offset in it, as ord writes a satpoint.
const satpoint = (lab, id) => { const c = holderCoin(lab.chain, id); return `${c.txid}:${c.vout}:${offsetOf(c.ranges, c.inscriptions.find(i => i.id === id).sat)}`; };
const charms = (lab, id) => { const index = new MetadataIndex(lab.chain); return charmsOf(lab.chain, index, index.events.find(e => e.id === id)); };
const tank = (lab, id) => { const s = lineageState(lab); return tankOf(lab.chain, keyOf(lab.keys, s, s.nodes.get(id)).wallet); };
test("a belt rides its recipient's own sat: the issuer authorizes it and pays two fees, the recipient co-signs, ord marks it ♻️", () => {
const { lab, acme, beta } = twoHoldings(), ops = act(lab, 'birth', acme, { name: 'ops' });
send(lab, acme, ops, SAT);
const bot = act(lab, 'birth', ops, { name: 'bot', kind: 'agent' }), had = [tank(lab, ops), tank(lab, bot)];
const white = act(lab, 'award', ops, { to: bot, skill: 'launch', belt: 'white' });
assert.deepEqual([gasCost('award'), had[0] - tank(lab, ops), tank(lab, bot) - had[1]], [1000, 1000, 0]); // no postage: it rides bot's sat
assert.equal(satpoint(lab, white), satpoint(lab, bot));
assert.deepEqual([charms(lab, bot), charms(lab, white)], [['vindicated'], ['reinscription', 'vindicated']]);
// A newer belt in the skill replaces it on the record; the sat keeps both, in order: its history travels with it.
const green = act(lab, 'award', ops, { to: bot, skill: 'launch', belt: 'green' });
assert.deepEqual(lineageState(lab).nodes.get(bot).badges.map(b => [b.skill, b.belt]), [['launch', 'green']]);
assert.deepEqual(holderCoin(lab.chain, bot).inscriptions.map(i => i.id), [bot, white, green]);
// A registry in another holding awards it too, and bot co-signs again.
const registry = act(lab, 'birth', beta, { name: 'registry' });
send(lab, beta, registry, SAT);
const listen = act(lab, 'award', registry, { to: bot, skill: 'listen', belt: 'white' });
assert.deepEqual([satpoint(lab, listen), lineageState(lab).log.at(-1).roots.length], [satpoint(lab, bot), 2]);
// Bot acts as itself, and its belts stay on its sat: an act spends its coin and gives it back whole.
send(lab, ops, bot, SAT / 10);
act(lab, 'decide', bot, { title: 'Ship it' });
assert.deepEqual(holderCoin(lab.chain, bot).inscriptions.map(i => i.id), [bot, white, green, listen]);
});
test('a belt never sells apart from its agent: it is no lot, it can not be awarded into a tree changing hands, and it travels with its tree', () => {
const { lab, acme, beta } = twoHoldings(), ops = act(lab, 'birth', acme, { name: 'ops' });
send(lab, acme, ops, SAT);
const bot = act(lab, 'birth', ops, { name: 'bot', kind: 'agent' }), belt = act(lab, 'award', ops, { to: bot, skill: 'launch', belt: 'green' });
assert.throws(() => sell(lab, belt, SAT), /^Error: Pick an item, an org or a holding to sell\.$/);
assert.throws(() => offer(lab, belt, beta, SAT), /^Error: Pick an item, an org or a holding to make an offer on\.$/);
const a = sell(lab, ops, 2 * SAT);
assert.deepEqual(lab.chain.utxos.get(askId(a)).inscriptions.map(i => i.id), [ops, bot, belt]);
assert.throws(() => act(lab, 'award', acme, { to: bot, skill: 'post', belt: 'white' }), /^Error: ops is listed for sale at 2 tBTC\. Withdraw its ask first\.$/);
take(lab, askId(a), beta);
// After the split, the belt is still on bot's sat, which is at bot's new key, under beta.
const s = lineageState(lab), node = s.nodes.get(bot);
assert.equal(satpoint(lab, belt), satpoint(lab, bot));
assert.equal(scriptHex(holderCoin(lab.chain, bot).scriptPubKey), scriptHex(keyOf(lab.keys, s, node).wallet.script));
assert.deepEqual([node.root, node.badges.map(b => [b.skill, b.belt, s.nodes.get(b.by).name])], [beta, [['launch', 'green', 'ops']]]);
});
// An award's two transactions by hand, with one thing changed, to try the chain's own rule for a reveal on a held sat.
function forge(lab, by, to, { pointer, parent = by, vout } = {}) {
const s = lineageState(lab), wallet = id => keyOf(lab.keys, s, s.nodes.get(id)).wallet, inscriber = wallet(by);
const held = holderCoin(lab.chain, to), own = holderCoin(lab.chain, by), coin = c => getVin(c.txid, c.vout, c.value, c.scriptPubKey);
const pay = lab.chain.coins(inscriber.script).find(c => !c.inscriptions?.length && c.value >= BELT_GAS + 330);
const script = envelope(inscriber.pubkey, 'award launch white', parent, { op: 'award', to, skill: 'launch', belt: 'white' },
{ pointer: pointer ?? offsetOf(held.ranges, held.inscriptions.find(i => i.id === to).sat), metaprotocol: PROTOCOL });
const leaf = tapleafOf(script), [tapkey, control] = Tap.getPubKey(inscriber.pubkey, { target: leaf });
const commit = Tx.create({ vin: [coin(pay)], vout: [getVout(FEE, ['OP_1', tapkey]), getVout(pay.value - BELT_GAS, inscriber.script)] });
commit.vin[0].witness = [inscriber.sign(commit)];
const reveal = Tx.create({ vin: [coin(held), coin(own), getVin(Tx.util.getTxid(commit), 0, FEE, ['OP_1', tapkey])],
vout: vout?.(held, own, inscriber) ?? [getVout(held.value, held.scriptPubKey), getVout(own.value, own.scriptPubKey)] });
reveal.vin[2].witness = [inscriber.sign(reveal, leaf, 2), Script.encode(script, false).hex, control];
reveal.vin[1].witness = [inscriber.sign(reveal, undefined, 1)];
reveal.vin[0].witness = [wallet(to).sign(reveal, undefined, 0)];
return () => lab.chain.submitPackage([Tx.encode(commit).hex, Tx.encode(reveal).hex]);
}
test('the chain takes a three-input reveal only to inscribe on a held sat: its parent spent, both coins back unchanged, the pointer on an inscribed sat', () => {
const { lab, acme } = twoHoldings(), ops = act(lab, 'birth', acme, { name: 'ops' });
send(lab, acme, ops, SAT);
const bot = act(lab, 'birth', ops, { name: 'bot', kind: 'agent' }), coin = holderCoin(lab.chain, bot);
const at = offsetOf(coin.ranges, coin.inscriptions[0].sat), mempool = lab.chain.mempool.length;
assert.throws(forge(lab, ops, bot, { pointer: at + 1 }), /^Error: An inscription on a held sat must point at an inscribed sat\.$/);
assert.throws(forge(lab, ops, bot, { pointer: coin.value }), /^Error: An inscription on a held sat must point at an inscribed sat\.$/);
assert.throws(forge(lab, ops, bot, { parent: acme }), /^Error: An inscription on a held sat needs its parent spent, from the inscriber's key\.$/);
assert.throws(forge(lab, ops, bot, { vout: (held, own, me) => [getVout(held.value, me.script), getVout(own.value, own.scriptPubKey)] }),
/^Error: An inscription on a held sat returns both coins unchanged\.$/);
assert.throws(forge(lab, ops, bot, { vout: (held, own) => [getVout(held.value - 100, held.scriptPubKey), getVout(own.value + 100, own.scriptPubKey)] }),
/^Error: An inscription on a held sat returns both coins unchanged\.$/);
assert.equal(lab.chain.mempool.length, mempool); // each rejected package left nothing behind, its commit included
forge(lab, ops, bot)();
const belt = holderCoin(lab.chain, bot).inscriptions.at(-1).id;
assert.deepEqual([holderCoin(lab.chain, bot).inscriptions.length, satpoint(lab, belt)], [2, satpoint(lab, bot)]);
assert.deepEqual(lineageState(lab).nodes.get(bot).badges.map(b => [b.skill, b.belt]), [['launch', 'white']]);
});
test('a belt inscribed the old way, with postage on a sat of its own, does not count', () => {
const { lab, acme } = twoHoldings(), ops = act(lab, 'birth', acme, { name: 'ops' });
send(lab, acme, ops, SAT);
const bot = act(lab, 'birth', ops, { name: 'bot', kind: 'agent' }), s = lineageState(lab);
const id = new InscriptionAdapter(lab.chain, keyOf(lab.keys, s, s.nodes.get(ops)).wallet)
.inscribe('award launch white', ops, { op: 'award', to: bot, skill: 'launch', belt: 'white' }, { metaprotocol: PROTOCOL });
const after = lineageState(lab), entry = after.log.find(e => e.id === id);
assert.deepEqual([entry.ok, entry.why, after.nodes.get(bot).badges], [false, "a belt counts only on its recipient's own sat", []]);
assert.notEqual(satpoint(lab, id), satpoint(lab, bot));
});