Skip to content

Instantly share code, notes, and snippets.

@ottosch
Created September 2, 2023 20:50
Show Gist options
  • Save ottosch/9edc99a97267f4bc78b2ed57ba62813b to your computer and use it in GitHub Desktop.
Save ottosch/9edc99a97267f4bc78b2ed57ba62813b to your computer and use it in GitHub Desktop.
Identify BRC-20 sats in mempool
#! /usr/bin/env node
const rpc = require("node-bitcoin-rpc");
const fs = require("fs");
const inscriptionMark = Buffer.from("0063036f7264", "hex");
const keywords = ["brc-20", "op", "mint", "tick", "\"sats\"", "amt"];
const op_endif = 0x68;
async function run() {
init();
let txids = await getMempoolTxids();
console.log(`Tx count: ${txids.length}`);
let count = 0;
let inscriptionCount = 0;
let tokenCount = 0;
for (let txid of txids) {
count++;
if (count % 100 === 0) {
process.stdout.write(".");
}
let tx = await getRawTx(txid);
if (!tx) {
continue;
}
let obj = {
raw: Buffer.from(tx, "hex"),
pointer: 0,
data: null,
};
let inscription = getInscription(obj);
if (!inscription) {
continue;
}
inscriptionCount++;
if (isSatsToken(inscription)) {
tokenCount++;
}
}
console.log(`\ntx count:\t${count}`);
console.log(`inscriptions:\t${inscriptionCount}`);
console.log(`sats token:\t${tokenCount}`);
}
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], (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:
return null;
}
return readBytes(obj, length);
}
function getInscription(obj) {
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;
}
function isSatsToken(bytes) {
let text = bytes.toString();
for (let word of keywords) {
if (text.indexOf(word) === -1) {
return false;
}
}
return true;
}
run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment