New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@mysten/bcs

Package Overview
Dependencies
Maintainers
4
Versions
539
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mysten/bcs - npm Package Compare versions

Comparing version

to
0.0.0-experimental-20230928204950

dist/b58.d.ts

949

./dist/index.js

@@ -29,2 +29,20 @@ "use strict";

var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __accessCheck = (obj, member, msg) => {
if (!member.has(obj))
throw TypeError("Cannot " + msg);
};
var __privateGet = (obj, member, getter) => {
__accessCheck(obj, member, "read from private field");
return getter ? getter.call(obj) : member.get(obj);
};
var __privateAdd = (obj, member, value) => {
if (member.has(obj))
throw TypeError("Cannot add the same private member more than once");
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
};
var __privateSet = (obj, member, value, setter) => {
__accessCheck(obj, member, "write to private field");
setter ? setter.call(obj, value) : member.set(obj, value);
return value;
};

@@ -36,3 +54,6 @@ // src/index.ts

BcsReader: () => BcsReader,
BcsType: () => BcsType,
BcsWriter: () => BcsWriter,
SerializedBcs: () => SerializedBcs,
bcs: () => bcs,
decodeStr: () => decodeStr,

@@ -53,2 +74,7 @@ encodeStr: () => encodeStr,

// src/b58.ts
var import_bs58 = __toESM(require("bs58"));
var toB58 = (buffer) => import_bs58.default.encode(buffer);
var fromB58 = (str) => import_bs58.default.decode(str);
// src/b64.ts

@@ -104,17 +130,38 @@ function b64ToUint6(nChr) {

// src/index.ts
var import_bs58 = __toESM(require("bs58"));
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;
// src/uleb.ts
function ulebEncode(num) {
let arr = [];
let len = 0;
if (num === 0) {
return [0];
}
return result;
while (num > 0) {
arr[len] = num & 127;
if (num >>= 7) {
arr[len] |= 128;
}
len += 1;
}
return arr;
}
var toB58 = (buffer) => import_bs58.default.encode(buffer);
var fromB58 = (str) => import_bs58.default.decode(str);
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
};
}
// src/reader.ts
var BcsReader = class {

@@ -231,2 +278,53 @@ /**

};
// src/utils.ts
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 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;
}
// src/writer.ts
var BcsWriter = class {

@@ -372,35 +470,665 @@ constructor({ size = 1024, maxSize, allocateSize = 1024 } = {}) {

};
function ulebEncode(num) {
let arr = [];
let len = 0;
if (num === 0) {
return [0];
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;
}
while (num > 0) {
arr[len] = num & 127;
if (num >>= 7) {
arr[len] |= 128;
}
len += 1;
return result;
}
// src/bcs-type.ts
var _write, _serialize;
var _BcsType = class _BcsType {
constructor(options) {
__privateAdd(this, _write, void 0);
__privateAdd(this, _serialize, void 0);
this.name = options.name;
this.read = options.read;
this.serializedSize = options.serializedSize ?? (() => null);
__privateSet(this, _write, options.write);
__privateSet(this, _serialize, options.serialize ?? ((value, options2) => {
const writer = new BcsWriter({ size: this.serializedSize(value) ?? void 0, ...options2 });
__privateGet(this, _write).call(this, value, writer);
return writer.toBytes();
}));
this.validate = options.validate ?? (() => {
});
}
return arr;
write(value, writer) {
this.validate(value);
__privateGet(this, _write).call(this, value, writer);
}
serialize(value, options) {
this.validate(value);
return new SerializedBcs(this, __privateGet(this, _serialize).call(this, value, options));
}
parse(bytes) {
const reader = new BcsReader(bytes);
return this.read(reader);
}
transform({
name,
input,
output
}) {
return new _BcsType({
name: name ?? this.name,
read: (reader) => output(this.read(reader)),
write: (value, writer) => __privateGet(this, _write).call(this, input(value), writer),
serializedSize: (value) => this.serializedSize(input(value)),
serialize: (value, options) => __privateGet(this, _serialize).call(this, input(value), options),
validate: (value) => this.validate(input(value))
});
}
};
_write = new WeakMap();
_serialize = new WeakMap();
var BcsType = _BcsType;
var _schema, _bytes;
var SerializedBcs = class {
constructor(type, schema) {
__privateAdd(this, _schema, void 0);
__privateAdd(this, _bytes, void 0);
__privateSet(this, _schema, type);
__privateSet(this, _bytes, schema);
}
toBytes() {
return __privateGet(this, _bytes);
}
toHex() {
return toHEX(__privateGet(this, _bytes));
}
toBase64() {
return toB64(__privateGet(this, _bytes));
}
toBase58() {
return toB58(__privateGet(this, _bytes));
}
parse() {
return __privateGet(this, _schema).parse(__privateGet(this, _bytes));
}
};
_schema = new WeakMap();
_bytes = new WeakMap();
function fixedSizeBcsType({
size,
...options
}) {
return new BcsType({
...options,
serializedSize: () => size
});
}
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;
function uIntBcsType({
readMethod,
writeMethod,
...options
}) {
return fixedSizeBcsType({
...options,
read: (reader) => reader[readMethod](),
write: (value, writer) => writer[writeMethod](value),
validate: (value) => {
if (value < 0 || value > options.maxValue) {
throw new TypeError(
`Invalid ${options.name} value: ${value}. Expected value in range 0-${options.maxValue}`
);
}
options.validate?.(value);
}
shift += 7;
});
}
function bigUIntBcsType({
readMethod,
writeMethod,
...options
}) {
return fixedSizeBcsType({
...options,
read: (reader) => reader[readMethod](),
write: (value, writer) => writer[writeMethod](BigInt(value)),
validate: (val) => {
const value = BigInt(val);
if (value < 0 || value > options.maxValue) {
throw new TypeError(
`Invalid ${options.name} value: ${value}. Expected value in range 0-${options.maxValue}`
);
}
options.validate?.(value);
}
});
}
function dynamicSizeBcsType({
serialize,
...options
}) {
const type = new BcsType({
...options,
serialize,
write: (value, writer) => {
for (const byte of type.serialize(value).toBytes()) {
writer.write8(byte);
}
}
});
return type;
}
function stringLikeBcsType({
toBytes,
fromBytes,
...options
}) {
return new BcsType({
...options,
read: (reader) => {
const length = reader.readULEB();
const bytes = reader.readBytes(length);
return fromBytes(bytes);
},
write: (hex, writer) => {
const bytes = toBytes(hex);
writer.writeULEB(bytes.length);
for (let i = 0; i < bytes.length; i++) {
writer.write8(bytes[i]);
}
},
serialize: (value) => {
const bytes = toBytes(value);
const size = ulebEncode(bytes.length);
const result = new Uint8Array(size.length + bytes.length);
result.set(size, 0);
result.set(bytes, size.length);
return result;
},
validate: (value) => {
if (typeof value !== "string") {
throw new TypeError(`Invalid ${options.name} value: ${value}. Expected string`);
}
options.validate?.(value);
}
});
}
function lazyBcsType(cb) {
let lazyType = null;
function getType() {
if (!lazyType) {
lazyType = cb();
}
return lazyType;
}
return {
value: total,
length: len
};
return new BcsType({
name: "lazy",
read: (data) => getType().read(data),
serializedSize: (value) => getType().serializedSize(value),
write: (value, writer) => getType().write(value, writer),
serialize: (value, options) => getType().serialize(value, options).toBytes()
});
}
// src/bcs.ts
var bcs = {
/**
* Creates a BcsType that can be used to read and write an 8-bit unsigned integer.
* @example
* bcs.u8().serialize(255).toBytes() // Uint8Array [ 255 ]
*/
u8(options) {
return uIntBcsType({
name: "u8",
readMethod: "read8",
writeMethod: "write8",
size: 1,
maxValue: 2 ** 8 - 1,
...options
});
},
/**
* Creates a BcsType that can be used to read and write a 16-bit unsigned integer.
* @example
* bcs.u16().serialize(65535).toBytes() // Uint8Array [ 255, 255 ]
*/
u16(options) {
return uIntBcsType({
name: "u16",
readMethod: "read16",
writeMethod: "write16",
size: 2,
maxValue: 2 ** 16 - 1,
...options
});
},
/**
* Creates a BcsType that can be used to read and write a 32-bit unsigned integer.
* @example
* bcs.u32().serialize(4294967295).toBytes() // Uint8Array [ 255, 255, 255, 255 ]
*/
u32(options) {
return uIntBcsType({
name: "u32",
readMethod: "read32",
writeMethod: "write32",
size: 4,
maxValue: 2 ** 32 - 1,
...options
});
},
/**
* Creates a BcsType that can be used to read and write a 64-bit unsigned integer.
* @example
* bcs.u64().serialize(1).toBytes() // Uint8Array [ 1, 0, 0, 0, 0, 0, 0, 0 ]
*/
u64(options) {
return bigUIntBcsType({
name: "u64",
readMethod: "read64",
writeMethod: "write64",
size: 8,
maxValue: 2n ** 64n - 1n,
...options
});
},
/**
* Creates a BcsType that can be used to read and write a 128-bit unsigned integer.
* @example
* bcs.u128().serialize(1).toBytes() // Uint8Array [ 1, ..., 0 ]
*/
u128(options) {
return bigUIntBcsType({
name: "u128",
readMethod: "read128",
writeMethod: "write128",
size: 16,
maxValue: 2n ** 128n - 1n,
...options
});
},
/**
* Creates a BcsType that can be used to read and write a 256-bit unsigned integer.
* @example
* bcs.u256().serialize(1).toBytes() // Uint8Array [ 1, ..., 0 ]
*/
u256(options) {
return bigUIntBcsType({
name: "u256",
readMethod: "read256",
writeMethod: "write256",
size: 32,
maxValue: 2n ** 256n - 1n,
...options
});
},
/**
* Creates a BcsType that can be used to read and write boolean values.
* @example
* bcs.bool().serialize(true).toBytes() // Uint8Array [ 1 ]
*/
bool(options) {
return fixedSizeBcsType({
name: "bool",
size: 1,
read: (reader) => reader.read8() === 1,
write: (value, writer) => writer.write8(value ? 1 : 0),
...options,
validate: (value) => {
options?.validate?.(value);
if (typeof value !== "boolean") {
throw new TypeError(`Expected boolean, found ${typeof value}`);
}
}
});
},
/**
* Creates a BcsType that can be used to read and write unsigned LEB encoded integers
* @example
*
*/
uleb128(options) {
return dynamicSizeBcsType({
name: "uleb128",
read: (reader) => reader.readULEB(),
serialize: (value) => {
return Uint8Array.from(ulebEncode(value));
},
...options
});
},
/**
* Creates a BcsType representing a fixed length byte array
* @param size The number of bytes this types represents
* @example
* bcs.bytes(3).serialize(new Uint8Array([1, 2, 3])).toBytes() // Uint8Array [1, 2, 3]
*/
bytes(size, options) {
return fixedSizeBcsType({
name: `bytes[${size}]`,
size,
read: (reader) => reader.readBytes(size),
write: (value, writer) => {
for (let i = 0; i < size; i++) {
writer.write8(value[i] ?? 0);
}
},
...options,
validate: (value) => {
options?.validate?.(value);
if (!("length" in value)) {
throw new TypeError(`Expected array, found ${typeof value}`);
}
if (value.length !== size) {
throw new TypeError(`Expected array of length ${size}, found ${value.length}`);
}
}
});
},
/**
* Creates a BcsType that can ser/de string values. Strings will be UTF-8 encoded
* @example
* bcs.string().serialize('a').toBytes() // Uint8Array [ 1, 97 ]
*/
string(options) {
return stringLikeBcsType({
name: "string",
toBytes: (value) => new TextEncoder().encode(value),
fromBytes: (bytes) => new TextDecoder().decode(bytes),
...options
});
},
/**
* Creates a BcsType that represents a fixed length array of a given type
* @param size The number of elements in the array
* @param type The BcsType of each element in the array
* @example
* bcs.fixedArray(3, bcs.u8()).serialize([1, 2, 3]).toBytes() // Uint8Array [ 1, 2, 3 ]
*/
fixedArray(size, type, options) {
return new BcsType({
name: `${type.name}[${size}]`,
read: (reader) => {
const result = new Array(size);
for (let i = 0; i < size; i++) {
result[i] = type.read(reader);
}
return result;
},
write: (value, writer) => {
for (const item of value) {
type.write(item, writer);
}
},
...options,
validate: (value) => {
options?.validate?.(value);
if (!("length" in value)) {
throw new TypeError(`Expected array, found ${typeof value}`);
}
if (value.length !== size) {
throw new TypeError(`Expected array of length ${size}, found ${value.length}`);
}
}
});
},
/**
* Creates a BcsType representing an optional value
* @param type The BcsType of the optional value
* @example
* bcs.option(bcs.u8()).serialize(null).toBytes() // Uint8Array [ 0 ]
* bcs.option(bcs.u8()).serialize(1).toBytes() // Uint8Array [ 1, 1 ]
*/
option(type) {
return bcs.enum(`Option<${type.name}>`, {
None: null,
Some: type
}).transform({
input: (value) => {
if (value == null) {
return { None: true };
}
return { Some: value };
},
output: (value) => {
if ("Some" in value) {
return value.Some;
}
return null;
}
});
},
/**
* Creates a BcsType representing a variable length vector of a given type
* @param type The BcsType of each element in the vector
*
* @example
* bcs.vector(bcs.u8()).toBytes([1, 2, 3]) // Uint8Array [ 3, 1, 2, 3 ]
*/
vector(type, options) {
return new BcsType({
name: `vector<${type.name}>`,
read: (reader) => {
const length = reader.readULEB();
const result = new Array(length);
for (let i = 0; i < length; i++) {
result[i] = type.read(reader);
}
return result;
},
write: (value, writer) => {
writer.writeULEB(value.length);
for (const item of value) {
type.write(item, writer);
}
},
...options,
validate: (value) => {
options?.validate?.(value);
if (!("length" in value)) {
throw new TypeError(`Expected array, found ${typeof value}`);
}
}
});
},
/**
* Creates a BcsType representing a tuple of a given set of types
* @param types The BcsTypes for each element in the tuple
*
* @example
* const tuple = bcs.tuple([bcs.u8(), bcs.string(), bcs.bool()])
* tuple.serialize([1, 'a', true]).toBytes() // Uint8Array [ 1, 1, 97, 1 ]
*/
tuple(types, options) {
return new BcsType({
name: `(${types.map((t) => t.name).join(", ")})`,
serializedSize: (values) => {
let total = 0;
for (let i = 0; i < types.length; i++) {
const size = types[i].serializedSize(values[i]);
if (size == null) {
return null;
}
total += size;
}
return total;
},
read: (reader) => {
const result = [];
for (const type of types) {
result.push(type.read(reader));
}
return result;
},
write: (value, writer) => {
for (let i = 0; i < types.length; i++) {
types[i].write(value[i], writer);
}
},
...options,
validate: (value) => {
options?.validate?.(value);
if (!Array.isArray(value)) {
throw new TypeError(`Expected array, found ${typeof value}`);
}
if (value.length !== types.length) {
throw new TypeError(`Expected array of length ${types.length}, found ${value.length}`);
}
}
});
},
/**
* Creates a BcsType representing a struct of a given set of fields
* @param name The name of the struct
* @param fields The fields of the struct. The order of the fields affects how data is serialized and deserialized
*
* @example
* const struct = bcs.struct('MyStruct', {
* a: bcs.u8(),
* b: bcs.string(),
* })
* struct.serialize({ a: 1, b: 'a' }).toBytes() // Uint8Array [ 1, 1, 97 ]
*/
struct(name, fields, options) {
const canonicalOrder = Object.entries(fields);
return new BcsType({
name,
serializedSize: (values) => {
let total = 0;
for (const [field, type] of canonicalOrder) {
const size = type.serializedSize(values[field]);
if (size == null) {
return null;
}
total += size;
}
return total;
},
read: (reader) => {
const result = {};
for (const [field, type] of canonicalOrder) {
result[field] = type.read(reader);
}
return result;
},
write: (value, writer) => {
for (const [field, type] of canonicalOrder) {
type.write(value[field], writer);
}
},
...options,
validate: (value) => {
options?.validate?.(value);
if (typeof value !== "object" || value == null) {
throw new TypeError(`Expected object, found ${typeof value}`);
}
}
});
},
/**
* Creates a BcsType representing an enum of a given set of options
* @param name The name of the enum
* @param values The values of the enum. The order of the values affects how data is serialized and deserialized.
* null can be used to represent a variant with no data.
*
* @example
* const enum = bcs.enum('MyEnum', {
* A: bcs.u8(),
* B: bcs.string(),
* C: null,
* })
* enum.serialize({ A: 1 }).toBytes() // Uint8Array [ 0, 1 ]
* enum.serialize({ B: 'a' }).toBytes() // Uint8Array [ 1, 1, 97 ]
* enum.serialize({ C: true }).toBytes() // Uint8Array [ 2 ]
*/
enum(name, values, options) {
const canonicalOrder = Object.entries(values);
return new BcsType({
name,
read: (reader) => {
const index = reader.readULEB();
const [name2, type] = canonicalOrder[index];
return {
[name2]: type?.read(reader) ?? true
};
},
write: (value, writer) => {
const [name2, val] = Object.entries(value)[0];
for (let i = 0; i < canonicalOrder.length; i++) {
const [optionName, optionType] = canonicalOrder[i];
if (optionName === name2) {
writer.writeULEB(i);
optionType?.write(val, writer);
return;
}
}
},
...options,
validate: (value) => {
options?.validate?.(value);
if (typeof value !== "object" || value == null) {
throw new TypeError(`Expected object, found ${typeof value}`);
}
const keys = Object.keys(value);
if (keys.length !== 1) {
throw new TypeError(`Expected object with one key, found ${keys.length}`);
}
const [name2] = keys;
if (!Object.hasOwn(values, name2)) {
throw new TypeError(`Invalid enum variant ${name2}`);
}
}
});
},
/**
* Creates a BcsType representing a map of a given key and value type
* @param keyType The BcsType of the key
* @param valueType The BcsType of the value
* @example
* const map = bcs.map(bcs.u8(), bcs.string())
* map.serialize(new Map([[2, 'a']])).toBytes() // Uint8Array [ 1, 2, 1, 97 ]
*/
map(keyType, valueType) {
return bcs.vector(bcs.tuple([keyType, valueType])).transform({
name: `Map<${keyType.name}, ${valueType.name}>`,
input: (value) => {
return [...value.entries()];
},
output: (value) => {
const result = /* @__PURE__ */ new Map();
for (const [key, val] of value) {
result.set(key, val);
}
return result;
}
});
},
/**
* Creates a helper function representing a generic type. This method returns
* a function that can be used to create concrete version of the generic type.
* @param names The names of the generic parameters
* @param cb A callback that returns the generic type
* @example
* const MyStruct = bcs.generic(['T'], (T) => bcs.struct('MyStruct', { inner: T }))
* MyStruct(bcs.u8()).serialize({ inner: 1 }).toBytes() // Uint8Array [ 1 ]
* MyStruct(bcs.string()).serialize({ inner: 'a' }).toBytes() // Uint8Array [ 1, 97 ]
*/
generic(names, cb) {
return (...types) => {
return cb(...types).transform({
name: `${cb.name}<${types.map((t) => t.name).join(", ")}>`,
input: (value) => value,
output: (value) => value
});
};
},
/**
* Creates a BcsType that wraps another BcsType which is lazily evaluated. This is useful for creating recursive types.
* @param cb A callback that returns the BcsType
*/
lazy(cb) {
return lazyBcsType(cb);
}
};
// src/legacy-registry.ts
var SUI_ADDRESS_LENGTH = 32;
var _BCS = class _BCS {

@@ -602,2 +1330,67 @@ /**

/**
* Method to register BcsType instances to the registry
* Types are registered with a callback that provides BcsType instances for each generic
* passed to the type.
*
* - createType(...generics) - Return a BcsType instance
*
* @example
* // our type would be a string that consists only of numbers
* bcs.registerType('Box<T>', (T) => {
* return bcs.struct({
* value: T
* });
* });
* console.log(Array.from(bcs.ser('Box<string>', '12345').toBytes()) == [5,1,2,3,4,5]);
*
* @param name
* @param createType a Callback to create the BcsType with any passed in generics
*/
registerBcsType(typeName, createType) {
this.registerType(
typeName,
(writer, data, typeParams) => {
const generics = typeParams.map(
(param) => new BcsType({
name: String(param),
write: (data2, writer2) => {
const { name, params } = this.parseTypeName(param);
const typeInterface = this.getTypeInterface(name);
const typeMap = params.reduce((acc, value, index) => {
return Object.assign(acc, { [value]: typeParams[index] });
}, {});
return typeInterface._encodeRaw.call(this, writer2, data2, params, typeMap);
},
read: () => {
throw new Error("Not implemented");
}
})
);
createType(...generics).write(data, writer);
return writer;
},
(reader, typeParams) => {
const generics = typeParams.map(
(param) => new BcsType({
name: String(param),
write: (data, writer) => {
throw new Error("Not implemented");
},
read: (reader2) => {
const { name, params } = this.parseTypeName(param);
const typeInterface = this.getTypeInterface(name);
const typeMap = params.reduce((acc, value, index) => {
return Object.assign(acc, { [value]: typeParams[index] });
}, {});
return typeInterface._decodeRaw.call(this, reader2, params, typeMap);
}
})
);
return createType(...generics).read(reader);
}
);
return this;
}
/**
* Register an address type which is a sequence of U8s of specified length.

@@ -1031,3 +1824,3 @@ * @example

};
// Prefefined types constants
// Predefined types constants
_BCS.U8 = "u8";

@@ -1047,28 +1840,4 @@ _BCS.U16 = "u16";

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(
function registerPrimitives(bcs2) {
bcs2.registerType(
BCS.U8,

@@ -1083,3 +1852,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.U16,

@@ -1094,3 +1863,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.U32,

@@ -1105,3 +1874,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.U64,

@@ -1115,3 +1884,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.U128,

@@ -1125,3 +1894,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.U256,

@@ -1135,3 +1904,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.BOOL,

@@ -1145,3 +1914,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.STRING,

@@ -1156,3 +1925,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.HEX,

@@ -1167,3 +1936,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.BASE58,

@@ -1178,3 +1947,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.BASE64,

@@ -1206,25 +1975,2 @@ function(writer, data) {

}
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:

@@ -1234,3 +1980,6 @@ 0 && (module.exports = {

BcsReader,
BcsType,
BcsWriter,
SerializedBcs,
bcs,
decodeStr,

@@ -1237,0 +1986,0 @@ encodeStr,

# Change Log
## 0.0.0-experimental-20230907191300
## 0.0.0-experimental-20230928204950
### Minor Changes
- 1bc430161: Add new type-safe schema builder. See https://sui-typescript-docs.vercel.app/bcs for updated documentation
## 0.7.4
### Patch Changes

@@ -6,0 +12,0 @@

578

dist/index.d.ts

@@ -1,567 +0,11 @@

import { toB64, fromB64 } from './b64';
import { toHEX, fromHEX } from './hex';
declare const toB58: (buffer: Uint8Array) => string;
declare const fromB58: (str: string) => Uint8Array;
export { toB58, fromB58, toB64, fromB64, fromHEX, toHEX };
/**
* Supported encodings.
* Used in `Reader.toString()` as well as in `decodeStr` and `encodeStr` functions.
*/
export type Encoding = 'base58' | 'base64' | 'hex';
/**
* Allows for array definitions for names.
* @example
* ```
* bcs.registerStructType(['vector', BCS.STRING], ...);
* // equals
* bcs.registerStructType('vector<string>', ...);
* ```
*/
export type TypeName = string | [string, ...(TypeName | string)[]];
/**
* Class used for reading BCS data chunk by chunk. Meant to be used
* by some wrapper, which will make sure that data is valid and is
* matching the desired format.
*
* @example
* // data for this example is:
* // { a: u8, b: u32, c: bool, d: u64 }
*
* let reader = new BcsReader("647f1a060001ffffe7890423c78a050102030405");
* let field1 = reader.read8();
* let field2 = reader.read32();
* let field3 = reader.read8() === '1'; // bool
* let field4 = reader.read64();
* // ....
*
* Reading vectors is another deal in bcs. To read a vector, you first need to read
* its length using {@link readULEB}. Here's an example:
* @example
* // data encoded: { field: [1, 2, 3, 4, 5] }
* let reader = new BcsReader("050102030405");
* let vec_length = reader.readULEB();
* let elements = [];
* for (let i = 0; i < vec_length; i++) {
* elements.push(reader.read8());
* }
* console.log(elements); // [1,2,3,4,5]
*
* @param {String} data HEX-encoded data (serialized BCS)
*/
export declare class BcsReader {
private dataView;
private bytePosition;
/**
* @param {Uint8Array} data Data to use as a buffer.
*/
constructor(data: Uint8Array);
/**
* Shift current cursor position by `bytes`.
*
* @param {Number} bytes Number of bytes to
* @returns {this} Self for possible chaining.
*/
shift(bytes: number): this;
/**
* Read U8 value from the buffer and shift cursor by 1.
* @returns
*/
read8(): number;
/**
* Read U16 value from the buffer and shift cursor by 2.
* @returns
*/
read16(): number;
/**
* Read U32 value from the buffer and shift cursor by 4.
* @returns
*/
read32(): number;
/**
* Read U64 value from the buffer and shift cursor by 8.
* @returns
*/
read64(): string;
/**
* Read U128 value from the buffer and shift cursor by 16.
*/
read128(): string;
/**
* Read U128 value from the buffer and shift cursor by 32.
* @returns
*/
read256(): string;
/**
* Read `num` number of bytes from the buffer and shift cursor by `num`.
* @param num Number of bytes to read.
*/
readBytes(num: number): Uint8Array;
/**
* Read ULEB value - an integer of varying size. Used for enum indexes and
* vector lengths.
* @returns {Number} The ULEB value.
*/
readULEB(): number;
/**
* 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: (reader: BcsReader, i: number, length: number) => any): any[];
}
/**
* Class used to write BCS data into a buffer. Initializer requires
* some size of a buffer to init; default value for this buffer is 1KB.
*
* Most methods are chainable, so it is possible to write them in one go.
*
* @example
* let serialized = new BcsWriter()
* .write8(10)
* .write32(1000000)
* .write64(10000001000000)
* .hex();
*/
interface BcsWriterOptions {
/** The initial size (in bytes) of the buffer tht will be allocated */
size?: number;
/** The maximum size (in bytes) that the buffer is allowed to grow to */
maxSize?: number;
/** The amount of bytes that will be allocated whenever additional memory is required */
allocateSize?: number;
}
export declare class BcsWriter {
private dataView;
private bytePosition;
private size;
private maxSize;
private allocateSize;
constructor({ size, maxSize, allocateSize }?: BcsWriterOptions);
private ensureSizeOrGrow;
/**
* Shift current cursor position by `bytes`.
*
* @param {Number} bytes Number of bytes to
* @returns {this} Self for possible chaining.
*/
shift(bytes: number): this;
/**
* Write a U8 value into a buffer and shift cursor position by 1.
* @param {Number} value Value to write.
* @returns {this}
*/
write8(value: number | bigint): this;
/**
* Write a U16 value into a buffer and shift cursor position by 2.
* @param {Number} value Value to write.
* @returns {this}
*/
write16(value: number | bigint): this;
/**
* Write a U32 value into a buffer and shift cursor position by 4.
* @param {Number} value Value to write.
* @returns {this}
*/
write32(value: number | bigint): this;
/**
* Write a U64 value into a buffer and shift cursor position by 8.
* @param {bigint} value Value to write.
* @returns {this}
*/
write64(value: number | bigint): this;
/**
* Write a U128 value into a buffer and shift cursor position by 16.
*
* @param {bigint} value Value to write.
* @returns {this}
*/
write128(value: number | bigint): this;
/**
* Write a U256 value into a buffer and shift cursor position by 16.
*
* @param {bigint} value Value to write.
* @returns {this}
*/
write256(value: number | bigint): 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: number): 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: any[], cb: (writer: BcsWriter, el: any, i: number, len: number) => void): this;
/**
* Adds support for iterations over the object.
* @returns {Uint8Array}
*/
[Symbol.iterator](): Iterator<number, Iterable<number>>;
/**
* Get underlying buffer taking only value bytes (in case initial buffer size was bigger).
* @returns {Uint8Array} Resulting bcs.
*/
toBytes(): Uint8Array;
/**
* Represent data as 'hex' or 'base64'
* @param encoding Encoding to use: 'base64' or 'hex'
*/
toString(encoding: Encoding): string;
}
/**
* Set of methods that allows data encoding/decoding as standalone
* BCS value or a part of a composed structure/vector.
*/
export interface TypeInterface {
encode: (self: BCS, data: any, options: BcsWriterOptions | undefined, typeParams: TypeName[]) => BcsWriter;
decode: (self: BCS, data: Uint8Array, typeParams: TypeName[]) => any;
_encodeRaw: (writer: BcsWriter, data: any, typeParams: TypeName[], typeMap: {
[key: string]: TypeName;
}) => BcsWriter;
_decodeRaw: (reader: BcsReader, typeParams: TypeName[], typeMap: {
[key: string]: TypeName;
}) => any;
}
/**
* Struct type definition. Used as input format in BcsConfig.types
* as well as an argument type for `bcs.registerStructType`.
*/
export type StructTypeDefinition = {
[key: string]: TypeName | StructTypeDefinition;
};
/**
* Enum type definition. Used as input format in BcsConfig.types
* as well as an argument type for `bcs.registerEnumType`.
*
* Value can be either `string` when invariant has a type or `null`
* when invariant is empty.
*
* @example
* bcs.registerEnumType('Option<T>', {
* some: 'T',
* none: null
* });
*/
export type EnumTypeDefinition = {
[key: string]: TypeName | StructTypeDefinition | null;
};
/**
* Configuration that is passed into BCS constructor.
*/
export type BcsConfig = {
/**
* Defines type name for the vector / array type.
* In Move: `vector<T>` or `vector`.
*/
vectorType: string;
/**
* Address length. Varies depending on a platform and
* has to be specified for the `address` type.
*/
addressLength: number;
/**
* Custom encoding for address. Supported values are
* either 'hex' or 'base64'.
*/
addressEncoding?: 'hex' | 'base64';
/**
* Opening and closing symbol for type parameters. Can be
* any pair of symbols (eg `['(', ')']`); default value follows
* Rust and Move: `<` and `>`.
*/
genericSeparators?: [string, string];
/**
* Type definitions for the BCS. This field allows spawning
* BCS instance from JSON or another prepared configuration.
* Optional.
*/
types?: {
structs?: {
[key: string]: StructTypeDefinition;
};
enums?: {
[key: string]: EnumTypeDefinition;
};
aliases?: {
[key: string]: string;
};
};
/**
* Whether to auto-register primitive types on launch.
*/
withPrimitives?: boolean;
};
/**
* BCS implementation for Move types and few additional built-ins.
*/
export declare class BCS {
static readonly U8: string;
static readonly U16: string;
static readonly U32: string;
static readonly U64: string;
static readonly U128: string;
static readonly U256: string;
static readonly BOOL: string;
static readonly VECTOR: string;
static readonly ADDRESS: string;
static readonly STRING: string;
static readonly HEX: string;
static readonly BASE58: string;
static readonly BASE64: string;
/**
* 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.
*/
types: Map<string, TypeInterface | string>;
/**
* Stored BcsConfig for the current instance of BCS.
*/
protected schema: BcsConfig;
/**
* Count temp keys to generate a new one when requested.
*/
protected counter: number;
/**
* Name of the key to use for temporary struct definitions.
* Returns a temp key + index (for a case when multiple temp
* structs are processed).
*/
private tempKey;
/**
* 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: BcsConfig | BCS);
/**
* 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: TypeName | StructTypeDefinition, data: any, options?: BcsWriterOptions): BcsWriter;
/**
* 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: TypeName | StructTypeDefinition, data: Uint8Array | string, encoding?: Encoding): any;
/**
* Check whether a `TypeInterface` has been loaded for a `type`.
* @param type Name of the type to check.
* @returns
*/
hasType(type: string): boolean;
/**
* 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: string, forType: string): BCS;
/**
* 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: TypeName, encodeCb: (writer: BcsWriter, data: any, typeParams: TypeName[], typeMap: {
[key: string]: TypeName;
}) => BcsWriter, decodeCb: (reader: BcsReader, typeParams: TypeName[], typeMap: {
[key: string]: TypeName;
}) => any, validateCb?: (data: any) => boolean): BCS;
/**
* 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: string, length: number, encoding?: Encoding | void): BCS;
/**
* 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.
*/
private registerVectorType;
/**
* 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: TypeName, fields: StructTypeDefinition): BCS;
/**
* 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: TypeName, variants: EnumTypeDefinition): BCS;
/**
* Get a set of encoders/decoders for specific type.
* Mainly used to define custom type de/serialization logic.
*
* @param type
* @returns {TypeInterface}
*/
getTypeInterface(type: string): 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: TypeName): {
name: string;
params: TypeName[];
};
}
/**
* Encode data with either `hex` or `base64`.
*
* @param {Uint8Array} data Data to encode.
* @param {String} encoding Encoding to use: base64 or hex
* @return {String} Encoded value.
*/
export declare function encodeStr(data: Uint8Array, encoding: Encoding): string;
/**
* Decode either `base64` or `hex` data.
*
* @param {String} data Data to encode.
* @param {String} encoding Encoding to use: base64 or hex
* @return {Uint8Array} Encoded value.
*/
export declare function decodeStr(data: string, encoding: Encoding): Uint8Array;
/**
* Register the base set of primitive and common types.
* Is called in the `BCS` constructor automatically but can
* be ignored if the `withPrimitives` argument is not set.
*/
export declare function registerPrimitives(bcs: BCS): void;
export declare function getRustConfig(): BcsConfig;
export declare function getSuiMoveConfig(): BcsConfig;
export declare function splitGenericParameters(str: string, genericSeparators?: [string, string]): string[];
import { fromB58, toB58 } from './b58.js';
import { fromB64, toB64 } from './b64.js';
import { BcsType, BcsTypeOptions, SerializedBcs } from './bcs-type.js';
import { bcs } from './bcs.js';
import { fromHEX, toHEX } from './hex.js';
import { BcsReader } from './reader.js';
import { type InferBcsInput, type InferBcsType } from './types.js';
import { decodeStr, encodeStr, splitGenericParameters } from './utils.js';
import { BcsWriter, BcsWriterOptions } from './writer.js';
export * from './legacy-registry.js';
export { bcs, BcsType, type BcsTypeOptions, SerializedBcs, toB58, fromB58, toB64, fromB64, fromHEX, toHEX, encodeStr, decodeStr, splitGenericParameters, BcsReader, BcsWriter, type BcsWriterOptions, type InferBcsInput, type InferBcsType, };

@@ -29,2 +29,20 @@ "use strict";

var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __accessCheck = (obj, member, msg) => {
if (!member.has(obj))
throw TypeError("Cannot " + msg);
};
var __privateGet = (obj, member, getter) => {
__accessCheck(obj, member, "read from private field");
return getter ? getter.call(obj) : member.get(obj);
};
var __privateAdd = (obj, member, value) => {
if (member.has(obj))
throw TypeError("Cannot add the same private member more than once");
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
};
var __privateSet = (obj, member, value, setter) => {
__accessCheck(obj, member, "write to private field");
setter ? setter.call(obj, value) : member.set(obj, value);
return value;
};

@@ -36,3 +54,6 @@ // src/index.ts

BcsReader: () => BcsReader,
BcsType: () => BcsType,
BcsWriter: () => BcsWriter,
SerializedBcs: () => SerializedBcs,
bcs: () => bcs,
decodeStr: () => decodeStr,

@@ -53,2 +74,7 @@ encodeStr: () => encodeStr,

// src/b58.ts
var import_bs58 = __toESM(require("bs58"));
var toB58 = (buffer) => import_bs58.default.encode(buffer);
var fromB58 = (str) => import_bs58.default.decode(str);
// src/b64.ts

@@ -104,17 +130,38 @@ function b64ToUint6(nChr) {

// src/index.ts
var import_bs58 = __toESM(require("bs58"));
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;
// src/uleb.ts
function ulebEncode(num) {
let arr = [];
let len = 0;
if (num === 0) {
return [0];
}
return result;
while (num > 0) {
arr[len] = num & 127;
if (num >>= 7) {
arr[len] |= 128;
}
len += 1;
}
return arr;
}
var toB58 = (buffer) => import_bs58.default.encode(buffer);
var fromB58 = (str) => import_bs58.default.decode(str);
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
};
}
// src/reader.ts
var BcsReader = class {

@@ -231,2 +278,53 @@ /**

};
// src/utils.ts
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 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;
}
// src/writer.ts
var BcsWriter = class {

@@ -372,35 +470,665 @@ constructor({ size = 1024, maxSize, allocateSize = 1024 } = {}) {

};
function ulebEncode(num) {
let arr = [];
let len = 0;
if (num === 0) {
return [0];
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;
}
while (num > 0) {
arr[len] = num & 127;
if (num >>= 7) {
arr[len] |= 128;
}
len += 1;
return result;
}
// src/bcs-type.ts
var _write, _serialize;
var _BcsType = class _BcsType {
constructor(options) {
__privateAdd(this, _write, void 0);
__privateAdd(this, _serialize, void 0);
this.name = options.name;
this.read = options.read;
this.serializedSize = options.serializedSize ?? (() => null);
__privateSet(this, _write, options.write);
__privateSet(this, _serialize, options.serialize ?? ((value, options2) => {
const writer = new BcsWriter({ size: this.serializedSize(value) ?? void 0, ...options2 });
__privateGet(this, _write).call(this, value, writer);
return writer.toBytes();
}));
this.validate = options.validate ?? (() => {
});
}
return arr;
write(value, writer) {
this.validate(value);
__privateGet(this, _write).call(this, value, writer);
}
serialize(value, options) {
this.validate(value);
return new SerializedBcs(this, __privateGet(this, _serialize).call(this, value, options));
}
parse(bytes) {
const reader = new BcsReader(bytes);
return this.read(reader);
}
transform({
name,
input,
output
}) {
return new _BcsType({
name: name ?? this.name,
read: (reader) => output(this.read(reader)),
write: (value, writer) => __privateGet(this, _write).call(this, input(value), writer),
serializedSize: (value) => this.serializedSize(input(value)),
serialize: (value, options) => __privateGet(this, _serialize).call(this, input(value), options),
validate: (value) => this.validate(input(value))
});
}
};
_write = new WeakMap();
_serialize = new WeakMap();
var BcsType = _BcsType;
var _schema, _bytes;
var SerializedBcs = class {
constructor(type, schema) {
__privateAdd(this, _schema, void 0);
__privateAdd(this, _bytes, void 0);
__privateSet(this, _schema, type);
__privateSet(this, _bytes, schema);
}
toBytes() {
return __privateGet(this, _bytes);
}
toHex() {
return toHEX(__privateGet(this, _bytes));
}
toBase64() {
return toB64(__privateGet(this, _bytes));
}
toBase58() {
return toB58(__privateGet(this, _bytes));
}
parse() {
return __privateGet(this, _schema).parse(__privateGet(this, _bytes));
}
};
_schema = new WeakMap();
_bytes = new WeakMap();
function fixedSizeBcsType({
size,
...options
}) {
return new BcsType({
...options,
serializedSize: () => size
});
}
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;
function uIntBcsType({
readMethod,
writeMethod,
...options
}) {
return fixedSizeBcsType({
...options,
read: (reader) => reader[readMethod](),
write: (value, writer) => writer[writeMethod](value),
validate: (value) => {
if (value < 0 || value > options.maxValue) {
throw new TypeError(
`Invalid ${options.name} value: ${value}. Expected value in range 0-${options.maxValue}`
);
}
options.validate?.(value);
}
shift += 7;
});
}
function bigUIntBcsType({
readMethod,
writeMethod,
...options
}) {
return fixedSizeBcsType({
...options,
read: (reader) => reader[readMethod](),
write: (value, writer) => writer[writeMethod](BigInt(value)),
validate: (val) => {
const value = BigInt(val);
if (value < 0 || value > options.maxValue) {
throw new TypeError(
`Invalid ${options.name} value: ${value}. Expected value in range 0-${options.maxValue}`
);
}
options.validate?.(value);
}
});
}
function dynamicSizeBcsType({
serialize,
...options
}) {
const type = new BcsType({
...options,
serialize,
write: (value, writer) => {
for (const byte of type.serialize(value).toBytes()) {
writer.write8(byte);
}
}
});
return type;
}
function stringLikeBcsType({
toBytes,
fromBytes,
...options
}) {
return new BcsType({
...options,
read: (reader) => {
const length = reader.readULEB();
const bytes = reader.readBytes(length);
return fromBytes(bytes);
},
write: (hex, writer) => {
const bytes = toBytes(hex);
writer.writeULEB(bytes.length);
for (let i = 0; i < bytes.length; i++) {
writer.write8(bytes[i]);
}
},
serialize: (value) => {
const bytes = toBytes(value);
const size = ulebEncode(bytes.length);
const result = new Uint8Array(size.length + bytes.length);
result.set(size, 0);
result.set(bytes, size.length);
return result;
},
validate: (value) => {
if (typeof value !== "string") {
throw new TypeError(`Invalid ${options.name} value: ${value}. Expected string`);
}
options.validate?.(value);
}
});
}
function lazyBcsType(cb) {
let lazyType = null;
function getType() {
if (!lazyType) {
lazyType = cb();
}
return lazyType;
}
return {
value: total,
length: len
};
return new BcsType({
name: "lazy",
read: (data) => getType().read(data),
serializedSize: (value) => getType().serializedSize(value),
write: (value, writer) => getType().write(value, writer),
serialize: (value, options) => getType().serialize(value, options).toBytes()
});
}
// src/bcs.ts
var bcs = {
/**
* Creates a BcsType that can be used to read and write an 8-bit unsigned integer.
* @example
* bcs.u8().serialize(255).toBytes() // Uint8Array [ 255 ]
*/
u8(options) {
return uIntBcsType({
name: "u8",
readMethod: "read8",
writeMethod: "write8",
size: 1,
maxValue: 2 ** 8 - 1,
...options
});
},
/**
* Creates a BcsType that can be used to read and write a 16-bit unsigned integer.
* @example
* bcs.u16().serialize(65535).toBytes() // Uint8Array [ 255, 255 ]
*/
u16(options) {
return uIntBcsType({
name: "u16",
readMethod: "read16",
writeMethod: "write16",
size: 2,
maxValue: 2 ** 16 - 1,
...options
});
},
/**
* Creates a BcsType that can be used to read and write a 32-bit unsigned integer.
* @example
* bcs.u32().serialize(4294967295).toBytes() // Uint8Array [ 255, 255, 255, 255 ]
*/
u32(options) {
return uIntBcsType({
name: "u32",
readMethod: "read32",
writeMethod: "write32",
size: 4,
maxValue: 2 ** 32 - 1,
...options
});
},
/**
* Creates a BcsType that can be used to read and write a 64-bit unsigned integer.
* @example
* bcs.u64().serialize(1).toBytes() // Uint8Array [ 1, 0, 0, 0, 0, 0, 0, 0 ]
*/
u64(options) {
return bigUIntBcsType({
name: "u64",
readMethod: "read64",
writeMethod: "write64",
size: 8,
maxValue: 2n ** 64n - 1n,
...options
});
},
/**
* Creates a BcsType that can be used to read and write a 128-bit unsigned integer.
* @example
* bcs.u128().serialize(1).toBytes() // Uint8Array [ 1, ..., 0 ]
*/
u128(options) {
return bigUIntBcsType({
name: "u128",
readMethod: "read128",
writeMethod: "write128",
size: 16,
maxValue: 2n ** 128n - 1n,
...options
});
},
/**
* Creates a BcsType that can be used to read and write a 256-bit unsigned integer.
* @example
* bcs.u256().serialize(1).toBytes() // Uint8Array [ 1, ..., 0 ]
*/
u256(options) {
return bigUIntBcsType({
name: "u256",
readMethod: "read256",
writeMethod: "write256",
size: 32,
maxValue: 2n ** 256n - 1n,
...options
});
},
/**
* Creates a BcsType that can be used to read and write boolean values.
* @example
* bcs.bool().serialize(true).toBytes() // Uint8Array [ 1 ]
*/
bool(options) {
return fixedSizeBcsType({
name: "bool",
size: 1,
read: (reader) => reader.read8() === 1,
write: (value, writer) => writer.write8(value ? 1 : 0),
...options,
validate: (value) => {
options?.validate?.(value);
if (typeof value !== "boolean") {
throw new TypeError(`Expected boolean, found ${typeof value}`);
}
}
});
},
/**
* Creates a BcsType that can be used to read and write unsigned LEB encoded integers
* @example
*
*/
uleb128(options) {
return dynamicSizeBcsType({
name: "uleb128",
read: (reader) => reader.readULEB(),
serialize: (value) => {
return Uint8Array.from(ulebEncode(value));
},
...options
});
},
/**
* Creates a BcsType representing a fixed length byte array
* @param size The number of bytes this types represents
* @example
* bcs.bytes(3).serialize(new Uint8Array([1, 2, 3])).toBytes() // Uint8Array [1, 2, 3]
*/
bytes(size, options) {
return fixedSizeBcsType({
name: `bytes[${size}]`,
size,
read: (reader) => reader.readBytes(size),
write: (value, writer) => {
for (let i = 0; i < size; i++) {
writer.write8(value[i] ?? 0);
}
},
...options,
validate: (value) => {
options?.validate?.(value);
if (!("length" in value)) {
throw new TypeError(`Expected array, found ${typeof value}`);
}
if (value.length !== size) {
throw new TypeError(`Expected array of length ${size}, found ${value.length}`);
}
}
});
},
/**
* Creates a BcsType that can ser/de string values. Strings will be UTF-8 encoded
* @example
* bcs.string().serialize('a').toBytes() // Uint8Array [ 1, 97 ]
*/
string(options) {
return stringLikeBcsType({
name: "string",
toBytes: (value) => new TextEncoder().encode(value),
fromBytes: (bytes) => new TextDecoder().decode(bytes),
...options
});
},
/**
* Creates a BcsType that represents a fixed length array of a given type
* @param size The number of elements in the array
* @param type The BcsType of each element in the array
* @example
* bcs.fixedArray(3, bcs.u8()).serialize([1, 2, 3]).toBytes() // Uint8Array [ 1, 2, 3 ]
*/
fixedArray(size, type, options) {
return new BcsType({
name: `${type.name}[${size}]`,
read: (reader) => {
const result = new Array(size);
for (let i = 0; i < size; i++) {
result[i] = type.read(reader);
}
return result;
},
write: (value, writer) => {
for (const item of value) {
type.write(item, writer);
}
},
...options,
validate: (value) => {
options?.validate?.(value);
if (!("length" in value)) {
throw new TypeError(`Expected array, found ${typeof value}`);
}
if (value.length !== size) {
throw new TypeError(`Expected array of length ${size}, found ${value.length}`);
}
}
});
},
/**
* Creates a BcsType representing an optional value
* @param type The BcsType of the optional value
* @example
* bcs.option(bcs.u8()).serialize(null).toBytes() // Uint8Array [ 0 ]
* bcs.option(bcs.u8()).serialize(1).toBytes() // Uint8Array [ 1, 1 ]
*/
option(type) {
return bcs.enum(`Option<${type.name}>`, {
None: null,
Some: type
}).transform({
input: (value) => {
if (value == null) {
return { None: true };
}
return { Some: value };
},
output: (value) => {
if ("Some" in value) {
return value.Some;
}
return null;
}
});
},
/**
* Creates a BcsType representing a variable length vector of a given type
* @param type The BcsType of each element in the vector
*
* @example
* bcs.vector(bcs.u8()).toBytes([1, 2, 3]) // Uint8Array [ 3, 1, 2, 3 ]
*/
vector(type, options) {
return new BcsType({
name: `vector<${type.name}>`,
read: (reader) => {
const length = reader.readULEB();
const result = new Array(length);
for (let i = 0; i < length; i++) {
result[i] = type.read(reader);
}
return result;
},
write: (value, writer) => {
writer.writeULEB(value.length);
for (const item of value) {
type.write(item, writer);
}
},
...options,
validate: (value) => {
options?.validate?.(value);
if (!("length" in value)) {
throw new TypeError(`Expected array, found ${typeof value}`);
}
}
});
},
/**
* Creates a BcsType representing a tuple of a given set of types
* @param types The BcsTypes for each element in the tuple
*
* @example
* const tuple = bcs.tuple([bcs.u8(), bcs.string(), bcs.bool()])
* tuple.serialize([1, 'a', true]).toBytes() // Uint8Array [ 1, 1, 97, 1 ]
*/
tuple(types, options) {
return new BcsType({
name: `(${types.map((t) => t.name).join(", ")})`,
serializedSize: (values) => {
let total = 0;
for (let i = 0; i < types.length; i++) {
const size = types[i].serializedSize(values[i]);
if (size == null) {
return null;
}
total += size;
}
return total;
},
read: (reader) => {
const result = [];
for (const type of types) {
result.push(type.read(reader));
}
return result;
},
write: (value, writer) => {
for (let i = 0; i < types.length; i++) {
types[i].write(value[i], writer);
}
},
...options,
validate: (value) => {
options?.validate?.(value);
if (!Array.isArray(value)) {
throw new TypeError(`Expected array, found ${typeof value}`);
}
if (value.length !== types.length) {
throw new TypeError(`Expected array of length ${types.length}, found ${value.length}`);
}
}
});
},
/**
* Creates a BcsType representing a struct of a given set of fields
* @param name The name of the struct
* @param fields The fields of the struct. The order of the fields affects how data is serialized and deserialized
*
* @example
* const struct = bcs.struct('MyStruct', {
* a: bcs.u8(),
* b: bcs.string(),
* })
* struct.serialize({ a: 1, b: 'a' }).toBytes() // Uint8Array [ 1, 1, 97 ]
*/
struct(name, fields, options) {
const canonicalOrder = Object.entries(fields);
return new BcsType({
name,
serializedSize: (values) => {
let total = 0;
for (const [field, type] of canonicalOrder) {
const size = type.serializedSize(values[field]);
if (size == null) {
return null;
}
total += size;
}
return total;
},
read: (reader) => {
const result = {};
for (const [field, type] of canonicalOrder) {
result[field] = type.read(reader);
}
return result;
},
write: (value, writer) => {
for (const [field, type] of canonicalOrder) {
type.write(value[field], writer);
}
},
...options,
validate: (value) => {
options?.validate?.(value);
if (typeof value !== "object" || value == null) {
throw new TypeError(`Expected object, found ${typeof value}`);
}
}
});
},
/**
* Creates a BcsType representing an enum of a given set of options
* @param name The name of the enum
* @param values The values of the enum. The order of the values affects how data is serialized and deserialized.
* null can be used to represent a variant with no data.
*
* @example
* const enum = bcs.enum('MyEnum', {
* A: bcs.u8(),
* B: bcs.string(),
* C: null,
* })
* enum.serialize({ A: 1 }).toBytes() // Uint8Array [ 0, 1 ]
* enum.serialize({ B: 'a' }).toBytes() // Uint8Array [ 1, 1, 97 ]
* enum.serialize({ C: true }).toBytes() // Uint8Array [ 2 ]
*/
enum(name, values, options) {
const canonicalOrder = Object.entries(values);
return new BcsType({
name,
read: (reader) => {
const index = reader.readULEB();
const [name2, type] = canonicalOrder[index];
return {
[name2]: type?.read(reader) ?? true
};
},
write: (value, writer) => {
const [name2, val] = Object.entries(value)[0];
for (let i = 0; i < canonicalOrder.length; i++) {
const [optionName, optionType] = canonicalOrder[i];
if (optionName === name2) {
writer.writeULEB(i);
optionType?.write(val, writer);
return;
}
}
},
...options,
validate: (value) => {
options?.validate?.(value);
if (typeof value !== "object" || value == null) {
throw new TypeError(`Expected object, found ${typeof value}`);
}
const keys = Object.keys(value);
if (keys.length !== 1) {
throw new TypeError(`Expected object with one key, found ${keys.length}`);
}
const [name2] = keys;
if (!Object.hasOwn(values, name2)) {
throw new TypeError(`Invalid enum variant ${name2}`);
}
}
});
},
/**
* Creates a BcsType representing a map of a given key and value type
* @param keyType The BcsType of the key
* @param valueType The BcsType of the value
* @example
* const map = bcs.map(bcs.u8(), bcs.string())
* map.serialize(new Map([[2, 'a']])).toBytes() // Uint8Array [ 1, 2, 1, 97 ]
*/
map(keyType, valueType) {
return bcs.vector(bcs.tuple([keyType, valueType])).transform({
name: `Map<${keyType.name}, ${valueType.name}>`,
input: (value) => {
return [...value.entries()];
},
output: (value) => {
const result = /* @__PURE__ */ new Map();
for (const [key, val] of value) {
result.set(key, val);
}
return result;
}
});
},
/**
* Creates a helper function representing a generic type. This method returns
* a function that can be used to create concrete version of the generic type.
* @param names The names of the generic parameters
* @param cb A callback that returns the generic type
* @example
* const MyStruct = bcs.generic(['T'], (T) => bcs.struct('MyStruct', { inner: T }))
* MyStruct(bcs.u8()).serialize({ inner: 1 }).toBytes() // Uint8Array [ 1 ]
* MyStruct(bcs.string()).serialize({ inner: 'a' }).toBytes() // Uint8Array [ 1, 97 ]
*/
generic(names, cb) {
return (...types) => {
return cb(...types).transform({
name: `${cb.name}<${types.map((t) => t.name).join(", ")}>`,
input: (value) => value,
output: (value) => value
});
};
},
/**
* Creates a BcsType that wraps another BcsType which is lazily evaluated. This is useful for creating recursive types.
* @param cb A callback that returns the BcsType
*/
lazy(cb) {
return lazyBcsType(cb);
}
};
// src/legacy-registry.ts
var SUI_ADDRESS_LENGTH = 32;
var _BCS = class _BCS {

@@ -602,2 +1330,67 @@ /**

/**
* Method to register BcsType instances to the registry
* Types are registered with a callback that provides BcsType instances for each generic
* passed to the type.
*
* - createType(...generics) - Return a BcsType instance
*
* @example
* // our type would be a string that consists only of numbers
* bcs.registerType('Box<T>', (T) => {
* return bcs.struct({
* value: T
* });
* });
* console.log(Array.from(bcs.ser('Box<string>', '12345').toBytes()) == [5,1,2,3,4,5]);
*
* @param name
* @param createType a Callback to create the BcsType with any passed in generics
*/
registerBcsType(typeName, createType) {
this.registerType(
typeName,
(writer, data, typeParams) => {
const generics = typeParams.map(
(param) => new BcsType({
name: String(param),
write: (data2, writer2) => {
const { name, params } = this.parseTypeName(param);
const typeInterface = this.getTypeInterface(name);
const typeMap = params.reduce((acc, value, index) => {
return Object.assign(acc, { [value]: typeParams[index] });
}, {});
return typeInterface._encodeRaw.call(this, writer2, data2, params, typeMap);
},
read: () => {
throw new Error("Not implemented");
}
})
);
createType(...generics).write(data, writer);
return writer;
},
(reader, typeParams) => {
const generics = typeParams.map(
(param) => new BcsType({
name: String(param),
write: (data, writer) => {
throw new Error("Not implemented");
},
read: (reader2) => {
const { name, params } = this.parseTypeName(param);
const typeInterface = this.getTypeInterface(name);
const typeMap = params.reduce((acc, value, index) => {
return Object.assign(acc, { [value]: typeParams[index] });
}, {});
return typeInterface._decodeRaw.call(this, reader2, params, typeMap);
}
})
);
return createType(...generics).read(reader);
}
);
return this;
}
/**
* Register an address type which is a sequence of U8s of specified length.

@@ -1031,3 +1824,3 @@ * @example

};
// Prefefined types constants
// Predefined types constants
_BCS.U8 = "u8";

@@ -1047,28 +1840,4 @@ _BCS.U16 = "u16";

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(
function registerPrimitives(bcs2) {
bcs2.registerType(
BCS.U8,

@@ -1083,3 +1852,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.U16,

@@ -1094,3 +1863,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.U32,

@@ -1105,3 +1874,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.U64,

@@ -1115,3 +1884,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.U128,

@@ -1125,3 +1894,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.U256,

@@ -1135,3 +1904,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.BOOL,

@@ -1145,3 +1914,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.STRING,

@@ -1156,3 +1925,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.HEX,

@@ -1167,3 +1936,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.BASE58,

@@ -1178,3 +1947,3 @@ function(writer, data) {

);
bcs.registerType(
bcs2.registerType(
BCS.BASE64,

@@ -1206,25 +1975,2 @@ function(writer, data) {

}
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:

@@ -1234,3 +1980,6 @@ 0 && (module.exports = {

BcsReader,
BcsType,
BcsWriter,
SerializedBcs,
bcs,
decodeStr,

@@ -1237,0 +1986,0 @@ encodeStr,

{
"name": "@mysten/bcs",
"version": "0.0.0-experimental-20230907191300",
"version": "0.0.0-experimental-20230928204950",
"description": "BCS - Canonical Binary Serialization implementation for JavaScript",

@@ -5,0 +5,0 @@ "license": "Apache-2.0",

# BCS - Binary Canonical Serialization
This small and lightweight library implements
[Binary Canonical Serialization (BCS)](https://github.com/zefchain/bcs) in JavaScript, making BCS
available in both Browser and NodeJS environments.
[Binary Canonical Serialization (BCS)](https://github.com/zefchain/bcs) in TypeScript, making BCS
available in both Browser and NodeJS environments in a type-safe way.`

@@ -12,3 +12,3 @@ ## Install

```sh
```sh npm2yarn
npm i @mysten/bcs

@@ -20,30 +20,27 @@ ```

```ts
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
import { bcs } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
// define UID as a 32-byte array, then add a transform to/from hex strings
const UID = bcs.array(32, bcs.u8()).transform({
input: (id: string) => fromHex(id),
output: (id) => toHex(id),
});
// registering types so we can use them
bcs.registerAlias('UID', BCS.ADDRESS);
bcs.registerEnumType('Option<T>', {
none: null,
some: 'T',
const Coin = bcs.struct('Coin', {
id: UID,
value: bcs.u64(),
});
bcs.registerStructType('Coin', {
id: 'UID',
value: BCS.U64,
});
// deserialization: BCS bytes into Coin
let bcsBytes = bcs
.ser('Coin', {
id: '0000000000000000000000000000000000000000000000000000000000000001',
value: 1000000n,
})
.toBytes();
let coin = bcs.de('Coin', bcsBytes, 'hex');
const bcsBytes = Coin.serialize({
id: '0000000000000000000000000000000000000000000000000000000000000001',
value: 1000000n,
}).toBytes();
const coin = Coin.parse(bcsBytes);
// serialization: Object into bytes - an Option with <T = Coin>
let data = bcs.ser('Option<Coin>', { some: coin }).toString('hex');
const hex = bcs.option(Coin).serialize(coin).toHex();
console.log(data);
console.log(hex);
```

@@ -53,223 +50,176 @@

BCS defines the way the data is serialized, and the result contains no type information. To be able
to serialize the data and later deserialize, a schema has to be created (based on the built-in
primitives, such as `address` or `u64`). There's no tip in the serialized bytes on what they mean,
so the receiving part needs to know how to treat it.
BCS defines the way the data is serialized, and the serialized results contains no type information.
To be able to serialize the data and later deserialize it, a schema has to be created (based on the
built-in primitives, such as `string` or `u64`). There are no type hints in the serialized bytes on
what they mean, so the schema used for decoding must match the schema used to encode the data.
## Configuration
The `@mysten/bcs` library can be used to define schemas that can serialize and deserialize BCS
encoded data, and and can infer the correct TypeScript for the schema from the definitions
themselves rather than having to define them manually.
BCS constructor is configurable for the target. The following parameters are available for custom
configuration:
## Basic types
| parameter | required | description |
| ------------------- | -------- | ------------------------------------------------------------------------- |
| `vectorType` | + | Defines the type of the vector (`vector<T>` in SuiMove, `Vec<T>` in Rust) |
| `addressLength` | + | Length of the built-in `address` type. 20 for SuiMove, 32 for Core Move |
| `addressEncoding` | - | Custom encoding for addresses - "hex" or "base64" |
| `genericSeparators` | - | Generic type parameters syntax, default is `['<', '>']` |
| `types` | - | Define enums, structs and aliases at initialization stage |
| `withPrimitives` | - | Whether to register primitive types (`true` by default) |
bcs supports a number of built in base types that can be combined to create more complex types. The
following table lists the primitive types available:
```ts
// Example: All options used
import { BCS } from '@mysten/bcs';
| Method | TS Type | TS Input Type | Description |
| --------------------- | ------------ | ---------------------------- | --------------------------------------------------------------------------- |
| `bool` | `boolean` | `boolean` | Boolean type (converts to `true` / `false`) |
| `u8`, `u16`, `u32` | `number` | `number` | Unsigned Integer types |
| `u64`, `u128`, `u256` | `string` | `number \| string \| bigint` | Unsigned Integer types, decoded as `string` to allow for JSON serialization |
| `uleb128` | `number` | `number` | Unsigned LEB128 integer type |
| `string` | `string` | `string` | UTF-8 encoded string |
| `bytes(size)` | `Uint8Array` | `Iterable<number>` | Fixed length bytes |
const SUI_ADDRESS_LENGTH = 32;
const bcs = new BCS({
vectorType: 'vector<T>',
addressLength: SUI_ADDRESS_LENGTH,
addressEncoding: 'hex',
genericSeparators: ['<', '>'],
types: {
// define schema in the initializer
structs: {
User: {
name: BCS.STRING,
age: BCS.U8,
},
},
enums: {},
aliases: { hex: BCS.HEX },
},
withPrimitives: true,
});
let bytes = bcs.ser('User', { name: 'Adam', age: '30' }).toString('base64');
console.log(bytes);
```
For Sui Move there's already a pre-built configuration which can be used through the
`getSuiMoveConfig()` call.
```ts
// Example: Sui Move Config
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
import { bcs } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
// Integers
const u8 = bcs.u8().serialize(100).toBytes();
const u64 = bcs.u64().serialize(1000000n).toBytes();
const u128 = bcs.u128().serialize('100000010000001000000').toBytes();
// use bcs.ser() to serialize data
const val = [1, 2, 3, 4];
const ser = bcs.ser(['vector', BCS.U8], val).toBytes();
// Other types
const str = bcs.string().serialize('this is an ascii string').toBytes();
const hex = bcs.hex().serialize('C0FFEE').toBytes();
const bytes = bcs.bytes(4).serialize([1, 2, 3, 4]).toBytes();
// use bcs.de() to deserialize data
const res = bcs.de(['vector', BCS.U8], ser);
// Parsing data back into original types
const parsedU8 = bcs.u8().parse(u8);
// u64-u256 will be represented as bigints regardless of how they were provided when serializing them
const parsedU64 = bcs.u64().parse(u64);
const parsedU128 = bcs.u128().parse(u128);
console.assert(res.toString() === val.toString());
const parsedStr = bcs.string().parse(str);
const parsedHex = bcs.hex().parse(hex);
const parsedBytes = bcs.bytes(4).parse(bytes);
```
Similar configuration exists for Rust, the difference is the `Vec<T>` for vectors and `address`
(being a special Move type) is not needed:
## Compound types
```ts
// Example: Rust Config
import { BCS, getRustConfig } from '@mysten/bcs';
For most use-cases you'll want to combine primitive types into more complex types like `vectors`,
`structs` and `enums`. The following table lists methods available for creating compound types:
const bcs = new BCS(getRustConfig());
const val = [1, 2, 3, 4];
const ser = bcs.ser(['Vec', BCS.U8], val).toBytes();
const res = bcs.de(['Vec', BCS.U8], ser);
| Method | Description |
| ---------------------- | ----------------------------------------------------- |
| `vector(type: T)` | A variable length list of values of type `T` |
| `fixedArray(size, T)` | A fixed length array of values of type `T` |
| `option(type: T)` | A value of type `T` or `null` |
| `enum(name, values)` | An enum value representing one of the provided values |
| `struct(name, fields)` | A struct with named fields of the provided types |
| `tuple(types)` | A tuple of the provided types |
| `map(K, V)` | A map of keys of type `K` to values of type `V` |
console.assert(res.toString() === val.toString());
```
```ts
import { bcs } from '@mysten/bcs';
## Built-in types
// Vectors
const intList = bcs.vector(bcs.u8()).serialize([1, 2, 3, 4, 5]).toBytes();
const stringList = bcs.vector(bcs.string()).serialize(['a', 'b', 'c']).toBytes();
By default, BCS will have a set of built-in type definitions and handy abstractions; all of them are
supported in Move.
// Arrays
const intArray = bcs.array(4, bcs.u8()).serialize([1, 2, 3, 4]).toBytes();
const stringArray = bcs.array(3, bcs.string()).serialize(['a', 'b', 'c']).toBytes();
Supported integer types are: u8, u16, u32, u64, u128 and u256. Constants `BCS.U8` to `BCS.U256` are
provided by the library.
// Option
const option = bcs.option(bcs.string()).serialize('some value').toBytes();
const nullOption = bcs.option(bcs.string()).serialize(null).toBytes();
| Type | Constant | Description |
| --------------------------- | ----------------------------- | ------------------------------------------------------ |
| 'bool' | `BCS.BOOL` | Boolean type (converts to `true` / `false`) |
| 'u8'...'u256' | `BCS.U8` ... `BCS.U256` | Integer types |
| 'address' | `BCS.ADDRESS` | Address type (also used for IDs in Sui Move) |
| 'vector\<T\>' \| 'Vec\<T\>' | _Only custom use, requires T_ | Generic vector of any element |
| 'string' | `BCS.STRING` | `vector<u8>` that (de)serializes to/from ASCII string |
| 'hex-string' | `BCS.HEX` | `vector<u8>` that (de)serializes to/from HEX string |
| 'base64-string' | `BCS.BASE64` | `vector<u8>` that (de)serializes to/from Base64 string |
| 'base58-string' | `BCS.BASE58` | `vector<u8>` that (de)serializes to/from Base58 string |
// Enum
const MyEnum = bcs.enum('MyEnum', {
NoType: null,
Int: bcs.u8(),
String: bcs.string(),
Array: bcs.array(3, bcs.u8()),
});
---
const noTypeEnum = MyEnum.serialize({ NoType: null }).toBytes();
const intEnum = MyEnum.serialize({ Int: 100 }).toBytes();
const stringEnum = MyEnum.serialize({ String: 'string' }).toBytes();
const arrayEnum = MyEnum.serialize({ Array: [1, 2, 3] }).toBytes();
All of the type usage examples below can be used for `bcs.de(<type>, ...)` as well.
// Struct
const MyStruct = bcs.struct('MyStruct', {
id: bcs.u8(),
name: bcs.string(),
});
```ts
// Example: Primitive types
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
const struct = MyStruct.serialize({ id: 1, name: 'name' }).toBytes();
// Integers
let _u8 = bcs.ser(BCS.U8, 100).toBytes();
let _u64 = bcs.ser(BCS.U64, 1000000n).toString('hex');
let _u128 = bcs.ser(BCS.U128, '100000010000001000000').toString('base64');
// Tuple
const tuple = bcs.tuple([bcs.u8(), bcs.string()]).serialize([1, 'name']).toBytes();
// Other types
let _bool = bcs.ser(BCS.BOOL, true).toString('hex');
let _addr = bcs.ser(BCS.ADDRESS, '0000000000000000000000000000000000000001').toBytes();
let _str = bcs.ser(BCS.STRING, 'this is an ascii string').toBytes();
// Map
const map = bcs.map(bcs.u8(), bcs.string()).serialize(.toBytes()[
[1, 'one'],
[2, 'two'],
]);
// Vectors (vector<T>)
let _u8_vec = bcs.ser(['vector', BCS.U8], [1, 2, 3, 4, 5, 6, 7]).toBytes();
let _bool_vec = bcs.ser(['vector', BCS.BOOL], [true, true, false]).toBytes();
let _str_vec = bcs.ser('vector<bool>', ['string1', 'string2', 'string3']).toBytes();
// Parsing data back into original types
// Even vector of vector (...of vector) is an option
let _matrix = bcs
.ser('vector<vector<u8>>', [
[0, 0, 0],
[1, 1, 1],
[2, 2, 2],
])
.toBytes();
```
// Vectors
const parsedIntList = bcs.vector(bcs.u8()).parse(intList);
const parsedStringList = bcs.vector(bcs.string()).parse(stringList);
## Ser/de and formatting
// Arrays
const parsedIntArray = bcs.array(4, bcs.u8()).parse(intArray);
To serialize and deserialize data to and from BCS there are two methods: `bcs.ser()` and `bcs.de()`.
// Option
const parsedOption = bcs.option(bcs.string()).parse(option);
const parsedNullOption = bcs.option(bcs.string()).parse(nullOption);
```ts
// Example: Ser/de and Encoding
import { BCS, getSuiMoveConfig, BcsWriter } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
// Enum
const parsedNoTypeEnum = MyEnum.parse(noTypeEnum);
const parsedIntEnum = MyEnum.parse(intEnum);
const parsedStringEnum = MyEnum.parse(stringEnum);
const parsedArrayEnum = MyEnum.parse(arrayEnum);
// bcs.ser() returns an instance of BcsWriter which can be converted to bytes or a string
let bcsWriter: BcsWriter = bcs.ser(BCS.STRING, 'this is a string');
// Struct
const parsedStruct = MyStruct.parse(struct);
// writer.toBytes() returns a Uint8Array
let bytes: Uint8Array = bcsWriter.toBytes();
// Tuple
const parsedTuple = bcs.tuple([bcs.u8(), bcs.string()]).parse(tuple);
// custom encodings can be chosen when needed (just like Buffer)
let hex: string = bcsWriter.toString('hex');
let base64: string = bcsWriter.toString('base64');
let base58: string = bcsWriter.toString('base58');
// bcs.de() reads BCS data and returns the value
// by default it expects data to be `Uint8Array`
let str1 = bcs.de(BCS.STRING, bytes);
// alternatively, an encoding of input can be specified
let str2 = bcs.de(BCS.STRING, hex, 'hex');
let str3 = bcs.de(BCS.STRING, base64, 'base64');
let str4 = bcs.de(BCS.STRING, base58, 'base58');
console.assert((str1 == str2) == (str3 == str4), 'Result is the same');
// Map
const parsedMap = bcs.map(bcs.u8(), bcs.string()).parse(map);
```
## Registering new types
## Generics
> Tip: all registering methods start with `bcs.register*` (eg `bcs.registerStructType`).
To define a generic struct or an enum, you can use `bcs.generic` to create a generic type helper
### Alias
Alias is a way to create custom name for a registered type. It is helpful for fine-tuning a
predefined schema without making changes deep in the tree.
```ts
// Example: Alias
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
// Example: Generics
import { bcs } from '@mysten/bcs';
bcs.registerAlias('ObjectDigest', BCS.BASE58);
// Container -> the name of the type
// [T] -> A list of names for the generic types
// The second argument is a function that takes the generic types as arguments and returns a bcs type
const Container = bcs.generic(['T'], (T) =>
bcs.struct('Container<T>', {
contents: T,
}),
);
// ObjectDigest is now treated as base58 string
let _b58 = bcs.ser('ObjectDigest', 'Ldp').toBytes();
// When serializing, we have to pass the type to use for `T`
const bytes = Container(bcs.u8()).serialize({ contents: 100 }).toBytes();
// we can override already existing definition to make it a HEX string
bcs.registerAlias('ObjectDigest', BCS.HEX);
// Alternatively we can save the concrete type as a variable
const U8Container = Container(bcs.u8());
const bytes = U8Container.serialize({ contents: 100 }).toBytes();
let _hex = bcs.ser('ObjectDigest', 'C0FFEE').toBytes();
```
// Using multiple generics
### Struct
const VecMap = bcs.generic(['K', 'V'], (K, V) =>
bcs.struct('VecMap<K, V>', {
keys: bcs.vector(K),
values: bcs.vector(V),
}),
);
Structs are the most common way of working with data; in BCS, a struct is simply a sequence of base
types.
```ts
// Example: Struct
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
// register a custom type (it becomes available for using)
bcs.registerStructType('Balance', {
value: BCS.U64,
});
bcs.registerStructType('Coin', {
id: BCS.ADDRESS,
// reference another registered type
balance: 'Balance',
});
// value passed into ser function has to have the same
// structure as the definition
let _bytes = bcs
.ser('Coin', {
id: '0x0000000000000000000000000000000000000000000000000000000000000005',
balance: {
value: 100000000n,
},
// To serialize VecMap, we can use:
VecMap(bcs.string(), bcs.string())
.serialize({
keys: ['key1', 'key2', 'key3'],
values: ['value1', 'value2', 'value3'],
})

@@ -279,183 +229,124 @@ .toBytes();

## Using Generics
## Transforms
To define a generic struct or an enum, pass the type parameters. It can either be done as a part of
a string or as an Array. See below:
If you the format you use in your code is different from the format expected for BCS serialization,
you can use the `transform` API to map between the types you use in your application, and the types
needed for serialization.
The `address` type used by Move code is a good example of this. In many cases, you'll want to
represent an address as a hex string, but the BCS serialization format for addresses is a 32 byte
array. To handle this, you can use the `transform` API to map between the two formats:
```ts
// Example: Generics
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
// Container -> the name of the type
// T -> type parameter which has to be passed in `ser()` or `de()` methods
// If you're not familiar with generics, treat them as type Templates
bcs.registerStructType(['Container', 'T'], {
contents: 'T',
const Address = bcs.bytes(32).transform({
// To change the input type, you need to provide a type definition for the input
input: (val: string) => fromHEX(val),
output: (val) => toHEX(val),
});
// When serializing, we have to pass the type to use for `T`
bcs
.ser(['Container', BCS.U8], {
contents: 100,
})
.toString('hex');
// Reusing the same Container type with different contents.
// Mind that generics need to be passed as Array after the main type.
bcs
.ser(['Container', ['vector', BCS.BOOL]], {
contents: [true, false, true],
})
.toString('hex');
// Using multiple generics - you can use any string for convenience and
// readability. See how we also use array notation for a field definition.
bcs.registerStructType(['VecMap', 'Key', 'Val'], {
keys: ['vector', 'Key'],
values: ['vector', 'Val'],
});
// To serialize VecMap, we can use:
bcs.ser(['VecMap', BCS.STRING, BCS.STRING], {
keys: ['key1', 'key2', 'key3'],
values: ['value1', 'value2', 'value3'],
});
const serialized = Address.serialize('0x000000...').toBytes();
const parsed = Address.parse(serialized); // will return a hex string
```
### Enum
When using a transform, a new type is created that uses the inferred return value of `output` as the
return type of the `parse` method, and the type of the `input` argument as the allowed input type
when calling `serialize`. The `output` type can generally be inferred from the definition, but the
input type will need to be provided explicitly. In some cases, for complex transforms, you'll need
to manually type the return
In BCS enums are encoded in a special way - first byte marks the order and then the value. Enum is
an object, only one property of which is used; if an invariant is empty, `null` should be used to
mark it (see `Option<T>` below).
transforms can also handle more complex types. For instance, `@mysten/sui.js` uses the following
definition to transform enums into a more TypeScript friends format:
```ts
// Example: Enum
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
type Merge<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
type EnumKindTransform<T> = T extends infer U
? Merge<(U[keyof U] extends null | boolean ? object : U[keyof U]) & { kind: keyof U }>
: never;
bcs.registerEnumType('Option<T>', {
none: null,
some: 'T',
});
function enumKind<T extends object, Input extends object>(type: BcsType<T, Input>) {
return type.transform({
input: ({ kind, ...val }: EnumKindTransform<Input>) =>
({
[kind]: val,
}) as Input,
output: (val) => {
const key = Object.keys(val)[0] as keyof T;
bcs.registerEnumType('TransactionType', {
single: 'vector<u8>',
batch: 'vector<vector<u8>>',
});
return { kind: key, ...val[key] } as EnumKindTransform<T>;
},
});
}
// any truthy value marks empty in struct value
let _optionNone = bcs.ser('Option<TransactionType>', {
none: true,
});
const MyEnum = enumKind(
bcs.enum('MyEnum', {
A: bcs.struct('A', {
id: bcs.u8(),
}),
B: bcs.struct('B', {
val: bcs.string(),
}),
}),
);
// some now contains a value of type TransactionType
let _optionTx = bcs.ser('Option<TransactionType>', {
some: {
single: [1, 2, 3, 4, 5, 6],
},
});
// Enums wrapped with enumKind flatten the enum variants and add a `kind` field to differentiate them
const A = MyEnum.serialize({ kind: 'A', id: 1 }).toBytes();
const B = MyEnum.serialize({ kind: 'B', val: 'xyz' }).toBytes();
// same type signature but a different enum invariant - batch
let _optionTxBatch = bcs.ser('Option<TransactionType>', {
some: {
batch: [
[1, 2, 3, 4, 5, 6],
[1, 2, 3, 4, 5, 6],
],
},
});
const parsedA = MyEnum.parse(A); // returns { kind: 'A', id: 1 }
```
### Inline (de)serialization
## Formats for serialized bytes
Sometimes it is useful to get a value without registering a new struct. For that inline struct
definition can be used.
When you call `serialize` on a `BcsType`, you will receive a `SerializedBcs` instance. This wrapper
preserves type information for the serialized bytes, and can be used to get raw data in various
formats.
> Nested struct definitions are not yet supported, only first level properties can be used (but they
> can reference any type, including other struct types).
```ts
// Example: Inline Struct
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
import { bcs, fromB58, fromB64, fromHex } from '@mysten/bcs';
// Some value we want to serialize
const coin = {
id: '0000000000000000000000000000000000000000000000000000000000000005',
value: 1111333333222n,
};
const serializedString = bcs.string().serialize('this is a string');
// Instead of defining a type we pass struct schema as the first argument
let coin_bytes = bcs.ser({ id: BCS.ADDRESS, value: BCS.U64 }, coin).toBytes();
// SerializedBcs.toBytes() returns a Uint8Array
const bytes: Uint8Array = serializedString.toBytes();
// Same with deserialization
let coin_restored = bcs.de({ id: BCS.ADDRESS, value: BCS.U64 }, coin_bytes);
// You can get the serialized bytes encoded as hex, base64 or base58
const hex: string = serializedString.toHex();
const base64: string = bcsWriter.toBase64();
const base58: string = bcsWriter.toBase58();
console.assert(coin.id == coin_restored.id, '`id` must match');
console.assert(coin.value == coin_restored.value, '`value` must match');
```
// To parse a BCS value from bytes, the bytes need to be a Uint8Array
const str1 = bcs.string().parse(bytes);
## Aligning schema with Move
// If your data is encoded as string, you need to convert it to Uint8Array first
const str2 = bcs.string().parse(fromHex(hex));
const str3 = bcs.string().parse(fromB64(base64));
const str4 = bcs.string().parse(fromB58(base58));
Currently, main applications of this library are:
console.assert((str1 == str2) == (str3 == str4), 'Result is the same');
```
1. Serializing transactions and data passed into a transaction
2. Deserializing onchain data for performance and formatting reasons
3. Deserializing events
## Inferring types
In this library, all of the primitive Move types are present as built-ins, however, there's a set of
special types in Sui which can be simplified to a primitive.
BCS types have both a `type` and an `inputType`. For some types these are the same, but for others
(like `u64`) the types diverge slightly to make inputs more flexible. For instance, `u64` will
always be `string` for it's type, but can be a `number`, `string` or `bigint` for it's input type.
```rust
// Definition in Move which we want to read in JS
module me::example {
struct Metadata has store {
name: std::ascii::String,
}
You can infer these types in one of 2 ways, either using the `$inferType` and `$inferInput`
properties on a `BcsType`, or using the `InferBcsType` and `InferBcsInput` type helpers.
struct ChainObject has key {
id: sui::object::UID,
owner: address,
meta: Metadata
}
// ...
}
```
Definition for the above should be the following:
```ts
// Example: Simplifying UID
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
import { bcs, type InferBcsType, type InferBcsInput } from '@mysten/bcs';
// If there's a deep nested struct we can ignore Move type
// structure and use only the value.
bcs.registerAlias('UID', BCS.ADDRESS);
// Simply follow the definition onchain
bcs.registerStructType('Metadata', {
name: BCS.STRING,
const MyStruct = bcs.struct('MyStruct', {
id: bcs.u64(),
name: bcs.string(),
});
// Same for the main object that we intend to read
bcs.registerStructType('ChainObject', {
id: 'UID',
owner: BCS.ADDRESS,
meta: 'Metadata',
});
// using the $inferType and $inferInput properties
type MyStructType = typeof MyStruct.$inferType; // { id: string; name: string; }
type MyStructInput = typeof MyStruct.$inferInput; // { id: number | string | bigint; name: string; }
// using the InferBcsType and InferBcsInput type helpers
type MyStructType = InferBcsType<typeof MyStruct>; // { id: string; name: string; }
type MyStructInput = InferBcsInput<typeof MyStruct>; // { id: number | string | bigint; name: string; }
```
<details><summary>See definition of the UID here</summary>
<pre>
struct UID has store {
id: ID
}
struct ID has store, copy, drop { bytes: address }
// { id: { bytes: '0x.....' } }
</pre>
</details>

@@ -14,1533 +14,34 @@ // Copyright (c) Mysten Labs, Inc.

import { toB64, fromB64 } from './b64';
import { toHEX, fromHEX } from './hex';
import bs58 from 'bs58';
import { fromB58, toB58 } from './b58.js';
import { fromB64, toB64 } from './b64.js';
import { BcsType, BcsTypeOptions, SerializedBcs } from './bcs-type.js';
import { bcs } from './bcs.js';
import { fromHEX, toHEX } from './hex.js';
import { BcsReader } from './reader.js';
import { type InferBcsInput, type InferBcsType } from './types.js';
import { decodeStr, encodeStr, splitGenericParameters } from './utils.js';
import { BcsWriter, BcsWriterOptions } from './writer.js';
const SUI_ADDRESS_LENGTH = 32;
export * from './legacy-registry.js';
function toLittleEndian(bigint: bigint, size: number) {
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;
}
const toB58 = (buffer: Uint8Array) => bs58.encode(buffer);
const fromB58 = (str: string) => bs58.decode(str);
// Re-export all encoding dependencies.
export { toB58, fromB58, toB64, fromB64, fromHEX, toHEX };
/**
* Supported encodings.
* Used in `Reader.toString()` as well as in `decodeStr` and `encodeStr` functions.
*/
export type Encoding = 'base58' | 'base64' | 'hex';
/**
* Allows for array definitions for names.
* @example
* ```
* bcs.registerStructType(['vector', BCS.STRING], ...);
* // equals
* bcs.registerStructType('vector<string>', ...);
* ```
*/
export type TypeName = string | [string, ...(TypeName | string)[]];
/**
* Class used for reading BCS data chunk by chunk. Meant to be used
* by some wrapper, which will make sure that data is valid and is
* matching the desired format.
*
* @example
* // data for this example is:
* // { a: u8, b: u32, c: bool, d: u64 }
*
* let reader = new BcsReader("647f1a060001ffffe7890423c78a050102030405");
* let field1 = reader.read8();
* let field2 = reader.read32();
* let field3 = reader.read8() === '1'; // bool
* let field4 = reader.read64();
* // ....
*
* Reading vectors is another deal in bcs. To read a vector, you first need to read
* its length using {@link readULEB}. Here's an example:
* @example
* // data encoded: { field: [1, 2, 3, 4, 5] }
* let reader = new BcsReader("050102030405");
* let vec_length = reader.readULEB();
* let elements = [];
* for (let i = 0; i < vec_length; i++) {
* elements.push(reader.read8());
* }
* console.log(elements); // [1,2,3,4,5]
*
* @param {String} data HEX-encoded data (serialized BCS)
*/
export class BcsReader {
private dataView: DataView;
private bytePosition: number = 0;
/**
* @param {Uint8Array} data Data to use as a buffer.
*/
constructor(data: Uint8Array) {
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: number) {
this.bytePosition += bytes;
return this;
}
/**
* Read U8 value from the buffer and shift cursor by 1.
* @returns
*/
read8(): number {
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(): number {
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(): number {
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(): string {
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(): string {
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(): string {
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: number): Uint8Array {
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(): number {
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: (reader: BcsReader, i: number, length: number) => any): any[] {
let length = this.readULEB();
let result = [];
for (let i = 0; i < length; i++) {
result.push(cb(this, i, length));
}
return result;
}
}
/**
* Class used to write BCS data into a buffer. Initializer requires
* some size of a buffer to init; default value for this buffer is 1KB.
*
* Most methods are chainable, so it is possible to write them in one go.
*
* @example
* let serialized = new BcsWriter()
* .write8(10)
* .write32(1000000)
* .write64(10000001000000)
* .hex();
*/
interface BcsWriterOptions {
/** The initial size (in bytes) of the buffer tht will be allocated */
size?: number;
/** The maximum size (in bytes) that the buffer is allowed to grow to */
maxSize?: number;
/** The amount of bytes that will be allocated whenever additional memory is required */
allocateSize?: number;
}
export class BcsWriter {
private dataView: DataView;
private bytePosition: number = 0;
private size: number;
private maxSize: number;
private allocateSize: number;
constructor({ size = 1024, maxSize, allocateSize = 1024 }: BcsWriterOptions = {}) {
this.size = size;
this.maxSize = maxSize || size;
this.allocateSize = allocateSize;
this.dataView = new DataView(new ArrayBuffer(size));
}
private ensureSizeOrGrow(bytes: number) {
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: number): this {
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: number | bigint): this {
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: number | bigint): this {
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: number | bigint): this {
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: number | bigint): this {
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: number | bigint): this {
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: number | bigint): this {
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: number): this {
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: any[], cb: (writer: BcsWriter, el: any, i: number, len: number) => void): this {
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](): Iterator<number, Iterable<number>> {
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(): Uint8Array {
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: Encoding): string {
return encodeStr(this.toBytes(), encoding);
}
}
// Helper utility: write number as an ULEB array.
// Original code is taken from: https://www.npmjs.com/package/uleb128 (no longer exists)
function ulebEncode(num: number): number[] {
let arr = [];
let len = 0;
if (num === 0) {
return [0];
}
while (num > 0) {
arr[len] = num & 0x7f;
if ((num >>= 7)) {
arr[len] |= 0x80;
}
len += 1;
}
return arr;
}
// Helper utility: decode ULEB as an array of numbers.
// Original code is taken from: https://www.npmjs.com/package/uleb128 (no longer exists)
function ulebDecode(arr: number[] | Uint8Array): {
value: number;
length: number;
} {
let total = 0;
let shift = 0;
let len = 0;
// eslint-disable-next-line no-constant-condition
while (true) {
let byte = arr[len];
len += 1;
total |= (byte & 0x7f) << shift;
if ((byte & 0x80) === 0) {
break;
}
shift += 7;
}
return {
value: total,
length: len,
};
}
/**
* Set of methods that allows data encoding/decoding as standalone
* BCS value or a part of a composed structure/vector.
*/
export interface TypeInterface {
encode: (
self: BCS,
data: any,
options: BcsWriterOptions | undefined,
typeParams: TypeName[],
) => BcsWriter;
decode: (self: BCS, data: Uint8Array, typeParams: TypeName[]) => any;
_encodeRaw: (
writer: BcsWriter,
data: any,
typeParams: TypeName[],
typeMap: { [key: string]: TypeName },
) => BcsWriter;
_decodeRaw: (
reader: BcsReader,
typeParams: TypeName[],
typeMap: { [key: string]: TypeName },
) => any;
}
/**
* Struct type definition. Used as input format in BcsConfig.types
* as well as an argument type for `bcs.registerStructType`.
*/
export type StructTypeDefinition = {
[key: string]: TypeName | StructTypeDefinition;
export {
bcs,
BcsType,
type BcsTypeOptions,
SerializedBcs,
toB58,
fromB58,
toB64,
fromB64,
fromHEX,
toHEX,
encodeStr,
decodeStr,
splitGenericParameters,
BcsReader,
BcsWriter,
type BcsWriterOptions,
type InferBcsInput,
type InferBcsType,
};
/**
* Enum type definition. Used as input format in BcsConfig.types
* as well as an argument type for `bcs.registerEnumType`.
*
* Value can be either `string` when invariant has a type or `null`
* when invariant is empty.
*
* @example
* bcs.registerEnumType('Option<T>', {
* some: 'T',
* none: null
* });
*/
export type EnumTypeDefinition = {
[key: string]: TypeName | StructTypeDefinition | null;
};
/**
* Configuration that is passed into BCS constructor.
*/
export type BcsConfig = {
/**
* Defines type name for the vector / array type.
* In Move: `vector<T>` or `vector`.
*/
vectorType: string;
/**
* Address length. Varies depending on a platform and
* has to be specified for the `address` type.
*/
addressLength: number;
/**
* Custom encoding for address. Supported values are
* either 'hex' or 'base64'.
*/
addressEncoding?: 'hex' | 'base64';
/**
* Opening and closing symbol for type parameters. Can be
* any pair of symbols (eg `['(', ')']`); default value follows
* Rust and Move: `<` and `>`.
*/
genericSeparators?: [string, string];
/**
* Type definitions for the BCS. This field allows spawning
* BCS instance from JSON or another prepared configuration.
* Optional.
*/
types?: {
structs?: { [key: string]: StructTypeDefinition };
enums?: { [key: string]: EnumTypeDefinition };
aliases?: { [key: string]: string };
};
/**
* Whether to auto-register primitive types on launch.
*/
withPrimitives?: boolean;
};
/**
* BCS implementation for Move types and few additional built-ins.
*/
export class BCS {
// Prefefined types constants
static readonly U8: string = 'u8';
static readonly U16: string = 'u16';
static readonly U32: string = 'u32';
static readonly U64: string = 'u64';
static readonly U128: string = 'u128';
static readonly U256: string = 'u256';
static readonly BOOL: string = 'bool';
static readonly VECTOR: string = 'vector';
static readonly ADDRESS: string = 'address';
static readonly STRING: string = 'string';
static readonly HEX: string = 'hex-string';
static readonly BASE58: string = 'base58-string';
static readonly BASE64: string = 'base64-string';
/**
* 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.
*/
public types: Map<string, TypeInterface | string> = new Map();
/**
* Stored BcsConfig for the current instance of BCS.
*/
protected schema: BcsConfig;
/**
* Count temp keys to generate a new one when requested.
*/
protected counter: number = 0;
/**
* Name of the key to use for temporary struct definitions.
* Returns a temp key + index (for a case when multiple temp
* structs are processed).
*/
private tempKey() {
return `bcs-struct-${++this.counter}`;
}
/**
* 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: BcsConfig | BCS) {
// if BCS instance is passed -> clone its schema
if (schema instanceof BCS) {
this.schema = schema.schema;
this.types = new Map(schema.types);
return;
}
this.schema = schema;
// Register address type under key 'address'.
this.registerAddressType(BCS.ADDRESS, schema.addressLength, schema.addressEncoding);
this.registerVectorType(schema.vectorType);
// Register struct types if they were passed.
if (schema.types && schema.types.structs) {
for (let name of Object.keys(schema.types.structs)) {
this.registerStructType(name, schema.types.structs[name]);
}
}
// Register enum types if they were passed.
if (schema.types && schema.types.enums) {
for (let name of Object.keys(schema.types.enums)) {
this.registerEnumType(name, schema.types.enums[name]);
}
}
// Register aliases if they were passed.
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);
}
}
/**
* 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()`
*/
public ser(
type: TypeName | StructTypeDefinition,
data: any,
options?: BcsWriterOptions,
): BcsWriter {
if (typeof type === 'string' || Array.isArray(type)) {
const { name, params } = this.parseTypeName(type);
return this.getTypeInterface(name).encode(this, data, options, params as string[]);
}
// Quick serialization without registering the type in the main struct.
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. \n${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.
*/
public de(
type: TypeName | StructTypeDefinition,
data: Uint8Array | string,
encoding?: Encoding,
): any {
if (typeof data === 'string') {
if (encoding) {
data = decodeStr(data, encoding);
} else {
throw new Error('To pass a string to `bcs.de`, specify encoding');
}
}
// In case the type specified is already registered.
if (typeof type === 'string' || Array.isArray(type)) {
const { name, params } = this.parseTypeName(type);
return this.getTypeInterface(name).decode(this, data, params as string[]);
}
// Deserialize without registering a type using a temporary clone.
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. \n${JSON.stringify(type)}`);
}
/**
* Check whether a `TypeInterface` has been loaded for a `type`.
* @param type Name of the type to check.
* @returns
*/
public hasType(type: string): boolean {
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');
* ```
*/
public registerAlias(name: string, forType: string): BCS {
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.
*/
public registerType(
typeName: TypeName,
encodeCb: (
writer: BcsWriter,
data: any,
typeParams: TypeName[],
typeMap: { [key: string]: TypeName },
) => BcsWriter,
decodeCb: (
reader: BcsReader,
typeParams: TypeName[],
typeMap: { [key: string]: TypeName },
) => any,
validateCb: (data: any) => boolean = () => true,
): BCS {
const { name, params: generics } = this.parseTypeName(typeName);
this.types.set(name, {
encode(self: BCS, data, options: BcsWriterOptions, typeParams) {
const typeMap = (generics as string[]).reduce((acc: any, value: string, index) => {
return Object.assign(acc, { [value]: typeParams[index] });
}, {});
return this._encodeRaw.call(self, new BcsWriter(options), data, typeParams, typeMap);
},
decode(self: BCS, data, typeParams) {
const typeMap = (generics as string[]).reduce((acc: any, value: string, 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);
},
} as TypeInterface);
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
*/
public registerAddressType(name: string, length: number, encoding: Encoding | void = 'hex'): BCS {
switch (encoding) {
case 'base64':
return this.registerType(
name,
function encodeAddress(writer, data: string) {
return fromB64(data).reduce((writer, el) => writer.write8(el), writer);
},
function decodeAddress(reader) {
return toB64(reader.readBytes(length));
},
);
case 'hex':
return this.registerType(
name,
function encodeAddress(writer, data: string) {
return fromHEX(data).reduce((writer, el) => writer.write8(el), writer);
},
function decodeAddress(reader) {
return toHEX(reader.readBytes(length));
},
);
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.
*/
private registerVectorType(typeName: string): BCS {
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(
this: BCS,
writer: BcsWriter,
data: any[],
typeParams: TypeName[],
typeMap,
) {
return writer.writeVec(data, (writer, el) => {
let elementType: TypeName = typeParams[0];
if (!elementType) {
throw new Error(`Incorrect number of type parameters passed a to vector '${typeName}'`);
}
let { name, params } = this.parseTypeName(elementType);
if (this.hasType(name)) {
return this.getTypeInterface(name)._encodeRaw.call(this, writer, el, params, typeMap);
}
if (!(name in typeMap)) {
throw new Error(
`Unable to find a matching type definition for ${name} in vector; make sure you passed a generic`,
);
}
let { name: innerName, params: innerParams } = this.parseTypeName(typeMap[name]);
return this.getTypeInterface(innerName)._encodeRaw.call(
this,
writer,
el,
innerParams,
typeMap,
);
});
},
function decodeVector(this: BCS, reader: BcsReader, typeParams, typeMap) {
return reader.readVec((reader) => {
let elementType: TypeName = typeParams[0];
if (!elementType) {
throw new Error(`Incorrect number of type parameters passed to a vector '${typeName}'`);
}
let { name, params } = this.parseTypeName(elementType);
if (this.hasType(name)) {
return this.getTypeInterface(name)._decodeRaw.call(this, reader, params, typeMap);
}
if (!(name in typeMap)) {
throw new Error(
`Unable to find a matching type definition for ${name} in vector; make sure you passed a generic`,
);
}
let { name: innerName, params: innerParams } = this.parseTypeName(typeMap[name]);
return this.getTypeInterface(innerName)._decodeRaw.call(
this,
reader,
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.
*/
public registerStructType(typeName: TypeName, fields: StructTypeDefinition): BCS {
// When an Object is passed, we register it under a new key and store it
// in the registered type system. This way we allow nested inline definitions.
for (let key in fields) {
let internalName = this.tempKey();
let value = fields[key];
// TODO: add a type guard here?
if (!Array.isArray(value) && typeof value !== 'string') {
fields[key] = internalName;
this.registerStructType(internalName, value as StructTypeDefinition);
}
}
let struct = Object.freeze(fields); // Make sure the order doesn't get changed
// IMPORTANT: we need to store canonical order of fields for each registered
// struct so we maintain it and allow developers to use any field ordering in
// their code (and not cause mismatches based on field order).
let canonicalOrder = Object.keys(struct);
// Holds generics for the struct definition. At this stage we can check that
// generic parameter matches the one defined in the struct.
let { name: structName, params: generics } = this.parseTypeName(typeName);
// Make sure all the types in the fields description are already known
// and that all the field types are strings.
return this.registerType(
typeName,
function encodeStruct(
this: BCS,
writer: BcsWriter,
data: { [key: string]: any },
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}`,
);
}
// follow the canonical order when serializing
for (let key of canonicalOrder) {
if (!(key in data)) {
throw new Error(`Struct ${structName} requires field ${key}:${struct[key]}`);
}
// Before deserializing, read the canonical field type.
const { name: fieldType, params: fieldParams } = this.parseTypeName(
struct[key] as TypeName,
);
// Check whether this type is a generic defined in this struct.
// If it is -> read the type parameter matching its index.
// If not - tread as a regular field.
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 the type from the type parameters already exists
// and known -> proceed with type decoding.
if (this.hasType(name)) {
this.getTypeInterface(name)._encodeRaw.call(
this,
writer,
data[key],
params as string[],
typeMap,
);
continue;
}
// Alternatively, if it's a global generic parameter...
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(this: BCS, reader: BcsReader, typeParams, typeMap) {
if (typeParams.length !== generics.length) {
throw new Error(
`Incorrect number of generic parameters passed; expected: ${generics.length}, got: ${typeParams.length}`,
);
}
let result: { [key: string]: any } = {};
for (let key of canonicalOrder) {
const { name: fieldName, params: fieldParams } = this.parseTypeName(
struct[key] as TypeName,
);
// if it's not a generic
if (!generics.includes(fieldName)) {
result[key] = this.getTypeInterface(fieldName)._decodeRaw.call(
this,
reader,
fieldParams as string[],
typeMap,
);
} else {
const paramIdx = generics.indexOf(fieldName);
let { name, params } = this.parseTypeName(typeParams[paramIdx]);
// If the type from the type parameters already exists
// and known -> proceed with type decoding.
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
*/
public registerEnumType(typeName: TypeName, variants: EnumTypeDefinition): BCS {
// When an Object is passed, we register it under a new key and store it
// in the registered type system. This way we allow nested inline definitions.
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 as StructTypeDefinition);
}
}
let struct = Object.freeze(variants); // Make sure the order doesn't get changed
// IMPORTANT: enum is an ordered type and we have to preserve ordering in BCS
let canonicalOrder = Object.keys(struct);
// Parse type parameters in advance to know the index of each generic parameter.
let { name, params: canonicalTypeParams } = this.parseTypeName(typeName);
return this.registerType(
typeName,
function encodeEnum(
this: BCS,
writer: BcsWriter,
data: { [key: string]: any | null },
typeParams,
typeMap,
) {
if (!data) {
throw new Error(`Unable to write enum "${name}", missing data.\nReceived: "${data}"`);
}
if (typeof data !== 'object') {
throw new Error(
`Incorrect data passed into enum "${name}", expected object with properties: "${canonicalOrder.join(
' | ',
)}".\nReceived: "${JSON.stringify(data)}"`,
);
}
let key = Object.keys(data)[0];
if (key === undefined) {
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] as TypeName | null;
// write order byte
writer.write8(orderByte);
// When { "key": null } - empty value for the invariant.
if (invariantType === null) {
return writer;
}
let paramIndex = canonicalTypeParams.indexOf(invariantType);
let typeOrParam = paramIndex === -1 ? invariantType : typeParams[paramIndex];
{
let { name, params } = this.parseTypeName(typeOrParam);
return this.getTypeInterface(name)._encodeRaw.call(
this,
writer,
data[key],
params,
typeMap,
);
}
},
function decodeEnum(this: BCS, reader: BcsReader, typeParams, typeMap) {
let orderByte = reader.readULEB();
let invariant = canonicalOrder[orderByte];
let invariantType = struct[invariant] as TypeName | null;
if (orderByte === -1) {
throw new Error(
`Decoding type mismatch, expected enum "${name}" invariant index, received "${orderByte}"`,
);
}
// Encode an empty value for the enum.
if (invariantType === null) {
return { [invariant]: true };
}
let paramIndex = canonicalTypeParams.indexOf(invariantType);
let typeOrParam = paramIndex === -1 ? invariantType : typeParams[paramIndex];
{
let { name, params } = this.parseTypeName(typeOrParam);
return {
[invariant]: this.getTypeInterface(name)._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}
*/
public getTypeInterface(type: string): TypeInterface {
let typeInterface = this.types.get(type);
// Special case - string means an alias.
// Goes through the alias chain and tracks recursion.
if (typeof typeInterface === 'string') {
let chain: string[] = [];
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 === undefined) {
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
*/
public parseTypeName(name: TypeName): {
name: string;
params: TypeName[];
} {
if (Array.isArray(name)) {
let [typeName, ...params] = name;
return { name: typeName, params };
}
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 there are no generics - exit gracefully.
if (l_bound === -1 && r_bound === -1) {
return { name: name, params: [] };
}
// if one of the bounds is not defined - throw an Error.
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 };
}
}
/**
* Encode data with either `hex` or `base64`.
*
* @param {Uint8Array} data Data to encode.
* @param {String} encoding Encoding to use: base64 or hex
* @return {String} Encoded value.
*/
export function encodeStr(data: Uint8Array, encoding: Encoding): string {
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');
}
}
/**
* Decode either `base64` or `hex` data.
*
* @param {String} data Data to encode.
* @param {String} encoding Encoding to use: base64 or hex
* @return {Uint8Array} Encoded value.
*/
export function decodeStr(data: string, encoding: Encoding): Uint8Array {
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');
}
}
/**
* Register the base set of primitive and common types.
* Is called in the `BCS` constructor automatically but can
* be ignored if the `withPrimitives` argument is not set.
*/
export function registerPrimitives(bcs: BCS): void {
bcs.registerType(
BCS.U8,
function (writer: BcsWriter, data) {
return writer.write8(data);
},
function (reader: BcsReader) {
return reader.read8();
},
(u8) => u8 < 256,
);
bcs.registerType(
BCS.U16,
function (writer: BcsWriter, data) {
return writer.write16(data);
},
function (reader: BcsReader) {
return reader.read16();
},
(u16) => u16 < 65536,
);
bcs.registerType(
BCS.U32,
function (writer: BcsWriter, data) {
return writer.write32(data);
},
function (reader: BcsReader) {
return reader.read32();
},
(u32) => u32 <= 4294967296n,
);
bcs.registerType(
BCS.U64,
function (writer: BcsWriter, data) {
return writer.write64(data);
},
function (reader: BcsReader) {
return reader.read64();
},
);
bcs.registerType(
BCS.U128,
function (writer: BcsWriter, data: bigint) {
return writer.write128(data);
},
function (reader: BcsReader) {
return reader.read128();
},
);
bcs.registerType(
BCS.U256,
function (writer: BcsWriter, data) {
return writer.write256(data);
},
function (reader: BcsReader) {
return reader.read256();
},
);
bcs.registerType(
BCS.BOOL,
function (writer: BcsWriter, data) {
return writer.write8(data);
},
function (reader: BcsReader) {
return reader.read8().toString(10) === '1';
},
);
bcs.registerType(
BCS.STRING,
function (writer: BcsWriter, data: string) {
return writer.writeVec(Array.from(data), (writer, el) => writer.write8(el.charCodeAt(0)));
},
function (reader: BcsReader) {
return reader
.readVec((reader) => reader.read8())
.map((el: bigint) => String.fromCharCode(Number(el)))
.join('');
},
(_str: string) => true,
);
bcs.registerType(
BCS.HEX,
function (writer: BcsWriter, data: string) {
return writer.writeVec(Array.from(fromHEX(data)), (writer, el) => writer.write8(el));
},
function (reader: BcsReader) {
let bytes = reader.readVec((reader) => reader.read8());
return toHEX(new Uint8Array(bytes));
},
);
bcs.registerType(
BCS.BASE58,
function (writer: BcsWriter, data: string) {
return writer.writeVec(Array.from(fromB58(data)), (writer, el) => writer.write8(el));
},
function (reader: BcsReader) {
let bytes = reader.readVec((reader) => reader.read8());
return toB58(new Uint8Array(bytes));
},
);
bcs.registerType(
BCS.BASE64,
function (writer: BcsWriter, data: string) {
return writer.writeVec(Array.from(fromB64(data)), (writer, el) => writer.write8(el));
},
function (reader: BcsReader) {
let bytes = reader.readVec((reader) => reader.read8());
return toB64(new Uint8Array(bytes));
},
);
}
export function getRustConfig(): BcsConfig {
return {
genericSeparators: ['<', '>'],
vectorType: 'Vec',
addressLength: SUI_ADDRESS_LENGTH,
addressEncoding: 'hex',
};
}
export function getSuiMoveConfig(): BcsConfig {
return {
genericSeparators: ['<', '>'],
vectorType: 'vector',
addressLength: SUI_ADDRESS_LENGTH,
addressEncoding: 'hex',
};
}
export function splitGenericParameters(
str: string,
genericSeparators: [string, string] = ['<', '>'],
) {
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;
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet