Last active
December 17, 2023 15:40
-
-
Save ottosch/dfe6bcd79e5b60c37b3d187b7c6ef3f8 to your computer and use it in GitHub Desktop.
Mempool BRC-20 status
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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