Skip to main content

ABI Types

MultiversX smart contracts use a rich type system for encoding and decoding data. abidock_mvx implements the whole of it: every type in a contract's ABI maps to an AbiType (the description) and a TypedValue (an instance of it).

Type Categories

CategoryTypesDescription
Primitiveu8-u64, i8-i64, BigUint, BigInt, bool, bytes, utf-8 stringBasic value types
CollectionList<T>, array<N>, Option<T>Container types
Compositestruct, tuple, enum, explicit-enumStructured types
SpecialAddress, TokenIdentifier, H256, CodeMetadata, ManagedDecimal, ManagedByteArrayDomain-specific types
Argument-shapevariadic<T>, optional<T>, multi<...>, MultiArg/MultiResultOnly valid at the argument boundary

Built-in framework types the contract's ABI never spells out are recognised intrinsically: EsdtTokenPayment, EgldOrEsdtTokenPayment, EgldOrMultiEsdtPayment, Payment, FungiblePayment, TokenId (a TokenIdentifier) and NonZeroBigUint (a BigUint).

Two encodings for every type

The chain encodes each value in one of two ways, and the difference is the single most important thing to understand about this type system:

  • Top-level - the value owns a whole buffer (a transaction argument, or one return-data part). Its length is implied by the buffer, so lengths and zeroes can be omitted: a zero integer is an empty buffer, false is an empty buffer, None is an empty buffer.
  • Nested - the value sits inside a larger buffer (a struct field, a list element, a tuple slot). Nothing else knows where it ends, so it is written self-delimiting: fixed-width integers, explicit 4-byte big-endian length prefixes, explicit marker bytes.
TypeTop-levelNested
u8/u16/u32/u64minimal big-endian bytes, zero = emptyfixed 1 / 2 / 4 / 8 bytes, big-endian
i8/i16/i32/i64minimal two's-complement, zero = emptyfixed 1 / 2 / 4 / 8 bytes, two's-complement
BigUintmagnitude bytes, zero = empty[u32 length][magnitude]
BigInttwo's-complement bytes, zero = empty[u32 length][two's-complement]
bool0x01 for true, empty for false1 byte: 0x01 / 0x00
bytes, utf-8 stringraw bytes[u32 length][bytes]
Address32 bytes32 bytes
TokenIdentifierUTF-8 bytes[u32 length][UTF-8]
Option<T>empty = None, else 0x01 + nested T0x00 = None, else 0x01 + nested T
List<T>items concatenated, nested-encoded[u32 count] + items nested-encoded
array<N,T>N items nested-encodedN items nested-encoded
struct / tuplefields nested-encoded, in declaration ordersame
enumempty for unit variant 0, else discriminant byte + nested fieldsdiscriminant byte + nested fields
explicit-enumUTF-8 variant name[u32 length][UTF-8 variant name]

Each type page repeats the rule that matters for it, and the codec doc comments in lib/src/abi/codecs/ are the authoritative specification.

Creating Values

There are three ways to create any value:

// Method 1: Static factory - Type.create(value)
final value1 = U64Type.create(123); // int or BigInt for U64

// Method 2: Direct constructor - ValueClass(value)
final value2 = U64Value(BigInt.from(123)); // BigInt required

// Method 3: Via type instance - type.createValue(value)
final value3 = U64Type.type.createValue(123);

// Access the native Dart value
print(value1.nativeValue); // BigInt 123
createValue returns TypedValue

AbiType.createValue is declared to return the base TypedValue, because a type instance is only known at runtime. Cast when you need the concrete API (as StructValue, as ListValue, ...). The static Type.create(...) factories return the concrete value class directly, so they need no cast.

Type Parameter Reference

Typecreate() acceptsValue constructor
u8, u16, u32intint
i8, i16, i32intint
u64, i64int or BigIntBigInt
BigUint, BigIntint or BigIntBigInt

Native Value Conversion

Every TypedValue exposes .nativeValue:

