As explained in a previous step, running the CLI
command papi add
, downloads the metadata for a chain and then uses that metadata to generate all the type descriptors, placing them under the .papi
directory.
It's important to know that the descriptors exported by the codegen should be treated as a black box. When importing the descriptors of a chain, the type might not actually match what's in runtime, and its internals are subject to change. It has to be treated as an opaque token, passing it directly to client's method: .getTypedApi()
.
That method returns a TypedApi
interface.
The TypedApi
interface, simplifies interaction with the runtime metadata, making it easy to query storage, submit transactions, and more — all while offering a smooth developer experience.
It achieves this by utilizing the type descriptors generated by the PAPI CLI (discussed earlier), which are used to define the types available during development.
This structure allows for seamless access to chain functionality, ensuring that everything from runtime calls to storage queries is fully typed, reducing errors and making your code more reliable.
The structure of the TypedApi object looks like this:
type TypedApi = {query: StorageApi;tx: TxApi;event: EvApi;apis: RuntimeCallsApi;constants: ConstApi;compatibilityToken: Promise<CompatibilityToken>;};
In order to "access the chain functionality" as described the following code snippet can be used:
// Import the generated descriptorimport { dot } from "@polkadot-api/descriptors";....// after creating the provider and initializing the client// use that client to get the interface by passing the decsriptorconst dotApi = client.getTypedApi(dot);...// Then the strongly typed interface can be used in a sync or async// manner depending on what needs to be called from the interface// (deep dive into more here: https://papi.how/typed/ )const accountInfo = await dotApi.query.System.Account.getValue(address);
Looking at the last line, it is obvious that we are trying to query a chain's account information based on its address.
The returned value typically includes details such as the account's free balance, reserved balance, and potentially other state data. e.g.:
const { free, reserved } = accountInfo.data;
In Polkadot, the total balance of an account is computed as the sum of two components: free balance and reserved balance. Here's a breakdown:
Free Balance: The free balance represents the amount of tokens that an account has available for transactions, staking, or other operations. This is the liquid balance that the user can actively spend, transfer, or use for transactions.
Reserved Balance: The reserved balance is the portion of the account’s tokens that cannot be spent freely. Tokens may be reserved for various reasons such as bonding for staking, governance, certain on-chain operations (e.g., creating identity, reserving a slot in parachain auctions) a.k.a deposits. While the reserved balance is still owned by the account holder, it cannot be transferred or used until it's released from its reserved state.
Total Balance: The total balance of an account is calculated as:
total_balance = free_balance + reserved_balance
This formula gives the overall account balance, but not all of it is available for transactions. The free balance is the spendable portion, while the reserved balance remains locked.