Skip to content

Instantly share code, notes, and snippets.

@ottosch
Last active December 17, 2023 15:40
Show Gist options
  • Save ottosch/dfe6bcd79e5b60c37b3d187b7c6ef3f8 to your computer and use it in GitHub Desktop.
Save ottosch/dfe6bcd79e5b60c37b3d187b7c6ef3f8 to your computer and use it in GitHub Desktop.
Mempool BRC-20 status
#! /usr/bin/env node
// To run, start a project and install the dependency with:
// `npm init` and `npm i node-bitcoin-rpc`.
const rpc = require("node-bitcoin-rpc");
const fs = require("fs");
const opMap = new Map();
const tickMap = new Map();
let txCount;
let inscriptionCount = 0;
let brc20Count = 0;
let runeCount = 0;
async function run() {
init();
let txids = await getMempoolTxids();
txCount = txids.length;
console.log(`Tx count: ${txCount}`);
let count = 0;
for (let txid of txids) {
count++;
if (count % 100 === 0) {
process.stdout.write(".");
}
let tx = await getRawTx(txid);
processTransaction(tx);
}
printResults();
}
async function getMempoolTxids() {
return new Promise(resolve => {
rpc.call("getrawmempool", [], (e, r) => {
if (e) {
console.error(e);
resolve(null);
}
resolve(r.result);
});
});
}
async function getRawTx(txid) {
return new Promise(resolve => {
rpc.call("getrawtransaction", [txid, 1], (e, r) => {
if (e) {
console.error(e);
resolve(null);
}
resolve(r.result);
});
});
}
function init() {
let cookie = fs.readFileSync(`${process.env.HOME}/.bitcoin/.cookie`).toString();
let credentials = cookie.split(":");
rpc.init("127.0.0.1", 8332, credentials[0], credentials[1]);
rpc.setTimeout(Number(process.env.TIMEOUT) || 30000);
}
function readBytes(obj, n = 1) {
let value = obj.raw.subarray(obj.pointer, obj.pointer + n);
obj.pointer += n;
return value;
}
function readPushdata(obj, opcode) {
if (opcode >= 0x01 && opcode <= 0x4b) {
return readBytes(obj, opcode);
}
let length;
switch (opcode) {
case 0x4c:
length = readBytes(obj, 1).readUint8();
break;
case 0x4d:
length = readBytes(obj, 2).readUint16LE();
break;
case 0x4e:
length = readBytes(obj, 4).readUint32LE();
break;
default:
console.debug(obj.raw.hexSlice());
console.debug(obj.pointer);
console.debug(obj.data.hexSlice());
console.debug(opcode);
return null;
}
return readBytes(obj, length);
}
function processTransaction(tx) {
if (!tx) {
return;
}
let inscription = getInscription(tx["hex"]);
if (!inscription) {
let outputs = tx["vout"];
for (let out of outputs) {
if (out["scriptPubKey"]["type"] === "nulldata") {
if (/^6a0152/.test(out["scriptPubKey"]["hex"])) {
runeCount++;
}
}
}
return;
}
inscriptionCount++;
let text = inscription.toString();
if (text.indexOf("brc-20") === -1) {
return;
}
let json;
try {
json = JSON.parse(text);
} catch (err) {
return;
}
brc20Count++;
incr(opMap, json["op"]);
incr(tickMap, json["tick"]);
}
function incr(map, key) {
key = key.toLowerCase();
let val = map.get(key) || 0;
map.set(key, val + 1);
}
function printResults() {
const maxArraySize = 40;
console.log();
console.log();
console.log();
console.log(`${"Transactions:".padEnd(15)}\t${txCount}`);
console.log(`${"Inscriptions:".padEnd(15)}\t${inscriptionCount}\t${((inscriptionCount / txCount) * 100).toFixed(2)}%\t(includes BRC-20)`);
console.log(`${"BRC-20:".padEnd(15)}\t${brc20Count}\t${((brc20Count / txCount) * 100).toFixed(2)}%`);
console.log(`${"Runes:".padEnd(15)}\t${runeCount}\t${((runeCount / txCount) * 100).toFixed(2)}%`);
console.log();
let opEntries = Array.from(opMap.entries());
opEntries.sort((a, b) => b[1] - a[1]);
console.log("BRC-20 operations:");
console.log(opEntries);
let tickEntries = Array.from(tickMap.entries());
tickEntries.sort((a, b) => b[1] - a[1]);
if (tickEntries.length > maxArraySize) {
tickEntries = tickEntries.slice(0, maxArraySize);
}
console.log();
console.log();
console.log("BRC-20 tokens:")
console.log(tickEntries);
console.log();
console.log();
}
function getInscription(tx) {
const inscriptionMark = Buffer.from("0063036f7264", "hex");
const op_endif = 0x68;
let obj = {
raw: Buffer.from(tx, "hex"),
pointer: 0,
data: null,
};
let markIndex = obj.raw.indexOf(inscriptionMark);
if (markIndex === -1) {
return null;
}
obj.pointer = markIndex + inscriptionMark.length;
let b = readBytes(obj, 2);
if (b.hexSlice() != "0101") {
return null;
}
let contentTypeLength = readBytes(obj).readUInt8();
readBytes(obj, contentTypeLength);
if (readBytes(obj)[0] != 0x00) {
return null;
}
obj.data = Buffer.alloc(0);
let opcode = readBytes(obj)[0];
while (opcode != op_endif) {
let chunk = readPushdata(obj, opcode);
obj.data = Buffer.concat([obj.data, chunk]);
opcode = readBytes(obj)[0]
}
return obj.data;
}
run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment