Primitive Types
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:
false→0x00true→0x01
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:
- Compact-encoded length prefix
- 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 Type | Dart Type | Size | Codec |
|---|---|---|---|
| bool | bool | 1 byte | BoolCodec.codec |
| u8 | int | 1 byte | U8Codec.codec |
| u16 | int | 2 bytes | U16Codec.codec |
| u32 | int | 4 bytes | U32Codec.codec |
| u64 | int | 8 bytes | U64Codec.codec |
| u128 | BigInt | 16 bytes | U128Codec.codec |
| u256 | BigInt | 32 bytes | U256Codec.codec |
| i8 | int | 1 byte | I8Codec.codec |
| i16 | int | 2 bytes | I16Codec.codec |
| i32 | int | 4 bytes | I32Codec.codec |
| i64 | int | 8 bytes | I64Codec.codec |
| i128 | BigInt | 16 bytes | I128Codec.codec |
| i256 | BigInt | 32 bytes | I256Codec.codec |
| String | String | Variable | StrCodec.codec |
| Vec<u8> | List<int> | Variable | U8SequenceCodec.codec |
| u8; N | List<int> | N bytes | U8ArrayCodec(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), useU8ArrayCodec instead of U8SequenceCodec to save the length prefix byte.Next Steps
- Compact Encoding - Learn space-efficient integer encoding
- Collections - Work with sequences and maps
- Generic Types - Use Option and Result