Polkadart Logo
SCALE Codec

Primitive Types

Understanding and using SCALE primitive type codecs

Primitive Types Overview

SCALE primitive types are the building blocks of all blockchain data. The polkadart_scale_codec package provides codecs for all SCALE primitive types, enabling you to encode and decode basic values.

Boolean Type

The simplest type - a single byte representing true or false:

import 'package:polkadart_scale_codec/polkadart_scale_codec.dart';

// Encoding
final output = ByteOutput();
BoolCodec.codec.encodeTo(true, output);
print(output.toBytes()); // [1]

BoolCodec.codec.encodeTo(false, output);
print(output.toBytes()); // [1, 0]

// Decoding
final input = Input.fromBytes([1]);
final value = BoolCodec.codec.decode(input);
print(value); // true

Encoding Rules:

  • false0x00
  • true0x01

Unsigned Integers

SCALE provides unsigned integers of various sizes, all encoded in little-endian format:

U8 - 8-bit Unsigned Integer

final codec = U8Codec.codec;

// Encode
final output = ByteOutput();
codec.encodeTo(42, output);
print(output.toBytes()); // [42]

// Decode
final input = Input.fromBytes([42]);
final value = codec.decode(input);
print(value); // 42

Range: 0 to 255 Size: 1 byte

U16 - 16-bit Unsigned Integer

final codec = U16Codec.codec;

// Encode
final output = ByteOutput();
codec.encodeTo(258, output); // 0x0102 in little-endian
print(output.toBytes()); // [2, 1]

// Decode
final input = Input.fromBytes([2, 1]);
final value = codec.decode(input);
print(value); // 258

Range: 0 to 65,535 Size: 2 bytes Encoding: Little-endian

U32 - 32-bit Unsigned Integer

final codec = U32Codec.codec;

// Encode
final output = ByteOutput();
codec.encodeTo(16909060, output); // 0x01020304
print(output.toBytes()); // [4, 3, 2, 1]

// Decode
final input = Input.fromHex('0x04030201');
final value = codec.decode(input);
print(value); // 16909060

Range: 0 to 4,294,967,295 Size: 4 bytes Encoding: Little-endian

U64 - 64-bit Unsigned Integer

final codec = U64Codec.codec;

// Large values
final value = 1234567890123456789;
final encoded = codec.encode(value);

final decoded = codec.decode(Input.fromBytes(encoded));
print(decoded); // 1234567890123456789

Range: 0 to 2^64 - 1 Size: 8 bytes Encoding: Little-endian

U128 - 128-bit Unsigned Integer

For very large numbers, U128 uses BigInt:

final codec = U128Codec.codec;

// Very large balance
final balance = BigInt.parse('340282366920938463463374607431768211455');
final encoded = codec.encode(balance);

final decoded = codec.decode(Input.fromBytes(encoded));
print(decoded); // 340282366920938463463374607431768211455

Range: 0 to 2^128 - 1 Size: 16 bytes Type: BigInt

U256 - 256-bit Unsigned Integer

For cryptographic values and extremely large numbers:

final codec = U256Codec.codec;

// Maximum U256 value
final maxValue = BigInt.parse(
  '115792089237316195423570985008687907853269984665640564039457584007913129639935'
);

final output = HexOutput();
codec.encodeTo(maxValue, output);
print(output.toString());
// 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

Range: 0 to 2^256 - 1 Size: 32 bytes Type: BigInt

Signed Integers

Signed integers use two's complement representation:

I8 - 8-bit Signed Integer

final codec = I8Codec.codec;

// Positive value
codec.encodeTo(42, ByteOutput()); // [42]

// Negative value
final output = ByteOutput();
codec.encodeTo(-42, output);
print(output.toBytes()); // [214] (two's complement of -42)

// Decode
final value = codec.decode(Input.fromBytes([214]));
print(value); // -42

Range: -128 to 127 Size: 1 byte

I16 - 16-bit Signed Integer

final codec = I16Codec.codec;

// Encode -1000
final output = ByteOutput();
codec.encodeTo(-1000, output);
print(output.toBytes()); // [24, 252] in little-endian

// Decode
final input = Input.fromBytes([24, 252]);
final value = codec.decode(input);
print(value); // -1000

Range: -32,768 to 32,767 Size: 2 bytes

I32 - 32-bit Signed Integer

final codec = I32Codec.codec;

// Large negative number
final value = -1234567890;
final encoded = codec.encode(value);

final decoded = codec.decode(Input.fromBytes(encoded));
print(decoded); // -1234567890

Range: -2^31 to 2^31 - 1 Size: 4 bytes

I64 - 64-bit Signed Integer

final codec = I64Codec.codec;

// Very large negative
final value = -9223372036854775808; // Minimum i64
final encoded = codec.encode(value);

Range: -2^63 to 2^63 - 1 Size: 8 bytes

I128 - 128-bit Signed Integer

final codec = I128Codec.codec;

// Large signed value
final value = BigInt.parse('-170141183460469231731687303715884105728');
final encoded = codec.encode(value);

