Skip to content

Instantly share code, notes, and snippets.

@owenkellogg
Created June 20, 2023 15:42
Show Gist options
  • Save owenkellogg/37acdb2d744293af8f106fa3e203ccd7 to your computer and use it in GitHub Desktop.
Save owenkellogg/37acdb2d744293af8f106fa3e203ccd7 to your computer and use it in GitHub Desktop.
parse_ordinals.ts
import * as fs from 'fs';
import * as https from 'https';
import * as base64 from 'base64-js';
import { ArgumentParser } from 'argparse';
interface CLIArgs {
dataUri: boolean;
output?: string;
txId: string;
}
interface TransactionResponse {
vin: Array<{ witness: string[] }>;
}
let args: CLIArgs;
let raw: Buffer;
let pointer: number;
function getCLIArgs(): CLIArgs {
const ap = new ArgumentParser({
description: 'Parse and output the ordinal inscription inside transaction',
});
ap.addArgument('txId', { help: 'transaction ID to retrieve from the API' });
ap.addArgument('-du', '--data-uri', {
action: 'storeTrue',
help: 'print inscription as data-uri instead of writing to a file',
});
ap.addArgument('-o', '--output', { help: 'write inscription to OUTPUT file' });
return ap.parseArgs();
}
function getRawData(txId: string): Buffer {
const url = `https://mempool.space/api/tx/${txId}`;
return new Promise<Buffer>((resolve, reject) => {
https.get(url, (response) => {
let data = '';
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
if (response.statusCode !== 200) {
console.error(`Failed to retrieve transaction data for ${txId} from the API`);
process.exit(1);
}
const txResponse: TransactionResponse = JSON.parse(data);
const txWitness = txResponse.vin[0].witness;
const txWitnessString = txWitness.join('');
resolve(Buffer.from(txWitnessString, 'hex'));
});
}).on('error', (error) => {
console.error(`Failed to retrieve transaction data for ${txId} from the API`);
process.exit(1);
});
});
}
function readBytes(n: number = 1): Buffer {
const value = raw.slice(pointer, pointer + n);
pointer += n;
return value;
}
function getInitialPosition(): number {
const inscriptionMark = Buffer.from('0063036f7264', 'hex');
try {
return raw.indexOf(inscriptionMark) + inscriptionMark.length;
} catch (error) {
console.error('No ordinal inscription found in transaction');
process.exit(1);
}
}
function readContentType(): string {
const OP_1 = Buffer.from('51', 'hex');
const byte = readBytes();
if (!byte.equals(OP_1)) {
if (!byte.equals(Buffer.from('01', 'hex'))) {
console.assert(readBytes().equals(Buffer.from('01', 'hex')));
}
}
const size = readBytes().readUIntBE(0, byte.length);
const contentType = readBytes(size).toString('utf8');
return contentType;
}
function readPushData(opcode: Buffer): Buffer {
const intOpcode = opcode.readUIntBE(0, opcode.length);
if (intOpcode >= 0x01 && intOpcode <= 0x4b) {
return readBytes(intOpcode);
}
let numBytes = 0;
switch (intOpcode) {
case 0x4c:
numBytes = 1;
break;
case 0x4d:
numBytes = 2;
break;
case 0x4e:
numBytes = 4;
break;
default:
console.error(`Invalid push opcode ${opcode.toString('hex')} at position ${pointer}`);
process.exit(1);
}
const size = readBytes(numBytes).readUIntLE(0, numBytes);
return readBytes(size);
}
function writeDataUri(data: Buffer, contentType: string): void {
const dataBase64 = base64.fromByteArray(data).replace(/\n/g, '');
console.log(`data:${contentType};base64,${dataBase64}`);
}
function writeFile(data: Buffer, ext: string): void {
let filename = args.output;
if (filename === undefined) {
filename = 'out';
}
let baseFilename = filename;
let i = 1;
while (fs.existsSync(filename)) {
i++;
filename = `${baseFilename}${i}`;
}
console.log(`Writing contents to file "${filename}"`);
fs.writeFileSync(`${filename}.${ext}`, data);
}
function getFileExtension(contentType: string): string {
const switcher: Record<string, string> = {
'text/plain;charset=utf-8': 'txt',
'text/html;charset=utf-8': 'html',
};
const fileExtension = switcher[contentType] || contentType.split('/')[1];
return fileExtension;
}
async function main() {
args = getCLIArgs();
raw = await getRawData(args.txId);
pointer = getInitialPosition();
const contentType = readContentType();
console.log(`Content type: ${contentType}`);
const fileExtension = getFileExtension(contentType);
console.assert(readBytes().equals(Buffer.from('00', 'hex')));
const data: Buffer[] = [];
const OP_ENDIF = Buffer.from('68', 'hex');
let opcode = readBytes();
while (!opcode.equals(OP_ENDIF)) {
const chunk = readPushData(opcode);
data.push(chunk);
opcode = readBytes();
}
const finalData = Buffer.concat(data);
console.log(`Total size: ${finalData.length} bytes`);
if (args.dataUri) {
writeDataUri(finalData, contentType);
} else {
writeFile(finalData, fileExtension);
}
console.log('\nDone');
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment