UNPKG

charms-js

Version:

TypeScript SDK for decoding Bitcoin transactions containing Charms data

244 lines (243 loc) 9.79 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.extractSpellData = extractSpellData; const bitcoin = __importStar(require("bitcoinjs-lib")); // Extracts spell data from Bitcoin transaction function extractSpellData(txHex) { try { const tx = bitcoin.Transaction.fromHex(txHex); // First, try to extract from witness data in all inputs const witnessSpell = extractSpellFromWitnessInputs(tx); if (witnessSpell) { return witnessSpell; } // If no witness data found, try OP_RETURN outputs const opReturnSpell = extractSpellFromOpReturnOutputs(tx); if (opReturnSpell) { return opReturnSpell; } console.log('No spell data found in transaction'); return null; } catch (error) { console.log(`Error extracting spell data: ${error.message}`); return null; } } // Extract spell data from witness inputs (current method) function extractSpellFromWitnessInputs(tx) { if (!tx.ins || tx.ins.length === 0) { return null; } // Try all inputs, not just the second one for (let i = 0; i < tx.ins.length; i++) { const input = tx.ins[i]; if (input.witness && input.witness.length >= 2) { // Try taproot leaf script (second witness element) const witnessScript = input.witness[1]; const spellData = extractSpellFromWitnessScript(witnessScript); if (spellData) { console.log(`Found spell data in input ${i} witness`); return spellData; } } } return null; } // Extract spell data from OP_RETURN outputs function extractSpellFromOpReturnOutputs(tx) { if (!tx.outs || tx.outs.length === 0) { return null; } // Search all outputs for OP_RETURN with spell data for (let i = 0; i < tx.outs.length; i++) { const output = tx.outs[i]; if (output.script && output.script.length > 0) { // Check if it's an OP_RETURN script (starts with 0x6a) if (output.script[0] === 0x6a) { const spellData = extractSpellFromOpReturnScript(output.script); if (spellData) { console.log(`Found spell data in output ${i} OP_RETURN`); return spellData; } } } } return null; } // Extract spell data from OP_RETURN script function extractSpellFromOpReturnScript(script) { try { const scriptHex = script.toString('hex'); // Skip OP_RETURN opcode (6a) and look for spell data const SPELL_HEX = '7370656c6c'; // "spell" in hex const spellIndex = scriptHex.indexOf(SPELL_HEX); if (spellIndex === -1) { return null; } // Extract data after "spell" marker const dataStartIndex = spellIndex + SPELL_HEX.length; const remainingHex = scriptHex.substring(dataStartIndex); // Try to parse as direct CBOR data if (remainingHex.length > 0) { const cborData = Buffer.from(remainingHex, 'hex'); console.log('Extracted CBOR data from OP_RETURN (first 100 chars):', cborData.toString('hex').substring(0, 100) + '...'); console.log('CBOR data length:', cborData.length, 'bytes'); return cborData; } return null; } catch (error) { console.log(`Error extracting spell from OP_RETURN script: ${error.message}`); return null; } } // Extracts spell data from witness script function extractSpellFromWitnessScript(witnessScript) { try { const scriptHex = witnessScript.toString('hex'); const OP_ENDIF = '68'; const SPELL_HEX = '7370656c6c'; // "spell" in hex const spellIndex = scriptHex.indexOf(SPELL_HEX); if (spellIndex === -1) { return null; } const endIfIndex = scriptHex.lastIndexOf(OP_ENDIF); if (endIfIndex === -1 || endIfIndex <= spellIndex) { return null; } let dataStartIndex = spellIndex + SPELL_HEX.length; const cborData = extractPushData(scriptHex, dataStartIndex, endIfIndex); if (cborData && cborData.length > 0) { console.log('Extracted CBOR data hex (first 100 chars):', cborData.toString('hex').substring(0, 100) + '...'); console.log('CBOR data length:', cborData.length, 'bytes'); return cborData; } else { return null; } } catch (error) { console.log(`Error extracting spell from witness script: ${error.message}`); return null; } } // Dynamically extracts push data from script hex function extractPushData(scriptHex, startIndex, endIndex) { try { let currentIndex = startIndex; const allData = []; while (currentIndex < endIndex) { const opcode = scriptHex.substring(currentIndex, currentIndex + 2); const opcodeValue = parseInt(opcode, 16); if (opcodeValue >= 1 && opcodeValue <= 75) { // OP_PUSHDATA with length 1-75 bytes const dataLength = opcodeValue; const dataStart = currentIndex + 2; const dataEnd = dataStart + (dataLength * 2); if (dataEnd <= endIndex) { const data = Buffer.from(scriptHex.substring(dataStart, dataEnd), 'hex'); allData.push(data); currentIndex = dataEnd; } else { break; } } else if (opcodeValue === 0x4c) { // OP_PUSHDATA1: next byte is length const lengthByte = scriptHex.substring(currentIndex + 2, currentIndex + 4); const dataLength = parseInt(lengthByte, 16); const dataStart = currentIndex + 4; const dataEnd = dataStart + (dataLength * 2); if (dataEnd <= endIndex) { const data = Buffer.from(scriptHex.substring(dataStart, dataEnd), 'hex'); allData.push(data); currentIndex = dataEnd; } else { break; } } else if (opcodeValue === 0x4d) { // OP_PUSHDATA2: next 2 bytes are length (little endian) const lengthBytes = scriptHex.substring(currentIndex + 2, currentIndex + 6); const lengthHex = lengthBytes.substring(2, 4) + lengthBytes.substring(0, 2); const dataLength = parseInt(lengthHex, 16); const dataStart = currentIndex + 6; const dataEnd = dataStart + (dataLength * 2); if (dataEnd <= endIndex) { const data = Buffer.from(scriptHex.substring(dataStart, dataEnd), 'hex'); allData.push(data); currentIndex = dataEnd; } else { break; } } else if (opcodeValue === 0x4e) { // OP_PUSHDATA4: next 4 bytes are length (little endian) const lengthBytes = scriptHex.substring(currentIndex + 2, currentIndex + 10); // Convert little endian to big endian const lengthHex = lengthBytes.substring(6, 8) + lengthBytes.substring(4, 6) + lengthBytes.substring(2, 4) + lengthBytes.substring(0, 2); const dataLength = parseInt(lengthHex, 16); const dataStart = currentIndex + 10; const dataEnd = dataStart + (dataLength * 2); if (dataEnd <= endIndex) { const data = Buffer.from(scriptHex.substring(dataStart, dataEnd), 'hex'); allData.push(data); currentIndex = dataEnd; } else { break; } } else { // Skip unknown opcodes currentIndex += 2; } } if (allData.length > 0) { return Buffer.concat(allData); } return null; } catch (error) { console.log(`Error in extractPushData: ${error.message}`); return null; } }