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 -
addresscan be an
Uint8ArraypublicKey, a hex publicKey or an ss58 formatted address;
valuecan be a
Balance, a value encoded in hex, a
BNobject, a base-10 string, a JS
number, a JS
BigIntor even a SCALE-encoded
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.
With 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
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.
In most cases, you would always want to use the
api.createType helper. What this does is call the underlying
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 -
The 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.