parseSLP function
Implementation
ParseResult parseSLP(List<int> scriptPubKey) {
int it = 0;
var itObj = Uint8List(scriptPubKey.length);
itObj.setAll(0, scriptPubKey);
const int OP_0 = 0x00;
const int OP_RETURN = 0x6a;
const int OP_PUSHDATA1 = 0x4c;
const int OP_PUSHDATA2 = 0x4d;
const int OP_PUSHDATA4 = 0x4e;
final PARSE_CHECK = /* void */ (bool v , String str ) {
if (v) {
throw(str);
}
};
final extractU8 = /* BN */ () {
var r = itObj.buffer.asByteData().getUint8(it);
it += 1;
return r;
};
final extractU16 = /* BN */ (Endian endian) {
var r = itObj.buffer.asByteData().getUint16(it, endian);
it += 2;
return r;
};
final extractU32 = /* BN */ (Endian endian) {
var r = itObj.buffer.asByteData().getUint32(it, endian);
it += 4;
return r;
};
final extractU64 = /* BN */ (Endian endian) {
var r1 = itObj.buffer.asByteData().getUint32(it, endian);
it += 4;
var r2 = itObj.buffer.asByteData().getUint32(it, endian);
it += 4;
var b1 = BigInt.from(r1);
var b2 = BigInt.from(r2);
if (endian == Endian.big) {
return (b1 * BigInt.from(2).pow(32)) + b2;
} else {
return (b2 * BigInt.from(2).pow(32)) + b1;
}
};
PARSE_CHECK(itObj.lengthInBytes == 0, "scriptpubkey cannot be empty");
PARSE_CHECK(itObj.buffer.asByteData().getUint8(it) != OP_RETURN, "scriptpubkey not op_return" );
PARSE_CHECK(itObj.lengthInBytes < 10 ,"scriptpubkey too small" );
++it;
final extractPushdata = /* num */ () {
if (it == itObj.length) { return -1;}
final int cnt = extractU8();
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();
} else if (cnt == OP_PUSHDATA2) {
if (it + 2 >= itObj.length ) { --it; return -1;}
return extractU16(Endian.little);
} else if (cnt == OP_PUSHDATA4) {
if (it + 4 >= itObj.length ) { --it; return -1;}
return extractU32(Endian.little);
}
// other opcodes not allowed
--it;
return -1;
};
final bufferToBN = /* BN */ () {
if (itObj.length == 1) {
return BigInt.from(extractU8());
}
if (itObj.length == 2) {
return BigInt.from(extractU16(Endian.big));
}
if (itObj.length == 4) {
return BigInt.from(extractU32(Endian.big));
}
if (itObj.length == 8) {
return extractU64(Endian.big);
}
throw("extraction of number from buffer failed");
};
final checkValidTokenId = (Uint8List tokenId) => tokenId.length == 32;
final List<Uint8List> chunks = [];
for (var len = extractPushdata();len >= 0;len = extractPushdata()) {
final buf = Uint8List(len);
buf.setAll(0, itObj.getRange(it, it + len));
PARSE_CHECK(it + len > itObj.length, "pushdata data extraction failed" );
it += len;
chunks.add(buf);
if (chunks.length == 1) {
final lokadIdStr = chunks[0];
PARSE_CHECK(lokadIdStr.length != 4, "lokad id wrong size");
PARSE_CHECK(
lokadIdStr.buffer.asByteData().getUint8(0) != "S".codeUnitAt(0)
|| lokadIdStr.buffer.asByteData().getUint8(1) != "L".codeUnitAt(0)
|| lokadIdStr.buffer.asByteData().getUint8(2) != "P".codeUnitAt(0)
|| lokadIdStr.buffer.asByteData().getUint8(3) != 0x00, "SLP not in first chunk");
}
}
PARSE_CHECK(it != itObj.length, "trailing data");
PARSE_CHECK(chunks.isEmpty, "chunks empty");
var cit = 0;
final CHECK_NEXT = /* void */ () {
++cit;
PARSE_CHECK(cit == chunks.length, "parsing ended early");
it = 0;
itObj = chunks[cit];
};
CHECK_NEXT();
final tokenTypeBuf = itObj;
PARSE_CHECK(tokenTypeBuf.length != 1 && tokenTypeBuf.length != 2,
"token_type string length must be 1 or 2" );
int tokenType = 0;
if (tokenTypeBuf.length == 1) {
tokenType = extractU8();
} else if (tokenTypeBuf.length == 2) {
tokenType = extractU16(Endian.big);
}
PARSE_CHECK(![ 0x01 , 0x41 , 0x81 ].contains(tokenType),
"token_type not token-type1, nft1-group, or nft1-child" );
CHECK_NEXT();
final transactionType = utf8.decode(itObj);
if (transactionType == "GENESIS") {
PARSE_CHECK(chunks.length != 10, "wrong number of chunks" );
CHECK_NEXT();
final ticker = itObj;
CHECK_NEXT();
final name = itObj;
CHECK_NEXT();
final documentUri = itObj;
CHECK_NEXT();
final documentHash = itObj;
PARSE_CHECK(documentHash.length != 0 && documentHash.length != 32 , "document_hash must be size 0 or 32" );
CHECK_NEXT();
final decimalsBuf = itObj;
PARSE_CHECK(decimalsBuf.length != 1, "decimals string length must be 1" );
final decimals = bufferToBN().toInt();
PARSE_CHECK(decimals > 9,
"decimals bigger than 9" );
CHECK_NEXT();
final mintBatonVoutBuf = itObj;
int mintBatonVout = 0;
PARSE_CHECK(mintBatonVoutBuf.length >= 2 , "mint_baton_vout string length must be 0 or 1" );
if (mintBatonVoutBuf.length > 0 ) {
mintBatonVout = bufferToBN().toInt();
PARSE_CHECK(mintBatonVout < 2, "mint_baton_vout must be at least 2" );
}
CHECK_NEXT();
final qtyBuf = itObj;//.reversed;
PARSE_CHECK(qtyBuf.length != 8 ,
"initial_qty must be provided as an 8-byte buffer" );
final 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.compareTo(BigInt.from(1)) != 0, "NFT1 child token must have quantity of 1" );
}
final GenesisParseResult actionData = new GenesisParseResult(
tickerBuf: ticker,
nameBuf: name,
documentUriBuf: documentUri,
documentHashBuf: documentHash,
decimals: decimals,
mintBatonVout: mintBatonVout,
qty: qty
);
return new ParseResult(
tokenType: tokenType,
transactionType: 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();
final tokenId = itObj;
PARSE_CHECK(! checkValidTokenId(tokenId), "tokenId invalid size");
CHECK_NEXT();
final mintBatonVoutBuf = itObj;
var mintBatonVout = 0;
PARSE_CHECK(mintBatonVoutBuf.length >= 2 , "mint_baton_vout string length must be 0 or 1" );
if (mintBatonVoutBuf.length > 0 ) {
if (mintBatonVoutBuf.length == 1) {
mintBatonVout = extractU8();
} else if (mintBatonVoutBuf.length == 2) {
mintBatonVout = extractU16(Endian.big);
}
PARSE_CHECK(mintBatonVout < 2 , "mint_baton_vout must be at least 2");
}
CHECK_NEXT();
final additionalQtyBuf = itObj;//.reversed;
PARSE_CHECK(additionalQtyBuf.length != 8, "additional_qty must be provided as an 8-byte buffer" );
final qty = bufferToBN();
final actionData = new MintParseResult(
tokenIdBuf: tokenId,
mintBatonVout: mintBatonVout,
qty: qty
);
return new ParseResult(
tokenType: tokenType,
transactionType: transactionType,
data: actionData
);
} else if (transactionType == "SEND") {
PARSE_CHECK(chunks.length < 4, "wrong number of chunks");
CHECK_NEXT();
final tokenId = itObj;
PARSE_CHECK(!checkValidTokenId(tokenId ) , "tokenId invalid size");
CHECK_NEXT();
final List<BigInt> amounts = [];
while (cit != chunks.length) {
final amountBuf = itObj; //.reversed;
PARSE_CHECK(amountBuf.length != 8, "amount string size not 8 bytes");
final value = bufferToBN();
amounts.add(value);
++cit;
if (cit < chunks.length) {
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");
final actionData = new SendParseResult(
tokenIdBuf: tokenId,
amounts: amounts
);
return new ParseResult(
tokenType: tokenType,
transactionType: transactionType,
data: actionData
);
} else {
PARSE_CHECK(true , "unknown action type");
}
// unreachable code
return new ParseResult(
tokenType: tokenType,
transactionType: transactionType,
data: new ImpossibleParseResult()
);
}