An org now changes hands with its tree, the derived orgs and agents below it and the items they hold, and a holding changes hands whole, sold by its owner, whom the lab wallet plays (the price goes there). Sales stay cash-free: the money in the tree, every gas tank included, goes to the seller first, and the items go with the tree. An ask signs one input, so listing a tree bundles every inscription in it into one output at the lot's key; an offer spends every output the tree sits in and merges them. After the join and the adopt, which now move a holding too (it becomes its buyer's next org), the buyer splits the output back, one inscription to an output at its new key, each item with the node that held it, by the layout the deal recorded. A listed or sold tree is frozen whole: nothing in it acts, sends, is funded or sells alone, nothing above it graduates or closes it, and a listed holding mines nothing. Graduation roots are kept by graduation event, since a node can graduate, move and graduate again. Transactions take up to 64 inputs and outputs. Rehearsed on Bitcoin Core regtest from height 144: sixteen beats and 131 transactions, time with its six below it bought on an offer, and acme sold whole on an ask, eleven inscriptions in one output, then split again; ord puts every inscription in the same output as the lab. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H4upF5LVwmPDDqrhdUfnXh
35 lines
2.9 KiB
JavaScript
35 lines
2.9 KiB
JavaScript
#!/usr/bin/env node
|
|
// lineage-index: read lineage/v0 events from an ord server and print the trees they imply, with the same reducer the
|
|
// lab uses. --compare <lab.json> checks that ord and a lab backup tell the same story (tools/regtest/README.md).
|
|
// node bin/lineage-index.mjs [--ord http://127.0.0.1:8088] [--from <height>] [--compare <lab.json>]
|
|
import { readFileSync } from 'node:fs';
|
|
import { parseArgs } from 'node:util';
|
|
import { decodeLab } from '../src/orgchain/store.js';
|
|
import { census, rows, summary } from '../src/orgchain/lineage.js';
|
|
import { ordClient, ordEvents, ordState } from '../src/indexer/ord.js';
|
|
import { compareWithLab } from '../src/indexer/compare.js';
|
|
|
|
const { values } = parseArgs({ options: { ord: { type: 'string', default: 'http://127.0.0.1:8088' }, from: { type: 'string', default: '0' },
|
|
compare: { type: 'string' } } });
|
|
const events = await ordEvents(ordClient(values.ord), { from: Number(values.from) });
|
|
const state = ordState(events), plural = (n, w) => `${n} ${w}${n === 1 ? '' : 's'}`;
|
|
for (const root of state.roots) {
|
|
const c = census(state, root.id);
|
|
console.log(`\n${root.name} · ${plural(c.orgs, 'org')}, ${plural(c.agents, 'agent')}, ${plural(c.belts, 'belt')}, ${plural(c.decisions, 'decision')}`);
|
|
console.log(rows(state, root.id).map(r => (r.type === 'group' ? `${r.guide}${r.label}`
|
|
: `${r.guide}${r.path} ${r.node.name}${r.moved ? ` · moved to ${state.nodes.get(r.node.parent).name}` : r.away ? ' · graduated' : ''}`)).join('\n'));
|
|
}
|
|
const moves = [...state.nodes.values()].flatMap(n => n.moves.map(m => `${n.name} ${m.from ? `from ${state.nodes.get(m.from).name}` : 'whole'} to ${state.nodes.get(m.to).name}`));
|
|
if (moves.length) console.log(`\n${plural(moves.length, 'tree')} changed hands: ${moves.join('; ')}`);
|
|
const rejected = state.log.filter(e => !e.ok);
|
|
console.log(`\n${plural(events.length, 'lineage/v0 event')} on ord, ${plural(state.items.size, 'item')}${rejected.length ? `, ${rejected.length} rejected:` : ''}`);
|
|
for (const e of rejected) console.log(` ${summary(e, state)} (${e.id})`);
|
|
const rings = [...state.nodes.values()].filter(n => n.keyring && !n.keyring.retired);
|
|
if (rings.length) console.log(`${plural(rings.length, 'keyring')} bind ${plural(state.bound.size, 'key')}: ${rings.map(n => n.name).join(', ')}`);
|
|
if (values.compare) {
|
|
const { problems, ...n } = compareWithLab(decodeLab(readFileSync(values.compare, 'utf8')), events);
|
|
console.log(problems.length ? `\nord and the lab differ:\n${problems.map(p => ` ${p}`).join('\n')}`
|
|
: `\nSame story as ${values.compare}: ${plural(n.events, "event")}, ${plural(n.nodes, "node")} and ${plural(n.items, "item")}, each event in the same beat and every inscription in the same output.${n.reordered ? ` The miner ordered ${n.reordered} independent events differently within their blocks, which changes nothing.` : ""}`);
|
|
process.exitCode = problems.length ? 1 : 0;
|
|
}
|