Security News
RubyGems.org Adds New Maintainer Role
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
@mysten/bcs
Advanced tools
This small and lightweight library implements Binary Canonical Serialization (BCS) in JavaScript, making BCS available in both Browser and NodeJS environments.
To install, add the @mysten/bcs
package to your
project:
npm i @mysten/bcs
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
// registering types so we can use them
bcs.registerAlias('UID', BCS.ADDRESS);
bcs.registerEnumType('Option<T>', {
none: null,
some: 'T',
});
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');
// serialization: Object into bytes - an Option with <T = Coin>
let data = bcs.ser('Option<Coin>', { some: coin }).toString('hex');
console.log(data);
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 constructor is configurable for the target. The following parameters are available for custom configuration:
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) |
// Example: All options used
import { BCS } from '@mysten/bcs';
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.
// Example: Sui Move Config
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
// use bcs.ser() to serialize data
const val = [1, 2, 3, 4];
const ser = bcs.ser(['vector', BCS.U8], val).toBytes();
// use bcs.de() to deserialize data
const res = bcs.de(['vector', BCS.U8], ser);
console.assert(res.toString() === val.toString());
Similar configuration exists for Rust, the difference is the Vec<T>
for vectors and address
(being a special Move type) is not needed:
// Example: Rust Config
import { BCS, getRustConfig } from '@mysten/bcs';
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);
console.assert(res.toString() === val.toString());
By default, BCS will have a set of built-in type definitions and handy abstractions; all of them are supported in Move.
Supported integer types are: u8, u16, u32, u64, u128 and u256. Constants BCS.U8
to BCS.U256
are
provided by the library.
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 |
All of the type usage examples below can be used for bcs.de(<type>, ...)
as well.
// Example: Primitive types
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
// 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');
// 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();
// 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();
// 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();
To serialize and deserialize data to and from BCS there are two methods: bcs.ser()
and bcs.de()
.
// Example: Ser/de and Encoding
import { BCS, getSuiMoveConfig, BcsWriter } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
// 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');
// writer.toBytes() returns a Uint8Array
let bytes: Uint8Array = bcsWriter.toBytes();
// 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');
Tip: all registering methods start with
bcs.register*
(egbcs.registerStructType
).
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.
// Example: Alias
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
bcs.registerAlias('ObjectDigest', BCS.BASE58);
// ObjectDigest is now treated as base58 string
let _b58 = bcs.ser('ObjectDigest', 'Ldp').toBytes();
// we can override already existing definition to make it a HEX string
bcs.registerAlias('ObjectDigest', BCS.HEX);
let _hex = bcs.ser('ObjectDigest', 'C0FFEE').toBytes();
Structs are the most common way of working with data; in BCS, a struct is simply a sequence of base types.
// 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,
},
})
.toBytes();
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:
// 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',
});
// 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'],
});
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).
// Example: Enum
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
bcs.registerEnumType('Option<T>', {
none: null,
some: 'T',
});
bcs.registerEnumType('TransactionType', {
single: 'vector<u8>',
batch: 'vector<vector<u8>>',
});
// any truthy value marks empty in struct value
let _optionNone = bcs.ser('Option<TransactionType>', {
none: true,
});
// some now contains a value of type TransactionType
let _optionTx = bcs.ser('Option<TransactionType>', {
some: {
single: [1, 2, 3, 4, 5, 6],
},
});
// 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],
],
},
});
Sometimes it is useful to get a value without registering a new struct. For that inline struct definition can be used.
Nested struct definitions are not yet supported, only first level properties can be used (but they can reference any type, including other struct types).
// Example: Inline Struct
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
// Some value we want to serialize
const coin = {
id: '0000000000000000000000000000000000000000000000000000000000000005',
value: 1111333333222n,
};
// 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();
// Same with deserialization
let coin_restored = bcs.de({ id: BCS.ADDRESS, value: BCS.U64 }, coin_bytes);
console.assert(coin.id == coin_restored.id, '`id` must match');
console.assert(coin.value == coin_restored.value, '`value` must match');
Currently, main applications of this library are:
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.
// Definition in Move which we want to read in JS
module me::example {
struct Metadata has store {
name: std::ascii::String,
}
struct ChainObject has key {
id: sui::object::UID,
owner: address,
meta: Metadata
}
// ...
}
Definition for the above should be the following:
// Example: Simplifying UID
import { BCS, getSuiMoveConfig } from '@mysten/bcs';
const bcs = new BCS(getSuiMoveConfig());
// 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,
});
// Same for the main object that we intend to read
bcs.registerStructType('ChainObject', {
id: 'UID',
owner: BCS.ADDRESS,
meta: 'Metadata',
});
struct UID has store { id: ID }struct ID has store, copy, drop { bytes: address }
// { id: { bytes: '0x.....' } }
FAQs
BCS - Canonical Binary Serialization implementation for JavaScript
The npm package @mysten/bcs receives a total of 64,032 weekly downloads. As such, @mysten/bcs popularity was classified as popular.
We found that @mysten/bcs demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.
Security News
Research
Socket's threat research team has detected five malicious npm packages targeting Roblox developers, deploying malware to steal credentials and personal data.