charms-js
Version:
TypeScript SDK for decoding Bitcoin transactions containing Charms data
135 lines (134 loc) • 5.29 kB
JavaScript
;
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;
};
})();
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchTransactionHex = fetchTransactionHex;
exports.decodeTransaction = decodeTransaction;
exports.hasCharmsData = hasCharmsData;
exports.decodeTransactionById = decodeTransactionById;
const bitcoin = __importStar(require("bitcoinjs-lib"));
const extractor_1 = require("./extractor");
const decoder_1 = require("./decoder");
const formatter_1 = require("./formatter");
const address_1 = require("./address");
// Add transaction inputs to spell
function addTransactionInputs(spell, tx) {
// Validate spell structure
if (spell.tx.ins !== undefined && spell.tx.ins !== null) {
throw new Error("spell must inherit inputs from the enchanted tx");
}
if (spell.tx.outs.length > tx.outs.length) {
throw new Error("spell tx outs mismatch");
}
// Add transaction inputs (excluding the spell input which is last)
const txIns = tx.ins.slice(0, -1);
const utxoIds = txIns.map(txIn => {
const txid = Buffer.from(txIn.hash).reverse().toString('hex');
return `${txid}:${txIn.index}`;
});
const spellWithInputs = { ...spell };
spellWithInputs.tx = { ...spell.tx, ins: utxoIds };
return spellWithInputs;
}
// Fetch transaction hex from API
async function fetchTransactionHex(txId, config) {
const network = config?.network || 'testnet4';
const baseUrl = config?.apiBaseUrl || (network === 'mainnet'
? 'https://mempool.space/api/tx'
: 'https://mempool.space/testnet4/api/tx');
try {
const response = await fetch(`${baseUrl}/${txId}/hex`);
if (!response.ok) {
return null;
}
return await response.text();
}
catch (error) {
return null;
}
}
// Decodes Bitcoin transaction containing Charms data
async function decodeTransaction(txHex, config) {
try {
const spellData = (0, extractor_1.extractSpellData)(txHex);
if (!spellData) {
return { error: 'No spell data found in transaction' };
}
const normalizedSpell = (0, decoder_1.decodeCbor)(spellData);
if (!normalizedSpell) {
return { error: 'Failed to decode CBOR data' };
}
// Validate version is not negative
if (normalizedSpell.version < 0) {
return { error: `Invalid spell version: ${normalizedSpell.version}. Version must be >= 0.` };
}
const tx = bitcoin.Transaction.fromHex(txHex);
const spellWithInputs = addTransactionInputs(normalizedSpell, tx);
const charmInfo = (0, decoder_1.denormalizeSpell)(spellWithInputs);
const txId = tx.getId();
// Extract addresses from outputs using helper
tx.outs.forEach((output, index) => {
if (charmInfo.outs[index]) {
charmInfo.outs[index].address = (0, address_1.extractAddress)(output.script);
}
});
return (0, formatter_1.createCharmInstances)(charmInfo, txId);
}
catch (error) {
return { error: `Decoding failed: ${error.message}` };
}
}
// Checks if transaction contains Charms data
function hasCharmsData(txHex) {
try {
const spellData = (0, extractor_1.extractSpellData)(txHex);
return spellData !== null;
}
catch (error) {
return false;
}
}
// Decode transaction by ID (fetches from API)
async function decodeTransactionById(txId, config) {
const txHex = await fetchTransactionHex(txId, config);
if (!txHex) {
return { error: `Failed to fetch transaction ${txId}` };
}
return decodeTransaction(txHex, config);
}
__exportStar(require("./types"), exports);
__exportStar(require("./utils"), exports);