UNPKG

@benfen/bcs

Version:

BCS - Canonical Binary Serialization implementation for JavaScript

1,232 lines (1,228 loc) 39.7 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { BCS: () => BCS, BcsReader: () => BcsReader, BcsWriter: () => BcsWriter, decodeStr: () => decodeStr, encodeStr: () => encodeStr, fromB58: () => fromB58, fromB64: () => fromB64, fromHEX: () => fromHEX, getRustConfig: () => getRustConfig, getSuiMoveConfig: () => getSuiMoveConfig, registerPrimitives: () => registerPrimitives, splitGenericParameters: () => splitGenericParameters, toB58: () => toB58, toB64: () => toB64, toHEX: () => toHEX }); module.exports = __toCommonJS(src_exports); // src/b64.ts function b64ToUint6(nChr) { return nChr > 64 && nChr < 91 ? nChr - 65 : nChr > 96 && nChr < 123 ? nChr - 71 : nChr > 47 && nChr < 58 ? nChr + 4 : nChr === 43 ? 62 : nChr === 47 ? 63 : 0; } function fromB64(sBase64, nBlocksSize) { var sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, ""), nInLen = sB64Enc.length, nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen); for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { nMod4 = nInIdx & 3; nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 6 * (3 - nMod4); if (nMod4 === 3 || nInLen - nInIdx === 1) { for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; } nUint24 = 0; } } return taBytes; } function uint6ToB64(nUint6) { return nUint6 < 26 ? nUint6 + 65 : nUint6 < 52 ? nUint6 + 71 : nUint6 < 62 ? nUint6 - 4 : nUint6 === 62 ? 43 : nUint6 === 63 ? 47 : 65; } function toB64(aBytes) { var nMod3 = 2, sB64Enc = ""; for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { nMod3 = nIdx % 3; nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); if (nMod3 === 2 || aBytes.length - nIdx === 1) { sB64Enc += String.fromCodePoint( uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63) ); nUint24 = 0; } } return sB64Enc.slice(0, sB64Enc.length - 2 + nMod3) + (nMod3 === 2 ? "" : nMod3 === 1 ? "=" : "=="); } // src/hex.ts function fromHEX(hexStr) { let intArr = (/^BFC/i.test(hexStr) ? hexStr.slice(3, -4) : hexStr.replace("0x", "")).match(/.{1,2}/g).map((byte) => parseInt(byte, 16)); if (intArr === null) { throw new Error(`Unable to parse HEX: ${hexStr}`); } return Uint8Array.from(intArr); } function toHEX(bytes) { return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), ""); } // src/index.ts var import_bs58 = __toESM(require("bs58")); var import_fast_sha256 = __toESM(require("fast-sha256")); var SUI_ADDRESS_LENGTH = 32; function toLittleEndian(bigint, size) { let result = new Uint8Array(size); let i = 0; while (bigint > 0) { result[i] = Number(bigint % BigInt(256)); bigint = bigint / BigInt(256); i += 1; } return result; } var toB58 = (buffer) => import_bs58.default.encode(buffer); var fromB58 = (str) => import_bs58.default.decode(str); var BcsReader = class { /** * @param {Uint8Array} data Data to use as a buffer. */ constructor(data) { this.bytePosition = 0; this.dataView = new DataView(data.buffer); } /** * Shift current cursor position by `bytes`. * * @param {Number} bytes Number of bytes to * @returns {this} Self for possible chaining. */ shift(bytes) { this.bytePosition += bytes; return this; } /** * Read U8 value from the buffer and shift cursor by 1. * @returns */ read8() { let value = this.dataView.getUint8(this.bytePosition); this.shift(1); return value; } /** * Read U16 value from the buffer and shift cursor by 2. * @returns */ read16() { let value = this.dataView.getUint16(this.bytePosition, true); this.shift(2); return value; } /** * Read U32 value from the buffer and shift cursor by 4. * @returns */ read32() { let value = this.dataView.getUint32(this.bytePosition, true); this.shift(4); return value; } /** * Read U64 value from the buffer and shift cursor by 8. * @returns */ read64() { let value1 = this.read32(); let value2 = this.read32(); let result = value2.toString(16) + value1.toString(16).padStart(8, "0"); return BigInt("0x" + result).toString(10); } /** * Read U128 value from the buffer and shift cursor by 16. */ read128() { let value1 = BigInt(this.read64()); let value2 = BigInt(this.read64()); let result = value2.toString(16) + value1.toString(16).padStart(16, "0"); return BigInt("0x" + result).toString(10); } /** * Read U128 value from the buffer and shift cursor by 32. * @returns */ read256() { let value1 = BigInt(this.read128()); let value2 = BigInt(this.read128()); let result = value2.toString(16) + value1.toString(16).padStart(32, "0"); return BigInt("0x" + result).toString(10); } /** * Read `num` number of bytes from the buffer and shift cursor by `num`. * @param num Number of bytes to read. */ readBytes(num) { let start = this.bytePosition + this.dataView.byteOffset; let value = new Uint8Array(this.dataView.buffer, start, num); this.shift(num); return value; } /** * Read ULEB value - an integer of varying size. Used for enum indexes and * vector lengths. * @returns {Number} The ULEB value. */ readULEB() { let start = this.bytePosition + this.dataView.byteOffset; let buffer = new Uint8Array(this.dataView.buffer, start); let { value, length } = ulebDecode(buffer); this.shift(length); return value; } /** * Read a BCS vector: read a length and then apply function `cb` X times * where X is the length of the vector, defined as ULEB in BCS bytes. * @param cb Callback to process elements of vector. * @returns {Array<Any>} Array of the resulting values, returned by callback. */ readVec(cb) { let length = this.readULEB(); let result = []; for (let i = 0; i < length; i++) { result.push(cb(this, i, length)); } return result; } }; var BcsWriter = class { constructor({ size = 1024, maxSize, allocateSize = 1024 } = {}) { this.bytePosition = 0; this.size = size; this.maxSize = maxSize || size; this.allocateSize = allocateSize; this.dataView = new DataView(new ArrayBuffer(size)); } ensureSizeOrGrow(bytes) { const requiredSize = this.bytePosition + bytes; if (requiredSize > this.size) { const nextSize = Math.min(this.maxSize, this.size + this.allocateSize); if (requiredSize > nextSize) { throw new Error( `Attempting to serialize to BCS, but buffer does not have enough size. Allocated size: ${this.size}, Max size: ${this.maxSize}, Required size: ${requiredSize}` ); } this.size = nextSize; const nextBuffer = new ArrayBuffer(this.size); new Uint8Array(nextBuffer).set(new Uint8Array(this.dataView.buffer)); this.dataView = new DataView(nextBuffer); } } /** * Shift current cursor position by `bytes`. * * @param {Number} bytes Number of bytes to * @returns {this} Self for possible chaining. */ shift(bytes) { this.bytePosition += bytes; return this; } /** * Write a U8 value into a buffer and shift cursor position by 1. * @param {Number} value Value to write. * @returns {this} */ write8(value) { this.ensureSizeOrGrow(1); this.dataView.setUint8(this.bytePosition, Number(value)); return this.shift(1); } /** * Write a U16 value into a buffer and shift cursor position by 2. * @param {Number} value Value to write. * @returns {this} */ write16(value) { this.ensureSizeOrGrow(2); this.dataView.setUint16(this.bytePosition, Number(value), true); return this.shift(2); } /** * Write a U32 value into a buffer and shift cursor position by 4. * @param {Number} value Value to write. * @returns {this} */ write32(value) { this.ensureSizeOrGrow(4); this.dataView.setUint32(this.bytePosition, Number(value), true); return this.shift(4); } /** * Write a U64 value into a buffer and shift cursor position by 8. * @param {bigint} value Value to write. * @returns {this} */ write64(value) { toLittleEndian(BigInt(value), 8).forEach((el) => this.write8(el)); return this; } /** * Write a U128 value into a buffer and shift cursor position by 16. * * @param {bigint} value Value to write. * @returns {this} */ write128(value) { toLittleEndian(BigInt(value), 16).forEach((el) => this.write8(el)); return this; } /** * Write a U256 value into a buffer and shift cursor position by 16. * * @param {bigint} value Value to write. * @returns {this} */ write256(value) { toLittleEndian(BigInt(value), 32).forEach((el) => this.write8(el)); return this; } /** * Write a ULEB value into a buffer and shift cursor position by number of bytes * written. * @param {Number} value Value to write. * @returns {this} */ writeULEB(value) { ulebEncode(value).forEach((el) => this.write8(el)); return this; } /** * Write a vector into a buffer by first writing the vector length and then calling * a callback on each passed value. * * @param {Array<Any>} vector Array of elements to write. * @param {WriteVecCb} cb Callback to call on each element of the vector. * @returns {this} */ writeVec(vector, cb) { this.writeULEB(vector.length); Array.from(vector).forEach((el, i) => cb(this, el, i, vector.length)); return this; } /** * Adds support for iterations over the object. * @returns {Uint8Array} */ *[Symbol.iterator]() { for (let i = 0; i < this.bytePosition; i++) { yield this.dataView.getUint8(i); } return this.toBytes(); } /** * Get underlying buffer taking only value bytes (in case initial buffer size was bigger). * @returns {Uint8Array} Resulting bcs. */ toBytes() { return new Uint8Array(this.dataView.buffer.slice(0, this.bytePosition)); } /** * Represent data as 'hex' or 'base64' * @param encoding Encoding to use: 'base64' or 'hex' */ toString(encoding) { return encodeStr(this.toBytes(), encoding); } }; function ulebEncode(num) { let arr = []; let len = 0; if (num === 0) { return [0]; } while (num > 0) { arr[len] = num & 127; if (num >>= 7) { arr[len] |= 128; } len += 1; } return arr; } function ulebDecode(arr) { let total = 0; let shift = 0; let len = 0; while (true) { let byte = arr[len]; len += 1; total |= (byte & 127) << shift; if ((byte & 128) === 0) { break; } shift += 7; } return { value: total, length: len }; } var _BCS = class _BCS { /** * Construct a BCS instance with a prepared schema. * * @param schema A prepared schema with type definitions * @param withPrimitives Whether to register primitive types by default */ constructor(schema) { /** * Map of kind `TypeName => TypeInterface`. Holds all * callbacks for (de)serialization of every registered type. * * If the value stored is a string, it is treated as an alias. */ this.types = /* @__PURE__ */ new Map(); /** * Count temp keys to generate a new one when requested. */ this.counter = 0; if (schema instanceof _BCS) { this.schema = schema.schema; this.types = new Map(schema.types); return; } this.schema = schema; this.registerAddressType(_BCS.ADDRESS, schema.addressLength, schema.addressEncoding); this.registerVectorType(schema.vectorType); if (schema.types && schema.types.structs) { for (let name of Object.keys(schema.types.structs)) { this.registerStructType(name, schema.types.structs[name]); } } if (schema.types && schema.types.enums) { for (let name of Object.keys(schema.types.enums)) { this.registerEnumType(name, schema.types.enums[name]); } } if (schema.types && schema.types.aliases) { for (let name of Object.keys(schema.types.aliases)) { this.registerAlias(name, schema.types.aliases[name]); } } if (schema.withPrimitives !== false) { registerPrimitives(this); } } /** * Name of the key to use for temporary struct definitions. * Returns a temp key + index (for a case when multiple temp * structs are processed). */ tempKey() { return `bcs-struct-${++this.counter}`; } /** * Serialize data into bcs. * * @example * bcs.registerVectorType('vector<u8>', 'u8'); * * let serialized = BCS * .set('vector<u8>', [1,2,3,4,5,6]) * .toBytes(); * * console.assert(toHex(serialized) === '06010203040506'); * * @param type Name of the type to serialize (must be registered) or a struct type. * @param data Data to serialize. * @param size Serialization buffer size. Default 1024 = 1KB. * @return A BCS reader instance. Usually you'd want to call `.toBytes()` */ ser(type, data, options) { if (typeof type === "string" || Array.isArray(type)) { const { name, params } = this.parseTypeName(type); return this.getTypeInterface(name).encode(this, data, options, params); } if (typeof type === "object") { const key = this.tempKey(); const temp = new _BCS(this); return temp.registerStructType(key, type).ser(key, data, options); } throw new Error(`Incorrect type passed into the '.ser()' function. ${JSON.stringify(type)}`); } /** * Deserialize BCS into a JS type. * * @example * let num = bcs.ser('u64', '4294967295').toString('hex'); * let deNum = bcs.de('u64', num, 'hex'); * console.assert(deNum.toString(10) === '4294967295'); * * @param type Name of the type to deserialize (must be registered) or a struct type definition. * @param data Data to deserialize. * @param encoding Optional - encoding to use if data is of type String * @return Deserialized data. */ de(type, data, encoding) { if (typeof data === "string") { if (encoding) { data = decodeStr(data, encoding); } else { throw new Error("To pass a string to `bcs.de`, specify encoding"); } } if (typeof type === "string" || Array.isArray(type)) { const { name, params } = this.parseTypeName(type); return this.getTypeInterface(name).decode(this, data, params); } if (typeof type === "object") { const temp = new _BCS(this); const key = this.tempKey(); return temp.registerStructType(key, type).de(key, data, encoding); } throw new Error(`Incorrect type passed into the '.de()' function. ${JSON.stringify(type)}`); } /** * Check whether a `TypeInterface` has been loaded for a `type`. * @param type Name of the type to check. * @returns */ hasType(type) { return this.types.has(type); } /** * Create an alias for a type. * WARNING: this can potentially lead to recursion * @param name Alias to use * @param forType Type to reference * @returns * * @example * ``` * let bcs = new BCS(getSuiMoveConfig()); * bcs.registerAlias('ObjectDigest', BCS.BASE58); * let b58_digest = bcs.de('ObjectDigest', '<digest_bytes>', 'base64'); * ``` */ registerAlias(name, forType) { this.types.set(name, forType); return this; } /** * Method to register new types for BCS internal representation. * For each registered type 2 callbacks must be specified and one is optional: * * - encodeCb(writer, data) - write a way to serialize data with BcsWriter; * - decodeCb(reader) - write a way to deserialize data with BcsReader; * - validateCb(data) - validate data - either return bool or throw an error * * @example * // our type would be a string that consists only of numbers * bcs.registerType('number_string', * (writer, data) => writer.writeVec(data, (w, el) => w.write8(el)), * (reader) => reader.readVec((r) => r.read8()).join(''), // read each value as u8 * (value) => /[0-9]+/.test(value) // test that it has at least one digit * ); * console.log(Array.from(bcs.ser('number_string', '12345').toBytes()) == [5,1,2,3,4,5]); * * @param name * @param encodeCb Callback to encode a value. * @param decodeCb Callback to decode a value. * @param validateCb Optional validator Callback to check type before serialization. */ registerType(typeName, encodeCb, decodeCb, validateCb = () => true) { const { name, params: generics } = this.parseTypeName(typeName); this.types.set(name, { encode(self, data, options, typeParams) { const typeMap = generics.reduce((acc, value, index) => { return Object.assign(acc, { [value]: typeParams[index] }); }, {}); return this._encodeRaw.call(self, new BcsWriter(options), data, typeParams, typeMap); }, decode(self, data, typeParams) { const typeMap = generics.reduce((acc, value, index) => { return Object.assign(acc, { [value]: typeParams[index] }); }, {}); return this._decodeRaw.call(self, new BcsReader(data), typeParams, typeMap); }, // these methods should always be used with caution as they require pre-defined // reader and writer and mainly exist to allow multi-field (de)serialization; _encodeRaw(writer, data, typeParams, typeMap) { if (validateCb(data)) { return encodeCb.call(this, writer, data, typeParams, typeMap); } else { throw new Error(`Validation failed for type ${name}, data: ${data}`); } }, _decodeRaw(reader, typeParams, typeMap) { return decodeCb.call(this, reader, typeParams, typeMap); } }); return this; } /** * Register an address type which is a sequence of U8s of specified length. * @example * bcs.registerAddressType('address', SUI_ADDRESS_LENGTH); * let addr = bcs.de('address', 'c3aca510c785c7094ac99aeaa1e69d493122444df50bb8a99dfa790c654a79af'); * * @param name Name of the address type. * @param length Byte length of the address. * @param encoding Encoding to use for the address type * @returns */ registerAddressType(name, length, encoding = "hex") { switch (encoding) { case "base64": return this.registerType( name, function encodeAddress(writer, data) { return fromB64(data).reduce((writer2, el) => writer2.write8(el), writer); }, function decodeAddress(reader) { return toB64(reader.readBytes(length)); } ); case "hex": return this.registerType( name, function encodeAddress(writer, data) { return fromHEX(data).reduce((writer2, el) => writer2.write8(el), writer); }, function decodeAddress(reader) { const hex = toHEX(reader.readBytes(length)).padStart(2 * length, "0"); const hash = toHEX((0, import_fast_sha256.default)(new TextEncoder().encode(hex))); return `BFC${hex}${hash.slice(0, 4)}`; } ); default: throw new Error("Unsupported encoding! Use either hex or base64"); } } /** * Register custom vector type inside the bcs. * * @example * bcs.registerVectorType('vector<T>'); // generic registration * let array = bcs.de('vector<u8>', '06010203040506', 'hex'); // [1,2,3,4,5,6]; * let again = bcs.ser('vector<u8>', [1,2,3,4,5,6]).toString('hex'); * * @param name Name of the type to register * @param elementType Optional name of the inner type of the vector * @return Returns self for chaining. */ registerVectorType(typeName) { let { name, params } = this.parseTypeName(typeName); if (params.length > 1) { throw new Error("Vector can have only one type parameter; got " + name); } return this.registerType( typeName, function encodeVector(writer, data, typeParams, typeMap) { return writer.writeVec(data, (writer2, el) => { let elementType = typeParams[0]; if (!elementType) { throw new Error(`Incorrect number of type parameters passed a to vector '${typeName}'`); } let { name: name2, params: params2 } = this.parseTypeName(elementType); if (this.hasType(name2)) { return this.getTypeInterface(name2)._encodeRaw.call(this, writer2, el, params2, typeMap); } if (!(name2 in typeMap)) { throw new Error( `Unable to find a matching type definition for ${name2} in vector; make sure you passed a generic` ); } let { name: innerName, params: innerParams } = this.parseTypeName(typeMap[name2]); return this.getTypeInterface(innerName)._encodeRaw.call( this, writer2, el, innerParams, typeMap ); }); }, function decodeVector(reader, typeParams, typeMap) { return reader.readVec((reader2) => { let elementType = typeParams[0]; if (!elementType) { throw new Error(`Incorrect number of type parameters passed to a vector '${typeName}'`); } let { name: name2, params: params2 } = this.parseTypeName(elementType); if (this.hasType(name2)) { return this.getTypeInterface(name2)._decodeRaw.call(this, reader2, params2, typeMap); } if (!(name2 in typeMap)) { throw new Error( `Unable to find a matching type definition for ${name2} in vector; make sure you passed a generic` ); } let { name: innerName, params: innerParams } = this.parseTypeName(typeMap[name2]); return this.getTypeInterface(innerName)._decodeRaw.call( this, reader2, innerParams, typeMap ); }); } ); } /** * Safe method to register a custom Move struct. The first argument is a name of the * struct which is only used on the FrontEnd and has no affect on serialization results, * and the second is a struct description passed as an Object. * * The description object MUST have the same order on all of the platforms (ie in Move * or in Rust). * * @example * // Move / Rust struct * // struct Coin { * // value: u64, * // owner: vector<u8>, // name // Vec<u8> in Rust * // is_locked: bool, * // } * * bcs.registerStructType('Coin', { * value: bcs.U64, * owner: bcs.STRING, * is_locked: bcs.BOOL * }); * * // Created in Rust with diem/bcs * // let rust_bcs_str = '80d1b105600000000e4269672057616c6c65742047757900'; * let rust_bcs_str = [ // using an Array here as BCS works with Uint8Array * 128, 209, 177, 5, 96, 0, 0, * 0, 14, 66, 105, 103, 32, 87, * 97, 108, 108, 101, 116, 32, 71, * 117, 121, 0 * ]; * * // Let's encode the value as well * let test_set = bcs.ser('Coin', { * owner: 'Big Wallet Guy', * value: '412412400000', * is_locked: false, * }); * * console.assert(Array.from(test_set.toBytes()) === rust_bcs_str, 'Whoopsie, result mismatch'); * * @param name Name of the type to register. * @param fields Fields of the struct. Must be in the correct order. * @return Returns BCS for chaining. */ registerStructType(typeName, fields) { for (let key in fields) { let internalName = this.tempKey(); let value = fields[key]; if (!Array.isArray(value) && typeof value !== "string") { fields[key] = internalName; this.registerStructType(internalName, value); } } let struct = Object.freeze(fields); let canonicalOrder = Object.keys(struct); let { name: structName, params: generics } = this.parseTypeName(typeName); return this.registerType( typeName, function encodeStruct(writer, data, typeParams, typeMap) { if (!data || data.constructor !== Object) { throw new Error(`Expected ${structName} to be an Object, got: ${data}`); } if (typeParams.length !== generics.length) { throw new Error( `Incorrect number of generic parameters passed; expected: ${generics.length}, got: ${typeParams.length}` ); } for (let key of canonicalOrder) { if (!(key in data)) { throw new Error(`Struct ${structName} requires field ${key}:${struct[key]}`); } const { name: fieldType, params: fieldParams } = this.parseTypeName( struct[key] ); if (!generics.includes(fieldType)) { this.getTypeInterface(fieldType)._encodeRaw.call( this, writer, data[key], fieldParams, typeMap ); } else { const paramIdx = generics.indexOf(fieldType); let { name, params } = this.parseTypeName(typeParams[paramIdx]); if (this.hasType(name)) { this.getTypeInterface(name)._encodeRaw.call( this, writer, data[key], params, typeMap ); continue; } if (!(name in typeMap)) { throw new Error( `Unable to find a matching type definition for ${name} in ${structName}; make sure you passed a generic` ); } let { name: innerName, params: innerParams } = this.parseTypeName(typeMap[name]); this.getTypeInterface(innerName)._encodeRaw.call( this, writer, data[key], innerParams, typeMap ); } } return writer; }, function decodeStruct(reader, typeParams, typeMap) { if (typeParams.length !== generics.length) { throw new Error( `Incorrect number of generic parameters passed; expected: ${generics.length}, got: ${typeParams.length}` ); } let result = {}; for (let key of canonicalOrder) { const { name: fieldName, params: fieldParams } = this.parseTypeName( struct[key] ); if (!generics.includes(fieldName)) { result[key] = this.getTypeInterface(fieldName)._decodeRaw.call( this, reader, fieldParams, typeMap ); } else { const paramIdx = generics.indexOf(fieldName); let { name, params } = this.parseTypeName(typeParams[paramIdx]); if (this.hasType(name)) { result[key] = this.getTypeInterface(name)._decodeRaw.call( this, reader, params, typeMap ); continue; } if (!(name in typeMap)) { throw new Error( `Unable to find a matching type definition for ${name} in ${structName}; make sure you passed a generic` ); } let { name: innerName, params: innerParams } = this.parseTypeName(typeMap[name]); result[key] = this.getTypeInterface(innerName)._decodeRaw.call( this, reader, innerParams, typeMap ); } } return result; } ); } /** * Safe method to register custom enum type where each invariant holds the value of another type. * @example * bcs.registerStructType('Coin', { value: 'u64' }); * bcs.registerEnumType('MyEnum', { * single: 'Coin', * multi: 'vector<Coin>', * empty: null * }); * * console.log( * bcs.de('MyEnum', 'AICWmAAAAAAA', 'base64'), // { single: { value: 10000000 } } * bcs.de('MyEnum', 'AQIBAAAAAAAAAAIAAAAAAAAA', 'base64') // { multi: [ { value: 1 }, { value: 2 } ] } * ) * * // and serialization * bcs.ser('MyEnum', { single: { value: 10000000 } }).toBytes(); * bcs.ser('MyEnum', { multi: [ { value: 1 }, { value: 2 } ] }); * * @param name * @param variants */ registerEnumType(typeName, variants) { for (let key in variants) { let internalName = this.tempKey(); let value = variants[key]; if (value !== null && !Array.isArray(value) && typeof value !== "string") { variants[key] = internalName; this.registerStructType(internalName, value); } } let struct = Object.freeze(variants); let canonicalOrder = Object.keys(struct); let { name, params: canonicalTypeParams } = this.parseTypeName(typeName); return this.registerType( typeName, function encodeEnum(writer, data, typeParams, typeMap) { if (!data) { throw new Error(`Unable to write enum "${name}", missing data. Received: "${data}"`); } if (typeof data !== "object") { throw new Error( `Incorrect data passed into enum "${name}", expected object with properties: "${canonicalOrder.join( " | " )}". Received: "${JSON.stringify(data)}"` ); } let key = Object.keys(data)[0]; if (key === void 0) { throw new Error(`Empty object passed as invariant of the enum "${name}"`); } let orderByte = canonicalOrder.indexOf(key); if (orderByte === -1) { throw new Error( `Unknown invariant of the enum "${name}", allowed values: "${canonicalOrder.join( " | " )}"; received "${key}"` ); } let invariant = canonicalOrder[orderByte]; let invariantType = struct[invariant]; writer.write8(orderByte); if (invariantType === null) { return writer; } let paramIndex = canonicalTypeParams.indexOf(invariantType); let typeOrParam = paramIndex === -1 ? invariantType : typeParams[paramIndex]; { let { name: name2, params } = this.parseTypeName(typeOrParam); return this.getTypeInterface(name2)._encodeRaw.call( this, writer, data[key], params, typeMap ); } }, function decodeEnum(reader, typeParams, typeMap) { let orderByte = reader.readULEB(); let invariant = canonicalOrder[orderByte]; let invariantType = struct[invariant]; if (orderByte === -1) { throw new Error( `Decoding type mismatch, expected enum "${name}" invariant index, received "${orderByte}"` ); } if (invariantType === null) { return { [invariant]: true }; } let paramIndex = canonicalTypeParams.indexOf(invariantType); let typeOrParam = paramIndex === -1 ? invariantType : typeParams[paramIndex]; { let { name: name2, params } = this.parseTypeName(typeOrParam); return { [invariant]: this.getTypeInterface(name2)._decodeRaw.call(this, reader, params, typeMap) }; } } ); } /** * Get a set of encoders/decoders for specific type. * Mainly used to define custom type de/serialization logic. * * @param type * @returns {TypeInterface} */ getTypeInterface(type) { let typeInterface = this.types.get(type); if (typeof typeInterface === "string") { let chain = []; while (typeof typeInterface === "string") { if (chain.includes(typeInterface)) { throw new Error(`Recursive definition found: ${chain.join(" -> ")} -> ${typeInterface}`); } chain.push(typeInterface); typeInterface = this.types.get(typeInterface); } } if (typeInterface === void 0) { throw new Error(`Type ${type} is not registered`); } return typeInterface; } /** * Parse a type name and get the type's generics. * @example * let { typeName, typeParams } = parseTypeName('Option<Coin<SUI>>'); * // typeName: Option * // typeParams: [ 'Coin<SUI>' ] * * @param name Name of the type to process * @returns Object with typeName and typeParams listed as Array */ parseTypeName(name) { if (Array.isArray(name)) { let [typeName2, ...params2] = name; return { name: typeName2, params: params2 }; } if (typeof name !== "string") { throw new Error(`Illegal type passed as a name of the type: ${name}`); } let [left, right] = this.schema.genericSeparators || ["<", ">"]; let l_bound = name.indexOf(left); let r_bound = Array.from(name).reverse().indexOf(right); if (l_bound === -1 && r_bound === -1) { return { name, params: [] }; } if (l_bound === -1 || r_bound === -1) { throw new Error(`Unclosed generic in name '${name}'`); } let typeName = name.slice(0, l_bound); let params = splitGenericParameters( name.slice(l_bound + 1, name.length - r_bound - 1), this.schema.genericSeparators ); return { name: typeName, params }; } }; // Prefefined types constants _BCS.U8 = "u8"; _BCS.U16 = "u16"; _BCS.U32 = "u32"; _BCS.U64 = "u64"; _BCS.U128 = "u128"; _BCS.U256 = "u256"; _BCS.BOOL = "bool"; _BCS.VECTOR = "vector"; _BCS.ADDRESS = "address"; _BCS.STRING = "string"; _BCS.HEX = "hex-string"; _BCS.BASE58 = "base58-string"; _BCS.BASE64 = "base64-string"; var BCS = _BCS; function encodeStr(data, encoding) { switch (encoding) { case "base58": return toB58(data); case "base64": return toB64(data); case "hex": return toHEX(data); default: throw new Error("Unsupported encoding, supported values are: base64, hex"); } } function decodeStr(data, encoding) { switch (encoding) { case "base58": return fromB58(data); case "base64": return fromB64(data); case "hex": return fromHEX(data); default: throw new Error("Unsupported encoding, supported values are: base64, hex"); } } function registerPrimitives(bcs) { bcs.registerType( BCS.U8, function(writer, data) { return writer.write8(data); }, function(reader) { return reader.read8(); }, (u8) => u8 < 256 ); bcs.registerType( BCS.U16, function(writer, data) { return writer.write16(data); }, function(reader) { return reader.read16(); }, (u16) => u16 < 65536 ); bcs.registerType( BCS.U32, function(writer, data) { return writer.write32(data); }, function(reader) { return reader.read32(); }, (u32) => u32 <= 4294967296n ); bcs.registerType( BCS.U64, function(writer, data) { return writer.write64(data); }, function(reader) { return reader.read64(); } ); bcs.registerType( BCS.U128, function(writer, data) { return writer.write128(data); }, function(reader) { return reader.read128(); } ); bcs.registerType( BCS.U256, function(writer, data) { return writer.write256(data); }, function(reader) { return reader.read256(); } ); bcs.registerType( BCS.BOOL, function(writer, data) { return writer.write8(data); }, function(reader) { return reader.read8().toString(10) === "1"; } ); bcs.registerType( BCS.STRING, function(writer, data) { return writer.writeVec(Array.from(data), (writer2, el) => writer2.write8(el.charCodeAt(0))); }, function(reader) { return reader.readVec((reader2) => reader2.read8()).map((el) => String.fromCharCode(Number(el))).join(""); }, (_str) => true ); bcs.registerType( BCS.HEX, function(writer, data) { return writer.writeVec(Array.from(fromHEX(data)), (writer2, el) => writer2.write8(el)); }, function(reader) { let bytes = reader.readVec((reader2) => reader2.read8()); return toHEX(new Uint8Array(bytes)); } ); bcs.registerType( BCS.BASE58, function(writer, data) { return writer.writeVec(Array.from(fromB58(data)), (writer2, el) => writer2.write8(el)); }, function(reader) { let bytes = reader.readVec((reader2) => reader2.read8()); return toB58(new Uint8Array(bytes)); } ); bcs.registerType( BCS.BASE64, function(writer, data) { return writer.writeVec(Array.from(fromB64(data)), (writer2, el) => writer2.write8(el)); }, function(reader) { let bytes = reader.readVec((reader2) => reader2.read8()); return toB64(new Uint8Array(bytes)); } ); } function getRustConfig() { return { genericSeparators: ["<", ">"], vectorType: "Vec", addressLength: SUI_ADDRESS_LENGTH, addressEncoding: "hex" }; } function getSuiMoveConfig() { return { genericSeparators: ["<", ">"], vectorType: "vector", addressLength: SUI_ADDRESS_LENGTH, addressEncoding: "hex" }; } function splitGenericParameters(str, genericSeparators = ["<", ">"]) { const [left, right] = genericSeparators; const tok = []; let word = ""; let nestedAngleBrackets = 0; for (let i = 0; i < str.length; i++) { const char = str[i]; if (char === left) { nestedAngleBrackets++; } if (char === right) { nestedAngleBrackets--; } if (nestedAngleBrackets === 0 && char === ",") { tok.push(word.trim()); word = ""; continue; } word += char; } tok.push(word.trim()); return tok; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { BCS, BcsReader, BcsWriter, decodeStr, encodeStr, fromB58, fromB64, fromHEX, getRustConfig, getSuiMoveConfig, registerPrimitives, splitGenericParameters, toB58, toB64, toHEX }); //# sourceMappingURL=index.js.map