Index delegates and sat locations
This commit is contained in:
1 parent
4190cedca9
commit
9f27897967
2 files changed
+52
-7
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { offsetOf } from './sats.js';
|
||||
import { Tx } from '@cmdcode/tapscript';
|
||||
import { readEnvelope } from './inscription.js';
|
||||
import { MetadataIndex } from './metadata-index.js';
|
||||
@@ -13,6 +14,15 @@ export class LocalOrdAPI {
|
||||
const chain = this.chain, index = this.index ?? new MetadataIndex(chain);
|
||||
const blockInfo = block => ({ height: block.height, hash: block.hash, previous: block.previous,
|
||||
time: block.time, txids: block.raws.map(raw => Tx.util.getTxid(raw)) });
|
||||
if (root === 'local' && kind === 'sat' && /^(0|[1-9][0-9]*)$/.test(key ?? '')) {
|
||||
const sat = Number(key), events = index.events.filter(e => e.sat === sat);
|
||||
const coin = [...index.utxos.values()].find(c => c.ranges.some(([a,b]) => sat >= a && sat < b));
|
||||
if (coin || events.length) return response({ sat, model: 'synthetic-local-fifo', inscriptions: events.map(e => e.id), location: coin ? { txid: coin.txid, vout: coin.vout, offset: offsetOf(coin.ranges, sat) } : null, ranges: coin?.ranges ?? [] });
|
||||
}
|
||||
if (root === 'local' && kind === 'utxo') {
|
||||
const coin = index.utxos.get(key);
|
||||
if (coin) return response({ ...coin, inscriptions: index.events.filter(e => e.location?.txid === coin.txid && e.location.vout === coin.vout).map(e => e.id) });
|
||||
}
|
||||
if (url.pathname === '/local/metadata-index') return response({ ...index.stats, events: index.events });
|
||||
if (url.pathname === '/local/protocols') return response(index.protocols.map(protocol => {
|
||||
const events = index.forProtocol(protocol);
|
||||
@@ -32,15 +42,16 @@ export class LocalOrdAPI {
|
||||
const event = index.events.find(e => e.id === id);
|
||||
if (event) {
|
||||
if (root === 'content') {
|
||||
const record = chain.transactions.get(id.slice(0, -2));
|
||||
const item = readEnvelope(Tx.decode(record.raw).vin[0].witness);
|
||||
return response(item.bytes, item.contentType);
|
||||
return resolveContent(index, id, target => {
|
||||
const record = chain.transactions.get(target.replace(/i[0-9]+$/, ''));
|
||||
return readEnvelope(Tx.decode(record.raw).vin.find(i => i.witness?.length === 3).witness);
|
||||
});
|
||||
}
|
||||
if (kind === 'inscription') return response(event);
|
||||
// ord's metadata endpoint returns CBOR hex. Expose decoded JSON in inscription/index responses.
|
||||
if (kind === 'metadata') {
|
||||
const record = chain.transactions.get(id.slice(0, -2));
|
||||
const item = readEnvelope(Tx.decode(record.raw).vin[0].witness);
|
||||
const item = readEnvelope(Tx.decode(record.raw).vin.find(i => i.witness?.length === 3).witness);
|
||||
return response(item.metadataBytes ? Array.from(item.metadataBytes, b => b.toString(16).padStart(2, '0')).join('') : null);
|
||||
}
|
||||
if (kind === 'parents') return response({ ids: event.parent ? [event.parent] : [] });
|
||||
@@ -49,3 +60,17 @@ export class LocalOrdAPI {
|
||||
return response({ error: 'Not found in this local lab.' }, undefined, 404);
|
||||
}
|
||||
}
|
||||
|
||||
// Identity is the requested id even when the bytes/type come from another inscription.
|
||||
export function resolveContent(index, id, read) {
|
||||
const visited = new Set(); let target = id;
|
||||
while (true) {
|
||||
if (visited.has(target)) return { status: 508, contentType: 'application/json', body: { error: 'Delegate loop detected.' } };
|
||||
visited.add(target);
|
||||
const event = index.events.find(e => e.id === target);
|
||||
if (!event) return { status: 404, contentType: 'application/json', body: { error: 'Delegate target not found.' } };
|
||||
if (event.delegate) { target = event.delegate; continue; }
|
||||
const item = read(target);
|
||||
return { status: 200, contentType: item.contentType, body: item.bytes, inscriptionId: id, contentId: target };
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { outputRanges, satAt, offsetOf } from './sats.js';
|
||||
import { Tx } from '@cmdcode/tapscript';
|
||||
import { readEnvelope } from './inscription.js';
|
||||
|
||||
@@ -6,12 +7,22 @@ import { readEnvelope } from './inscription.js';
|
||||
export class MetadataIndex {
|
||||
constructor(chain) {
|
||||
this.events = [];
|
||||
this.utxos = new Map();
|
||||
const visit = (raw, height, block) => {
|
||||
const tx = Tx.decode(raw);
|
||||
if (tx.vin[0]?.witness?.length !== 3) return;
|
||||
const item = readEnvelope(tx.vin[0].witness);
|
||||
const txid = Tx.util.getTxid(tx), coinbase = tx.vin[0].txid === '0'.repeat(64);
|
||||
const coins = coinbase ? [] : tx.vin.map(i => this.utxos.get(`${i.txid}:${i.vout}`));
|
||||
const ranges = outputRanges(tx, coins, height, coinbase);
|
||||
tx.vin.forEach(i => this.utxos.delete(`${i.txid}:${i.vout}`));
|
||||
tx.vout.forEach((out, vout) => this.utxos.set(`${txid}:${vout}`, { txid, vout, value: Number(out.value), ranges: ranges[vout] }));
|
||||
const witness = tx.vin.find(i => i.witness?.length === 3)?.witness;
|
||||
if (!witness) return;
|
||||
const item = readEnvelope(witness);
|
||||
const pointer = BigInt(item.pointer ?? 0), total = tx.vout.reduce((n, o) => n + Number(o.value), 0);
|
||||
const sat = satAt(ranges.flat(), pointer < BigInt(total) ? Number(pointer) : 0);
|
||||
const metadata = item.metadata;
|
||||
this.events.push({ id: `${Tx.util.getTxid(tx)}i0`, contentType: item.contentType,
|
||||
this.events.push({ id: `${Tx.util.getTxid(tx)}i0`, contentType: item.contentType, delegate: item.delegate, pointer: item.pointer, sat,
|
||||
reinscriptionPosition: this.events.filter(e => e.sat === sat).length,
|
||||
status: height === null ? 'pending' : 'confirmed', height, block,
|
||||
metadata: metadata ?? null, hasMetadata: metadata !== undefined, parent: item.parent,
|
||||
protocol: metadata && !Array.isArray(metadata) && typeof metadata === 'object' &&
|
||||
@@ -19,6 +30,15 @@ export class MetadataIndex {
|
||||
};
|
||||
for (const block of chain.blocks) for (const raw of block.raws) visit(raw, block.height, block.hash);
|
||||
for (const raw of chain.mempool) visit(raw, null, null);
|
||||
for (const event of this.events) {
|
||||
event.location = null;
|
||||
for (const coin of this.utxos.values()) {
|
||||
const offset = offsetOf(coin.ranges, event.sat);
|
||||
if (offset !== null) { event.location = { txid: coin.txid, vout: coin.vout, offset }; break; }
|
||||
}
|
||||
event.delegatedBy = this.events.filter(e => e.delegate === event.id).map(e => e.id);
|
||||
event.inscriptions = this.events.filter(e => e.sat === event.sat).map(e => e.id);
|
||||
}
|
||||
}
|
||||
forProtocol(name) { return this.events.filter(e => e.protocol === name); }
|
||||
get protocols() { return [...new Set(this.events.filter(e => e.protocol !== null).map(e => e.protocol))].sort(); }
|
||||
|
||||
Reference in new issue
Block a user