Type basics
We've touched upon types in most previous sections, i.e. that these are driven by metadata and that they are created and converted to/from automatically by the API. Since they appear in all results, we will divert a bit from the regularly scheduled program in explaining the API interfaces to giving some info on the base types.
#
Everything is a typeJust to re-iterate from the above. Everything returned by the API is a type and has a consistent interface: Codec
. This means that a Vec<u32>
(an array of u32
values) as well as a Struct
(an pre-defined object) or an Enum
has the same consistent base interface. Specific types will have values, based on the type - decorated and available.
As a minimum, anything returned by the API, be it a Vec<...>
, Option<...>
, Struct
or any normal type will always have the following methods - as defined on the Codec
interface:
.eq(<other value>)
- checks for equality against the other value. In all cases, it will accept "like" values, i.e. in the case of a number you can pass a primitive (such as1
), a hex value (such as0x01
) or even anUnit8Array
toHex()
- returns a hex-base representation of the value, always prefixed by0x
toHuman()
- returns Human-parsable JSON structure with values formatted as per the settingstoJSON()
- returns a JSON-like representation of the value, this is generally used when callingJSON.stringify(...)
on the valuetoString()
- returns a string representation, in some cases this performs additional encoding, i.e. forAddress
,AccountId
andAccountIndex
it will encode to the ss58 address.toU8a()
- returns aUint8Array
representation of the encoded value (generally exactly as passed to the node, where values are SCALE encoded)
Additionally, the following getters and utilities are available -
.isEmpty
-true
if the value is an all-empty value, i.e.0
in for numbers, all-zero for Arrays (or anythingUint8Array
),false
is non-zero.hash
- aHash
(once again with all the methods above) that is ablake2-256
representation of the contained value
#
Comparing typesTo reiterate the above API, the .eq
method is the preferred means of comparing base types, rather than the JavaScript equality operator (===
).
For example:
#
Working with numbersAll numbers wrap and extend an instance of bn.js. This means that in addition to the interfaces defined above, they have some additional methods -
.toNumber()
- a JS number (limited to 2^53 - 1). This does mean that for large values, e.g.Balance
(au128
extension), this can cause overflows.toBigInt()
- a JSBigInt
object (on supported platforms).add(...)
,.sub(...)
, ... - all the base methods available on theBN
object
In cases where a Compact
is returned, i.e. Compact<Balance>
, the value is wrapped. This object should be .unwrap()
-ed first to gain access to the underlying Balance
object.
#
Working with structuresAll structures, a wrapping of an object containing a number of member variables, is an implementation of a standard JS Map
object, so all the functions available on a Map
such as .entries()
are available. Additionally it is decorated with actual getters for the fields.
As an example, a Header
will have getters for the .parentHash
, .number
, .stateRoot
, .extrinsicsRoot
and .digest
fields. The same applies for all structures, as they are returned, each member will have an associated getter.
Be aware that in the JS version naming defaults to camelCase
where names of fields in Substrate defaults to snake_case
. (Each version aligning with conventions in the respective languages)
#
Working with enumsEach enum has additional getters which are injected based on the fields wrapped. These take the form of .is<Name>
and .as<Name>
to allow you to check if the enum is a certain value or to retrieve the underlying value as a specific type.
As a real-world example, when an extrinsic is applied, the Phase
enum has one of two states, ApplyExtrinsic(u32)
or Finalization
. In this case .isApplyExtrinsic
would be true
when an extrinsic is being applied, and .asApplyExtrinsic
would return the value as a u32
(which is the index of the extrinsic in the block, as it is being applied). When isApplyExtrinsic
is false
and asApplyExtrinsic
is called, the getter will throw.
#
Working with Option<Type>An Option<Type>
attempts to mimic the Rust approach of having None
and Some
available. This means the following getters & methods are available on an Option
-
.isNone
- istrue
if no underlying values is wrapped, effectively the same as.isEmpty
.isSome
- this istrue
if a value is wrapped, i.e. if aOption<u32>
has an actual underlyingu32
.unwrap()
- whenisSome
, this will return the wrapped value, i.e. forOption<u32>
, this would return theu32
. When the value isisNone
, this call will throw an exception..unwrapOr(<default value>)
- this extendsunwrap()
, returning the wrapped value whenisSome
and in the case ofisNone
it will return the<default value>
passed..unwrapOrDefault()
- returns either the wrapped value whenisSome
, or the default for the type whenisNone
#
Working with TuplesA tuple is defined in the form of (u32, AccountId)
. To access the individual values, you can access it via its index, i.e.
When making a call that expect a Tuple
input, pass it as an array, so to pass the example above into a call, it would be .call([123, '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'])
#
Boolean valuesAll bool
values are returned as nomal JS Boolean
objects, i.e. they extend the JS Boolean to allow it to be used as a Codec
type.
In addition to the default getValue()
on the JS Boolean and the default interfaces explained above, two additional getters have been added for ease-of-use. These are isTrue
and isFalse
that will just return a normal JS primitive boolean
for a quick check without using getValue()
.
#
Extending typesFor customized chains, the need exists to register types so the API is aware of how to decode values for those types. The next section will provide a walk-through for the definition of custom types allowing the definition or re-definition of any type the API is aware of.