UNPKG

ecash-lib

Version:

Library for eCash transaction building

251 lines 8.7 kB
"use strict"; // Copyright (c) 2025 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. Object.defineProperty(exports, "__esModule", { value: true }); exports.parseSlp = void 0; const str_js_1 = require("../io/str.js"); const hex_js_1 = require("../io/hex.js"); const opcode_js_1 = require("../opcode.js"); const op_js_1 = require("../op.js"); const common_js_1 = require("./common.js"); const slp_js_1 = require("./slp.js"); /** * Parse the given SLP OP_RETURN Script. * * For data that's clearly not SLP it will return `undefined`. * For example, if the OP_RETURN or LOKAD ID is missing. * * For an unknown token type, it'll return SlpUnknown. * * For a known token type, it'll parse the remaining data, or throw an error if * the format is invalid or if there's an unknown tx type. * * This behavior mirrors that of Chronik for consistency. **/ function parseSlp(opreturnScript) { const ops = opreturnScript.ops(); const opreturnOp = ops.next(); // Return undefined if not OP_RETURN if (opreturnOp === undefined || (0, op_js_1.isPushOp)(opreturnOp) || opreturnOp !== opcode_js_1.OP_RETURN) { return undefined; } // Return undefined if LOKAD ID is not "SLP\0" const lokadId = ops.next(); if (lokadId === undefined || !(0, op_js_1.isPushOp)(lokadId)) { return undefined; } if ((0, str_js_1.bytesToStr)(lokadId.data) !== slp_js_1.SLP_LOKAD_ID_STR) { return undefined; } // Parse token type const tokenTypeBytes = nextBytes(ops); if (tokenTypeBytes === undefined) { throw new Error('Missing tokenType'); } if (tokenTypeBytes.length !== 1) { throw new Error('tokenType must be exactly 1 byte'); } const tokenType = tokenTypeBytes[0]; if (tokenType !== slp_js_1.SLP_FUNGIBLE && tokenType !== slp_js_1.SLP_MINT_VAULT && tokenType !== slp_js_1.SLP_NFT1_GROUP && tokenType !== slp_js_1.SLP_NFT1_CHILD) { return { txType: common_js_1.UNKNOWN_STR, tokenType, }; } // Parse tx type (GENESIS, MINT, SEND, BURN) const txTypeBytes = nextBytes(ops); if (txTypeBytes === undefined) { throw new Error('Missing txType'); } const txType = (0, str_js_1.bytesToStr)(txTypeBytes); // Handle tx type specific parsing. // Advances the `ops` Script iterator switch (txType) { case common_js_1.GENESIS_STR: return nextGenesis(ops, tokenType); case common_js_1.MINT_STR: return nextMint(ops, tokenType); case common_js_1.SEND_STR: return nextSend(ops, tokenType); case common_js_1.BURN_STR: return nextBurn(ops, tokenType); default: throw new Error('Unknown txType'); } } exports.parseSlp = parseSlp; function nextGenesis(ops, tokenType) { // Parse genesis info const tokenTicker = (0, str_js_1.bytesToStr)(nextBytesRequired(ops, 'tokenTicker')); const tokenName = (0, str_js_1.bytesToStr)(nextBytesRequired(ops, 'tokenName')); const url = (0, str_js_1.bytesToStr)(nextBytesRequired(ops, 'url')); const hash = nextBytesRequired(ops, 'hash'); if (hash.length !== 0 && hash.length !== slp_js_1.SLP_GENESIS_HASH_NUM_BYTES) { throw new Error(`hash must be either 0 or ${slp_js_1.SLP_GENESIS_HASH_NUM_BYTES} bytes`); } const decimalsBytes = nextBytesRequired(ops, 'decimals'); if (decimalsBytes.length !== 1) { throw new Error('decimals must be exactly 1 byte'); } const decimals = decimalsBytes[0]; if (decimals > common_js_1.MAX_DECIMALS) { throw new Error(`decimals must be at most ${common_js_1.MAX_DECIMALS}`); } // Parse mint data let mintVaultScripthash = undefined; let mintBatonOutIdx = undefined; if (tokenType === slp_js_1.SLP_MINT_VAULT) { const scripthashBytes = nextBytesRequired(ops, 'mintVaultScripthash'); if (scripthashBytes.length !== slp_js_1.SLP_MINT_VAULT_SCRIPTHASH_NUM_BYTES) { throw new Error(`mintVaultScripthash must be exactly ${slp_js_1.SLP_MINT_VAULT_SCRIPTHASH_NUM_BYTES} ` + 'bytes long'); } mintVaultScripthash = (0, hex_js_1.toHex)(scripthashBytes); } else { mintBatonOutIdx = nextMintOutIdx(ops, tokenType); } const initialAtoms = parseSlpAtoms(nextBytesRequired(ops, 'initialAtoms')); nextEnd(ops, 'GENESIS'); return { txType: common_js_1.GENESIS_STR, tokenType, genesisInfo: { tokenTicker, tokenName, url, hash: hash.length !== 0 ? (0, hex_js_1.toHex)(hash) : undefined, mintVaultScripthash, decimals, }, initialAtoms, mintBatonOutIdx, }; } function nextMint(ops, tokenType) { const tokenId = nextTokenId(ops); if (tokenType === slp_js_1.SLP_MINT_VAULT) { const additionalAtomsArray = nextSlpAtomsArray(ops); return { txType: common_js_1.MINT_STR, tokenType, tokenId, additionalAtomsArray, }; } else if (tokenType === slp_js_1.SLP_NFT1_CHILD) { throw new Error('SLP_NFT1_CHILD cannot have MINT transactions'); } else { const mintBatonOutIdx = nextMintOutIdx(ops, tokenType); const additionalAtoms = parseSlpAtoms(nextBytesRequired(ops, 'additionalAtoms')); nextEnd(ops, 'MINT'); return { txType: common_js_1.MINT_STR, tokenType, tokenId, additionalAtoms, mintBatonOutIdx, }; } } function nextSend(ops, tokenType) { const tokenId = nextTokenId(ops); const sendAtomsArray = nextSlpAtomsArray(ops); return { txType: common_js_1.SEND_STR, tokenType, tokenId, sendAtomsArray, }; } function nextBurn(ops, tokenType) { const tokenId = nextTokenId(ops); const burnAtoms = parseSlpAtoms(nextBytesRequired(ops, 'burnAtoms')); nextEnd(ops, 'BURN'); return { txType: common_js_1.BURN_STR, tokenType, tokenId, burnAtoms, }; } function nextBytes(iter) { const op = iter.next(); if (op === undefined) { return undefined; } if (!(0, op_js_1.isPushOp)(op)) { throw new Error('SLP only supports push-ops'); } return op.data; } function nextBytesRequired(iter, name) { const bytes = nextBytes(iter); if (bytes === undefined) { throw new Error('Missing ' + name); } return bytes; } function nextMintOutIdx(iter, tokenType) { const outIdxBytes = nextBytesRequired(iter, 'mintBatonOutIdx'); if (outIdxBytes.length > 1) { throw new Error('mintBatonOutIdx must be at most 1 byte long'); } if (outIdxBytes.length === 1) { if (tokenType === slp_js_1.SLP_NFT1_CHILD) { throw new Error('SLP_NFT1_CHILD cannot have a mint baton'); } const mintBatonOutIdx = outIdxBytes[0]; if (mintBatonOutIdx < 2) { throw new Error('mintBatonOutIdx must be at least 2'); } return mintBatonOutIdx; } return undefined; } function nextTokenId(iter) { const tokenIdBytes = nextBytesRequired(iter, 'tokenId'); if (tokenIdBytes.length !== common_js_1.TOKEN_ID_NUM_BYTES) { throw new Error(`tokenId must be exactly ${common_js_1.TOKEN_ID_NUM_BYTES} bytes long`); } // Note: SLP token ID endianness is big-endian return (0, hex_js_1.toHex)(tokenIdBytes); } function nextSlpAtomsArray(iter) { const atomsArray = []; let bytes = undefined; while ((bytes = nextBytes(iter)) !== undefined) { atomsArray.push(parseSlpAtoms(bytes)); } if (atomsArray.length === 0) { throw new Error('atomsArray cannot be empty'); } if (atomsArray.length > slp_js_1.SLP_MAX_SEND_OUTPUTS) { throw new Error(`atomsArray can at most be ${slp_js_1.SLP_MAX_SEND_OUTPUTS} items long`); } return atomsArray; } function nextEnd(iter, txType) { if (iter.next() !== undefined) { throw new Error(`Superfluous ${txType} bytes`); } } function parseSlpAtoms(bytes) { if (bytes.length !== slp_js_1.SLP_ATOMS_NUM_BYTES) { throw new Error(`SLP atoms must be exactly ${slp_js_1.SLP_ATOMS_NUM_BYTES} bytes long`); } let number = 0n; for (let i = 0; i < slp_js_1.SLP_ATOMS_NUM_BYTES; ++i) { number <<= 8n; number |= BigInt(bytes[i]); } return number; } //# sourceMappingURL=slp.parse.js.map