Codegen
Technically, to connect to a chain, all you need is just the provider. But to interact with it, you need to know the list of storage, runtime, and transaction calls and their types.
During runtime, the library can request the metadata for the chain it's connected to, and from this, it generates all the codecs to interact with it. But as a developer, you need to get that information beforehand.
Polkadot-API has a CLI that downloads the metadata for a chain and then uses that metadata to generate all the type descriptors.
papi add
papi add registers a new chain. It requires a key, which is the name of the constant the codegen will create, and a source (-f, -w, -c, -n, or --wasm). The command download the fresh metadata for that chain and stores this information for later use into a .papi folder, with a configuration file polkadot-api.json and the metadata file ${key}.scale.
You can add as many chains as you want, but each has to have a unique key (which must be a valid JS variable name).
> npx papi add --help
Usage: polkadot-api add [options] <key>
Add a new chain spec to the list
Arguments:
key Key identifier for the chain spec
Options:
--config <filename> Source for the config file
-f, --file <filename> Source from metadata encoded file
-w, --wsUrl <URL> Source from websocket url
-c, --chainSpec <filename> Source from chain spec file
-n, --name <name> Source from a well-known chain (choices: "ksmcc3", "paseo",
"polkadot", "polkadot_collectives", "rococo_v2_2", "westend2", [...]")
--wasm <filename> Source from runtime wasm file
--at <block hash or number> Only for -w/--wsUrl. Fetch the metadata for a specific block or hash
--no-persist Do not persist the metadata as a file
--skip-codegen Skip running codegen after adding
--whitelist <filename> Use whitelist file to reduce descriptor size
-h, --help display help for commandpapi add by default runs codegen automatically. If you want to add multiple chains without having to rerun codegen, you can use the flag --skip-codegen, and then run papi generate command once you want to run the codegen.
npx papi generate
# `generate` is the default command, so you can just run
npx papipapi generate (or papi)
The code is generated into a local package located in .papi/descriptors, which gets installed as a regular @polkadot-api/descriptors node modules package.
The folder .papi should be added to your source control repository. PAPI already handles the ignored files, you don't need to ignore anything.
When the metadata is updated, the codegen will update the generated code and also the package version, to have package managers update the @polkadot-api/descriptors package.
Descriptors content
The generated code contains all of the types extracted from the metadata of all chains:
- For every pallet:
- Storage queries
- Transactions
- Events
- Errors
- Constants
- View Functions
- Every runtime call
These are consumed by getTypedApi() or getUnsafeApi(), which allows the IDE to reference any of these calls with autocompletion, etc. At runtime, it also contains a representation of the type for each of these calls, so that it can detect incompatibilities with the chain it's connected to.
The types are anonymous (they don't have a name in the metadata), but PolkadotAPI has a directory of well-known types for some of the most widely used Enums. If a chain is using one of these well-known types, it's also generated and exported.
papi update
papi update refreshes the stored metadata for one or more previously registered chains. It re-downloads the metadata from the original source (WebSocket, chain-spec, or well-known chain) and overwrites the persisted ${key}.scale file. For those entries generated from WASM, or directly from a metadata file, it is a no-op.
> npx papi update --help
Usage: polkadot-api update [options] [keys]
Update the metadata files and generate descriptor files
Arguments:
keys Keys of the metadata files to update, separated by commas. Leave empty for all
Options:
--config <filename> Source for the config file
--skip-codegen Skip running codegen after adding
--whitelist <filename> Use whitelist file to reduce descriptor size
-h, --help display help for commandIf you provide one or more keys (comma-separated), only those chains are updated. If you omit keys, papi update will attempt to update all chains.
After it, papi generate runs by default. Pass --skip-codegen to avoid this step.
Usage
Import from @polkadot-api/descriptors every chain and type that you need, then use it through getTypedApi().
import { dot, MultiAddress } from "@polkadot-api/descriptors"
const dotClient = createClient(getSmProvider(smoldot.addChain({ chainSpec })))
const dotApi = dotClient.getTypedApi(dot)
const tx = dotApi.tx.Balances.transfer_keep_alive({
dest: MultiAddress.Id("SS58ADDR"),
value: 10n,
})
const encodedData = await tx.getEncodedData()Whitelist
By default, PAPI generates the descriptors for every possible interaction for each chain. These are 50~150KB files that are lazy-loaded, and it's possible to optimize them by whitelisting which calls you'll be using in your dApp.
import type { WhitelistEntry } from "@polkadot-api/descriptors"
// the export name has to *EXACTLY* match `whitelist`
export const whitelist: WhitelistEntry[] = [
// this will get all calls, queries, and consts inside Balances pallet
"*.Balances",
// this just a specific tx
"tx.PolkadotXcm.transfer_assets",
// all queries inside system pallet
"query.System.*",
// all constants
"const.*",
]For projects that use more than one chain definition, you can choose to have different whitelisted entries per each key by using WhitelistEntriesByChain. This is an object interface which takes each chain for each key, and also has a "common" key "*" for a global one. The whitelist is additive: Each chain will have the interactions of their own key plus the common key.
The WhitelistEntriesByChain accepts every interaction from every chain. The descriptors package also exports a <Key>WhitelistEntry helper type for each defined chain that can be used to help better narrow down those types.
In the following example we have two chains defined: dot for Polkadot Asset Hub and dotRelay for Polkadot Relay Chain.
import type {
WhitelistEntriesByChain,
DotRelayWhitelistEntry,
DotWhitelistEntry,
} from "@polkadot-api/descriptors"
export const whitelist: WhitelistEntriesByChain = {
// Applies to every chain
"*": [
"query.System.Account",
"tx.Balances.transfer_keep_alive",
"tx.Balances.transfer_allow_death",
],
dotRelay: [
"tx.XcmPallet.transfer_assets",
// Optional. Typescript trick to get better narrowing down.
] satisfies DotRelayWhitelistEntry[],
dot: ["tx.PolkadotXcm.transfer_assets"] satisfies DotWhitelistEntry[],
}Any chain not specified in the object will just inherit the whitelist from the common key *. If neither the common key or the chain key is specified, it will result in an empty chain with no interactions.
To have papi use the whitelist, save it somewhere in your project (suggestion: .papi/whitelist.ts), and then add the option in the papi config file in .papi/polkadot-api.json:
{
"descriptorPath": …,
"entries": { … },
"options": {
"whitelist": ".papi/whitelist.ts"
}
}The path is relative to where the papi command is called from, usually in the root of your project.
Optionally, the CLI has a flag to specify a different path papi --whitelist .papi/whitelist.ts.
A full working example of a dApp with whitelist could be found at our repo.
Codegen without descriptors package
PAPI is designed to create a descriptors package, @polkadot-api/descriptors, allowing it to be imported like any other package. To ensure compatibility with most package managers and bundlers, the CLI automatically adds this package as a dependency to your project.
For advanced use cases, you can configure the CLI to perform only the code generation without installing it as a dependency. To do this, add the following option to your configuration file, .papi/polkadot-api.json:
{
"descriptorPath": …,
"entries": { … },
"options": {
"noDescriptorsPackage": true
}
}