UNPKG

slp-parser

Version:

Parse Simple Ledger Protocol OP_RETURN data segments with ease!

251 lines 8.82 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const bignumber_js_1 = __importDefault(require("bignumber.js")); exports.BN = bignumber_js_1.default; ; ; ; ; ; exports.parseSLP = (scriptpubkey) => { if (typeof scriptpubkey === "string") { scriptpubkey = Buffer.from(scriptpubkey, 'hex'); } let it = 0; // position in itObj let itObj = scriptpubkey; // object it refers to const OP_0 = 0x00; const OP_16 = 0x60; const OP_RETURN = 0x6a; const OP_PUSHDATA1 = 0x4c; const OP_PUSHDATA2 = 0x4d; const OP_PUSHDATA4 = 0x4e; const PARSE_CHECK = (v, str) => { if (v) { throw Error(str); } }; const extractU8 = () => { const r = itObj.readUInt8(it); it += 1; return new bignumber_js_1.default(r); }; const extractU16 = () => { const r = itObj.readUInt16LE(it); it += 2; return new bignumber_js_1.default(r); }; const extractU32 = () => { const r = itObj.readUInt32LE(it); it += 4; return new bignumber_js_1.default(r); }; const extractU64 = () => { const r1 = itObj.readUInt32LE(it); it += 4; const r2 = itObj.readUInt32LE(it); it += 4; return new bignumber_js_1.default(r2).multipliedBy(Math.pow(2, 32)).plus(r1); }; PARSE_CHECK(itObj.length === 0, "scriptpubkey cannot be empty"); PARSE_CHECK(itObj[it] !== OP_RETURN, "scriptpubkey not op_return"); PARSE_CHECK(itObj.length < 10, "scriptpubkey too small"); // TODO what is correct minimum size? ++it; const extractPushdata = () => { if (it === itObj.length) { return -1; } const cnt = extractU8().toNumber(); if (cnt > OP_0 && cnt < OP_PUSHDATA1) { if (it + cnt > itObj.length) { --it; return -1; } return cnt; } else if (cnt === OP_PUSHDATA1) { if (it + 1 >= itObj.length) { --it; return -1; } return extractU8().toNumber(); } else if (cnt === OP_PUSHDATA2) { if (it + 2 >= itObj.length) { --it; return -1; } return extractU16().toNumber(); } else if (cnt === OP_PUSHDATA4) { if (it + 4 >= itObj.length) { --it; return -1; } return extractU32().toNumber(); } // other opcodes not allowed --it; return -1; }; const bufferToBN = () => { if (itObj.length === 1) return extractU8(); if (itObj.length === 2) return extractU16(); if (itObj.length === 4) return extractU32(); if (itObj.length === 8) return extractU64(); throw new Error('extraction of number from buffer failed'); }; const checkValidTokenId = (tokenId) => tokenId.length === 32; const chunks = []; for (let len = extractPushdata(); len >= 0; len = extractPushdata()) { const buf = itObj.slice(it, it + len); PARSE_CHECK(it + len > itObj.length, "pushdata data extraction failed"); it += len; chunks.push(buf); if (chunks.length === 1) { const lokadIdStr = chunks[0]; PARSE_CHECK(lokadIdStr.length !== 4, "lokad id wrong size"); PARSE_CHECK(lokadIdStr[0] !== 'S'.charCodeAt(0) || lokadIdStr[1] !== 'L'.charCodeAt(0) || lokadIdStr[2] !== 'P'.charCodeAt(0) || lokadIdStr[3] !== 0x00, "SLP not in first chunk"); } } PARSE_CHECK(it !== itObj.length, "trailing data"); PARSE_CHECK(chunks.length === 0, "chunks empty"); let cit = 0; const CHECK_NEXT = () => { ++cit; PARSE_CHECK(cit === chunks.length, "parsing ended early"); it = 0; itObj = chunks[cit]; }; CHECK_NEXT(); // for quick exit check done above const tokenTypeBuf = itObj.reverse(); PARSE_CHECK(tokenTypeBuf.length !== 1 && tokenTypeBuf.length !== 2, "token_type string length must be 1 or 2"); const tokenType = bufferToBN().toNumber(); PARSE_CHECK(![0x01, 0x41, 0x81].includes(tokenType), "token_type not token-type1, nft1-group, or nft1-child"); CHECK_NEXT(); const transactionType = itObj.toString(); if (transactionType === 'GENESIS') { PARSE_CHECK(chunks.length !== 10, "wrong number of chunks"); CHECK_NEXT(); const ticker = itObj; CHECK_NEXT(); const name = itObj; CHECK_NEXT(); const documentUri = itObj; CHECK_NEXT(); const documentHash = itObj; PARSE_CHECK(!(documentHash.length === 0 || documentHash.length === 32), "document_hash must be size 0 or 32"); CHECK_NEXT(); const decimalsBuf = itObj; PARSE_CHECK(decimalsBuf.length !== 1, "decimals string length must be 1"); const decimals = bufferToBN().toNumber(); PARSE_CHECK(decimals > 9, "decimals bigger than 9"); CHECK_NEXT(); const mintBatonVoutBuf = itObj; let mintBatonVout = 0; PARSE_CHECK(mintBatonVoutBuf.length >= 2, "mint_baton_vout string length must be 0 or 1"); if (mintBatonVoutBuf.length > 0) { mintBatonVout = bufferToBN().toNumber(); PARSE_CHECK(mintBatonVout < 2, "mint_baton_vout must be at least 2"); } CHECK_NEXT(); const qtyBuf = itObj.reverse(); PARSE_CHECK(qtyBuf.length !== 8, "initial_qty must be provided as an 8-byte buffer"); const qty = bufferToBN(); if (tokenType === 0x41) { PARSE_CHECK(decimals !== 0, "NFT1 child token must have divisibility set to 0 decimal places"); PARSE_CHECK(mintBatonVout !== 0, "NFT1 child token must not have a minting baton"); PARSE_CHECK(!qty.isEqualTo(1), "NFT1 child token must have quantity of 1"); } const actionData = { ticker, name, documentUri, documentHash, decimals, mintBatonVout, qty }; return { tokenType, transactionType, data: actionData }; } else if (transactionType === "MINT") { PARSE_CHECK(tokenType === 0x41, "NFT1 Child cannot have MINT transaction type."); PARSE_CHECK(chunks.length !== 6, "wrong number of chunks"); CHECK_NEXT(); const tokenId = itObj; PARSE_CHECK(!checkValidTokenId(tokenId), "tokenId invalid size"); CHECK_NEXT(); const mintBatonVoutBuf = itObj; let mintBatonVout = 0; PARSE_CHECK(mintBatonVoutBuf.length >= 2, "mint_baton_vout string length must be 0 or 1"); if (mintBatonVoutBuf.length > 0) { mintBatonVout = bufferToBN().toNumber(); PARSE_CHECK(mintBatonVout < 2, "mint_baton_vout must be at least 2"); } CHECK_NEXT(); const additionalQtyBuf = itObj.reverse(); PARSE_CHECK(additionalQtyBuf.length !== 8, "additional_qty must be provided as an 8-byte buffer"); const qty = bufferToBN(); const actionData = { tokenId, mintBatonVout, qty }; return { tokenType, transactionType, data: actionData }; } else if (transactionType === "SEND") { PARSE_CHECK(chunks.length < 4, "wrong number of chunks"); CHECK_NEXT(); const tokenId = itObj; PARSE_CHECK(!checkValidTokenId(tokenId), "tokenId invalid size"); CHECK_NEXT(); const amounts = []; while (cit !== chunks.length) { const amountBuf = itObj.reverse(); PARSE_CHECK(amountBuf.length !== 8, "amount string size not 8 bytes"); const value = bufferToBN(); amounts.push(value); ++cit; itObj = chunks[cit]; it = 0; } PARSE_CHECK(amounts.length === 0, "token_amounts size is 0"); PARSE_CHECK(amounts.length > 19, "token_amounts size is greater than 19"); const actionData = { tokenId, amounts }; return { tokenType, transactionType, data: actionData }; } else { PARSE_CHECK(true, "unknown action type"); } // unreachable code return { tokenType, transactionType, data: {} }; }; //# sourceMappingURL=index.js.map