ABI value.nativeValue type
U8Value - U32Value, I8Value - I32Valueint
U64Value, I64Value, BigUIntValue, BigIntValueBigInt
BooleanValuebool
StringValueString
BytesValue, H256Value, ManagedByteArrayValueUint8List
AddressValueString (bech32)
TokenIdentifierValueString
CodeMetadataValueint (16-bit flags)
ManagedDecimalValueBigInt (raw, unscaled)
ListValue, ArrayValue, TupleValueList<dynamic>
OptionValue, OptionalValueinner native value, or null
StructValueMap<String, dynamic>
EnumValueString (unit variant) or Map<String, dynamic>
ExplicitEnumValueString (variant name)
VariadicValueList<TypedValue> (items keep their types)

Quick Reference

Integers

// Unsigned integers (u8/u16/u32 take int)
final u8 = U8Type.create(255);
final u16 = U16Type.create(65535);
final u32 = U32Type.create(4294967295);

// u64 takes int or BigInt
final u64 = U64Type.create(BigInt.parse('18446744073709551615'));
final u64Small = U64Type.create(1000); // int also works

// Big unsigned (arbitrary precision, takes int or BigInt)
final bigUint = BigUIntType.create(BigInt.parse('999999999999999999999'));

// Signed integers (i8/i16/i32 take int)
final i8 = I8Type.create(-128);
final i32 = I32Type.create(-2147483648);

// i64 and BigInt take int or BigInt
final i64 = I64Type.create(BigInt.from(-5000000000000));
final bigInt = BigIntType.create(BigInt.from(-999999));

Boolean & String

final flag = BooleanType.create(true);
final str = StringType.create('Hello, MultiversX!');

Address

// From bech32 string directly
final address = AddressType.create('erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu');

// Or from hex string
final addressHex = AddressType.create('0000000000000000000000000000000000000000000000000000000000000000');

// Or from bytes
final addressBytes = AddressType.create(List<int>.filled(32, 0));

Collections

// Define a list type, then create a value
final listType = ListType(U64Type.type);
final list = listType.createValue(<BigInt>[
BigInt.from(1),
BigInt.from(2),
BigInt.from(3),
]) as ListValue;

// Define an option type, then create Some or None
final optionType = OptionType(U64Type.type);
final some = optionType.createValue(BigInt.from(42)) as OptionValue; // Some(42)
final none = optionType.createValue(null) as OptionValue; // None

Struct

// Define a struct type, then create a value
final userType = StructBuilder('User')
.field('name', StringType.type)
.field('balance', BigUIntType.type)
.build();

final user = userType.createValue(<String, dynamic>{
'name': 'Alice',
'balance': BigInt.from(1000),
}) as StructValue;

Enum

// Define enum type with simple variants
final statusType = EnumBuilder('Status')
.variant('Active', 0)
.variant('Inactive', 1)
.build();

// Create a value
final status = statusType.createValue('Active') as EnumValue;

// Enum with fields
final resultType = EnumBuilder('Result')
.variantWithFields('Ok', 0, <AbiType>[U64Type.type])
.variantWithFields('Err', 1, <AbiType>[StringType.type])
.build();

// Create a value with fields
final result = resultType.createValue(<String, dynamic>{
'variant': 'Ok',
'fields': <dynamic>[BigInt.from(42)],
}) as EnumValue;

Working with Contract Results

controller.query() returns a QueryResult that carries both representations:

final result = await controller.query(
endpointName: 'getUser',
arguments: <dynamic>[],
);

// `values` / `first` are native Dart values: a struct arrives as a Map.
final userMap = result.first as Map<String, dynamic>;
print(userMap['name']);

// `typedValues` keeps the ABI types, for when you need the type metadata.
final user = result.typedValues.first as StructValue;
final name = user.getFieldValue('name').nativeValue as String;
final balance = user.getFieldValue('balance').nativeValue as BigInt;

getFieldValue throws ArgumentError for an unknown field name; use tryGetFieldValue when the field may be absent.

Next Steps