Type creation
Circling back to metadata. There are two important things to remember when using the API to make queries or send transactions.
The functionality available, e.g. exposed on
api.query.*
is not hard-coded in the API, rather this is decorated from the chain metadata. So the metadata lets the API know which endpoints are available and what the type for those endpoints are.When you supply a value to the API, internally it will convert that value to the correct type as expected by the chain, i.e. as determined by the metadata. This means that a function such as
balances.transfer(address: Address, value: Balance)
can take at least the following inputs, which are all converted to the correct types -address
can be anAddress
, anAccountId
, anUint8Array
publicKey, a hex publicKey or an ss58 formatted address;value
can be aBalance
, a value encoded in hex, aBN
object, a base-10 string, a JSnumber
, a JSBigInt
or even a SCALE-encodedUint8Array
In cases where a value is returned such as storage queries, the response from the chain is always encoded into the correct Codec
type. This means that while the node may return an encoded block (with encoded extrinsics) via api.rpc.chain.getBlock()
, this is decoded into a proper SignedBlock
by the API. Outputting this value via .toJSON()
will yield an encoding for RPC, so if you are not using TypeScript (which adds code helpers on decoded objects), a representation via .toHuman()
will be more representative of the actual object fields, re-formatted for human consumption.
#
Why create typesWith the conversions done in the API, there are limited reasons to create types "manually". However, just because there are not thousands of reasons, does not mean it is not valid. For instance, you may retrieve an Option
and for the sake of sanity would like to use .unwrapOr()
on it, returning a Codec
default value where the value .isNone
.
In the example above, we introduced the api.createType(<typeName>, [<value>])
. The same format is also exposed by the TypeRegistry
(more on this in a short while) as well as createType(...)
from the actual @polkadot/types
package. All doing exactly the same.
#
Choosing how to createIn most cases, you would always want to use the api.createType
helper. What this does is call the underlying @polkadot/types
createType
, passing through the registry that is attached to the API. Registry? Yes, registry.
The registry contains a listing of all internal types and their classes that have been registered. So upon creation of an API instance, a registry
object is attached to the API and this is passed through to all created types. This allows the type definitions to not pollute the global namespace, but rather be contained and able to reference one another.
As mentioned, the createType
functions all do exactly the same, and in 99.99% of the cases you would be recommended to just forget about everything and use api.createType
if and when required. In some cases, you may just have a type object and from that want to create another type instance. For that you can access the registry
on the type object and call createType
on it. (If this type object was created from an API instance, the registry on the type and on the API will point to the same instance.)
Basically, this means that we have equivalency in creation for all the items below, all creating on the same registry (containing all injected types), and all wrapping the same value -
#
Using with TypeScriptThe API is built with TypeScript (as are all projects in the polkadot-js organization and as such allows developers using TS to have access to all the type interfaces defined on the chain, as well as having access to typings on interacting with the api.*
namespaces. In the next section we will provide an overview of what is available in terms of types and TypeScript.