Add native metaprotocol field
This commit is contained in:
1 parent
5d1a1ae866
commit
bcc9f0f1cd
7 files changed
+52
-19
No files matched your search
@@ -83,7 +83,7 @@ export class LocalChain {
|
||||
}
|
||||
config = { pubkey: parsed.pubkey };
|
||||
inscription = { id: `${txid}i0`, txid, commitTxid: input.txid, text: parsed.text,
|
||||
bytes: Array.from(parsed.bytes), contentType: parsed.contentType, parent: parsed.parent, delegate: parsed.delegate, pointer: parsed.pointer, metadata: parsed.metadata, height, value: POSTAGE };
|
||||
bytes: Array.from(parsed.bytes), contentType: parsed.contentType, parent: parsed.parent, delegate: parsed.delegate, pointer: parsed.pointer, metaprotocol: parsed.metaprotocol, metadata: parsed.metadata, height, value: POSTAGE };
|
||||
}
|
||||
tx.vin.forEach((input, i) => {
|
||||
let valid = false;
|
||||
|
||||
@@ -40,7 +40,9 @@ function parseParent(value) {
|
||||
// Local extension: MIME bytes, 520-byte pushes, optional ord parent tag 3.
|
||||
export function envelope(pubkey, value, parent = null, metadata = undefined, options = {}) {
|
||||
const { contentType, bytes } = body(value);
|
||||
const { delegate = null, pointer } = options;
|
||||
const { delegate = null, pointer, metaprotocol = null } = options;
|
||||
const metaprotocolBytes = metaprotocol === null || metaprotocol === undefined || metaprotocol === "" ? null : new TextEncoder().encode(String(metaprotocol));
|
||||
if (metaprotocolBytes && metaprotocolBytes.length > 520) throw new Error("Metaprotocol identifier must be 520 bytes or less.");
|
||||
const metadataBytes = metadata === undefined ? undefined : encodeMetadata(metadata);
|
||||
const chunks = [];
|
||||
for (let i = 0; i < bytes.length; i += 520) chunks.push(hex(bytes.slice(i, i + 520)));
|
||||
@@ -57,7 +59,8 @@ export function envelope(pubkey, value, parent = null, metadata = undefined, opt
|
||||
(pointer === undefined ? '' : push('02') + push(encodePointer(pointer))) +
|
||||
(parent ? push('03') + push(serializeParent(parent)) : '') +
|
||||
(metadataBytes === undefined ? '' : Array.from({ length: Math.ceil(metadataBytes.length / 520) }, (_, i) =>
|
||||
push('05') + push(hex(metadataBytes.slice(i * 520, (i + 1) * 520)))).join('')) + '00' + chunks.map(push).join('') + '68';
|
||||
push('05') + push(hex(metadataBytes.slice(i * 520, (i + 1) * 520)))).join('')) +
|
||||
(metaprotocolBytes ? push('07') + push(hex(metaprotocolBytes)) : '') + '00' + chunks.map(push).join('') + '68';
|
||||
}
|
||||
export function readEnvelope(witness) {
|
||||
if (!Array.isArray(witness) || witness.length !== 3) throw new Error('Unsupported script-path witness.');
|
||||
@@ -72,19 +75,25 @@ export function readEnvelope(witness) {
|
||||
if (typeof chunk !== 'string' || !/^(?:[0-9a-f]{2}){1,520}$/.test(chunk)) throw new Error('Invalid metadata push.');
|
||||
metadataChunks.push(chunk); cursor += 2;
|
||||
}
|
||||
if (parts[cursor] !== 'OP_0') throw new Error('Unsupported ord tag or missing body separator.');
|
||||
const metadata = metadataChunks.length ? decodeMetadata(unhex(metadataChunks.join(''))) : undefined;
|
||||
let metaprotocol = null;
|
||||
if (parts[cursor] === '07') {
|
||||
const raw = parts[cursor + 1];
|
||||
if (typeof raw !== 'string' || !/^(?:[0-9a-f]{2}){1,520}$/.test(raw)) throw new Error('Invalid metaprotocol field.');
|
||||
metaprotocol = new TextDecoder().decode(unhex(raw)); cursor += 2;
|
||||
}
|
||||
if (parts[cursor] !== 'OP_0') throw new Error('Unsupported ord tag or missing body separator.');
|
||||
const payload = parts.slice(cursor + 1, -1);
|
||||
if (!payload.length || payload.some(p => typeof p !== 'string' || !/^(?:[0-9a-f]{2})+$/.test(p))) throw new Error('Invalid body envelope.');
|
||||
const contentType = new TextDecoder().decode(unhex(parts[6]));
|
||||
const value = body({ contentType, bytes: unhex(payload.join('')) });
|
||||
const expected = envelope(parts[0], value, parent, metadata, { delegate, pointer });
|
||||
const expected = envelope(parts[0], value, parent, metadata, { delegate, pointer, metaprotocol });
|
||||
// Read original v0 text snapshots whose content-type tag used OP_1.
|
||||
const legacy = metadata === undefined && !parent && contentType === MIME && value.bytes.length <= 280 &&
|
||||
Script.encode([parts[0], 'OP_CHECKSIG', 'OP_0', 'OP_IF', textHex('ord'), '01', textHex(MIME), 'OP_0', hex(value.bytes), 'OP_ENDIF'], false).hex;
|
||||
if (expected !== witness[1] && legacy !== witness[1]) throw new Error('Unsupported ord envelope.');
|
||||
if (!/^[0-9a-f]{64}$/.test(parts[0])) throw new Error('Invalid inscription signing key.');
|
||||
return { ...value, parent, delegate, pointer, metadata, metadataBytes: metadataChunks.length ? unhex(metadataChunks.join('')) : undefined, text: new TextDecoder().decode(value.bytes), pubkey: parts[0] };
|
||||
return { ...value, parent, delegate, pointer, metaprotocol, metadata, metadataBytes: metadataChunks.length ? unhex(metadataChunks.join('')) : undefined, text: new TextDecoder().decode(value.bytes), pubkey: parts[0] };
|
||||
}
|
||||
export class InscriptionAdapter {
|
||||
constructor(chain, wallet) { this.chain = chain; this.wallet = wallet; }
|
||||
@@ -93,6 +102,7 @@ export class InscriptionAdapter {
|
||||
if (metadata !== undefined) encodeMetadata(metadata);
|
||||
if (options.delegate) serializeParent(options.delegate);
|
||||
if (options.pointer !== undefined) encodePointer(options.pointer);
|
||||
if (options.metaprotocol !== undefined && options.metaprotocol !== null && options.metaprotocol !== '' && new TextEncoder().encode(String(options.metaprotocol)).length > 520) throw new Error('Metaprotocol identifier must be 520 bytes or less.');
|
||||
if (parentId && options.reinscribe) throw new Error('Choose parent or reinscription, not both.');
|
||||
const coins = this.chain.coins(this.wallet.script);
|
||||
const parent = parentId ? coins.find(c => c.inscriptions?.some(i => i.id === parentId)) : null;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Tx } from '@cmdcode/tapscript';
|
||||
import { readEnvelope } from './inscription.js';
|
||||
|
||||
// Rebuild solely from serialized transactions, never collectible/UI sidecars.
|
||||
// Discovery is a convention, not native ord metaprotocol tag 7 support.
|
||||
// Native ord metaprotocol tag 7 is authoritative; metadata.protocol remains a compatibility fallback.
|
||||
export class MetadataIndex {
|
||||
constructor(chain) {
|
||||
this.events = [];
|
||||
@@ -21,12 +21,12 @@ export class MetadataIndex {
|
||||
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, delegate: item.delegate, pointer: item.pointer, sat,
|
||||
this.events.push({ id: `${Tx.util.getTxid(tx)}i0`, contentType: item.contentType, delegate: item.delegate, pointer: item.pointer, metaprotocol: item.metaprotocol, 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' &&
|
||||
Object.hasOwn(metadata, 'protocol') && typeof metadata.protocol === 'string' ? metadata.protocol : null });
|
||||
protocol: item.metaprotocol ?? (metadata && !Array.isArray(metadata) && typeof metadata === 'object' &&
|
||||
Object.hasOwn(metadata, 'protocol') && typeof metadata.protocol === 'string' ? metadata.protocol : null) });
|
||||
};
|
||||
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);
|
||||
|
||||
+6
-5
@@ -58,7 +58,7 @@ const composerBody = () => ({ contentType: $('mime-type').value, bytes: fileByte
|
||||
function updateCount() { $('byte-count').textContent = `${composerBody().bytes.length} / ${MAX_BYTES} bytes`; }
|
||||
function resetComposer() {
|
||||
fileRevision++; fileBytes = null; $('body-file').value = ''; $('payload').value = '';
|
||||
$('delegate').value = ''; $('pointer').value = ''; $('reinscribe').value = ''; $('metadata').value = ''; updateMetadataCount(); $('parent').value = ''; closeReview(); updateCount();
|
||||
$('delegate').value = ''; $('pointer').value = ''; $('reinscribe').value = ''; $('metaprotocol').value = ''; $('metadata').value = ''; updateMetadataCount(); $('parent').value = ''; closeReview(); updateCount();
|
||||
}
|
||||
function lineage(item, chain) {
|
||||
const p = el('p', undefined, 'tx-links');
|
||||
@@ -86,6 +86,7 @@ function updateMetadataCount() {
|
||||
function metadataView(item) {
|
||||
const node = el('div', undefined, 'metadata-document');
|
||||
if (item.metadata !== undefined) node.append(el('strong', 'Decoded metadata'), el('pre', JSON.stringify(item.metadata, null, 2)));
|
||||
if (item.metaprotocol) node.append(el('strong', `Metaprotocol: ${item.metaprotocol}`));
|
||||
return node;
|
||||
}
|
||||
function renderIndexes(chain) {
|
||||
@@ -172,7 +173,7 @@ function render() {
|
||||
$('miner-interval').value = String(lab?.miner?.interval ?? 0);
|
||||
$('miner-status').textContent = minerStatus(lab);
|
||||
$('payload').disabled = disabled || !wallet || !!fileBytes || fileLoading || $('mime-type').value.startsWith('image/') && $('mime-type').value !== 'image/svg+xml';
|
||||
for (const id of ['mime-type', 'body-file', 'parent', 'metadata', 'delegate', 'pointer', 'reinscribe', 'recursive-demo']) $(id).disabled = disabled || !wallet || fileLoading;
|
||||
for (const id of ['mime-type', 'body-file', 'parent', 'metadata', 'metaprotocol', 'delegate', 'pointer', 'reinscribe', 'recursive-demo']) $(id).disabled = disabled || !wallet || fileLoading;
|
||||
$('review').disabled = disabled || !wallet || !chain?.coins(wallet.script).some(c => !c.inscription) || fileLoading || !composerBody().bytes.length;
|
||||
$('sign').disabled = disabled;
|
||||
$('export-wallet').disabled = disabled; $('export-lab').disabled = busy;
|
||||
@@ -245,7 +246,7 @@ $('metadata').oninput = () => { closeReview(); updateMetadataCount(); };
|
||||
MIME_TYPES.forEach(type => $('mime-type').append(new Option(type, type)));
|
||||
$('payload').oninput = () => { closeReview(); updateCount(); render(); };
|
||||
$('parent').onchange = $('reinscribe').onchange = closeReview;
|
||||
$('delegate').oninput = $('pointer').oninput = closeReview;
|
||||
$('delegate').oninput = $('pointer').oninput = $('metaprotocol').oninput = closeReview;
|
||||
$('recursive-demo').onclick = () => {
|
||||
fileRevision++; fileBytes = null; $('body-file').value = ''; $('mime-type').value = 'text/html';
|
||||
$('payload').value = RECURSIVE_DEMO; closeReview(); updateCount(); render();
|
||||
@@ -271,14 +272,14 @@ $('body-file').onchange = async event => {
|
||||
$('review').onclick = () => {
|
||||
try {
|
||||
const payload = composerBody(), parent = $('parent').value || null, metadata = parseMetadataJSON($('metadata').value);
|
||||
const options = { delegate: $('delegate').value.trim() || null, reinscribe: $('reinscribe').value || null, ...($('pointer').value.trim() ? { pointer: $('pointer').value.trim() } : {}) };
|
||||
const options = { delegate: $('delegate').value.trim() || null, metaprotocol: $('metaprotocol').value.trim() || null, reinscribe: $('reinscribe').value || null, ...($('pointer').value.trim() ? { pointer: $('pointer').value.trim() } : {}) };
|
||||
new InscriptionAdapter(lab.chain, lab.wallet).prepare(payload, parent, metadata, options);
|
||||
closeReview();
|
||||
reviewed = { payload, parent, metadata, options, address: lab.wallet.address };
|
||||
$('review-text').textContent = ['text/plain', 'application/json'].includes(payload.contentType) ? new TextDecoder().decode(payload.bytes) : `${payload.contentType} · ${payload.bytes.length} bytes`;
|
||||
if (!['text/plain', 'application/json'].includes(payload.contentType)) $('review-preview').append(preview(payload));
|
||||
$('review-parent').textContent = parent ? `Parent: ${parent}. Its output will be spent and its full value returned to this wallet. This relationship is immutable.` : 'No parent.';
|
||||
$('review-parent').textContent += ` Delegate: ${options.delegate ?? 'none'}. Reinscribe: ${options.reinscribe ?? 'none'}. Pointer: ${options.pointer ?? 'automatic'}.`;
|
||||
$('review-parent').textContent += ` Delegate: ${options.delegate ?? 'none'}. Reinscribe: ${options.reinscribe ?? 'none'}. Pointer: ${options.pointer ?? 'automatic'}. Metaprotocol: ${options.metaprotocol ?? 'none'}.`;
|
||||
$('review-metadata').textContent = metadata === undefined ? 'No metadata.' : `Metadata (CBOR, tag 5): ${JSON.stringify(metadata, null, 2)}`;
|
||||
$('review-panel').hidden = false;
|
||||
$('sign').focus(); message('Review the payload and fake-sat costs. Nothing has been signed yet.');
|
||||
|
||||
Reference in new issue
Block a user