| name | polkadot-chain-connection |
| description | Connect to Polkadot chains (Asset Hub, Bulletin, Individuality) using typed APIs from @polkadot-apps/chain-client and @polkadot-apps/descriptors. |
Polkadot Chain Connection
Connect to Polkadot ecosystem chains with fully typed APIs powered by polkadot-api (papi) descriptors.
WARNING: Only the "paseo" environment is currently available. Calling getChainAPI("polkadot") or getChainAPI("kusama") will throw.
WARNING: Descriptors use subpath imports only. Import @polkadot-apps/descriptors/bulletin, never @polkadot-apps/descriptors.
Two Ways to Connect
@polkadot-apps/chain-client offers two connection paths:
| Path | Function | When to Use |
|---|
| BYOD (bring your own descriptors) | createChainClient(config) | Recommended for production. Import only the chains you need, provide your own RPCs. Space-optimized. |
| Preset (zero-config) | getChainAPI(env) | Convenience / prototyping. Gives you Asset Hub + Bulletin + Individuality with built-in RPCs. No descriptor imports needed. |
Both return the same ChainClient<T> type.
Size note: BYOD imports only the descriptors you need. A bulletin-only app loads ~900 KB vs ~6.3 MB for the full preset.
BYOD (Bring Your Own Descriptors) -- Recommended
import { createChainClient } from "@polkadot-apps/chain-client";
import { paseo_asset_hub } from "@polkadot-apps/descriptors/paseo-asset-hub";
import { bulletin } from "@polkadot-apps/descriptors/bulletin";
const client = await createChainClient({
chains: { assetHub: paseo_asset_hub, bulletin },
rpcs: {
assetHub: ["wss://sys.ibp.network/asset-hub-paseo"],
bulletin: ["wss://paseo-bulletin-rpc.polkadot.io"],
},
});
const account = await client.assetHub.query.System.Account.getValue("5G...");
const fee = await client.bulletin.query.TransactionStorage.ByteFee.getValue();
client.destroy();
Preset (Zero-Config) -- Convenience / Prototyping
import { getChainAPI, destroyAll } from "@polkadot-apps/chain-client";
const client = await getChainAPI("paseo");
const account = await client.assetHub.query.System.Account.getValue("5G...");
console.log("Free balance:", account.data.free);
const rawAssetHub = client.raw.assetHub;
client.destroy();
Decision Guide: BYOD vs Preset
Use BYOD (createChainClient) when:
- You only need a subset of chains (e.g., just Asset Hub) -- smaller bundle
- You want to provide your own RPC endpoints
- You are connecting to chains not in the preset set
- You need fine-grained control over connection metadata
- You are building for production and want to minimize payload size
Use Preset (getChainAPI) when:
- You want all three chains (Asset Hub, Bulletin, Individuality) for a known environment
- You do not need custom RPC endpoints
- You want zero-config with no descriptor imports
- You are prototyping or exploring quickly
Packages
| Package | Purpose |
|---|
@polkadot-apps/chain-client | High-level connection manager. Two paths: getChainAPI (preset) and createChainClient (BYOD). |
@polkadot-apps/descriptors | Pre-generated papi descriptors. Subpath imports per chain. |
@polkadot-apps/host | Container detection, host-routed providers, and storage (Polkadot Browser / Polkadot Desktop). Re-exported by chain-client. |
Environments
type Environment = "polkadot" | "kusama" | "paseo";
Each environment connects to three chains:
- Asset Hub -- parachain for assets, balances, and smart contracts (environment-specific: polkadot-asset-hub, kusama-asset-hub, or paseo-asset-hub)
- Bulletin -- chain for transaction storage (shared genesis across environments)
- Individuality -- chain for identity/people (shared genesis across environments)
Currently available: Only "paseo" has live RPCs for all three chains.
How ChainClient Works
Both getChainAPI(env) and createChainClient(config) return a ChainClient<T> object:
type ChainClient<TChains extends Record<string, ChainDefinition>> = {
[K in keyof TChains]: TypedApi<TChains[K]>;
} & {
raw: { [K in keyof TChains]: PolkadotClient };
destroy: () => void;
};
For getChainAPI("paseo"), the return type is ChainClient<PresetChains<"paseo">> with keys assetHub, bulletin, and individuality.
For createChainClient(config), the return type matches your chains config -- you choose the chain names and descriptors.
Using Typed APIs
const client = await getChainAPI("paseo");
const account = await client.assetHub.query.System.Account.getValue(address);
const blockNumber = await client.assetHub.query.System.Number.getValue();
const byteFee = await client.bulletin.query.TransactionStorage.ByteFee.getValue();
const identity = await client.individuality.query.Identity.IdentityOf.getValue(address);
const version = await client.assetHub.constants.System.Version();
console.log(`Runtime: ${version.spec_name} v${version.spec_version}`);
const tx = client.assetHub.tx.System.remark({ remark: Binary.fromText("hello") });
const transfer = client.assetHub.tx.Balances.transfer_keep_alive({
dest: { type: "Id", value: recipientSs58 },
value: 1_000_000_000n,
});
Using the Raw PolkadotClient
Important: Always use createChainClient or getChainAPI to establish connections --
they automatically route through the Host API when inside a Polkadot container.
Do NOT bypass chain-client by creating raw PolkadotClient instances with createClient()
directly -- this skips host routing and breaks container integration.
The .raw property on ChainClient is safe to use (the connection is already established
through host routing). It is only needed for advanced APIs like createInkSdk.
The .raw property exposes the underlying PolkadotClient for each chain. Use it for advanced APIs like creating an Ink! SDK for contracts:
const client = await getChainAPI("paseo");
const rawAssetHub = client.raw.assetHub;
const rawBulletin = client.raw.bulletin;
import { createInkSdk } from "@polkadot-api/sdk-ink";
const inkSdk = createInkSdk(client.raw.assetHub, { atBest: true });
const contract = inkSdk.getContract(contractDescriptor, contractAddress);
Connection Lifecycle
Initialize
import { getChainAPI } from "@polkadot-apps/chain-client";
const client = await getChainAPI("paseo");
import { createChainClient } from "@polkadot-apps/chain-client";
import { paseo_asset_hub } from "@polkadot-apps/descriptors/paseo-asset-hub";
const client = await createChainClient({
chains: { assetHub: paseo_asset_hub },
rpcs: { assetHub: ["wss://sys.ibp.network/asset-hub-paseo"] },
});
Both functions are idempotent -- calling with the same config returns the same cached promise. Concurrent calls are deduplicated.
Use
Access chains via client.assetHub, client.bulletin, client.individuality (preset), or whatever chain names you configured (BYOD). Access raw clients via client.raw.<chainName>.
Destroy
import { destroyAll } from "@polkadot-apps/chain-client";
client.destroy();
destroyAll();
destroyAll() disconnects all clients, clears the cache, and terminates the smoldot light client if running.
Utility Functions
import { getClient, isConnected, isInsideContainer, isInsideContainerSync } from "@polkadot-apps/chain-client";
const inContainer = await isInsideContainer();
const inContainerSync = isInsideContainerSync();
import { bulletin } from "@polkadot-apps/descriptors/bulletin";
const connected = isConnected(bulletin);
const rawClient = getClient(bulletin);
Connection Strategy
chain-client automatically selects the best connection method:
- Host routing -- If running inside a Polkadot container (detected via
@polkadot-apps/host), connections route through the host's product-sdk.
- Direct RPC -- Falls back to WebSocket RPC endpoints when outside a container.
- Light client -- Smoldot-based connections when chain specs are available (not currently used for default chains).
Descriptors
Descriptors are pre-generated type definitions from live chain metadata. They enable fully typed storage queries, transactions, and events.
Always use subpath imports:
import { paseo_asset_hub } from "@polkadot-apps/descriptors/paseo-asset-hub";
import { bulletin } from "@polkadot-apps/descriptors/bulletin";
import { individuality } from "@polkadot-apps/descriptors/individuality";
Available subpaths:
@polkadot-apps/descriptors/polkadot-asset-hub
@polkadot-apps/descriptors/kusama-asset-hub
@polkadot-apps/descriptors/paseo-asset-hub
@polkadot-apps/descriptors/bulletin
@polkadot-apps/descriptors/individuality
See references/descriptors-guide.md for details on adding new chains.
PAPI Enum Parameters
Many chain calls use Rust enum types. In polkadot-api, pass enums as tagged objects:
{ type: "Id", value: ss58Address }
{ type: "Index", value: accountIndex }
{ type: "Address20", value: h160Address }
client.assetHub.tx.Balances.transfer_keep_alive({
dest: { type: "Id", value: "5GrwvaEF..." },
value: 1_000_000_000n,
});
Common Mistakes
Forgetting to await getChainAPI / createChainClient
const client = getChainAPI("paseo");
client.assetHub.query.System.Account.getValue(addr);
const client = await getChainAPI("paseo");
Not calling destroy / destroyAll
Leaking connections causes WebSocket handles to stay open. Always clean up in app teardown, test afterAll, or process exit handlers.
Using unavailable environments
const client = await getChainAPI("polkadot");
const client = await getChainAPI("paseo");
Importing descriptors without subpath
import { bulletin } from "@polkadot-apps/descriptors";
import { bulletin } from "@polkadot-apps/descriptors/bulletin";
Using the old .contracts property
const contract = client.contracts.getContract(descriptor, address);
import { createInkSdk } from "@polkadot-api/sdk-ink";
const inkSdk = createInkSdk(client.raw.assetHub, { atBest: true });
const contract = inkSdk.getContract(descriptor, address);
Integration with Bulletin
Both paths work with BulletinClient:
- BYOD:
BulletinClient.from(client.bulletin, gateway) -- use with any chain client
- Preset:
BulletinClient.create("paseo") -- auto-resolves via getChainAPI
Reference Files
Resources