final decoded = codec.decode(Input.fromBytes(encoded));
print(decoded);

Range: -2^127 to 2^127 - 1 Size: 16 bytes Type: BigInt

I256 - 256-bit Signed Integer

final codec = I256Codec.codec;

// Maximum positive I256
final maxValue = BigInt.parse(
  '57896044618658097711785492504343953926634992332820282019728792003956564819967'
);

final output = HexOutput();
codec.encodeTo(maxValue, output);
print(output.toString());
// 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f

Range: -2^255 to 2^255 - 1 Size: 32 bytes Type: BigInt

String Type

UTF-8 encoded strings with compact length prefix:

final codec = StrCodec.codec;

// Encode
final output = ByteOutput();
codec.encodeTo('Hello', output);
// First byte is compact-encoded length (5)
// Followed by UTF-8 bytes
print(output.toBytes()); // [20, 72, 101, 108, 108, 111]

// Decode
final input = Input.fromHex('0x1448656c6c6f');
final value = codec.decode(input);
print(value); // 'Hello'

Encoding:

  1. Compact-encoded length prefix
  2. UTF-8 bytes

Empty String

final output = ByteOutput();
StrCodec.codec.encodeTo('', output);
print(output.toBytes()); // [0] - just the length

Unicode Strings

final emoji = '👋🌍';
final encoded = StrCodec.codec.encode(emoji);
// Length prefix + UTF-8 bytes of emoji

Sequences (Vec)

Dynamic-length vectors for primitive types:

U8 Sequence (Vec)

Most efficient for byte arrays:

final codec = U8SequenceCodec.codec;

// Encode
final bytes = [1, 2, 3, 4, 5];
final output = ByteOutput();
codec.encodeTo(bytes, output);
// [20, 1, 2, 3, 4, 5] - 20 is compact(5)

// Decode
final input = Input.fromHex('0x14010203040');
final decoded = codec.decode(input);
print(decoded); // [1, 2, 3, 4, 5]

Other Sequences

For other types, use SequenceCodec:

// Vec<u32>
final codec = SequenceCodec(U32Codec.codec);

final values = [100, 200, 300];
final encoded = codec.encode(values);

Arrays

Fixed-length arrays:

U8 Array

final codec = U8ArrayCodec(4);

// Must be exact length
final bytes = [1, 2, 3, 4];
final output = ByteOutput();
codec.encodeTo(bytes, output);
// No length prefix - just [1, 2, 3, 4]

// Wrong length throws exception
try {
  codec.encodeTo([1, 2, 3], output); // Error!
} catch (e) {
  print('Invalid length');
}

Generic Arrays

final codec = ArrayCodec(U32Codec.codec, 3);

final values = [100, 200, 300];
final encoded = codec.encode(values);

Complete Example: Account Balance

Here's a real-world example encoding account balance information:

import 'package:polkadart_scale_codec/polkadart_scale_codec.dart';

void main() {
  // Account has free and reserved balance
  final free = BigInt.from(1000000000000); // 1 DOT
  final reserved = BigInt.from(100000000000); // 0.1 DOT

  // Encode
  final output = ByteOutput();
  U128Codec.codec.encodeTo(free, output);
  U128Codec.codec.encodeTo(reserved, output);

  print('Encoded balances: ${output.toBytes()}');

  // Decode
  final input = Input.fromBytes(output.toBytes());
  final decodedFree = U128Codec.codec.decode(input);
  final decodedReserved = U128Codec.codec.decode(input);

  print('Free: $decodedFree');
  print('Reserved: $decodedReserved');
}

Type Conversion Table

SCALE TypeDart TypeSizeCodec
boolbool1 byteBoolCodec.codec
u8int1 byteU8Codec.codec
u16int2 bytesU16Codec.codec
u32int4 bytesU32Codec.codec
u64int8 bytesU64Codec.codec
u128BigInt16 bytesU128Codec.codec
u256BigInt32 bytesU256Codec.codec
i8int1 byteI8Codec.codec
i16int2 bytesI16Codec.codec
i32int4 bytesI32Codec.codec
i64int8 bytesI64Codec.codec
i128BigInt16 bytesI128Codec.codec
i256BigInt32 bytesI256Codec.codec
StringStringVariableStrCodec.codec
Vec<u8>List<int>VariableU8SequenceCodec.codec
u8; NList<int>N bytesU8ArrayCodec(N)

Best Practices

Use Appropriate Integer Sizes

Choose the smallest type that fits your data to minimize storage and bandwidth.

Little-Endian Awareness

SCALE uses little-endian encoding. When working with hex strings, remember byte order is reversed.

BigInt for Large Values

For values larger than JavaScript's MAX_SAFE_INTEGER or Dart's int range, always use BigInt codecs.

Prefer U8Array for Fixed Sizes

For fixed-length byte arrays (like hashes), use U8ArrayCodec instead of U8SequenceCodec to save the length prefix byte.

Next Steps