A PHP implementation of BCS (Binary Canonical Serialization) — the serialization format used by Sui, Aptos, and other Move-based blockchains

Maintainers

Package info

github.com/inodrahq/php-bcs

pkg:composer/inodrahq/bcs

Statistics

Installs: 8

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-03-10 22:33 UTC

This package is auto-updated.

Last update: 2026-03-10 22:38:27 UTC


README

A PHP implementation of Binary Canonical Serialization (BCS) — the serialization format used by Sui, Aptos, and other Move-based blockchains.

Requirements

  • PHP 8.2+
  • ext-bcmath

Installation

composer require inodrahq/bcs

Quick Start

use Inodrahq\Bcs\Encoder;
use Inodrahq\Bcs\Decoder;
use Inodrahq\Bcs\U64;
use Inodrahq\Bcs\BcsString;
use Inodrahq\Bcs\Vector;
use Inodrahq\Bcs\Boolean;

// Encode a u64
$encoder = new Encoder();
(new U64(1000000))->encode($encoder);
$bytes = $encoder->getBytes(); // raw BCS bytes

// Decode it back
$decoder = new Decoder($bytes);
$value = U64::decode($decoder);
echo $value->getValue(); // "1000000"

// Encode a string
$encoder = new Encoder();
(new BcsString('hello'))->encode($encoder);

// Encode a vector of u64s
$encoder = new Encoder();
$vec = new Vector(U64::class, [new U64(1), new U64(2), new U64(3)]);
$vec->encode($encoder);

Supported Types

BCS Type PHP Class Description
bool Boolean True/false
u8 U8 Unsigned 8-bit integer
u16 U16 Unsigned 16-bit little-endian
u32 U32 Unsigned 32-bit little-endian
u64 U64 Unsigned 64-bit little-endian
u128 U128 Unsigned 128-bit little-endian
u256 U256 Unsigned 256-bit little-endian
i8 I8 Signed 8-bit integer
i16 I16 Signed 16-bit little-endian
i32 I32 Signed 32-bit little-endian
i64 I64 Signed 64-bit little-endian
i128 I128 Signed 128-bit little-endian
bytes Bytes Fixed-length byte array
string BcsString ULEB128-prefixed UTF-8 string
vector<T> Vector ULEB128-prefixed sequence
option<T> Option 0x00 (None) or 0x01 + value (Some)
map<K,V> Map Sorted key-value pairs
struct Struct Ordered named fields
enum Enum ULEB128 variant index + data
tuple Tuple Ordered unnamed fields
fixed_array FixedArray Fixed-length array (no length prefix)
byte_vector ByteVector ULEB128-prefixed raw bytes

Advanced Usage

Structs

use Inodrahq\Bcs\Struct;
use Inodrahq\Bcs\U64;
use Inodrahq\Bcs\BcsString;

// Define and encode a struct
$struct = new Struct([
    'name' => new BcsString('Alice'),
    'balance' => new U64(1000),
]);

$encoder = new Encoder();
$struct->encode($encoder);

Enums

use Inodrahq\Bcs\Enum;
use Inodrahq\Bcs\U64;
use Inodrahq\Bcs\BcsString;

// Variant 0 with a string payload
$enum = new Enum(0, new BcsString('hello'));

// Variant 1 with a u64 payload
$enum = new Enum(1, new U64(42));

Options

use Inodrahq\Bcs\Option;
use Inodrahq\Bcs\U64;

$some = Option::some(new U64(42));
$none = Option::none();

Maps

use Inodrahq\Bcs\Map;
use Inodrahq\Bcs\BcsString;
use Inodrahq\Bcs\U64;

$map = new Map(BcsString::class, U64::class, [
    [new BcsString('alice'), new U64(100)],
    [new BcsString('bob'), new U64(200)],
]);

Tuples

use Inodrahq\Bcs\Tuple;
use Inodrahq\Bcs\U64;
use Inodrahq\Bcs\BcsString;

// Heterogeneous ordered collection
$tuple = new Tuple([U64::class, BcsString::class], [new U64(42), new BcsString('hello')]);
$encoder = new Encoder();
$tuple->encode($encoder);

// Decode with type context
$decoder = new Decoder($encoder->getBytes());
$decoded = Tuple::decodeWithTypes($decoder, [U64::class, BcsString::class]);
echo $decoded->get(0)->getValue(); // "42"
echo $decoded->get(1)->getValue(); // "hello"

Fixed Arrays

use Inodrahq\Bcs\FixedArray;
use Inodrahq\Bcs\U8;

// Fixed-size array (no length prefix, unlike Vector)
$arr = new FixedArray(U8::class, [new U8(1), new U8(2), new U8(3)], 3);
$encoder = new Encoder();
$arr->encode($encoder);

// Decode with known size
$decoder = new Decoder($encoder->getBytes());
$decoded = FixedArray::decodeWithType($decoder, U8::class, 3);

Decoding

All container types require type context for decoding:

use Inodrahq\Bcs\Decoder;
use Inodrahq\Bcs\Vector;
use Inodrahq\Bcs\Struct;
use Inodrahq\Bcs\Enum;
use Inodrahq\Bcs\Option;
use Inodrahq\Bcs\Map;

// Decode from hex
$decoder = Decoder::fromHex('03010203');
$vec = Vector::decodeWithType($decoder, U8::class);

// Struct decoding (fields must match encoding order)
$decoder = new Decoder($bytes);
$struct = Struct::decodeWithTypes($decoder, [
    'name' => BcsString::class,
    'balance' => U64::class,
]);
echo $struct->getField('name')->getValue();

// Enum decoding (map variant tag => type class, null for no data)
$decoder = new Decoder($bytes);
$enum = Enum::decodeWithVariants($decoder, [
    0 => BcsString::class, // variant 0 has a string
    1 => U64::class,       // variant 1 has a u64
    2 => null,             // variant 2 has no data
]);

// Option decoding
$decoder = new Decoder($bytes);
$opt = Option::decodeWithType($decoder, U64::class);
if ($opt->isSome()) {
    echo $opt->getValue()->getValue();
}

// Map decoding
$decoder = new Decoder($bytes);
$map = Map::decodeWithTypes($decoder, BcsString::class, U64::class);

Custom Types

Implement the BcsType interface:

use Inodrahq\Bcs\BcsType;
use Inodrahq\Bcs\Encoder;
use Inodrahq\Bcs\Decoder;

class MyType implements BcsType
{
    public function encode(Encoder $encoder): void
    {
        // Write your bytes
    }

    public static function decode(Decoder $decoder): static
    {
        // Read and return
    }
}

Hex Helpers

All encoded types support hex output via the Encoder:

$encoder = new Encoder();
(new U64(255))->encode($encoder);
echo bin2hex($encoder->getBytes()); // "ff00000000000000"

Testing

composer test

228 tests verified against golden vectors generated by the Rust bcs crate.

License

MIT