Getting started
Install use-truapi, configure the provider and run your first query
Installation
bun add @use-truapi/react @tanstack/react-query polkadot-apibun add @use-truapi/vue @tanstack/vue-query polkadot-apiYou also need PAPI descriptors for the chains you talk to, either generated
with papi add or prebuilt ones such as @parity/product-sdk-descriptors.
Define your config
defineConfig describes your chains plus optional feature config (statements,
cloud storage, product account). The const generic preserves your chain
keys, which is what makes every hook chain-typed later.
import { paseo_asset_hub } from "@parity/product-sdk-descriptors/paseo-asset-hub";
import { defineConfig } from "@use-truapi/react"; // or "@use-truapi/vue"
export const config = defineConfig({
chains: {
assetHub: {
descriptor: paseo_asset_hub,
genesisHash: "0xbf0488dbe9daa1de1c08c5f743e26fdc2a4ecd74cf87dd1b4b1eeb99ae4ef19f",
// Used only when running standalone (outside a Polkadot host):
wsUrls: ["wss://paseo-asset-hub-next-rpc.polkadot.io"],
},
},
dappName: "my-app",
statements: { appName: "my-app" },
cloudStorage: { environment: "paseo" },
});Register the config type
Augment Register once and every hook gets typed chain keys and typed PAPI
apis. useTypedApi({ chain: "assetHub" }) autocompletes and
useChainQuery((api) => ...) knows your pallets:
declare module "@use-truapi/react" {
interface Register {
config: typeof config;
}
}Set up the provider
Wrap your app in TruapiProvider:
import { TruapiProvider } from "@use-truapi/react";
import { createRoot } from "react-dom/client";
import { App } from "./App";
import { config } from "./config";
createRoot(document.getElementById("root")!).render(
<TruapiProvider config={config}>
<App />
</TruapiProvider>,
);If you already use TanStack Query, render TruapiProvider inside your
QueryClientProvider and it reuses your client. Otherwise it creates one
for you. See Provider & runtime for details.
Install TruapiPlugin:
import { TruapiPlugin } from "@use-truapi/vue";
import { createApp } from "vue";
import App from "./App.vue";
import { config } from "./config";
createApp(App).use(TruapiPlugin, { config }).mount("#app");If your app already uses TanStack Query the plugin reuses it, otherwise it installs a client for you. See Provider & runtime.
Your first query
import { useBlockNumber, useBalance, useFormattedBalance } from "@use-truapi/react";
function Status({ address }: { address: string }) {
const { data: blockNumber } = useBlockNumber();
const { data: balance, isPending } = useBalance(address);
const free = useFormattedBalance(balance?.free, { decimals: 10, symbol: "PAS" });
if (isPending) return <p>Loading…</p>;
return (
<p>
Block #{blockNumber}, balance: {free}
</p>
);
}<script setup lang="ts">
import { useBlockNumber, useBalance, useFormattedBalance } from "@use-truapi/vue";
const props = defineProps<{ address: string }>();
const { data: blockNumber } = useBlockNumber();
const { data: balance, isPending } = useBalance(() => props.address);
const free = useFormattedBalance(
() => balance.value?.free,
{ decimals: 10, symbol: "PAS" },
);
</script>
<template>
<p v-if="isPending">Loading…</p>
<p v-else>Block #{{ blockNumber }}, balance: {{ free }}</p>
</template>Both hooks are live: the block number and balance update as new blocks arrive, through one shared subscription per query key.
Reads and actions
Every hook follows one of two shapes:
- Reads return a query result:
data,error,isPending,refetch. Pass options throughquery, for exampleuseBalance(address, { query: { refetchInterval: 30_000 } }). - Actions return mutation state plus a named method:
connect(),login(),submit(),upload(),publish(). The method returns a promise;data,error,isPendingandresettrack the last call. Pass callbacks throughmutation, for exampleuseLogin({ mutation: { onSuccess: (r) => console.log(r) } }).
Host vs standalone
Your app runs in one of two modes, detected at runtime:
- Host: embedded in a Polkadot host (Desktop/Mobile). Chain connections route through the host; signing, permissions, chat, statements, payments and notifications are available.
- Standalone: a plain browser tab. Chain connections use your configured
wsUrls. Host-only features either stay inert (statements, theme followsprefers-color-scheme) or throwHostUnavailableError(payments, chat, notifications).
Gate host-only UI with useHostMode or
useIsHost.