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 type#
Just 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 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 anUnit8ArraytoHex()- returns a hex-base representation of the value, always prefixed by0xtoHuman()- 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,AccountIdandAccountIndexit will encode to the ss58 address.toU8a()- returns aUint8Arrayrepresentation 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-trueif the value is an all-empty value, i.e.0in for numbers, all-zero for Arrays (or anythingUint8Array),falseis non-zero.hash- aHash(once again with all the methods above) that is ablake2-256representation of the contained value
Comparing types#
To 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 numbers#
All 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(au128extension), this can cause overflows.toBigInt()- a JSBigIntobject (on supported platforms).add(...),.sub(...), ... - all the base methods available on theBNobject
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 structures#
All 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 enums#
Each 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 is 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- istrueif no underlying values is wrapped, effectively the same as.isEmpty.isSome- this istrueis 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 whenisSomeand in the case ofisNoneit will return the<default value>passed..unwrapOrDefault()- returns either the wrapped value whenisSome, or the default for the type whenisNone
Working with Tuples#
A 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 values#
All bool values are returned as nomal JS Booolean 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 types#
For 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.