# AI-assisted development (/docs/ai) The repo ships an **agent skill** — a compact distillation of these docs (setup, all hooks, host-vs-standalone rules, error types) that coding agents like Claude Code, Cursor and Codex load on demand instead of scraping the full documentation: ```bash npx skills add justraman/use-truapi ``` Once installed, the agent pulls in the correct provider setup, hook signatures and gotchas whenever it writes code touching `@use-truapi/react` or `@use-truapi/vue`. ## llms.txt [#llmstxt] The docs site also serves machine-readable indexes: * [`/llms.txt`](https://justraman.github.io/use-truapi/llms.txt) — one-line summary of every page. * [`/llms-full.txt`](https://justraman.github.io/use-truapi/llms-full.txt) — the entire documentation as a single text file. # Getting started (/docs/getting-started) ## Installation [#installation] ```bash bun add @use-truapi/react @tanstack/react-query polkadot-api ``` ```bash bun add @use-truapi/vue @tanstack/vue-query polkadot-api ``` You 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 [#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. ```ts title="config.ts" 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 [#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: ```ts title="config.ts" declare module "@use-truapi/react" { interface Register { config: typeof config; } } ``` ## Set up the provider [#set-up-the-provider] Wrap your app in `TruapiProvider`: ```tsx title="main.tsx" 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( , ); ``` 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](/docs/provider) for details. Install `TruapiPlugin`: ```ts title="main.ts" 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](/docs/provider). ## Your first query [#your-first-query] ```tsx 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

Loading…

; return (

Block #{blockNumber}, balance: {free}

); } ```
```vue ```
Both hooks are live: the block number and balance update as new blocks arrive, through one shared subscription per query key. ## Reads and actions [#reads-and-actions] Every hook follows one of two shapes: * Reads return a query result: `data`, `error`, `isPending`, `refetch`. Pass options through `query`, for example `useBalance(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`, `isPending` and `reset` track the last call. Pass callbacks through `mutation`, for example `useLogin({ mutation: { onSuccess: (r) => console.log(r) } })`. ## Host vs standalone [#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 follows `prefers-color-scheme`) or throw `HostUnavailableError` (payments, chat, notifications). Gate host-only UI with [`useHostMode`](/docs/host/use-host-mode) or [`useIsHost`](/docs/host/use-is-host). # Introduction (/docs) **use-truapi** packages the entire [`@parity/product-sdk`](https://github.com/paritytech/product-sdk) surface — chain queries, accounts, transactions, contracts, chat, statements, payments, cloud storage — as [React hooks](https://www.npmjs.com/package/@use-truapi/react) and [Vue composables](https://www.npmjs.com/package/@use-truapi/vue). One install; the SDK packages stay behind the hooks. Use [`@parity/product-sdk`](https://paritytech.github.io/product-sdk/) directly — use-truapi is only the React/Vue binding layer on top of it. React Vue ```ts import { useBalance, useTx } from "@use-truapi/react"; function Transfer({ address }: { address: string }) { const { data: balance } = useBalance(address); const { submit, phase } = useTx(); // ... } ``` ```ts import { useBalance, useTx } from "@use-truapi/vue"; const { data: balance } = useBalance(() => props.address); const { submit, phase } = useTx(); ``` ## What you get [#what-you-get] * Reads are TanStack Query results, actions are mutations with a named method (`connect`, `submit`, `upload`); every hook accepts TanStack options. * Live data — balances, blocks, chat, statements and payments stream through shared subscriptions that detach with the last consumer. * Host or standalone — embedded in a Polkadot host or in a plain browser tab; chain access, theming and storage adapt automatically. * Types end to end — register your config once and every hook knows your chain keys and PAPI descriptors. ## Packages [#packages] | Package | Description | | ------------------- | --------------------------------------------- | | `@use-truapi/react` | React hooks (React ≥18) | | `@use-truapi/vue` | Vue composables (Vue ≥3.4) | | `@use-truapi/core` | Framework-agnostic runtime the adapters share | Install, configure the provider and run your first chain query. Typed one-shot reads and live subscriptions. Sign, submit and watch transactions. Wallet state, account selection and login. # Provider & runtime (/docs/provider) Every hook reads from a runtime, the object created from your config that owns chain connections, the signer manager and the host controllers. The provider makes it available to your component tree. ## Setting up [#setting-up] ```tsx import { TruapiProvider } from "@use-truapi/react"; import { config } from "./config"; ; ``` `TruapiProvider` accepts either a `config` (it creates and owns the runtime) or a pre-built `runtime` when you need to control its lifecycle yourself: ```tsx import { createRuntime, TruapiProvider } from "@use-truapi/react"; const runtime = createRuntime(config); ...; ``` A provider-owned runtime lives for the page lifetime. Destroying the signer manager is terminal, and StrictMode's throwaway unmount would otherwise kill it mid-app. Pass `runtime` to own teardown explicitly. ```ts import { TruapiPlugin } from "@use-truapi/vue"; import { config } from "./config"; app.use(TruapiPlugin, { config }); ``` `TruapiPlugin` accepts either a `config` (it creates the runtime) or a pre-built `runtime`: ```ts import { createRuntime, TruapiPlugin } from "@use-truapi/vue"; app.use(TruapiPlugin, { runtime: createRuntime(config) }); ``` ## The QueryClient [#the-queryclient] use-truapi is built on TanStack Query and works with an existing setup: * Your app already uses TanStack Query: the provider reuses your `QueryClient` (shared cache, one devtools instance). In React, render `TruapiProvider` inside your `QueryClientProvider`; in Vue it detects an installed `VueQueryPlugin`. * No TanStack Query yet: a client is created for you with a 5 second `staleTime` so remounts and duplicate hooks don't hammer the RPC. * Explicit control: pass `queryClient` to the provider/plugin, or create one with the defaults via `createTruapiQueryClient()`. ## useRuntime [#useruntime] `useRuntime` returns the runtime itself, the escape hatch when no hook covers what you need. It exposes the same controllers the hooks are built on: `runtime.chains`, `runtime.accounts`, `runtime.tx`, `runtime.contracts`, `runtime.chat`, `runtime.statements`, `runtime.payments`, `runtime.cloudStorage`, `runtime.host` and `runtime.config`. ```ts import { useRuntime } from "@use-truapi/react"; // or "@use-truapi/vue" const runtime = useRuntime(); await runtime.host.navigate("https://polkadot.network"); ``` It throws when called outside the provider. ## API reference [#api-reference] ### React [#react] ```ts function TruapiProvider(__namedParameters): | FunctionComponentElement | null>> | FunctionComponentElement; ``` ### Parameters [#parameters] | Parameter | Type | | ------------------- | --------------------- | | `__namedParameters` | `TruapiProviderProps` | ### Returns [#returns] \| `FunctionComponentElement`\<`ProviderProps`\< \| `TruapiRuntime`\<`AnyChains`> \| `null`>> \| `FunctionComponentElement`\<`QueryClientProviderProps`> ### Properties [#properties] #### children? [#children] ```ts optional children?: ReactNode; ``` *** #### config? [#config] ```ts optional config?: TruapiConfig; ``` *** #### queryClient? [#queryclient] ```ts optional queryClient?: QueryClient; ``` TanStack QueryClient to use. Defaults to the app's own client when the provider is rendered under a `QueryClientProvider`, otherwise a client with use-truapi defaults is created and provided for you. *** #### runtime? [#runtime] ```ts optional runtime?: TruapiRuntime; ``` Pass a pre-built runtime instead of `config` to control its lifecycle yourself. ### Vue [#vue] ### Properties [#properties-1] #### config? [#config-1] ```ts optional config?: TruapiConfig; ``` *** #### queryClient? [#queryclient-1] ```ts optional queryClient?: QueryClient; ``` TanStack QueryClient to use. Defaults to the app's own client when `VueQueryPlugin` is already installed, otherwise a client with use-truapi defaults is installed for you. *** #### runtime? [#runtime-1] ```ts optional runtime?: TruapiRuntime; ``` Pass a pre-built runtime instead of `config` to control its lifecycle yourself. # useAccounts (/docs/accounts/use-accounts) `useAccounts` returns the current wallet state (`accounts`, `selectedAccount`, `status`, `error`) together with the `connect`, `disconnect` and `select` actions. Every account hook reads the same shared store, so this hook, [`useSelectedAccount`](/docs/accounts/use-selected-account) and [`useSigner`](/docs/accounts/use-signer) update together the moment anything changes. `status` walks `"disconnected" → "connecting" → "connected"` (or `"error"`); `isConnected` and `isConnecting` are derived from it so you can render without string comparisons. `connect()` picks the provider automatically: the host provider when running inside a Polkadot host container, dev accounts standalone. It is safe to call from several components at once; concurrent callers share one attempt, and a failure evicts it so the next click retries. `select(address)` switches the selected account and throws if the address is not in `accounts`. The selection feeds every downstream consumer, including transaction hooks like [`useTx`](/docs/tx/use-tx). ## Usage [#usage] ```tsx import { useAccounts } from "@use-truapi/react"; function ConnectWallet() { const { accounts, selectedAccount, isConnected, isConnecting, error, connect, disconnect, select, } = useAccounts(); if (!isConnected) { return ( <> {error &&

{error.message}

} ); } return ( <>
    {accounts.map((account) => (
  • ))}
); } ```
```vue ``` In Vue every state field is a `ComputedRef`: use `.value` in script, they unwrap automatically in templates. The actions (`connect`, `disconnect`, `select`) are plain functions.
To force a provider instead of auto-detecting, pass `"host"` or `"dev"`: ```ts const accounts = await connect("dev"); ``` With `autoConnect: true` in your config the runtime calls `connect()` on startup, so components usually only need to render the resulting state. ## See also [#see-also] * [`useConnect`](/docs/accounts/use-connect) is the same connect with per-call state (`isPending`, `error`, `data`). * [`useSelectedAccount`](/docs/accounts/use-selected-account) returns just the selected account, for components that only read. * [`useSigner`](/docs/accounts/use-signer) returns the `PolkadotSigner` of the selected account. ## API reference [#api-reference] ### React [#react] ```ts function useAccounts(): AccountsResult; ``` Wallet state + connect/disconnect/select, backed by the shared SignerManager. ### Returns [#returns] `AccountsResult` ### Properties [#properties] #### accounts [#accounts] ```ts accounts: readonly SignerAccount[]; ``` *** #### connect [#connect] ```ts connect: (provider?) => Promise; ``` ##### Parameters [#parameters] | Parameter | Type | | ----------- | -------------- | | `provider?` | `ProviderType` | ##### Returns [#returns-1] `Promise`\<`SignerAccount`\[]> *** #### disconnect [#disconnect] ```ts disconnect: () => void; ``` ##### Returns [#returns-2] `void` *** #### error [#error] ```ts error: SignerError | null; ``` *** #### isConnected [#isconnected] ```ts isConnected: boolean; ``` *** #### isConnecting [#isconnecting] ```ts isConnecting: boolean; ``` *** #### select [#select] ```ts select: (address) => SignerAccount; ``` ##### Parameters [#parameters-1] | Parameter | Type | | --------- | -------- | | `address` | `string` | ##### Returns [#returns-3] `SignerAccount` *** #### selectedAccount [#selectedaccount] ```ts selectedAccount: SignerAccount | null; ``` *** #### status [#status] ```ts status: ConnectionStatus; ``` ### Vue [#vue] ```ts function useAccounts(): AccountsResult; ``` Wallet state + connect/disconnect/select, backed by the shared SignerManager. ### Returns [#returns-4] `AccountsResult` ### Properties [#properties-1] #### accounts [#accounts-1] ```ts accounts: ComputedRef; ``` *** #### connect [#connect-1] ```ts connect: (provider?) => Promise; ``` ##### Parameters [#parameters-2] | Parameter | Type | | ----------- | -------------- | | `provider?` | `ProviderType` | ##### Returns [#returns-5] `Promise`\<`SignerAccount`\[]> *** #### disconnect [#disconnect-1] ```ts disconnect: () => void; ``` ##### Returns [#returns-6] `void` *** #### error [#error-1] ```ts error: ComputedRef; ``` *** #### isConnected [#isconnected-1] ```ts isConnected: ComputedRef; ``` *** #### isConnecting [#isconnecting-1] ```ts isConnecting: ComputedRef; ``` *** #### select [#select-1] ```ts select: (address) => SignerAccount; ``` ##### Parameters [#parameters-3] | Parameter | Type | | --------- | -------- | | `address` | `string` | ##### Returns [#returns-7] `SignerAccount` *** #### selectedAccount [#selectedaccount-1] ```ts selectedAccount: ComputedRef; ``` *** #### status [#status-1] ```ts status: ComputedRef; ``` # useConnect (/docs/accounts/use-connect) `useConnect` exposes `connect(provider?)`, which resolves with the list of `SignerAccount`s, plus mutation state: `isPending` tracks the call, `error` carries a failure, `data` holds the last result and `reset()` clears them. Use it over the `connect` in [`useAccounts`](/docs/accounts/use-accounts) when a button needs its own pending or error state. Both drive the same shared connection, so a connect fired here updates `useAccounts`, [`useSelectedAccount`](/docs/accounts/use-selected-account) and [`useSigner`](/docs/accounts/use-signer) everywhere. Concurrent connects share a single attempt; a failed attempt is evicted so the next call retries. Called without an argument, `connect()` auto-detects the provider: the host provider inside a Polkadot host container, dev accounts standalone. Pass `"host"` or `"dev"` to force one. Failures reject the returned promise and also land in `error`, so a click handler can ignore the rejection and let the UI render the error state. ## Usage [#usage] ```tsx import { useConnect } from "@use-truapi/react"; function ConnectButton() { const { connect, isPending, error } = useConnect(); return ( <> {error &&

{error.message}

} ); } ```
```vue ```
`connect` resolves with the accounts, which is handy when connecting is step one of a larger flow: ```ts const { connect } = useConnect(); async function onGetStarted() { const accounts = await connect(); // or connect("dev") console.log("first account:", accounts[0]?.address); } ``` ## See also [#see-also] * [`useAccounts`](/docs/accounts/use-accounts) bundles connect with the full account state and `select`. * [`useDisconnect`](/docs/accounts/use-disconnect) is the inverse operation. * [`useLogin`](/docs/accounts/use-login) performs RFC-0009 login on top of the connected host session. ## API reference [#api-reference] ### React [#react] ```ts function useConnect(options?): NamedMutation> & object; ``` Connect with mutation state: `connect(provider?)` plus `isPending`/`error`/`data`. ### Parameters [#parameters] | Parameter | Type | | ------------------- | ----------------------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`SignerAccount`\[], `OptionalVariables`\<`ProviderType`>>; } | | `options.mutation?` | `MutationOptions`\<`SignerAccount`\[], `OptionalVariables`\<`ProviderType`>> | ### Returns [#returns] `NamedMutation`\<`SignerAccount`\[], `OptionalVariables`\<`ProviderType`>> & `object` ### Vue [#vue] ```ts function useConnect(options?): NamedMutation> & object; ``` Connect with mutation state: `connect(provider?)` plus `isPending`/`error`/`data`. ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | ----------------------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`SignerAccount`\[], `OptionalVariables`\<`ProviderType`>>; } | | `options.mutation?` | `MutationOptions`\<`SignerAccount`\[], `OptionalVariables`\<`ProviderType`>> | ### Returns [#returns-1] `NamedMutation`\<`SignerAccount`\[], `OptionalVariables`\<`ProviderType`>> & `object` # useDisconnect (/docs/accounts/use-disconnect) `useDisconnect` returns a stable function that disconnects the wallet: `accounts` empties, `selectedAccount` and the signer become `null`, and `status` returns to `"disconnected"`. Every account hook reads the same shared store, so one call updates [`useAccounts`](/docs/accounts/use-accounts), [`useSelectedAccount`](/docs/accounts/use-selected-account) and [`useSigner`](/docs/accounts/use-signer) across the whole app. Disconnecting is synchronous, there is nothing to await. It also resets the shared connect attempt, so a subsequent `connect()` starts fresh. ## Usage [#usage] ```tsx import { useDisconnect, useSelectedAccount } from "@use-truapi/react"; function DisconnectButton() { const account = useSelectedAccount(); const disconnect = useDisconnect(); if (!account) return null; return ( ); } ``` ```vue ``` ## See also [#see-also] * [`useConnect`](/docs/accounts/use-connect) is the inverse operation, with pending and error state. * [`useAccounts`](/docs/accounts/use-accounts) bundles the same `disconnect` with the full account state. ## API reference [#api-reference] ### React [#react] ```ts function useDisconnect(): () => void; ``` ### Returns [#returns] () => `void` ### Vue [#vue] ```ts function useDisconnect(): () => void; ``` ### Returns [#returns-1] () => `void` # useLogin (/docs/accounts/use-login) `useLogin` performs an RFC-0009 login against the Polkadot host. Call `login(reason?)` from a click (or similar) handler: the host shows its own consent UI and rejects requests that do not originate from a user gesture, so never call it from an effect or on mount. The optional `reason` is a human-readable string shown in the host's consent prompt. The promise resolves to a `LoginResult`, and the user declining is reported as data rather than an exception: * `"Success"`: the user approved the login. * `"AlreadyConnected"`: the session was already established. This is also what you get standalone (outside a host container), where login is a no-op success. * `"Rejected"`: the user declined. The hook also exposes mutation state (`data`, `error`, `isPending`, `reset`). After a successful login, [`useUserId`](/docs/accounts/use-user-id) can resolve the user's DotNS username. ## Usage [#usage] ```tsx import { useLogin } from "@use-truapi/react"; function LoginButton() { const { login, isPending, data, error } = useLogin(); return ( <> {data === "Rejected" &&

Login was declined.

} {error &&

{error.message}

} ); } ```
```vue ```
## See also [#see-also] * [`useUserId`](/docs/accounts/use-user-id) resolves the DotNS username available after login. * [`useConnect`](/docs/accounts/use-connect) connects the wallet, the usual first step. ## API reference [#api-reference] ### React [#react] ```ts function useLogin(options?): NamedMutation> & object; ``` RFC-0009 login — call `login()` from a user gesture. ### Parameters [#parameters] | Parameter | Type | | ------------------- | ------------------------------------------------------------------------------------ | | `options?` | \{ `mutation?`: `MutationOptions`\<`LoginResult`, `OptionalVariables`\<`string`>>; } | | `options.mutation?` | `MutationOptions`\<`LoginResult`, `OptionalVariables`\<`string`>> | ### Returns [#returns] `NamedMutation`\<`LoginResult`, `OptionalVariables`\<`string`>> & `object` ### Vue [#vue] ```ts function useLogin(options?): NamedMutation> & object; ``` RFC-0009 login — call `login()` from a user gesture. ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | ------------------------------------------------------------------------------------ | | `options?` | \{ `mutation?`: `MutationOptions`\<`LoginResult`, `OptionalVariables`\<`string`>>; } | | `options.mutation?` | `MutationOptions`\<`LoginResult`, `OptionalVariables`\<`string`>> | ### Returns [#returns-1] `NamedMutation`\<`LoginResult`, `OptionalVariables`\<`string`>> & `object` # useSelectedAccount (/docs/accounts/use-selected-account) `useSelectedAccount` returns the account the user is currently acting as, a `SignerAccount` with `address`, `h160Address`, `publicKey`, `name` and `source`, or `null` while no wallet is connected. It reads the same shared store as [`useAccounts`](/docs/accounts/use-accounts), so `select(address)` or `disconnect()` called anywhere updates every consumer at once. It is the natural input for read hooks: pass `account?.address` straight into [`useBalance`](/docs/chain/use-balance) or a chain query without guarding; those hooks stay disabled while the address is `undefined`. ## Usage [#usage] ```tsx import { useSelectedAccount, truncateAddress } from "@use-truapi/react"; function AccountBadge() { const account = useSelectedAccount(); if (!account) return

No account selected.

; return (

{account.name ?? "Unnamed"}: {truncateAddress(account.address)}

); } ```
```vue ``` The hook returns a `ComputedRef`: use `account.value` in script, it unwraps automatically in templates.
## See also [#see-also] * [`useAccounts`](/docs/accounts/use-accounts) exposes the full account list plus `select` to change the selection. * [`useSigner`](/docs/accounts/use-signer) returns the `PolkadotSigner` behind the selected account. * [`useBalance`](/docs/chain/use-balance) takes the selected address for a live balance. ## API reference [#api-reference] ### React [#react] ```ts function useSelectedAccount(): SignerAccount | null; ``` The currently selected account, or null. ### Returns [#returns] `SignerAccount` | `null` ### Vue [#vue] ```ts function useSelectedAccount(): ComputedRef; ``` The currently selected account, or null. ### Returns [#returns-1] `ComputedRef`\<`SignerAccount` | `null`> # useSignRaw (/docs/accounts/use-sign-raw) `useSignRaw` signs arbitrary bytes with the currently selected account. Call `sign(data)` with a `Uint8Array`; it resolves with the signature bytes. Use it for off-chain proofs (signed messages, authentication challenges, ownership checks), anything that is not a transaction. It signs with whatever account is currently selected, so it follows [`useAccounts`](/docs/accounts/use-accounts) and `select` automatically. With no connected account, or if the user rejects the signing prompt, the call rejects and the failure also lands in `error`. Unlike [`useConnect`](/docs/accounts/use-connect) and [`useLogin`](/docs/accounts/use-login), the argument here is required: `sign` always takes the bytes to sign. ## Usage [#usage] ```tsx import { useSignRaw, useSelectedAccount } from "@use-truapi/react"; function SignMessage() { const account = useSelectedAccount(); const { sign, isPending, error } = useSignRaw(); async function onSign() { const message = new TextEncoder().encode("I own this account"); const signature = await sign(message); console.log("signature:", signature); } return ( <> {error &&

{error.message}

} ); } ```
```vue ```
## See also [#see-also] * [`useSigner`](/docs/accounts/use-signer) exposes the underlying `PolkadotSigner` for raw `polkadot-api` use. * [`useTx`](/docs/tx/use-tx) signs and submits actual transactions. ## API reference [#api-reference] ### React [#react] ```ts function useSignRaw(options?): NamedMutation, Uint8Array> & object; ``` Sign arbitrary bytes with the selected account: `sign(data)`. ### Parameters [#parameters] | Parameter | Type | | ------------------- | --------------------------------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`Uint8Array`\<`ArrayBufferLike`>, `Uint8Array`\<`ArrayBufferLike`>>; } | | `options.mutation?` | `MutationOptions`\<`Uint8Array`\<`ArrayBufferLike`>, `Uint8Array`\<`ArrayBufferLike`>> | ### Returns [#returns] `NamedMutation`\<`Uint8Array`\<`ArrayBufferLike`>, `Uint8Array`\<`ArrayBufferLike`>> & `object` ### Vue [#vue] ```ts function useSignRaw(options?): NamedMutation, Uint8Array> & object; ``` Sign arbitrary bytes with the selected account: `sign(data)`. ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | --------------------------------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`Uint8Array`\<`ArrayBufferLike`>, `Uint8Array`\<`ArrayBufferLike`>>; } | | `options.mutation?` | `MutationOptions`\<`Uint8Array`\<`ArrayBufferLike`>, `Uint8Array`\<`ArrayBufferLike`>> | ### Returns [#returns-1] `NamedMutation`\<`Uint8Array`\<`ArrayBufferLike`>, `Uint8Array`\<`ArrayBufferLike`>> & `object` # useSigner (/docs/accounts/use-signer) `useSigner` returns the `PolkadotSigner` for the currently selected account, `null` until a wallet is connected. It reads the same shared store as [`useAccounts`](/docs/accounts/use-accounts) and [`useSelectedAccount`](/docs/accounts/use-selected-account): connecting, switching accounts or disconnecting immediately yields the new signer (or `null`). You rarely need this hook. [`useTx`](/docs/tx/use-tx) and the other write hooks pull the signer from the runtime themselves and handle host permissions for you. Reach for `useSigner` when handing the signer to raw `polkadot-api` calls or another library that expects a `PolkadotSigner`. ## Usage [#usage] ```tsx import { useSigner, useTypedApi } from "@use-truapi/react"; import { Binary } from "polkadot-api"; function RawRemark() { const signer = useSigner(); const api = useTypedApi(); async function onRemark() { if (!signer || !api) return; const tx = api.tx.System.remark({ remark: Binary.fromText("hello") }); await tx.signAndSubmit(signer); } return ( ); } ``` ```vue ``` The hook returns a `ComputedRef`: use `signer.value` in script, it unwraps automatically in templates. ## See also [#see-also] * [`useTx`](/docs/tx/use-tx) runs the full transaction lifecycle without touching the signer directly. * [`useSignRaw`](/docs/accounts/use-sign-raw) signs arbitrary bytes with the selected account. * [`useSelectedAccount`](/docs/accounts/use-selected-account) is the account this signer belongs to. ## API reference [#api-reference] ### React [#react] ```ts function useSigner(): PolkadotSigner | null; ``` PolkadotSigner of the selected account (null until connected). ### Returns [#returns] `PolkadotSigner` | `null` ### Vue [#vue] ```ts function useSigner(): ComputedRef; ``` PolkadotSigner of the selected account (null until connected). ### Returns [#returns-1] `ComputedRef`\<`PolkadotSigner` | `null`> # useUserId (/docs/accounts/use-user-id) `useUserId` fetches the user's primary DotNS username from the Polkadot host. `data` is a `string | null`: inside a host container with a logged-in user it resolves the primary username; standalone, or when the user has not logged in, it resolves `null` (no error, just no identity). It is a plain query, not a subscription: the value is fetched once and cached. After a successful [`useLogin`](/docs/accounts/use-login), call `refetch()` to pick up the fresh identity. ## Usage [#usage] ```tsx import { useLogin, useUserId } from "@use-truapi/react"; function Greeting() { const { data: userId, isPending, refetch } = useUserId(); const { login } = useLogin({ mutation: { onSuccess: () => refetch() }, }); if (isPending) return

Checking identity…

; if (!userId) { return ( ); } return

Hello, {userId}!

; } ```
```vue ```
## See also [#see-also] * [`useLogin`](/docs/accounts/use-login) establishes the identity this hook reads. * [`useSelectedAccount`](/docs/accounts/use-selected-account) is the on-chain account, as opposed to the DotNS identity. ## API reference [#api-reference] ### React [#react] ```ts function useUserId(options?): UseQueryResult; ``` The user's primary DotNS username; null standalone or when not logged in. ### Parameters [#parameters] | Parameter | Type | | ---------------- | --------------------------------------------------- | | `options?` | \{ `query?`: `QueryOptions`\<`string` \| `null`>; } | | `options.query?` | `QueryOptions`\<`string` \| `null`> | ### Returns [#returns] `UseQueryResult`\<`string` | `null`, `Error`> ### Vue [#vue] ```ts function useUserId(options?): QueryResult; ``` The user's primary DotNS username; null standalone or when not logged in. ### Parameters [#parameters-1] | Parameter | Type | | ---------------- | --------------------------------------------------- | | `options?` | \{ `query?`: `QueryOptions`\<`string` \| `null`>; } | | `options.query?` | `QueryOptions`\<`string` \| `null`> | ### Returns [#returns-1] `QueryResult`\<`string` | `null`> # useBalance (/docs/chain/use-balance) `useBalance` subscribes to the native token balance of an address. `data` is an `AccountBalance` with `free`, `reserved` and `frozen` as `bigint` planck values, updated as blocks arrive. Any number of components can watch the same `(chain, address)` pair; they share one storage subscription, which is dropped when the last consumer unmounts. While `address` is `undefined` the hook stays disabled, so you can pass `account?.address` without guarding. ## Usage [#usage] ```tsx import { useBalance, useFormattedBalance, useSelectedAccount } from "@use-truapi/react"; function Balance() { const account = useSelectedAccount(); const { data: balance, isPending, error } = useBalance(account?.address); const free = useFormattedBalance(balance?.free, { decimals: 10, symbol: "PAS" }); if (!account) return

Connect a wallet first.

; if (isPending) return

Loading balance…

; if (error) return

Failed to load balance: {error.message}

; return

{free} free, {balance.reserved.toString()} reserved

; } ```
```vue ``` Pass a getter (`() => account.value?.address`) so the subscription follows the address when it changes.
To watch a chain other than the default, pass its key: ```ts const { data } = useBalance(address, { chain: "people" }); ``` Values are pushed, not polled. There is nothing to refetch; new values arrive on their own. ## API reference [#api-reference] ### React [#react] ```ts function useBalance(address, options?): UseQueryResult; ``` Live native balance of `address` (free/reserved/frozen, in planck). ### Parameters [#parameters] | Parameter | Type | | ---------- | ---------------------------------- | | `address` | `string` \| `undefined` | | `options?` | `ChainScope`\<`string`> & `object` | ### Returns [#returns] `UseQueryResult`\<`AccountBalance`, `Error`> ### Vue [#vue] ```ts function useBalance(address, options?): QueryResult; ``` Live native balance of `address` (free/reserved/frozen, in planck). ### Parameters [#parameters-1] | Parameter | Type | | ---------- | --------------------------------------- | | `address` | `MaybeGetter`\<`string` \| `undefined`> | | `options?` | `ChainScope`\<`string`> & `object` | ### Returns [#returns-1] `QueryResult`\<`AccountBalance`> # useBlockNumber (/docs/chain/use-block-number) `useBlockNumber` tracks the best block number of a chain, updated as new blocks arrive. All components watching the same chain share one block subscription, dropped when the last consumer unmounts. Values are pushed, so there is nothing to refetch. Beyond showing chain liveness, the block number is a handy re-render tick: anything derived from it recomputes once per block. ## Usage [#usage] ```tsx import { useBlockNumber } from "@use-truapi/react"; function BlockIndicator() { const { data: blockNumber, isPending, error } = useBlockNumber(); if (isPending) return

Connecting…

; if (error) return

Lost the chain: {error.message}

; return

Best block: #{blockNumber}

; } ```
```vue ```
To watch a chain other than the default, pass its key: ```ts const { data } = useBlockNumber({ chain: "people" }); ``` Pass `enabled: false` to detach without unmounting, for views that are hidden but kept alive (in Vue, `enabled` may be a getter): ```ts const { data } = useBlockNumber({ enabled: isVisible }); ``` ## See also [#see-also] * [`useChainSubscription`](/docs/chain/use-chain-subscription) is the general-purpose live subscription this is built on. * [`useBalance`](/docs/chain/use-balance) watches the native balance of an address. ## API reference [#api-reference] ### React [#react] ```ts function useBlockNumber(options?): UseQueryResult; ``` Best-block number, live. ### Parameters [#parameters] | Parameter | Type | | ---------- | ---------------------------------- | | `options?` | `ChainScope`\<`string`> & `object` | ### Returns [#returns] `UseQueryResult`\<`number`, `Error`> ### Vue [#vue] ```ts function useBlockNumber(options?): QueryResult; ``` Best-block number, live. ### Parameters [#parameters-1] | Parameter | Type | | ---------- | ---------------------------------- | | `options?` | `ChainScope`\<`string`> & `object` | ### Returns [#returns-1] `QueryResult`\<`number`> # useChainClient (/docs/chain/use-chain-client) `useChainClient` hands you the underlying PAPI `PolkadotClient`. It is the escape hatch for anything the higher-level hooks don't wrap: block observables like `bestBlocks$`, `getFinalizedBlock()`, submitting a pre-signed extrinsic. For typed reads and transactions prefer [`useTypedApi`](/docs/chain/use-typed-api), [`useChainQuery`](/docs/chain/use-chain-query) and [`useChainSubscription`](/docs/chain/use-chain-subscription). The client is created lazily on first use and shared across the whole app: every hook touching the same chain awaits the same connection. If connecting fails, the failed attempt is dropped so the next consumer retries. How it connects depends on where the app runs. Inside a host (Polkadot Desktop or Mobile) the provider is requested from the host by the chain's `genesisHash`, no RPC endpoints needed; the request times out after `hostProviderTimeoutMs` (default 15000 ms). Standalone, it connects over WebSocket to the chain's configured `wsUrls`, and a chain without `wsUrls` throws. ## Usage [#usage] ```tsx import { useChainClient } from "@use-truapi/react"; function FinalizedBlock() { const { data: client, isPending, error } = useChainClient(); async function logFinalized() { if (!client) return; const block = await client.getFinalizedBlock(); console.log(`finalized #${block.number}`, block.hash); } if (isPending) return

Connecting…

; if (error) return

Failed to connect: {error.message}

; return ( ); } ```
```vue ```
To get the client of a chain other than the default, pass its key: ```ts const { data: client } = useChainClient({ chain: "people" }); ``` ## See also [#see-also] * [`useTypedApi`](/docs/chain/use-typed-api) is the descriptor-typed api built on this client. * [`useChainSubscription`](/docs/chain/use-chain-subscription) subscribes to client and api observables declaratively; its `select` callback receives the client too. ## API reference [#api-reference] ### React [#react] ```ts function useChainClient(options?): UseQueryResult; ``` The raw PAPI client — escape hatch for anything the hooks don't cover. ### Parameters [#parameters] | Parameter | Type | | ---------- | ---------------------------------- | | `options?` | `ChainScope`\<`string`> & `object` | ### Returns [#returns] `UseQueryResult`\<`PolkadotClient`, `Error`> ### Vue [#vue] ```ts function useChainClient(options?): QueryResult; ``` The raw PAPI client — escape hatch for anything the composables don't cover. ### Parameters [#parameters-1] | Parameter | Type | | ---------- | ---------------------------------- | | `options?` | `ChainScope`\<`string`> & `object` | ### Returns [#returns-1] `QueryResult`\<`PolkadotClient`> # useChainQuery (/docs/chain/use-chain-query) `useChainQuery` runs a one-shot read against the typed PAPI api. The `deps` array identifies the read: change a dep and it re-runs and is cached separately. Two components reading with the same chain and deps share one result. Deps are serialized bigint-safely, so `bigint` values are fine. The value is fetched once; it does not update as new blocks arrive. For values that should track the chain head use [`useChainSubscription`](/docs/chain/use-chain-subscription). To refresh a read, call `refetch()` on the result. While `enabled` is `false` nothing is fetched and the result stays pending, so you can depend on data that may not exist yet (an unconnected wallet, an unresolved route param) without guarding the callback. ## Usage [#usage] ```tsx import { useChainQuery, useSelectedAccount } from "@use-truapi/react"; function AccountInfo() { const account = useSelectedAccount(); const address = account?.address; // `address` is a dep: the read re-runs (and caches separately) per address. const { data, isPending, error } = useChainQuery( (api) => api.query.System.Account.getValue(address!), [address], { enabled: address !== undefined }, ); if (!account) return

Connect a wallet first.

; if (isPending) return

Loading account…

; if (error) return

Failed to read account: {error.message}

; return (

Nonce {data.nonce}, free {data.data.free.toString()} planck

); } ```
```vue ``` Pass getters in `deps` (and for `enabled`) so the read follows the reactive value when it changes.
A read with no inputs takes an empty `deps` array and is cached once per chain: ```ts const totalIssuance = useChainQuery( (api) => api.query.Balances.TotalIssuance.getValue(), [], ); ``` To read another chain, pass its key; the `read` callback is then typed against that chain's descriptor: ```ts const { data } = useChainQuery( (api) => api.query.Identity.IdentityOf.getValue(address!), [address], { chain: "people", enabled: address !== undefined }, ); ``` ## See also [#see-also] * [`useChainSubscription`](/docs/chain/use-chain-subscription) is the live counterpart: values pushed on every change. * [`useTypedApi`](/docs/chain/use-typed-api) gives you the typed api itself, for imperative calls. * [`useBalance`](/docs/chain/use-balance) is a purpose-built live balance watch. ## API reference [#api-reference] ### React [#react] ```ts function useChainQuery( read, deps, options?): UseQueryResult; ``` One-shot read against the typed api, cached under `queryKeys.chainQuery(chain, deps)` — `deps` are part of the query key, so the read re-runs (and caches separately) when they change. ```ts const total = useChainQuery((api) => api.query.Balances.TotalIssuance.getValue(), []); ``` ### Type Parameters [#type-parameters] | Type Parameter | Default type | | ---------------------- | ------------ | | `T` | - | | `K` *extends* `string` | `string` | ### Parameters [#parameters] | Parameter | Type | | ---------- | ----------------------------- | | `read` | (`api`) => `Promise`\<`T`> | | `deps` | readonly `unknown`\[] | | `options?` | `ChainScope`\<`K`> & `object` | ### Returns [#returns] `UseQueryResult`\<`T`, `Error`> ### Vue [#vue] ```ts function useChainQuery( read, deps?, options?): QueryResult; ``` One-shot read against the typed api, cached under `queryKeys.chainQuery(chain, deps)`. Pass getters in `deps` for reactive inputs — they become part of the query key, so the read re-runs (and caches separately) when they change. ### Type Parameters [#type-parameters-1] | Type Parameter | Default type | | ---------------------- | ------------ | | `T` | - | | `K` *extends* `string` | `string` | ### Parameters [#parameters-1] | Parameter | Type | Default value | | ---------- | ----------------------------- | ------------- | | `read` | (`api`) => `Promise`\<`T`> | `undefined` | | `deps` | readonly `unknown`\[] | `[]` | | `options?` | `ChainScope`\<`K`> & `object` | `undefined` | ### Returns [#returns-1] `QueryResult`\<`T`> # useChainSpec (/docs/chain/use-chain-spec) `useChainSpec` asks the host (Polkadot Desktop or Mobile) for a chain's spec, looked up by the chain's configured `genesisHash`. `data` is a `ChainSpec` with the human-readable `name`, parsed `properties` (`ss58Format`, `tokenDecimals`, `tokenSymbol`, plus chain-specific extras) and the raw JSON string in `propertiesRaw`. `properties` is `null` if the host's JSON couldn't be parsed. This is host-only information: running standalone the hook resolves with `null` instead of erroring, so guard on the data before rendering. The spec is fetched once per chain; it doesn't change at runtime. A typical use is feeding `tokenDecimals` and `tokenSymbol` into `useFormattedBalance` instead of hard-coding them. ## Usage [#usage] ```tsx import { useChainSpec } from "@use-truapi/react"; function ChainBadge() { const { data: spec, isPending, error } = useChainSpec(); if (isPending) return

Loading chain info…

; if (error) return

Failed to load chain spec: {error.message}

; if (!spec) return

Chain spec unavailable (running standalone).

; return (

{spec.name}: token {spec.properties?.tokenSymbol} with{" "} {spec.properties?.tokenDecimals} decimals

); } ```
```vue ```
To get the spec of a chain other than the default, pass its key; the host is asked for that chain's configured `genesisHash`: ```ts const { data: peopleSpec } = useChainSpec({ chain: "people" }); ``` ## See also [#see-also] * [`useChainClient`](/docs/chain/use-chain-client) also behaves differently between host and standalone. * [`useBalance`](/docs/chain/use-balance) pairs with the spec's decimals and symbol for display. ## API reference [#api-reference] ### React [#react] ```ts function useChainSpec(options?): UseQueryResult; ``` Chain name/properties as reported by the host (null standalone). ### Parameters [#parameters] | Parameter | Type | | ---------- | ----------------------- | | `options?` | `ChainScope`\<`string`> | ### Returns [#returns] `UseQueryResult`\<`ChainSpec` | `null`, `Error`> ### Vue [#vue] ```ts function useChainSpec(options?): QueryResult; ``` Chain name/properties as reported by the host (null standalone). ### Parameters [#parameters-1] | Parameter | Type | | ---------- | ----------------------- | | `options?` | `ChainScope`\<`string`> | ### Returns [#returns-1] `QueryResult`\<`ChainSpec` | `null`> # useChainSubscription (/docs/chain/use-chain-subscription) `useChainSubscription` subscribes to any observable exposed by the typed PAPI api or the raw client (storage watches, event streams, block streams) and surfaces the latest value as `data`. Your `select` callback receives the typed `api` and the `PolkadotClient` and returns anything with a `subscribe(observer)` method. The subscription is shared: any number of components mounting the same `(chain, deps)` pair hold a single subscription, dropped when the last consumer unmounts. Like [`useChainQuery`](/docs/chain/use-chain-query), `deps` identify the subscription: change a dep and the hook re-attaches under the new inputs. If the stream errors, the result flips into the error state but the last received `data` is kept. While `enabled` is `false` nothing is attached. Use this for values that should track the chain head; for a read that only needs to happen once, use [`useChainQuery`](/docs/chain/use-chain-query). ## Usage [#usage] ```tsx import { useChainSubscription } from "@use-truapi/react"; function Clock() { const { data: now, isPending, error } = useChainSubscription( (api) => api.query.Timestamp.Now.watchValue(), [], ); if (isPending) return

Waiting for the chain…

; if (error) return

Subscription failed: {error.message}

; return

On-chain time: {new Date(Number(now)).toLocaleTimeString()}

; } ```
```vue ```
Anything the observable closes over belongs in `deps`, so the subscription re-attaches when it changes (in Vue, pass getters): ```ts const { data: account } = useChainSubscription( (api) => api.query.System.Account.watchValue(address, { at: "best" }), [address], { enabled: address !== undefined }, ); ``` `select` also receives the raw `PolkadotClient`, so client streams work too. Give such subscriptions a distinguishing dep (a string label works) so they don't collide with other empty-deps subscriptions on the same chain: ```ts const { data: finalized } = useChainSubscription( (_api, client) => client.finalizedBlock$, ["finalizedBlock"], ); ``` Values are pushed, not polled. There is nothing to refetch; new values arrive on their own, and the result settles once the first value lands. ## See also [#see-also] * [`useChainQuery`](/docs/chain/use-chain-query) for one-shot reads without a live subscription. * [`useBlockNumber`](/docs/chain/use-block-number) is a ready-made best-block watch. * [`useBalance`](/docs/chain/use-balance) is a ready-made native balance watch. ## API reference [#api-reference] ### React [#react] ```ts function useChainSubscription( select, deps, options?): UseQueryResult; ``` Live subscription to any typed-api observable (storage watch, events, …); one shared subscription per key, values bridged into the query cache, automatically unsubscribed when the last consumer unmounts. ```ts const now = useChainSubscription( (api) => api.query.Timestamp.Now.watchValue(), [], ); ``` ### Type Parameters [#type-parameters] | Type Parameter | Default type | | ---------------------- | ------------ | | `T` | - | | `K` *extends* `string` | `string` | ### Parameters [#parameters] | Parameter | Type | | ---------- | ----------------------------- | | `select` | (`api`, `client`) => `object` | | `deps` | readonly `unknown`\[] | | `options?` | `ChainScope`\<`K`> & `object` | ### Returns [#returns] `UseQueryResult`\<`T`, `Error`> ### Vue [#vue] ```ts function useChainSubscription( select, deps?, options?): QueryResult; ``` Live subscription to any typed-api observable (storage watch, events, …); one shared subscription per key, values bridged into the query cache, torn down when the last consumer's scope disposes. ### Type Parameters [#type-parameters-1] | Type Parameter | Default type | | ---------------------- | ------------ | | `T` | - | | `K` *extends* `string` | `string` | ### Parameters [#parameters-1] | Parameter | Type | Default value | | ---------- | ----------------------------- | ------------- | | `select` | (`api`, `client`) => `object` | `undefined` | | `deps` | readonly `unknown`\[] | `[]` | | `options?` | `ChainScope`\<`K`> & `object` | `undefined` | ### Returns [#returns-1] `QueryResult`\<`T`> # useTypedApi (/docs/chain/use-typed-api) `useTypedApi` returns the PAPI typed api of a configured chain: the object behind `api.query`, `api.tx`, `api.constants`, `api.event` and `api.apis`, typed by the chain's descriptor. The underlying client connection is created once and shared with every other chain hook. Most of the time you don't need it directly: the `build`, `read` and `select` callbacks of [`useTx`](/docs/tx/use-tx), [`useChainQuery`](/docs/chain/use-chain-query) and [`useChainSubscription`](/docs/chain/use-chain-subscription) already hand you the typed api. Reach for `useTypedApi` when you need the api object itself: imperative calls from event handlers, passing it to helpers, runtime-api calls that don't fit a declarative hook. The result is typed per chain: `useTypedApi({ chain: "people" })` autocompletes that chain's pallets and calls. ## Usage [#usage] ```tsx import { useTypedApi } from "@use-truapi/react"; function DepositCheck() { const { data: api, isPending, error } = useTypedApi(); async function logDeposit() { if (!api) return; const existentialDeposit = await api.constants.Balances.ExistentialDeposit(); console.log("existential deposit:", existentialDeposit); } if (isPending) return

Connecting…

; if (error) return

Failed to connect: {error.message}

; return ( ); } ```
```vue ```
For values you want to render, wrap the read in [`useChainQuery`](/docs/chain/use-chain-query) instead of awaiting the api yourself; you get caching, `enabled` gating and error states for free: ```ts const deposit = useChainQuery( (api) => api.constants.Balances.ExistentialDeposit(), [], ); ``` ## See also [#see-also] * [`useChainQuery`](/docs/chain/use-chain-query) runs declarative one-shot reads against this api. * [`useChainSubscription`](/docs/chain/use-chain-subscription) subscribes to api observables. * [`useChainClient`](/docs/chain/use-chain-client) is the raw client underneath. ## API reference [#api-reference] ### React [#react] ```ts function useTypedApi(options?): UseQueryResult, Error>; ``` The descriptor-typed PAPI api for a configured chain. ### Type Parameters [#type-parameters] | Type Parameter | | ---------------------- | | `K` *extends* `string` | ### Parameters [#parameters] | Parameter | Type | | ---------- | ----------------------------- | | `options?` | `ChainScope`\<`K`> & `object` | ### Returns [#returns] `UseQueryResult`\<`TypedApiOf`\<`AnyChains`, `K`>, `Error`> ### Vue [#vue] ```ts function useTypedApi(options?): QueryResult>; ``` The descriptor-typed PAPI api for a configured chain. ### Type Parameters [#type-parameters-1] | Type Parameter | | ---------------------- | | `K` *extends* `string` | ### Parameters [#parameters-1] | Parameter | Type | | ---------- | ----------------------------- | | `options?` | `ChainScope`\<`K`> & `object` | ### Returns [#returns-1] `QueryResult`\<`TypedApiOf`\<`AnyChains`, `K`>> # useChatActions (/docs/chat/use-chat-actions) `useChatActions` subscribes to the raw stream of chat actions and invokes your handler for every event. It returns nothing; it is a pure side-effect hook, the imperative counterpart to [`useChatMessages`](/docs/chat/use-chat-messages). Each `ChatReceivedAction` carries the `roomId`, the `peer` who initiated it, and a tagged `payload`: * `MessagePosted`: a peer posted a message; `value` is the `ChatMessageContent`. * `ActionTriggered`: a user pressed an action button; `value` has the `messageId`, the `actionId` and an optional `payload`. * `Command`: a user issued a slash command; `value` has the `command` name and its `payload` string. In React the handler is read through a ref, so it always sees your latest closure without re-subscribing; passing an inline function is fine. The subscription is torn down on unmount and re-attached when `enabled` flips. Chat is host-only: running standalone the subscription fails and `onError` receives a `HostUnavailableError`; without an `onError` the failure is silent. ## Usage [#usage] The reactive half of a bot: reply when a user presses the button posted in the [`useChatBot`](/docs/chat/use-chat-bot) example, or asks for help. ```tsx import { useChatActions, useSendChatMessage } from "@use-truapi/react"; function DiceBotListener() { const { send } = useSendChatMessage(); useChatActions( (action) => { if (action.payload.tag === "ActionTriggered" && action.payload.value.actionId === "roll") { const roll = 1 + Math.floor(Math.random() * 6); void send(`You rolled a ${roll}!`, action.roomId).catch(() => {}); } if (action.payload.tag === "Command" && action.payload.value.command === "help") { void send("Press the Roll button to roll a die.", action.roomId).catch(() => {}); } }, { onError: (e) => console.error("chat actions interrupted", e) }, ); return null; } ``` ```vue ``` In Vue, `enabled` may be a getter (`() => isBotActive.value`); the subscription attaches and detaches as it flips. To pause the stream, pass `enabled`: ```ts useChatActions(handleAction, { enabled: isListening }); ``` While `enabled` is `false` no subscription is held; flipping it back to `true` re-attaches. Events that occurred in between are not replayed. ## See also [#see-also] * [`useChatMessages`](/docs/chat/use-chat-messages) is the same stream filtered to `MessagePosted`, accumulated into a list for rendering. * [`useSendChatMessage`](/docs/chat/use-send-chat-message) replies from inside the handler. ## API reference [#api-reference] ### React [#react] ```ts function useChatActions(onAction, options?): void; ``` Raw action stream (messages, button triggers, commands) via a stable handler. ### Parameters [#parameters] | Parameter | Type | | ------------------ | ------------------------------------------------------------ | | `onAction` | (`action`) => `void` | | `options?` | \{ `enabled?`: `boolean`; `onError?`: (`error`) => `void`; } | | `options.enabled?` | `boolean` | | `options.onError?` | (`error`) => `void` | ### Returns [#returns] `void` ### Vue [#vue] ```ts function useChatActions(onAction, options?): void; ``` Raw action stream (messages, button triggers, commands). ### Parameters [#parameters-1] | Parameter | Type | | ------------------ | ---------------------------------------------------------------------------- | | `onAction` | (`action`) => `void` | | `options?` | \{ `enabled?`: `MaybeGetter`\<`boolean`>; `onError?`: (`error`) => `void`; } | | `options.enabled?` | `MaybeGetter`\<`boolean`> | | `options.onError?` | (`error`) => `void` | ### Returns [#returns-1] `void` # useChatBot (/docs/chat/use-chat-bot) `useChatBot` registers a bot identity (a `botId`, display `name` and `icon` as a URL or base64 data URI) that your product can post under in chat rooms. `data` resolves to `"New"` or `"Exists"`, exactly like [`useChatRoom`](/docs/chat/use-chat-room). The result is cached per `botId`, so multiple components mounting the same bot share one registration, and re-registering an existing bot simply resolves `"Exists"`. Chat is host-only. Running standalone the query result settles in the error state with `HostUnavailableError`; gate bot UI with [`useIsHost`](/docs/host/use-is-host). A typical bot registers its identity, posts messages carrying action buttons, and reacts to the resulting `ActionTriggered` events via [`useChatActions`](/docs/chat/use-chat-actions). ## Usage [#usage] ```tsx import { useChatBot, useSendChatMessage } from "@use-truapi/react"; function DiceBot({ roomId }: { roomId: string }) { const { data: status, error } = useChatBot({ botId: "dice-bot", name: "Dice", icon: "https://example.com/dice.png", }); const { send } = useSendChatMessage(roomId); function offerRoll() { void send({ tag: "Actions", value: { text: "Feeling lucky?", actions: [{ actionId: "roll", title: "Roll the dice" }], layout: "Column", }, }).catch(() => {}); } if (error) return

Chat unavailable: {error.message}

; return ( ); } ```
```vue ```
See [`useChatActions`](/docs/chat/use-chat-actions) for the other half of this bot: reacting when a user presses the button. ## See also [#see-also] * [`useChatRoom`](/docs/chat/use-chat-room) registers the room the bot posts into. * [`useSendChatMessage`](/docs/chat/use-send-chat-message) posts messages, including action buttons. ## API reference [#api-reference] ### React [#react] ```ts function useChatBot(bot, options?): UseQueryResult<"New" | "Exists", Error>; ``` Register a bot identity for posting into rooms. ### Parameters [#parameters] | Parameter | Type | | ---------------- | ----------------------------------------------------------- | | `bot` | \{ `botId`: `string`; `icon`: `string`; `name`: `string`; } | | `bot.botId` | `string` | | `bot.icon?` | `string` | | `bot.name?` | `string` | | `options?` | \{ `query?`: `QueryOptions`\<`"New"` \| `"Exists"`>; } | | `options.query?` | `QueryOptions`\<`"New"` \| `"Exists"`> | ### Returns [#returns] `UseQueryResult`\<`"New"` | `"Exists"`, `Error`> ### Vue [#vue] ```ts function useChatBot(bot, options?): QueryResult<"New" | "Exists">; ``` Register a bot identity for posting into rooms. ### Parameters [#parameters-1] | Parameter | Type | | ---------------- | ----------------------------------------------------------- | | `bot` | \{ `botId`: `string`; `icon`: `string`; `name`: `string`; } | | `bot.botId` | `string` | | `bot.icon?` | `string` | | `bot.name?` | `string` | | `options?` | \{ `query?`: `QueryOptions`\<`"New"` \| `"Exists"`>; } | | `options.query?` | `QueryOptions`\<`"New"` \| `"Exists"`> | ### Returns [#returns-1] `QueryResult`\<`"New"` | `"Exists"`> # useChatMessages (/docs/chat/use-chat-messages) `useChatMessages` accumulates `MessagePosted` events for a room. `data` is a `ChatMessage[]`, newest last. This is a live tail, not a history fetch: the list starts empty and grows as events arrive while subscribed. It is bounded; once it reaches `limit` (default `200`) the oldest entries are dropped as new ones arrive. The result also exposes `clear()`, which resets the list to empty. The subscription is shared per room: any number of components can watch the same `roomId` and only one action watch is held. Accumulated messages live in the query cache, so they survive remounts while the cache entry is retained. While `roomId` is `undefined` the hook stays disabled, so you can pass it straight from route params without guarding. Chat is host-only: running standalone the result flips to the error state with `HostUnavailableError`. ## Usage [#usage] A minimal chat panel: register the room, list its messages, send on submit. ```tsx import { useState } from "react"; import { useChatMessages, useChatRoom, useSendChatMessage } from "@use-truapi/react"; const room = { roomId: "support", name: "Support", icon: "https://example.com/support.png", }; function SupportChat() { const { isPending: opening, error: roomError } = useChatRoom(room); const { data: messages, clear, error } = useChatMessages(room.roomId); const { send, isPending: sending } = useSendChatMessage(room.roomId); const [draft, setDraft] = useState(""); if (opening) return

Opening room…

; if (roomError) return

Chat unavailable: {roomError.message}

; return ( <> {error &&

{error.message}

}
    {messages?.map((m, i) => (
  • {m.peer}:{" "} {m.content.tag === "Text" ? m.content.value.text : `[${m.content.tag}]`}
  • ))}
{ e.preventDefault(); void send(draft).catch(() => {}); setDraft(""); }} > setDraft(e.target.value)} />
); } ```
```vue ``` In Vue, `roomId` may also be a getter (`() => route.params.roomId`); the subscription re-attaches when it changes.
To keep more (or fewer) messages, pass a `limit`: ```ts const { data } = useChatMessages(roomId, { limit: 50 }); ``` ## See also [#see-also] * [`useSendChatMessage`](/docs/chat/use-send-chat-message) posts into the room. * [`useChatActions`](/docs/chat/use-chat-actions) is the raw stream underneath, including button presses and commands. ## API reference [#api-reference] ### React [#react] ```ts function useChatMessages(roomId, options?): LiveListQueryResult; ``` Accumulated `MessagePosted` events for a room (bounded, newest last). ### Parameters [#parameters] | Parameter | Type | | ---------------- | --------------------------------------------------------------------- | | `roomId` | `string` \| `undefined` | | `options?` | \{ `limit?`: `number`; `query?`: `QueryOptions`\<`ChatMessage`\[]>; } | | `options.limit?` | `number` | | `options.query?` | `QueryOptions`\<`ChatMessage`\[]> | ### Returns [#returns] `LiveListQueryResult`\<`ChatMessage`> ### Properties [#properties] #### content [#content] ```ts content: ChatMessageContent; ``` *** #### peer [#peer] ```ts peer: string; ``` *** #### receivedAt [#receivedat] ```ts receivedAt: number; ``` *** #### roomId [#roomid] ```ts roomId: string; ``` ### Vue [#vue] ```ts function useChatMessages(roomId, options?): LiveListQueryResult; ``` Accumulated `MessagePosted` events for a room (bounded, newest last). ### Parameters [#parameters-1] | Parameter | Type | | ---------------- | --------------------------------------------------------------------- | | `roomId` | `MaybeGetter`\<`string` \| `undefined`> | | `options?` | \{ `limit?`: `number`; `query?`: `QueryOptions`\<`ChatMessage`\[]>; } | | `options.limit?` | `number` | | `options.query?` | `QueryOptions`\<`ChatMessage`\[]> | ### Returns [#returns-1] `LiveListQueryResult`\<`ChatMessage`> ### Properties [#properties-1] #### content [#content-1] ```ts content: ChatMessageContent; ``` *** #### peer [#peer-1] ```ts peer: string; ``` *** #### receivedAt [#receivedat-1] ```ts receivedAt: number; ``` *** #### roomId [#roomid-1] ```ts roomId: string; ``` # useChatRoom (/docs/chat/use-chat-room) `useChatRoom` registers a chat room your product hosts. `data` resolves to `"New"` (the room was just created) or `"Exists"` (it was already registered). Registration is idempotent: the result is cached per `roomId` for the runtime's lifetime, so mounting the hook from several components performs a single host call, and re-registering an existing room is harmless anyway, it just resolves `"Exists"`. Chat is host-only. Running standalone (outside a Polkadot host) the query result settles in the error state with `HostUnavailableError`; gate chat UI with [`useIsHost`](/docs/host/use-is-host), or render the error as a fallback. ## Usage [#usage] ```tsx import { useChatRoom } from "@use-truapi/react"; const room = { roomId: "support", name: "Support", icon: "https://example.com/support.png", }; function SupportRoom() { const { data: status, isPending, error } = useChatRoom(room); if (isPending) return

Opening room…

; if (error) return

Chat unavailable: {error.message}

; return

Room ready ({status === "New" ? "just created" : "already existed"}).

; } ```
```vue ```
Once registration has settled, wire up [`useChatMessages`](/docs/chat/use-chat-messages) and [`useSendChatMessage`](/docs/chat/use-send-chat-message) against the same `roomId` for a working chat panel. ## See also [#see-also] * [`useChatBot`](/docs/chat/use-chat-bot) registers a bot identity for posting into rooms. * [`useChatRooms`](/docs/chat/use-chat-rooms) lists the rooms the product participates in, live. ## API reference [#api-reference] ### React [#react] ```ts function useChatRoom(room, options?): UseQueryResult<"New" | "Exists", Error>; ``` Register a chat room (idempotent). Chat is host-only. ### Parameters [#parameters] | Parameter | Type | | ---------------- | ------------------------------------------------------ | | `room` | `ChatRoomDefinition` | | `options?` | \{ `query?`: `QueryOptions`\<`"New"` \| `"Exists"`>; } | | `options.query?` | `QueryOptions`\<`"New"` \| `"Exists"`> | ### Returns [#returns] `UseQueryResult`\<`"New"` | `"Exists"`, `Error`> ### Vue [#vue] ```ts function useChatRoom(room, options?): QueryResult<"New" | "Exists">; ``` Register a chat room (idempotent). Chat is host-only. ### Parameters [#parameters-1] | Parameter | Type | | ---------------- | ------------------------------------------------------ | | `room` | `ChatRoomDefinition` | | `options?` | \{ `query?`: `QueryOptions`\<`"New"` \| `"Exists"`>; } | | `options.query?` | `QueryOptions`\<`"New"` \| `"Exists"`> | ### Returns [#returns-1] `QueryResult`\<`"New"` | `"Exists"`> # useChatRooms (/docs/chat/use-chat-rooms) `useChatRooms` subscribes to the host's chat list. `data` is a `ChatRoom[]`; each entry carries the `roomId` and how the product participates in it (`participatingAs` is `"RoomHost"` or `"Bot"`). The list updates live as rooms are registered. The result is seeded with an empty array, so it settles immediately instead of staying pending until the first push. Any number of components can mount the hook; they share one watch against the host, which is dropped when the last consumer unmounts. Chat is host-only. Running standalone the subscription fails and the result flips to the error state with `HostUnavailableError` (the last data, here the empty seed, is retained on `data`). ## Usage [#usage] ```tsx import { useChatRooms } from "@use-truapi/react"; function RoomList() { const { data: rooms, error } = useChatRooms(); if (error) return

Chat unavailable: {error.message}

; if (!rooms?.length) return

No rooms yet.

; return (
    {rooms.map((room) => (
  • {room.roomId} (as {room.participatingAs})
  • ))}
); } ```
```vue ```
Values are pushed, not polled. There is nothing to refetch; fresh lists arrive on their own. ## See also [#see-also] * [`useChatRoom`](/docs/chat/use-chat-room) registers a room (it will then appear here). * [`useChatMessages`](/docs/chat/use-chat-messages) shows live messages for one of these rooms. ## API reference [#api-reference] ### React [#react] ```ts function useChatRooms(options?): UseQueryResult; ``` Rooms the product participates in, live. ### Parameters [#parameters] | Parameter | Type | | ---------------- | ---------------------------------------------- | | `options?` | \{ `query?`: `QueryOptions`\<`ChatRoom`\[]>; } | | `options.query?` | `QueryOptions`\<`ChatRoom`\[]> | ### Returns [#returns] `UseQueryResult`\<`ChatRoom`\[], `Error`> ### Vue [#vue] ```ts function useChatRooms(options?): QueryResult; ``` Rooms the product participates in, live. ### Parameters [#parameters-1] | Parameter | Type | | ---------------- | ---------------------------------------------- | | `options?` | \{ `query?`: `QueryOptions`\<`ChatRoom`\[]>; } | | `options.query?` | `QueryOptions`\<`ChatRoom`\[]> | ### Returns [#returns-1] `QueryResult`\<`ChatRoom`\[]> # useSendChatMessage (/docs/chat/use-send-chat-message) `useSendChatMessage` posts messages into a room. Call `send(content, roomId?)`; it resolves to the assigned `messageId`, and the usual mutation state (`data`, `error`, `isPending`, `reset`) tracks the last send. Plain strings become Text messages: `send("hi")` is sugar for sending `{ tag: "Text", value: { text: "hi" } }`; any other `ChatMessageContent` variant is passed through as-is. The target room can be set once on the hook or per call: pass `roomId` to the hook for a fixed room, or as the second argument of `send` to override it (handy for bots replying into whichever room an action came from). If neither is given, `send` rejects. Chat is host-only. Running standalone `send` rejects with `HostUnavailableError`; gate the send UI with [`useIsHost`](/docs/host/use-is-host). ## Usage [#usage] ```tsx import { useState } from "react"; import { useSendChatMessage } from "@use-truapi/react"; function Composer({ roomId }: { roomId: string }) { const { send, isPending, error } = useSendChatMessage(roomId); const [draft, setDraft] = useState(""); return (
{ e.preventDefault(); void send(draft).catch(() => {}); setDraft(""); }} > setDraft(e.target.value)} /> {error &&

{error.message}

}
); } ```
```vue ``` In Vue, the hook-level `roomId` may be a getter for reactive rooms.
In a fire-and-forget handler, `void send(draft).catch(() => {})` keeps the promise from going unhandled; the failure still lands in `error`. Await `send` instead when you need the result. Anything beyond plain text is sent as a tagged `ChatMessageContent`: ```ts const { send } = useSendChatMessage("support"); // Action buttons const { messageId } = await send({ tag: "Actions", value: { text: "Feeling lucky?", actions: [{ actionId: "roll", title: "Roll the dice" }], layout: "Column", }, }); // React to that message await send({ tag: "Reaction", value: { messageId, emoji: "🎲" } }); ``` To target rooms per call, omit the hook-level `roomId` (or keep it as a default) and pass the room as the second argument; the call-site room always wins: ```ts const { send } = useSendChatMessage(); void send("Done!", action.roomId).catch(() => {}); ``` ## See also [#see-also] * [`useChatRoom`](/docs/chat/use-chat-room) registers the room before posting into it. * [`useChatMessages`](/docs/chat/use-chat-messages) renders what lands in the room. ## API reference [#api-reference] ### React [#react] ```ts function useSendChatMessage(roomId?, options?): NamedMutation<{ messageId: string; }, SendChatMessageVariables> & object; ``` Send into a room: `send("hi")` or `send(content, roomId)`. ### Parameters [#parameters] | Parameter | Type | | ------------------- | ---------------------------------------------------------------------------------------------- | | `roomId?` | `string` | | `options?` | \{ `mutation?`: `MutationOptions`\<\{ `messageId`: `string`; }, `SendChatMessageVariables`>; } | | `options.mutation?` | `MutationOptions`\<\{ `messageId`: `string`; }, `SendChatMessageVariables`> | ### Returns [#returns] `NamedMutation`\<\{ `messageId`: `string`; }, `SendChatMessageVariables`> & `object` ### Properties [#properties] #### content [#content] ```ts content: string | ChatMessageContent; ``` Plain strings become Text messages. *** #### roomId? [#roomid] ```ts optional roomId?: string; ``` Overrides the roomId given to the hook. ### Vue [#vue] ```ts function useSendChatMessage(roomId?, options?): NamedMutation<{ messageId: string; }, SendChatMessageVariables> & object; ``` Send into a room: `send("hi")` or `send(content, roomId)`. ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | ---------------------------------------------------------------------------------------------- | | `roomId?` | `MaybeGetter`\<`string` \| `undefined`> | | `options?` | \{ `mutation?`: `MutationOptions`\<\{ `messageId`: `string`; }, `SendChatMessageVariables`>; } | | `options.mutation?` | `MutationOptions`\<\{ `messageId`: `string`; }, `SendChatMessageVariables`> | ### Returns [#returns-1] `NamedMutation`\<\{ `messageId`: `string`; }, `SendChatMessageVariables`> & `object` ### Properties [#properties-1] #### content [#content-1] ```ts content: string | ChatMessageContent; ``` Plain strings become Text messages. *** #### roomId? [#roomid-1] ```ts optional roomId?: string; ``` Overrides the roomId given to the composable. # useContractAt (/docs/contracts/use-contract-at) `useContractAt` builds a typed contract handle from a raw H160 address and an ABI, without a `cdm.json` manifest. `data` is the same kind of `Contract` handle [`useContract`](/docs/contracts/use-contract) returns, so [`useContractQuery`](/docs/contracts/use-contract-query) and [`useContractTx`](/docs/contracts/use-contract-tx) work identically. Reach for it when the address arrives at runtime (user input, route params, a registry lookup) or when you interact with a contract you didn't deploy. While `address` is `undefined` the hook stays disabled (`isPending` with no fetch), so you can pass a possibly-missing address without guarding. The result is cached under the chain key and the address. ## Usage [#usage] ```tsx import { useContractAt, useContractQuery } from "@use-truapi/react"; import { counterAbi } from "./counter-contract"; function CounterAt({ address }: { address?: `0x${string}` }) { const contract = useContractAt(address, counterAbi); const count = useContractQuery(contract.data, "getCount", []); if (!address) return

Enter a contract address.

; if (contract.error) return

Failed to load contract: {contract.error.message}

; return

Count: {count.data?.toString() ?? "…"}

; } ```
```vue ``` Pass a getter (`() => props.address`) so the handle re-resolves when the address changes, and pass the whole `contract` query result to the downstream composables.
To build against a specific chain, pass its key: ```ts const contract = useContractAt(address, counterAbi, { chain: "assetHub" }); ``` ## See also [#see-also] * [`useContract`](/docs/contracts/use-contract) resolves the handle from a `cdm.json` manifest. * [`useContractQuery`](/docs/contracts/use-contract-query) runs read-only dry-run calls on the handle. * [`useContractTx`](/docs/contracts/use-contract-tx) signs and submits contract writes. ## API reference [#api-reference] ### React [#react] ```ts function useContractAt( address, abi, options?): UseQueryResult, Error>; ``` Ad-hoc contract from an H160 address + ABI — no manifest needed. ### Parameters [#parameters] | Parameter | Type | | ---------------- | ------------------------------------------------------------------------------- | | `address` | `` `0x${string}` `` \| `undefined` | | `abi` | `AbiEntry`\[] | | `options?` | \{ `chain?`: `string`; `query?`: `QueryOptions`\<`Contract`\<`ContractDef`>>; } | | `options.chain?` | `string` | | `options.query?` | `QueryOptions`\<`Contract`\<`ContractDef`>> | ### Returns [#returns] `UseQueryResult`\<`Contract`\<`ContractDef`>, `Error`> ### Vue [#vue] ```ts function useContractAt( address, abi, options?): QueryResult>; ``` Ad-hoc contract from an H160 address + ABI — no manifest needed. ### Parameters [#parameters-1] | Parameter | Type | | ---------------- | ------------------------------------------------------------------------------- | | `address` | `MaybeGetter`\<`` `0x${string}` `` \| `undefined`> | | `abi` | `AbiEntry`\[] | | `options?` | \{ `chain?`: `string`; `query?`: `QueryOptions`\<`Contract`\<`ContractDef`>>; } | | `options.chain?` | `string` | | `options.query?` | `QueryOptions`\<`Contract`\<`ContractDef`>> | ### Returns [#returns-1] `QueryResult`\<`Contract`\<`ContractDef`>> # useContractQuery (/docs/contracts/use-contract-query) `useContractQuery` performs a read-only contract call: a `ReviveApi.call` dry-run that never signs or submits anything. `data` is the decoded return value; pass the expected type as the generic (`useContractQuery(…)`). Results are cached under the contract identity, the method name and the args, so several components reading the same value share one dry-run; different args are different cache entries. While the contract handle hasn't resolved the query stays disabled (`isPending`), no need to guard on the handle. If the call reverts the query errors with `contract query "method" reverted` and the revert value attached as `error.cause`; an unknown method name errors with `contract has no method "method"`. Reads are one shot, not subscriptions. Call `refetch()` (for example from a transaction's `onSuccess`) to observe a new value after a write. ## Usage [#usage] ```tsx import { useContract, useContractQuery } from "@use-truapi/react"; import { COUNTER_LIBRARY, cdmJson } from "./counter-contract"; function Count() { const contract = useContract(cdmJson, COUNTER_LIBRARY); const count = useContractQuery(contract.data, "getCount", []); if (count.isPending) return

Reading count…

; if (count.error) return

Read failed: {count.error.message}

; return (

Count: {count.data.toString()}{" "}

); } ``` In React, pass the resolved handle, `contract.data`, as the first argument.
```vue ``` In Vue, pass the whole query result returned by `useContract` or `useContractAt`; the read unwraps `contract.data` reactively and enables itself once the handle resolves. Args may be a getter (`() => [props.id]`) so reactive args re-run the read.
Gate the read with `enabled` while an input is incomplete; it combines with the built-in "contract resolved" gate: ```ts const balance = useContractQuery(contract.data, "balanceOf", [owner], { enabled: owner !== undefined, // Vue: () => owner.value !== undefined }); ``` ## See also [#see-also] * [`useContract`](/docs/contracts/use-contract) and [`useContractAt`](/docs/contracts/use-contract-at) obtain the handle to read from. * [`useContractTx`](/docs/contracts/use-contract-tx) is the write-side counterpart; refetch after it succeeds. ## API reference [#api-reference] ### React [#react] ```ts function useContractQuery( contract, method, args, options?): UseQueryResult; ``` Read-only contract call (`ReviveApi.call` dry-run) — cached under the contract identity, method and args. ```ts const count = useContractQuery(contract, "getCount", []); ``` ### Type Parameters [#type-parameters] | Type Parameter | Default type | | -------------- | ------------ | | `T` | `unknown` | ### Parameters [#parameters] | Parameter | Type | | ------------------ | ----------------------------------------------------------- | | `contract` | \| `Contract`\<`ContractDef`> \| `undefined` | | `method` | `string` | | `args` | readonly `unknown`\[] | | `options?` | \{ `enabled?`: `boolean`; `query?`: `QueryOptions`\<`T`>; } | | `options.enabled?` | `boolean` | | `options.query?` | `QueryOptions`\<`T`> | ### Returns [#returns] `UseQueryResult`\<`T`, `Error`> ### Vue [#vue] ```ts function useContractQuery( contract, method, args?, options?): QueryResult; ``` Read-only contract call (`ReviveApi.call` dry-run) — cached under the contract identity, method and args; reactive args re-run it. ### Type Parameters [#type-parameters-1] | Type Parameter | Default type | | -------------- | ------------ | | `T` | `unknown` | ### Parameters [#parameters-1] | Parameter | Type | Default value | | ------------------ | --------------------------------------------------------------------------- | ------------- | | `contract` | `QueryResult`\<`Contract`\<`ContractDef`>> | `undefined` | | `method` | `string` | `undefined` | | `args` | `MaybeGetter`\ | `[]` | | `options?` | \{ `enabled?`: `MaybeGetter`\<`boolean`>; `query?`: `QueryOptions`\<`T`>; } | `undefined` | | `options.enabled?` | `MaybeGetter`\<`boolean`> | `undefined` | | `options.query?` | `QueryOptions`\<`T`> | `undefined` | ### Returns [#returns-1] `QueryResult`\<`T`> # useContractTx (/docs/contracts/use-contract-tx) `useContractTx` binds one contract method as a write. Call `send(args?)`: it dry-runs the call first, then signs with the connected account, submits and watches the transaction, resolving with a `TxResult` once it is in a block. Args are optional, so call `send()` bare for no-argument methods or pass the args array (`send([5n])`). Mutation state (`data`, `error`, `isPending`, `reset`) tracks the last send. Like [`useTx`](/docs/tx/use-tx), the resolved `TxResult` reports dispatch failure as data: check `result.ok`, a failed dispatch carries the decoded `dispatchError`. `send` rejects (and sets `error`) when the handle hasn't resolved yet, when the method name doesn't exist on the handle, when the dry-run fails, or when the user rejects the signature. Before the first contract transaction from an account, the account must be mapped in pallet-revive; see [`useEnsureAccountMapped`](/docs/contracts/use-ensure-account-mapped). ## Usage [#usage] ```tsx import { useContract, useContractQuery, useContractTx } from "@use-truapi/react"; import { COUNTER_LIBRARY, cdmJson } from "./counter-contract"; function Counter() { const contract = useContract(cdmJson, COUNTER_LIBRARY); const count = useContractQuery(contract.data, "getCount", []); const increment = useContractTx(contract.data, "increment", { mutation: { onSuccess: () => count.refetch() }, }); const add = useContractTx(contract.data, "add", { mutation: { onSuccess: () => count.refetch() }, }); return ( <>

Count: {count.data?.toString() ?? "…"}

{increment.error &&

{increment.error.message}

} ); } ``` In React, pass the resolved handle, `contract.data`, as the first argument.
```vue ``` In Vue, pass the whole query result returned by `useContract` or `useContractAt`; the write unwraps `contract.data` when it runs.
Failures always land in `error`, so a fire-and-forget click handler only needs `.catch(() => {})` to silence the rejected promise. To inspect the dispatch outcome, await the result: ```ts const result = await increment.send(); if (!result.ok) console.error("dispatch failed", result.dispatchError); ``` ## See also [#see-also] * [`useEnsureAccountMapped`](/docs/contracts/use-ensure-account-mapped) is required once before the first contract tx. * [`useContractQuery`](/docs/contracts/use-contract-query) is the read-side counterpart; refetch it after writes. * [`useTx`](/docs/tx/use-tx) is the same lifecycle for plain transactions, with a granular `phase`. ## API reference [#api-reference] ### React [#react] ```ts function useContractTx( contract, method, options?): NamedMutation> & object; ``` Contract transaction: dry-run pre-flight, then sign/submit/watch. ```ts const increment = useContractTx(contract, "increment"); await increment.send(); ``` ### Parameters [#parameters] | Parameter | Type | | ------------------- | ---------------------------------------------------------------------------------------------- | | `contract` | \| `Contract`\<`ContractDef`> \| `undefined` | | `method` | `string` | | `options?` | \{ `mutation?`: `MutationOptions`\<`TxResult`, `OptionalVariables`\>; } | | `options.mutation?` | `MutationOptions`\<`TxResult`, `OptionalVariables`\> | ### Returns [#returns] `NamedMutation`\<`TxResult`, `OptionalVariables`\> & `object` ### Vue [#vue] ```ts function useContractTx( contract, method, options?): NamedMutation> & object; ``` Contract transaction: dry-run pre-flight, then sign/submit/watch. ```ts const increment = useContractTx(contract, "increment"); await increment.send(); ``` ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | ---------------------------------------------------------------------------------------------- | | `contract` | `QueryResult`\<`Contract`\<`ContractDef`>> | | `method` | `string` | | `options?` | \{ `mutation?`: `MutationOptions`\<`TxResult`, `OptionalVariables`\>; } | | `options.mutation?` | `MutationOptions`\<`TxResult`, `OptionalVariables`\> | ### Returns [#returns-1] `NamedMutation`\<`TxResult`, `OptionalVariables`\> & `object` # useContract (/docs/contracts/use-contract) `useContract` resolves a typed contract handle from a `cdm.json` manifest, the file `cdm deploy` produces. `data` is a `Contract` handle with one property per contract method, each exposing a dry-run read and a transaction; feed it to [`useContractQuery`](/docs/contracts/use-contract-query) and [`useContractTx`](/docs/contracts/use-contract-tx). Contract managers are cached per (chain, manifest) and share the runtime's signer, so any number of components can resolve the same manifest without redundant setup. The result is cached under the chain key, the manifest name (or a session-stable id when the manifest has no name), the library name and the `live` flag. Addresses come from the manifest snapshot by default. Pass `live: true` to resolve them from the on-chain CDM registry instead, so the handle follows re-deployments without shipping a new manifest. ## Usage [#usage] Keep the manifest import in one module so every component shares the same object (the cache key is derived from it): ```ts title="counter-contract.ts" import type { CdmJson } from "@use-truapi/react"; // or "@use-truapi/vue" import manifest from "../contracts/cdm.json"; export const COUNTER_LIBRARY = "@my-app/counter"; export const cdmJson = manifest as unknown as CdmJson; ``` ```tsx import { useContract, useContractQuery } from "@use-truapi/react"; import { COUNTER_LIBRARY, cdmJson } from "./counter-contract"; function Counter() { const contract = useContract(cdmJson, COUNTER_LIBRARY); const count = useContractQuery(contract.data, "getCount", []); if (contract.isPending) return

Resolving contract…

; if (contract.error) return

Failed to load contract: {contract.error.message}

; return

Count: {count.data?.toString() ?? "…"}

; } ``` In React, pass the resolved handle, `contract.data`, to `useContractQuery` and `useContractTx`.
```vue ``` In Vue, pass the whole `contract` query result to `useContractQuery` and `useContractTx`; they unwrap `contract.data` reactively.
To resolve addresses from the live registry, or build against a specific chain: ```ts const contract = useContract(cdmJson, COUNTER_LIBRARY, { live: true, chain: "assetHub", }); ``` ## See also [#see-also] * [`useContractAt`](/docs/contracts/use-contract-at) builds a handle from an address and an ABI, no manifest. * [`useContractQuery`](/docs/contracts/use-contract-query) runs read-only dry-run calls on the handle. * [`useContractTx`](/docs/contracts/use-contract-tx) signs and submits contract writes. * [`useEnsureAccountMapped`](/docs/contracts/use-ensure-account-mapped) maps the account once before the first write. ## API reference [#api-reference] ### React [#react] ```ts function useContract( cdmJson, library, options?): UseQueryResult, Error>; ``` Typed contract handle from a `cdm.json` manifest. Managers are cached per (chain, manifest) and share the runtime's SignerManager. ### Parameters [#parameters] | Parameter | Type | | ---------- | ------------------------------------- | | `cdmJson` | `CdmJson` | | `library` | `string` | | `options?` | `ContractScope`\<`string`> & `object` | ### Returns [#returns] `UseQueryResult`\<`Contract`\<`ContractDef`>, `Error`> ### Vue [#vue] ```ts function useContract( cdmJson, library, options?): QueryResult>; ``` Typed contract handle from a `cdm.json` manifest. Managers are cached per (chain, manifest) and share the runtime's SignerManager. ### Parameters [#parameters-1] | Parameter | Type | | ---------- | ------------------------------------- | | `cdmJson` | `CdmJson` | | `library` | `string` | | `options?` | `ContractScope`\<`string`> & `object` | ### Returns [#returns-1] `QueryResult`\<`Contract`\<`ContractDef`>> # useEnsureAccountMapped (/docs/contracts/use-ensure-account-mapped) Before an account can call PolkaVM contracts, pallet-revive needs a one-time account mapping that gives the Substrate account an H160 identity. `useEnsureAccountMapped` wraps that step: `ensureMapped()` maps the connected account on the contract's chain and resolves with no data once done. Mutation state (`error`, `isPending`, `reset`) tracks the last attempt. The operation is idempotent: if the account is already mapped it completes without submitting anything, so it is safe to call defensively (for example before the first write in a session). It only needs to succeed once per account per chain. The manifest argument scopes the mapping to the same cached contract manager that [`useContract`](/docs/contracts/use-contract) uses, so pass the same `cdmJson` object. ## Usage [#usage] ```tsx import { useContract, useContractTx, useEnsureAccountMapped } from "@use-truapi/react"; import { COUNTER_LIBRARY, cdmJson } from "./counter-contract"; function Setup() { const contract = useContract(cdmJson, COUNTER_LIBRARY); const mapAccount = useEnsureAccountMapped(cdmJson); const increment = useContractTx(contract.data, "increment"); async function onFirstIncrement() { try { await mapAccount.ensureMapped(); // no-op if already mapped await increment.send(); } catch { // failures are also reported in mapAccount.error / increment.error } } return ( <> {mapAccount.error &&

{mapAccount.error.message}

} ); } ```
```vue ```
## See also [#see-also] * [`useContractTx`](/docs/contracts/use-contract-tx) is the write this mapping unlocks. * [`useContract`](/docs/contracts/use-contract) takes the same `cdmJson` manifest. * [`useTx`](/docs/tx/use-tx) submits plain transactions, which don't require mapping. ## API reference [#api-reference] ### React [#react] ```ts function useEnsureAccountMapped(cdmJson, options?): NamedMutation & object; ``` Idempotent pallet-revive account mapping — required once before contract txs: `ensureMapped()`. ### Parameters [#parameters] | Parameter | Type | | ------------------- | ------------------------------------------------------------------------- | | `cdmJson` | `CdmJson` | | `options?` | \{ `chain?`: `string`; `mutation?`: `MutationOptions`\<`void`, `void`>; } | | `options.chain?` | `string` | | `options.mutation?` | `MutationOptions`\<`void`, `void`> | ### Returns [#returns] `NamedMutation`\<`void`, `void`> & `object` ### Vue [#vue] ```ts function useEnsureAccountMapped(cdmJson, options?): NamedMutation & object; ``` Idempotent pallet-revive account mapping — required once before contract txs: `ensureMapped()`. ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | ------------------------------------------------------------------------- | | `cdmJson` | `CdmJson` | | `options?` | \{ `chain?`: `string`; `mutation?`: `MutationOptions`\<`void`, `void`>; } | | `options.chain?` | `string` | | `options.mutation?` | `MutationOptions`\<`void`, `void`> | ### Returns [#returns-1] `NamedMutation`\<`void`, `void`> & `object` # useFormattedBalance (/docs/format/use-formatted-balance) `useFormattedBalance` turns a raw planck `bigint` into a display-ready string: locale-aware thousand separators, fraction truncation and an optional token symbol. It is null-safe: `undefined` or `null` input returns an empty string, so you can pass `balance?.free` straight from a pending [`useBalance`](/docs/chain/use-balance) result without guarding. In React the result is a memoized `string`, recomputed only when the value or options change. In Vue it is a `ComputedRef` of `string`, and the input may be a getter for reactive sources. ## Usage [#usage] ```tsx import { useBalance, useFormattedBalance, useSelectedAccount } from "@use-truapi/react"; function FreeBalance() { const account = useSelectedAccount(); const { data: balance } = useBalance(account?.address); const free = useFormattedBalance(balance?.free, { decimals: 10, maxDecimals: 2, symbol: "PAS", }); // "" while the balance is loading, e.g. "1,234.56 PAS" once it arrives return

{free || "Loading…"}

; } ```
```vue ``` Pass a getter (`() => balance.value?.free`) for reactive values; the computed string updates as the balance changes.
What the output looks like: ```ts useFormattedBalance(10_000_000_000n); // "1" (trailing .0 omitted) useFormattedBalance(15_000_000_000n, { symbol: "DOT" }); // "1.5 DOT" useFormattedBalance(10_000_000_000_000n, { symbol: "DOT" }); // "1,000 DOT" useFormattedBalance(12_345_678_900n, { maxDecimals: 2 }); // "1.23" useFormattedBalance(undefined); // "" ``` For one-off formatting outside components, the underlying `formatBalance` (and the lower-level `formatPlanck` / `parseToPlanck`) are exported from every package. ## See also [#see-also] * [`useBalance`](/docs/chain/use-balance) provides the live planck values this hook typically formats. ## API reference [#api-reference] ### React [#react] ```ts function useFormattedBalance(planck, options?): string; ``` Format a planck bigint for display, memoized. ### Parameters [#parameters] | Parameter | Type | | ---------- | --------------------------------- | | `planck` | `bigint` \| `null` \| `undefined` | | `options?` | `FormatBalanceOptions` | ### Returns [#returns] `string` ### Properties [#properties] #### decimals? [#decimals] ```ts optional decimals?: number; ``` *** #### locale? [#locale] ```ts optional locale?: string; ``` *** #### maxDecimals? [#maxdecimals] ```ts optional maxDecimals?: number; ``` *** #### symbol? [#symbol] ```ts optional symbol?: string; ``` ### Vue [#vue] ```ts function useFormattedBalance(planck, options?): ComputedRef; ``` Format a planck bigint for display, reactively. ### Parameters [#parameters-1] | Parameter | Type | | ---------- | ------------------------------------------------- | | `planck` | `MaybeGetter`\<`bigint` \| `null` \| `undefined`> | | `options?` | `FormatBalanceOptions` | ### Returns [#returns-1] `ComputedRef`\<`string`> ### Properties [#properties-1] #### decimals? [#decimals-1] ```ts optional decimals?: number; ``` *** #### locale? [#locale-1] ```ts optional locale?: string; ``` *** #### maxDecimals? [#maxdecimals-1] ```ts optional maxDecimals?: number; ``` *** #### symbol? [#symbol-1] ```ts optional symbol?: string; ``` # useDeriveEntropy (/docs/host/use-derive-entropy) `useDeriveEntropy` derives deterministic entropy from the user's wallet (RFC-0007). Call `derive(key)` with a `Uint8Array`; it resolves to 32 bytes that are stable across sessions and devices: the same key with the same wallet always yields the same bytes, while a different key or a different wallet yields unrelated ones. The hook also returns mutation state (`isPending`, `error`, `data`, `reset`). That property makes it the building block for secrets you never have to store or sync: derive an encryption key for user data, a seed for an app-specific identity, or a deterministic salt, and re-derive it on any device where the wallet is available. Use distinct, namespaced keys (`my-app/settings-encryption/v1`) so unrelated features get unrelated entropy. This is a host feature: standalone, `derive` rejects with `HostUnavailableError`, so gate it on [`useIsHost`](/docs/host/use-is-host). ## Usage [#usage] ```tsx import { useDeriveEntropy } from "@use-truapi/react"; function UnlockNotes({ onKey }: { onKey: (key: Uint8Array) => void }) { const { derive, isPending, error } = useDeriveEntropy(); async function onUnlock() { const entropy = await derive( new TextEncoder().encode("my-app/notes-encryption/v1"), ); onKey(entropy); // 32 bytes, feed into your cipher of choice } return ( <> {error &&

{error.message}

} ); } ```
```vue ```
## See also [#see-also] * [`useIsHost`](/docs/host/use-is-host) gates the affordance when the app also runs standalone. * [`useHostStorage`](/docs/host/use-host-storage) is a natural place to keep data encrypted with a derived key. ## API reference [#api-reference] ### React [#react] ```ts function useDeriveEntropy(options?): NamedMutation, Uint8Array> & object; ``` RFC-0007 deterministic entropy — same key, same wallet ⇒ same 32 bytes: `derive(key)`. ### Parameters [#parameters] | Parameter | Type | | ------------------- | --------------------------------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`Uint8Array`\<`ArrayBufferLike`>, `Uint8Array`\<`ArrayBufferLike`>>; } | | `options.mutation?` | `MutationOptions`\<`Uint8Array`\<`ArrayBufferLike`>, `Uint8Array`\<`ArrayBufferLike`>> | ### Returns [#returns] `NamedMutation`\<`Uint8Array`\<`ArrayBufferLike`>, `Uint8Array`\<`ArrayBufferLike`>> & `object` ### Vue [#vue] ```ts function useDeriveEntropy(options?): NamedMutation, Uint8Array> & object; ``` RFC-0007 deterministic entropy — same key, same wallet ⇒ same 32 bytes: `derive(key)`. ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | --------------------------------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`Uint8Array`\<`ArrayBufferLike`>, `Uint8Array`\<`ArrayBufferLike`>>; } | | `options.mutation?` | `MutationOptions`\<`Uint8Array`\<`ArrayBufferLike`>, `Uint8Array`\<`ArrayBufferLike`>> | ### Returns [#returns-1] `NamedMutation`\<`Uint8Array`\<`ArrayBufferLike`>, `Uint8Array`\<`ArrayBufferLike`>> & `object` # useDevicePermission (/docs/host/use-device-permission) `useDevicePermission` asks the host to grant access to a device capability (camera, microphone, notifications, clipboard and so on). Call `request(kind)`: it resolves to `true` when granted, `false` when the user declined. A denial is data, not an exception; thrown errors mean the request itself failed. The hook also returns mutation state (`isPending`, `error`, `data`, `reset`). Unlike [`usePermission`](/docs/host/use-permission), which covers remote operations the host performs on the user's behalf, device permissions cover local hardware and OS integrations. Request them right before the feature that needs them: a camera prompt on tapping "Scan QR" converts far better than one at startup. This is a host feature: standalone, `request` rejects with `HostUnavailableError`. In a plain browser use the regular web APIs (`navigator.mediaDevices`, the Notification API) instead. ## Usage [#usage] ```tsx import { useDevicePermission } from "@use-truapi/react"; function ScanButton({ onReady }: { onReady: () => void }) { const { request, isPending, error } = useDevicePermission(); async function onScan() { const granted = await request("Camera"); if (granted) onReady(); } return ( <> {error &&

{error.message}

} ); } ```
```vue ```
## See also [#see-also] * [`usePermission`](/docs/host/use-permission) covers RFC-0002 remote permissions. * [`useNotifications`](/docs/host/use-notifications) typically pairs with the `"Notifications"` permission. ## API reference [#api-reference] ### React [#react] ```ts function useDevicePermission(options?): NamedMutation & object; ``` Device permissions (Camera, Notifications, Clipboard, …): `request(kind)`. ### Parameters [#parameters] | Parameter | Type | | ------------------- | ------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`boolean`, `HostDevicePermissionRequest`>; } | | `options.mutation?` | `MutationOptions`\<`boolean`, `HostDevicePermissionRequest`> | ### Returns [#returns] `NamedMutation`\<`boolean`, `HostDevicePermissionRequest`> & `object` ### Vue [#vue] ```ts function useDevicePermission(options?): NamedMutation & object; ``` Device permissions (Camera, Notifications, Clipboard, …): `request(kind)`. ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | ------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`boolean`, `HostDevicePermissionRequest`>; } | | `options.mutation?` | `MutationOptions`\<`boolean`, `HostDevicePermissionRequest`> | ### Returns [#returns-1] `NamedMutation`\<`boolean`, `HostDevicePermissionRequest`> & `object` # useFeatureSupported (/docs/host/use-feature-supported) `useFeatureSupported` asks the host whether it supports a given `Feature` and returns the boolean answer as `data` on a query result. The only feature variant today is `Chain`, carrying a chain's `0x`-prefixed genesis hash. While `feature` is `undefined` the query resolves to `false` without asking the host, so you can pass a value that may not be ready yet without guarding. Results are cached per feature, so any number of components can probe the same feature with a single host round-trip. The probe is a host call: standalone it surfaces `HostUnavailableError` on the query's `error`. Gate it on [`useIsHost`](/docs/host/use-is-host) when your app also runs outside a container. ## Usage [#usage] ```tsx import { useFeatureSupported } from "@use-truapi/react"; const PEOPLE_GENESIS = "0x…" as const; function PeopleChainSection() { const { data: supported, isPending } = useFeatureSupported({ tag: "Chain", value: PEOPLE_GENESIS, }); if (isPending) return

Checking chain support…

; if (!supported) return

This host does not support the People chain.

; return
{/* People-chain features */}
; } ```
```vue ``` Pass a getter for reactive features; the query re-runs when the value changes.
## See also [#see-also] * [`useIsHost`](/docs/host/use-is-host) skips the probe entirely outside a host. * [`useChainSpec`](/docs/chain/use-chain-spec) fetches the spec of a supported chain. ## API reference [#api-reference] ### React [#react] ```ts function useFeatureSupported(feature, options?): UseQueryResult; ``` ### Parameters [#parameters] | Parameter | Type | | ---------------- | ------------------------------------------ | | `feature` | `Feature` \| `undefined` | | `options?` | \{ `query?`: `QueryOptions`\<`boolean`>; } | | `options.query?` | `QueryOptions`\<`boolean`> | ### Returns [#returns] `UseQueryResult`\<`boolean`, `Error`> ### Vue [#vue] ```ts function useFeatureSupported(feature, options?): QueryResult; ``` ### Parameters [#parameters-1] | Parameter | Type | | ---------------- | ------------------------------------------ | | `feature` | `MaybeGetter`\<`Feature` \| `undefined`> | | `options?` | \{ `query?`: `QueryOptions`\<`boolean`>; } | | `options.query?` | `QueryOptions`\<`boolean`> | ### Returns [#returns-1] `QueryResult`\<`boolean`> # useHostMode (/docs/host/use-host-mode) `useHostMode` reports where the app is running. It returns `"unknown"` while async host detection is still pending, then settles on `"host"` (embedded in a host container such as Polkadot Desktop or Mobile) or `"standalone"` (a plain web page). If the container can be detected synchronously the mode is `"host"` from the very first render, so host-only UI never flashes. Use this hook when the `"unknown"` window matters, for example to render a splash instead of prematurely showing standalone-only UI. When a plain boolean is enough, reach for [`useIsHost`](/docs/host/use-is-host) instead. ## Usage [#usage] ```tsx import { useHostMode } from "@use-truapi/react"; function Shell({ children }: { children: React.ReactNode }) { const mode = useHostMode(); if (mode === "unknown") return

Detecting environment…

; return ( <> {mode === "standalone" && ( )} {children} ); } ```
```vue ``` The composable returns a `ShallowRef`: use `mode.value` in script, plain `mode` in templates.
## See also [#see-also] * [`useIsHost`](/docs/host/use-is-host) is the boolean convenience over the same store. * [`useTheme`](/docs/host/use-theme) also follows host detection. ## API reference [#api-reference] ### React [#react] ```ts function useHostMode(): HostMode; ``` `"unknown"` until async detection resolves, then `"host"` | `"standalone"`. ### Returns [#returns] `HostMode` ### Vue [#vue] ```ts function useHostMode(): ShallowRef; ``` `"unknown"` until async detection resolves, then `"host"` | `"standalone"`. ### Returns [#returns-1] `ShallowRef`\<`HostMode`> # useHostNavigate (/docs/host/use-host-navigate) `useHostNavigate` returns a stable async function that asks the host to navigate to a URL. The host resolves the destination itself: a `.dot` deep link (`https://search.dot`) routes to another app or route inside the container, a regular `https://` URL opens externally in the system browser. It is a plain function, not a mutation: there is no `isPending` or `error` state to render. It rejects with `HostUnavailableError` when no host is present and `HostCallFailedError` when the host denies the navigation, so catch the promise if a failure matters. Standalone, fall back to a regular anchor or `window.open`. ## Usage [#usage] ```tsx import { useHostNavigate, useIsHost } from "@use-truapi/react"; function DocsLink() { const isHost = useIsHost(); const navigate = useHostNavigate(); if (!isHost) { return Docs; } return ( ); } ``` ```vue ``` ## See also [#see-also] * [`useIsHost`](/docs/host/use-is-host) decides between host navigation and a plain link. ## API reference [#api-reference] ### React [#react] ```ts function useHostNavigate(): (url) => Promise; ``` Host deep-link navigation: `.dot` routes in-container, `https://` external. ### Returns [#returns] (`url`) => `Promise`\<`void`> ### Vue [#vue] ```ts function useHostNavigate(): (url) => Promise; ``` Host deep-link navigation: `.dot` routes in-container, `https://` external. ### Returns [#returns-1] (`url`) => `Promise`\<`void`> # useHostStorage (/docs/host/use-host-storage) `useHostStorage` reads and writes a product-scoped key-value store. It returns a query result whose `data` is the JSON-parsed value (`T | null`, `null` when the key is unset), extended with two writers: `set(value)` serializes and persists, `remove()` deletes the key. The backing store follows the environment: host localStorage inside a container (persisted by the host, scoped to your product), browser localStorage standalone. The hook behaves identically in both, making this one of the few host APIs that needs no gating. Writes update the cached value in place, so every component reading the same key sees the new value immediately, with no refetch and no flicker. Values are JSON round-tripped: store plain serializable data (no `bigint`, `Date` or `Uint8Array`). ## Usage [#usage] Type the stored value with the generic parameter to get a typed `data` and a typed `set`. ```tsx import { useHostStorage } from "@use-truapi/react"; interface Settings { currency: "USD" | "EUR"; compactMode: boolean; } const DEFAULTS: Settings = { currency: "USD", compactMode: false }; function SettingsPanel() { const { data, isPending, set, remove } = useHostStorage("settings"); const settings = data ?? DEFAULTS; if (isPending) return

Loading settings…

; return (
); } ```
```vue ``` The key can be a getter (`() => "draft:" + props.id`); the query re-runs and the writers retarget when it changes.
## See also [#see-also] * [`useDeriveEntropy`](/docs/host/use-derive-entropy) derives a wallet-bound key to encrypt stored data. * [`useHostMode`](/docs/host/use-host-mode) tells you which backing store is in use. ## API reference [#api-reference] ### React [#react] ```ts function useHostStorage(key, options?): HostStorageValue; ``` Product-scoped KV storage: host localStorage inside a container, browser localStorage standalone. JSON-serialized. Writes update the query cache in place under `queryKeys.hostStorage(key)`. ### Type Parameters [#type-parameters] | Type Parameter | | -------------- | | `T` | ### Parameters [#parameters] | Parameter | Type | | ---------------- | ---------------------------------------------- | | `key` | `string` | | `options?` | \{ `query?`: `QueryOptions`\<`T` \| `null`>; } | | `options.query?` | `QueryOptions`\<`T` \| `null`> | ### Returns [#returns] `HostStorageValue`\<`T`> ```ts type HostStorageValue = UseQueryResult & object; ``` ### Type Declaration [#type-declaration] #### remove [#remove] ```ts remove: () => Promise; ``` ##### Returns [#returns-1] `Promise`\<`void`> #### set [#set] ```ts set: (value) => Promise; ``` ##### Parameters [#parameters-1] | Parameter | Type | | --------- | ---- | | `value` | `T` | ##### Returns [#returns-2] `Promise`\<`void`> ### Type Parameters [#type-parameters-1] | Type Parameter | | -------------- | | `T` | ### Vue [#vue] ```ts function useHostStorage(key, options?): HostStorageValue; ``` Product-scoped KV storage: host localStorage inside a container, browser localStorage standalone. JSON-serialized. Writes update the query cache in place under `queryKeys.hostStorage(key)`. ### Type Parameters [#type-parameters-2] | Type Parameter | | -------------- | | `T` | ### Parameters [#parameters-2] | Parameter | Type | | ---------------- | ---------------------------------------------- | | `key` | `MaybeGetter`\<`string`> | | `options?` | \{ `query?`: `QueryOptions`\<`T` \| `null`>; } | | `options.query?` | `QueryOptions`\<`T` \| `null`> | ### Returns [#returns-3] `HostStorageValue`\<`T`> ```ts type HostStorageValue = QueryResult & object; ``` ### Type Declaration [#type-declaration-1] #### remove [#remove-1] ```ts remove: () => Promise; ``` ##### Returns [#returns-4] `Promise`\<`void`> #### set [#set-1] ```ts set: (value) => Promise; ``` ##### Parameters [#parameters-3] | Parameter | Type | | --------- | ---- | | `value` | `T` | ##### Returns [#returns-5] `Promise`\<`void`> ### Type Parameters [#type-parameters-3] | Type Parameter | | -------------- | | `T` | # useIsHost (/docs/host/use-is-host) `useIsHost` is the boolean convenience over [`useHostMode`](/docs/host/use-host-mode): it returns `true` exactly when the mode is `"host"`. Note the asymmetry: it is `false` both while detection is still pending and when the app genuinely runs standalone. That makes it ideal for gating host-only UI (a notifications button, a deep-link menu), which should stay hidden until the host is confirmed. If you need to distinguish "still detecting" from "definitely standalone", use `useHostMode` directly. ## Usage [#usage] ```tsx import { useIsHost } from "@use-truapi/react"; function Toolbar() { const isHost = useIsHost(); return ( ); } ``` ```vue ``` The composable returns a `ComputedRef`: use `isHost.value` in script, plain `isHost` in templates. ## See also [#see-also] * [`useHostMode`](/docs/host/use-host-mode) is the three-state variant for when the pending window matters. * [`useNotifications`](/docs/host/use-notifications) is a host-only API you would typically gate with this hook. ## API reference [#api-reference] ### React [#react] ```ts function useIsHost(): boolean; ``` False while detection is pending — gate host-only UI on `useHostMode()` if the distinction matters. ### Returns [#returns] `boolean` ### Vue [#vue] ```ts function useIsHost(): ComputedRef; ``` False while detection is pending — gate host-only UI on `useHostMode()` if the distinction matters. ### Returns [#returns-1] `ComputedRef`\<`boolean`> # useNotifications (/docs/host/use-notifications) `useNotifications` exposes the host's RFC-0019 notification manager. Call `push(input)` to deliver or schedule a notification; it resolves to a `NotificationId` you can later pass to `cancel(id)` to cancel a pending scheduled notification. `scheduledAt` is a Unix timestamp in milliseconds; omit it for immediate delivery. The push mutation's state lives on the returned object itself: `isPending` while a push is in flight, `data` holds the last id, `error` the last failure, `reset()` clears them. This is host-only: there is no browser fallback, and standalone both `push` and `cancel` reject with `HostUnavailableError`. Gate the whole feature on [`useIsHost`](/docs/host/use-is-host). Failures such as the host's pending-notification cap land in `error`. ## Usage [#usage] ```tsx import { useIsHost, useNotifications } from "@use-truapi/react"; import { useState } from "react"; function AuctionReminder({ endsAt }: { endsAt: bigint }) { const isHost = useIsHost(); const { push, cancel, isPending, error } = useNotifications(); const [id, setId] = useState(null); async function onRemind() { const notificationId = await push({ text: "Auction ends in 10 minutes, place your final bid!", deeplink: "https://auctions.dot/lot/42", scheduledAt: endsAt - 600_000n, }); setId(notificationId); } async function onCancel() { if (id !== null) await cancel(id); setId(null); } if (!isHost) return null; return ( <> {id === null ? ( ) : ( )} {error &&

{error.message}

} ); } ```
```vue ```
For a fire-and-forget immediate notification, swallow the promise; failures still show up in `error`: ```tsx ``` ## See also [#see-also] * [`useIsHost`](/docs/host/use-is-host) hides the feature outside a host. * [`useDevicePermission`](/docs/host/use-device-permission) requests the `"Notifications"` device permission first. * [`useHostNavigate`](/docs/host/use-host-navigate) uses the same deep-link scheme as `deeplink`. ## API reference [#api-reference] ### React [#react] ```ts function useNotifications(): NotificationsApi; ``` RFC-0019 scheduled push notifications (host-only; throws HostUnavailableError standalone). ### Returns [#returns] `NotificationsApi` ### Extends [#extends] * `NamedMutation`\<`NotificationId`, `Parameters`\<`HostController`\[`"pushNotification"`]>\[`0`]> ### Properties [#properties] #### cancel [#cancel] ```ts cancel: (id) => Promise; ``` ##### Parameters [#parameters] | Parameter | Type | | --------- | -------- | | `id` | `number` | ##### Returns [#returns-1] `Promise`\<`void`> *** #### context [#context] ```ts context: unknown; ``` ##### Inherited from [#inherited-from] ```ts NamedMutation.context ``` *** #### data [#data] ```ts data: number | undefined; ``` The last successfully resolved data for the mutation. ##### Inherited from [#inherited-from-1] ```ts NamedMutation.data ``` *** #### error [#error] ```ts error: Error | null; ``` The error object for the mutation, if an error was encountered. * Defaults to `null`. ##### Inherited from [#inherited-from-2] ```ts NamedMutation.error ``` *** #### failureCount [#failurecount] ```ts failureCount: number; ``` ##### Inherited from [#inherited-from-3] ```ts NamedMutation.failureCount ``` *** #### failureReason [#failurereason] ```ts failureReason: Error | null; ``` ##### Inherited from [#inherited-from-4] ```ts NamedMutation.failureReason ``` *** #### isError [#iserror] ```ts isError: boolean; ``` A boolean variable derived from `status`. * `true` if the last mutation attempt resulted in an error. ##### Inherited from [#inherited-from-5] ```ts NamedMutation.isError ``` *** #### isIdle [#isidle] ```ts isIdle: boolean; ``` A boolean variable derived from `status`. * `true` if the mutation is in its initial state prior to executing. ##### Inherited from [#inherited-from-6] ```ts NamedMutation.isIdle ``` *** #### isPaused [#ispaused] ```ts isPaused: boolean; ``` ##### Inherited from [#inherited-from-7] ```ts NamedMutation.isPaused ``` *** #### isPending [#ispending] ```ts isPending: boolean; ``` A boolean variable derived from `status`. * `true` if the mutation is currently executing. ##### Inherited from [#inherited-from-8] ```ts NamedMutation.isPending ``` *** #### isSuccess [#issuccess] ```ts isSuccess: boolean; ``` A boolean variable derived from `status`. * `true` if the last mutation attempt was successful. ##### Inherited from [#inherited-from-9] ```ts NamedMutation.isSuccess ``` *** #### push [#push] ```ts push: (input) => Promise; ``` ##### Parameters [#parameters-1] | Parameter | Type | | --------- | ----------------------------- | | `input` | `HostPushNotificationRequest` | ##### Returns [#returns-2] `Promise`\<`number`> *** #### reset [#reset] ```ts reset: () => void; ``` A function to clean the mutation internal state (i.e., it resets the mutation to its initial state). ##### Returns [#returns-3] `void` ##### Inherited from [#inherited-from-10] ```ts NamedMutation.reset ``` *** #### status [#status] ```ts status: "idle" | "success" | "error" | "pending"; ``` The status of the mutation. * Will be: * `idle` initial status prior to the mutation function executing. * `pending` if the mutation is currently executing. * `error` if the last mutation attempt resulted in an error. * `success` if the last mutation attempt was successful. ##### Inherited from [#inherited-from-11] ```ts NamedMutation.status ``` *** #### submittedAt [#submittedat] ```ts submittedAt: number; ``` ##### Inherited from [#inherited-from-12] ```ts NamedMutation.submittedAt ``` *** #### variables [#variables] ```ts variables: HostPushNotificationRequest | undefined; ``` The variables object passed to the `mutationFn`. ##### Inherited from [#inherited-from-13] ```ts NamedMutation.variables ``` ### Vue [#vue] ```ts function useNotifications(): NotificationsApi; ``` RFC-0019 scheduled push notifications (host-only; throws HostUnavailableError standalone). ### Returns [#returns-4] `NotificationsApi` ### Extends [#extends-1] * `NamedMutation`\<`NotificationId`, `Parameters`\<`HostController`\[`"pushNotification"`]>\[`0`]> ### Properties [#properties-1] #### cancel [#cancel-1] ```ts cancel: (id) => Promise; ``` ##### Parameters [#parameters-2] | Parameter | Type | | --------- | -------- | | `id` | `number` | ##### Returns [#returns-5] `Promise`\<`void`> *** #### context [#context-1] ```ts readonly context: Ref; ``` ##### Inherited from [#inherited-from-14] ```ts NamedMutation.context ``` *** #### data [#data-1] ```ts readonly data: Ref | Ref; ``` The last successfully resolved data for the mutation. ##### Inherited from [#inherited-from-15] ```ts NamedMutation.data ``` *** #### error [#error-1] ```ts readonly error: Ref | Ref; ``` The error object for the mutation, if an error was encountered. * Defaults to `null`. ##### Inherited from [#inherited-from-16] ```ts NamedMutation.error ``` *** #### failureCount [#failurecount-1] ```ts readonly failureCount: Ref; ``` ##### Inherited from [#inherited-from-17] ```ts NamedMutation.failureCount ``` *** #### failureReason [#failurereason-1] ```ts readonly failureReason: Ref; ``` ##### Inherited from [#inherited-from-18] ```ts NamedMutation.failureReason ``` *** #### isError [#iserror-1] ```ts readonly isError: Ref | Ref; ``` A boolean variable derived from `status`. * `true` if the last mutation attempt resulted in an error. ##### Inherited from [#inherited-from-19] ```ts NamedMutation.isError ``` *** #### isIdle [#isidle-1] ```ts readonly isIdle: Ref | Ref; ``` A boolean variable derived from `status`. * `true` if the mutation is in its initial state prior to executing. ##### Inherited from [#inherited-from-20] ```ts NamedMutation.isIdle ``` *** #### isPaused [#ispaused-1] ```ts readonly isPaused: Ref; ``` ##### Inherited from [#inherited-from-21] ```ts NamedMutation.isPaused ``` *** #### isPending [#ispending-1] ```ts readonly isPending: Ref | Ref; ``` A boolean variable derived from `status`. * `true` if the mutation is currently executing. ##### Inherited from [#inherited-from-22] ```ts NamedMutation.isPending ``` *** #### isSuccess [#issuccess-1] ```ts readonly isSuccess: Ref | Ref; ``` A boolean variable derived from `status`. * `true` if the last mutation attempt was successful. ##### Inherited from [#inherited-from-23] ```ts NamedMutation.isSuccess ``` *** #### push [#push-1] ```ts push: (input) => Promise; ``` ##### Parameters [#parameters-3] | Parameter | Type | | --------- | ----------------------------- | | `input` | `HostPushNotificationRequest` | ##### Returns [#returns-6] `Promise`\<`number`> *** #### reset [#reset-1] ```ts reset: () => void; ``` ##### Returns [#returns-7] `void` ##### Inherited from [#inherited-from-24] ```ts NamedMutation.reset ``` *** #### status [#status-1] ```ts readonly status: | Ref<"idle", "idle"> | Ref<"pending", "pending"> | Ref<"error", "error"> | Ref<"success", "success">; ``` The status of the mutation. * Will be: * `idle` initial status prior to the mutation function executing. * `pending` if the mutation is currently executing. * `error` if the last mutation attempt resulted in an error. * `success` if the last mutation attempt was successful. ##### Inherited from [#inherited-from-25] ```ts NamedMutation.status ``` *** #### submittedAt [#submittedat-1] ```ts readonly submittedAt: Ref; ``` ##### Inherited from [#inherited-from-26] ```ts NamedMutation.submittedAt ``` *** #### variables [#variables-1] ```ts readonly variables: | Ref | Ref; ``` The variables object passed to the `mutationFn`. ##### Inherited from [#inherited-from-27] ```ts NamedMutation.variables ``` # usePermission (/docs/host/use-permission) `usePermission` asks the host to grant one RFC-0002 remote permission, such as `ChainSubmit`, `StatementSubmit` or `Remote` domain access. Call `request(permission)`: it resolves to `true` when the host granted and `false` when the user declined. A denial is data, not an exception; thrown errors mean the request itself failed. The hook also returns mutation state (`isPending`, `error`, `data`, `reset`). You rarely need `ChainSubmit`, `StatementSubmit` or `PreimageSubmit` explicitly: the corresponding business hooks request them on first use ([`useTx`](/docs/tx/use-tx) requests `ChainSubmit` on first submit, for example). Reach for `usePermission` to front-load the prompt at a natural moment in your UX, or for permissions with no implicit trigger such as `Remote` domains. This is a host feature: standalone, `request` rejects with `HostUnavailableError`, so gate the affordance on [`useIsHost`](/docs/host/use-is-host). ## Usage [#usage] ```tsx import { useIsHost, usePermission } from "@use-truapi/react"; function EnableApiAccess() { const isHost = useIsHost(); const { request, isPending, error } = usePermission(); async function onEnable() { const granted = await request({ tag: "Remote", value: { domains: ["api.example.com"] }, }); if (!granted) console.warn("user declined API access"); } if (!isHost) return null; return ( <> {error &&

{error.message}

} ); } ```
```vue ```
## See also [#see-also] * [`useDevicePermission`](/docs/host/use-device-permission) covers device capabilities (camera, notifications, clipboard). * [`useResourceAllocation`](/docs/host/use-resource-allocation) batches RFC-0010 allowances into one prompt. * [`useTx`](/docs/tx/use-tx) requests `ChainSubmit` implicitly on first submit. ## API reference [#api-reference] ### React [#react] ```ts function usePermission(options?): NamedMutation & object; ``` RFC-0002 remote permissions (ChainSubmit, StatementSubmit, Remote domains, …): `request(permission)`. ### Parameters [#parameters] | Parameter | Type | | ------------------- | -------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`boolean`, `RemotePermission`>; } | | `options.mutation?` | `MutationOptions`\<`boolean`, `RemotePermission`> | ### Returns [#returns] `NamedMutation`\<`boolean`, `RemotePermission`> & `object` ### Vue [#vue] ```ts function usePermission(options?): NamedMutation & object; ``` RFC-0002 remote permissions (ChainSubmit, StatementSubmit, Remote domains, …): `request(permission)`. ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | -------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`boolean`, `RemotePermission`>; } | | `options.mutation?` | `MutationOptions`\<`boolean`, `RemotePermission`> | ### Returns [#returns-1] `NamedMutation`\<`boolean`, `RemotePermission`> & `object` # useResourceAllocation (/docs/host/use-resource-allocation) `useResourceAllocation` asks the host to pre-allocate RFC-0010 resources for the product. Call `request(resources)` with an array of `AllocatableResource` items (`StatementStoreAllowance`, `AutoSigning` and friends) so several allowances collapse into a single user prompt. It resolves to one `AllocationOutcome` per requested resource, in the same order: `"Allocated"`, `"Rejected"` or `"NotAvailable"`. Per-resource rejection is data, not an exception; thrown errors mean the request itself failed. The hook also returns mutation state (`isPending`, `error`, `data`, `reset`). For the allowance resources pre-allocation is opportunistic: the host may also fulfil the allowance implicitly on the first submission. `AutoSigning` has no implicit path and must be requested explicitly through this hook. This is a host feature: standalone, `request` rejects with `HostUnavailableError`, so gate it on [`useIsHost`](/docs/host/use-is-host). ## Usage [#usage] ```tsx import { useResourceAllocation } from "@use-truapi/react"; function Onboarding({ onDone }: { onDone: () => void }) { const { request, isPending, error } = useResourceAllocation(); async function onSetUp() { const [statements, autoSigning] = await request([ { tag: "StatementStoreAllowance" }, { tag: "AutoSigning" }, ]); if (statements !== "Allocated") console.warn("no statement allowance:", statements); if (autoSigning !== "Allocated") console.warn("auto-signing declined:", autoSigning); onDone(); } return ( <> {error &&

{error.message}

} ); } ```
```vue ```
## See also [#see-also] * [`usePermission`](/docs/host/use-permission) covers one-off RFC-0002 remote permissions. * [`usePublishStatement`](/docs/statements/use-publish-statement) benefits from a pre-allocated `StatementStoreAllowance`. ## API reference [#api-reference] ### React [#react] ```ts function useResourceAllocation(options?): NamedMutation & object; ``` RFC-0010 allowances (StatementStoreAllowance, AutoSigning, …), one prompt up front: `request(resources)`. ### Parameters [#parameters] | Parameter | Type | | ------------------- | --------------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`AllocationOutcome`\[], `AllocatableResource`\[]>; } | | `options.mutation?` | `MutationOptions`\<`AllocationOutcome`\[], `AllocatableResource`\[]> | ### Returns [#returns] `NamedMutation`\<`AllocationOutcome`\[], `AllocatableResource`\[]> & `object` ### Vue [#vue] ```ts function useResourceAllocation(options?): NamedMutation & object; ``` RFC-0010 allowances (StatementStoreAllowance, AutoSigning, …), one prompt up front: `request(resources)`. ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | --------------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`AllocationOutcome`\[], `AllocatableResource`\[]>; } | | `options.mutation?` | `MutationOptions`\<`AllocationOutcome`\[], `AllocatableResource`\[]> | ### Returns [#returns-1] `NamedMutation`\<`AllocationOutcome`\[], `AllocatableResource`\[]> & `object` # useTheme (/docs/host/use-theme) `useTheme` returns the current `ThemeState` and keeps it live. Embedded in a host it follows the container's light or dark setting, including custom host themes, and updates the moment the user switches. Standalone it tracks the `prefers-color-scheme` media query instead; `source` tells you which of the two you are getting, and `custom` is always `null` outside a host. Before host detection resolves the hook reports the system theme, then flips to the host theme once the subscription attaches, so there is always a usable value to render with. ## Usage [#usage] ```tsx import { useTheme } from "@use-truapi/react"; import { useEffect } from "react"; function ThemeSync() { const theme = useTheme(); useEffect(() => { document.documentElement.dataset.theme = theme.variant; }, [theme.variant]); return theme.custom ? Host theme: {theme.custom} : null; } ``` ```vue ``` The composable returns a `ShallowRef`: use `theme.value.variant` in script, plain `theme.variant` in templates. ## See also [#see-also] * [`useHostMode`](/docs/host/use-host-mode) is the detection the theme source follows. ## API reference [#api-reference] ### React [#react] ```ts function useTheme(): ThemeState; ``` Live theme: host theme when embedded, `prefers-color-scheme` standalone. ### Returns [#returns] `ThemeState` ### Vue [#vue] ```ts function useTheme(): ShallowRef; ``` Live theme: host theme when embedded, `prefers-color-scheme` standalone. ### Returns [#returns-1] `ShallowRef`\<`ThemeState`> # usePaymentBalance (/docs/payments/use-payment-balance) `usePaymentBalance` subscribes to the app's RFC-0006 payment balance. `data` is a `PaymentBalance` with the spendable `available` amount as a `bigint` in planck, updated as the host pushes changes. By default the main purse is watched; pass `purse` to watch another product purse. Components watching the same purse share one host subscription, dropped when the last consumer unmounts. Payments are host-only. Standalone the hook surfaces a `HostUnavailableError` on the result instead of loading forever: check `error` (or gate on [`useIsHost`](/docs/host/use-is-host)) and hide payment UI when it fires. ## Usage [#usage] ```tsx import { useFormattedBalance, usePaymentBalance } from "@use-truapi/react"; function PaymentBalance() { const { data: balance, isPending, error } = usePaymentBalance(); const available = useFormattedBalance(balance?.available, { decimals: 10, symbol: "PAS", }); if (error) return

Payments unavailable: {error.message}

; if (isPending) return

Loading balance…

; return

Available: {available}

; } ```
```vue ```
A `PaymentPurseId` is a number selecting one of the product's purses; omit it for the main purse: ```ts const { data } = usePaymentBalance({ purse: 1 }); ``` Values are pushed, not polled. There is nothing to refetch; new values arrive on their own. ## See also [#see-also] * [`useRequestPayment`](/docs/payments/use-request-payment) asks the user to pay; the balance updates when it settles. * [`useTopUp`](/docs/payments/use-top-up) funds the purse from a product account or provided keys. * [`usePaymentStatus`](/docs/payments/use-payment-status) tracks an individual payment to its terminal state. ## API reference [#api-reference] ### React [#react] ```ts function usePaymentBalance(options?): UseQueryResult; ``` Live RFC-0006 payment balance (host-only; errors standalone). ### Parameters [#parameters] | Parameter | Type | | ------------------ | ------------------------------------------------------------------------------------------------------------- | | `options?` | \{ `enabled?`: `boolean`; `purse?`: `number`; `query?`: `QueryOptions`\<`HostPaymentBalanceSubscribeItem`>; } | | `options.enabled?` | `boolean` | | `options.purse?` | `number` | | `options.query?` | `QueryOptions`\<`HostPaymentBalanceSubscribeItem`> | ### Returns [#returns] `UseQueryResult`\<`HostPaymentBalanceSubscribeItem`, `Error`> ### Vue [#vue] ```ts function usePaymentBalance(options?): QueryResult; ``` Live RFC-0006 payment balance (host-only; errors standalone). ### Parameters [#parameters-1] | Parameter | Type | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------- | | `options?` | \{ `enabled?`: `MaybeGetter`\<`boolean`>; `purse?`: `number`; `query?`: `QueryOptions`\<`HostPaymentBalanceSubscribeItem`>; } | | `options.enabled?` | `MaybeGetter`\<`boolean`> | | `options.purse?` | `number` | | `options.query?` | `QueryOptions`\<`HostPaymentBalanceSubscribeItem`> | ### Returns [#returns-1] `QueryResult`\<`HostPaymentBalanceSubscribeItem`> # usePaymentStatus (/docs/payments/use-payment-status) `usePaymentStatus` subscribes to the status of a single RFC-0006 payment, the `id` resolved by [`useRequestPayment`](/docs/payments/use-request-payment). `data` is a `PaymentStatus` tagged union: `"Processing"` while in flight, then the terminal `"Completed"` or `"Failed"` (which carries a human-readable `reason`). While `paymentId` is `undefined` the hook stays disabled, so you can pass the id straight from your request-payment state without guarding; the subscription attaches the moment an id exists, and is shared per payment id. Payments are host-only: standalone (with an id set) the hook surfaces a `HostUnavailableError` on the result. ## Usage [#usage] ```tsx import { usePaymentStatus } from "@use-truapi/react"; function PaymentTracker({ paymentId }: { paymentId: string | undefined }) { const { data: status, error } = usePaymentStatus(paymentId); if (!paymentId) return null; if (error) return

{error.message}

; switch (status?.tag) { case "Processing": return

Payment processing…

; case "Completed": return

Payment completed ✓

; case "Failed": return

Payment failed: {status.value.reason}

; default: return

Waiting for status…

; } } ```
```vue ``` Pass the id as a getter (`() => props.paymentId`): the subscription attaches when the id appears and re-attaches when it changes.
Statuses are pushed, not polled. There is nothing to refetch. ## See also [#see-also] * [`useRequestPayment`](/docs/payments/use-request-payment) yields the payment id this hook consumes. * [`usePaymentBalance`](/docs/payments/use-payment-balance) is the balance a completed payment settles against. ## API reference [#api-reference] ### React [#react] ```ts function usePaymentStatus(paymentId, options?): UseQueryResult; ``` Track a payment to its terminal state (Processing → Completed | Failed). ### Parameters [#parameters] | Parameter | Type | | ---------------- | ----------------------------------------------------------------- | | `paymentId` | `string` \| `undefined` | | `options?` | \{ `query?`: `QueryOptions`\<`HostPaymentStatusSubscribeItem`>; } | | `options.query?` | `QueryOptions`\<`HostPaymentStatusSubscribeItem`> | ### Returns [#returns] `UseQueryResult`\<`HostPaymentStatusSubscribeItem`, `Error`> ### Vue [#vue] ```ts function usePaymentStatus(paymentId, options?): QueryResult; ``` Track a payment to its terminal state (Processing → Completed | Failed). ### Parameters [#parameters-1] | Parameter | Type | | ---------------- | ----------------------------------------------------------------- | | `paymentId` | `MaybeGetter`\<`string` \| `undefined`> | | `options?` | \{ `query?`: `QueryOptions`\<`HostPaymentStatusSubscribeItem`>; } | | `options.query?` | `QueryOptions`\<`HostPaymentStatusSubscribeItem`> | ### Returns [#returns-1] `QueryResult`\<`HostPaymentStatusSubscribeItem`> # useRequestPayment (/docs/payments/use-request-payment) `useRequestPayment` requests an RFC-0006 payment from the user. Call `request(amount, destination, from?)`: your app never renders a confirmation dialog, the host shows its own payment UI and the user approves or rejects there. The promise resolves an `{ id }` you can hand straight to [`usePaymentStatus`](/docs/payments/use-payment-status); a rejection or failure surfaces as the mutation's `error`. Payments are host-only: standalone the call rejects with `HostUnavailableError`. ## Usage [#usage] A complete pay-button flow: request the payment, track its status live, and show the purse balance updating as it settles. ```tsx import { useFormattedBalance, usePaymentBalance, usePaymentStatus, useRequestPayment, } from "@use-truapi/react"; import { useState } from "react"; function PayButton({ price, merchant }: { price: bigint; merchant: `0x${string}` }) { const [paymentId, setPaymentId] = useState(); const { request, isPending, error } = useRequestPayment(); const { data: status } = usePaymentStatus(paymentId); const { data: balance } = usePaymentBalance(); const available = useFormattedBalance(balance?.available, { decimals: 10, symbol: "PAS", }); async function onPay() { const { id } = await request(price, merchant); setPaymentId(id); } return ( <> {error &&

{error.message}

} {status?.tag === "Processing" &&

Payment processing…

} {status?.tag === "Completed" &&

Paid ✓

} {status?.tag === "Failed" &&

Failed: {status.value.reason}

}

Balance: {available}

); } ``` A rejected request throws out of `request`, so an async handler like `onPay` should be the only place that awaits it; the same error also lands in `error`.
```vue ``` Pass the payment id to `usePaymentStatus` as a getter (`() => paymentId.value`) so the status subscription attaches once the id exists.
To pay from a non-main purse, pass its `PaymentPurseId` as the third argument: ```ts const { id } = await request(amount, destination, 1); ``` ## See also [#see-also] * [`usePaymentStatus`](/docs/payments/use-payment-status) tracks the resolved id to Completed or Failed. * [`usePaymentBalance`](/docs/payments/use-payment-balance) is the live purse balance. * [`useTopUp`](/docs/payments/use-top-up) funds the purse before requesting payments. ## API reference [#api-reference] ### React [#react] ```ts function useRequestPayment(options?): NamedMutation<{ id: string; }, RequestPaymentVariables> & object; ``` Request a payment from the user — the host shows the confirmation UI: `request(amount, destination)`. ### Parameters [#parameters] | Parameter | Type | | ------------------- | -------------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<\{ `id`: `string`; }, `RequestPaymentVariables`>; } | | `options.mutation?` | `MutationOptions`\<\{ `id`: `string`; }, `RequestPaymentVariables`> | ### Returns [#returns] `NamedMutation`\<\{ `id`: `string`; }, `RequestPaymentVariables`> & `object` ### Properties [#properties] #### amount [#amount] ```ts amount: bigint; ``` *** #### destination [#destination] ```ts destination: `0x${string}`; ``` *** #### from? [#from] ```ts optional from?: number; ``` ### Vue [#vue] ```ts function useRequestPayment(options?): NamedMutation<{ id: string; }, RequestPaymentVariables> & object; ``` Request a payment from the user — the host shows the confirmation UI: `request(amount, destination)`. ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | -------------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<\{ `id`: `string`; }, `RequestPaymentVariables`>; } | | `options.mutation?` | `MutationOptions`\<\{ `id`: `string`; }, `RequestPaymentVariables`> | ### Returns [#returns-1] `NamedMutation`\<\{ `id`: `string`; }, `RequestPaymentVariables`> & `object` ### Properties [#properties-1] #### amount [#amount-1] ```ts amount: bigint; ``` *** #### destination [#destination-1] ```ts destination: `0x${string}`; ``` *** #### from? [#from-1] ```ts optional from?: number; ``` # useTopUp (/docs/payments/use-top-up) `useTopUp` moves funds into the app's RFC-0006 payment purse. Call `topUp(amount, source, into?)`; the promise resolves once the host has performed the top-up. There is no return value to inspect: watch [`usePaymentBalance`](/docs/payments/use-payment-balance) to see the new funds land. `source` is a `PaymentTopUpSource`, a tagged union naming where the funds come from: `"ProductAccount"` (one of the product's scoped accounts, by `derivationIndex`), `"PrivateKey"` (a one-time account by its sr25519 secret key) or `"Coins"` (coin secret keys, one per coin). Payments are host-only: standalone the call rejects with `HostUnavailableError`. ## Usage [#usage] ```tsx import { useTopUp } from "@use-truapi/react"; function TopUpButton({ amount }: { amount: bigint }) { const { topUp, isPending, isSuccess, error } = useTopUp(); function onTopUp() { void topUp(amount, { tag: "ProductAccount", value: { derivationIndex: 0 }, }).catch(() => {}); } return ( <> {isSuccess &&

Purse funded ✓

} {error &&

{error.message}

} ); } ```
```vue ```
The handlers above are fire-and-forget: the empty `catch` silences the rejected promise, and the failure still shows up in `error`. To fund a non-main purse, pass its `PaymentPurseId` as the third argument: ```ts await topUp(amount, source, 1); ``` ## See also [#see-also] * [`usePaymentBalance`](/docs/payments/use-payment-balance) watches the purse fill up. * [`useRequestPayment`](/docs/payments/use-request-payment) spends from the purse. ## API reference [#api-reference] ### React [#react] ```ts function useTopUp(options?): NamedMutation & object; ``` Top up the payment balance from a product account or provided keys: `topUp(amount, source)`. ### Parameters [#parameters] | Parameter | Type | | ------------------- | --------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`void`, `TopUpVariables`>; } | | `options.mutation?` | `MutationOptions`\<`void`, `TopUpVariables`> | ### Returns [#returns] `NamedMutation`\<`void`, `TopUpVariables`> & `object` ### Properties [#properties] #### amount [#amount] ```ts amount: bigint; ``` *** #### into? [#into] ```ts optional into?: number; ``` *** #### source [#source] ```ts source: PaymentTopUpSource; ``` ### Vue [#vue] ```ts function useTopUp(options?): NamedMutation & object; ``` Top up the payment balance from a product account or provided keys: `topUp(amount, source)`. ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | --------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`void`, `TopUpVariables`>; } | | `options.mutation?` | `MutationOptions`\<`void`, `TopUpVariables`> | ### Returns [#returns-1] `NamedMutation`\<`void`, `TopUpVariables`> & `object` ### Properties [#properties-1] #### amount [#amount-1] ```ts amount: bigint; ``` *** #### into? [#into-1] ```ts optional into?: number; ``` *** #### source [#source-1] ```ts source: PaymentTopUpSource; ``` # usePublishStatement (/docs/statements/use-publish-statement) `usePublishStatement` publishes ephemeral JSON payloads to your app's statement-store topic. Call `publish(data, options?)` with any JSON-serializable value; subscribers on the same topic (see [`useStatements`](/docs/statements/use-statements)) receive it within moments. Mutation state (`data`, `error`, `isPending`, `reset`) tracks the last publish. The resolved `boolean` is data, not an exception: `true` means the host's statement store accepted the statement, `false` means it was undeliverable (the store rejected it, or the app runs standalone). Thrown errors are reserved for real failures, such as a payload whose JSON encoding exceeds the 512 byte statement limit. ## Usage [#usage] A publish and subscribe round trip: every mounted `PingBoard` sees every ping, including its own. ```tsx import { usePublishStatement, useStatements } from "@use-truapi/react"; interface Ping { from: string; sentAt: number; } function PingBoard({ name }: { name: string }) { const { data: pings } = useStatements(); const { publish, isPending, error } = usePublishStatement(); async function onPing() { const delivered = await publish({ from: name, sentAt: Date.now() }); if (!delivered) console.warn("ping not delivered (rejected or standalone)"); } return ( <> {error &&

{error.message}

}
    {pings?.map((p, i) => (
  • {p.data.from} pinged at {new Date(p.data.sentAt).toLocaleTimeString()}
  • ))}
); } ```
```vue ```
The second argument forwards `PublishOptions` to the store: scope with `topic2` (subscribers only see it if they filter on the same value) or override the time-to-live with `ttlSeconds`: ```ts await publish( { from: "alice", sentAt: Date.now() }, { topic2: "room-42", ttlSeconds: 60 }, ); ``` In a fire-and-forget handler, `void publish(data).catch(() => {})` is enough; failures also land in `error`. ## See also [#see-also] * [`useStatements`](/docs/statements/use-statements) subscribes to what you (and everyone else) publish. * [`useStatementChannel`](/docs/statements/use-statement-channel) is the better fit for last-write-wins values. ## API reference [#api-reference] ### React [#react] ```ts function usePublishStatement(options?): NamedMutation> & object; ``` Publish JSON payloads (≤512 bytes) to the app topic: `publish(data)`. Resolves `false` when the store rejects the statement or the app runs standalone. ### Type Parameters [#type-parameters] | Type Parameter | Default type | | -------------- | ------------ | | `T` | `unknown` | ### Parameters [#parameters] | Parameter | Type | | ------------------- | ----------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`boolean`, `PublishStatementVariables`\<`T`>>; } | | `options.mutation?` | `MutationOptions`\<`boolean`, `PublishStatementVariables`\<`T`>> | ### Returns [#returns] `NamedMutation`\<`boolean`, `PublishStatementVariables`\<`T`>> & `object` ### Type Parameters [#type-parameters-1] | Type Parameter | | -------------- | | `T` | ### Properties [#properties] #### data [#data] ```ts data: T; ``` *** #### options? [#options] ```ts optional options?: PublishOptions; ``` ### Vue [#vue] ```ts function usePublishStatement(options?): NamedMutation> & object; ``` Publish JSON payloads (≤512 bytes) to the app topic: `publish(data)`. Resolves `false` when the store rejects the statement or the app runs standalone. ### Type Parameters [#type-parameters-2] | Type Parameter | Default type | | -------------- | ------------ | | `T` | `unknown` | ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | ----------------------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`boolean`, `PublishStatementVariables`\<`T`>>; } | | `options.mutation?` | `MutationOptions`\<`boolean`, `PublishStatementVariables`\<`T`>> | ### Returns [#returns-1] `NamedMutation`\<`boolean`, `PublishStatementVariables`\<`T`>> & `object` ### Type Parameters [#type-parameters-3] | Type Parameter | | -------------- | | `T` | ### Properties [#properties-1] #### data [#data-1] ```ts data: T; ``` *** #### options? [#options-1] ```ts optional options?: PublishOptions; ``` # useStatementChannel (/docs/statements/use-statement-channel) `useStatementChannel` layers last-write-wins channels on top of the statement store. Where [`useStatements`](/docs/statements/use-statements) accumulates a feed, a channel keeps exactly one value per channel name: a newer write replaces the previous one. Use it for presence, live cursors, typing indicators and other state where only the latest value matters. Keys in `values` are hex-encoded channel hashes, not the names you wrote to, so put identifying fields (peer id, user name) in the payload and render from those. A `timestamp` field in the payload drives last-write-wins ordering; when omitted, the current time is filled in on write. Channels are host-only. Standalone, `ready` stays `false`, `values` stays empty, and `write` resolves `false`. ## Usage [#usage] A typed presence channel: each client heartbeats its own channel on an interval and renders everyone's latest announcement from the `values` map. ```tsx import { useStatementChannel } from "@use-truapi/react"; import { useEffect } from "react"; interface Presence { peerId: string; name: string; timestamp: number; } function WhoIsOnline({ peerId, name }: { peerId: string; name: string }) { const { values, write, ready } = useStatementChannel(); useEffect(() => { if (!ready) return; const announce = () => write(`presence/${peerId}`, { peerId, name, timestamp: Date.now() }); void announce(); const interval = setInterval(announce, 10_000); return () => clearInterval(interval); }, [ready, write, peerId, name]); if (!ready) return

Presence unavailable (running standalone).

; return (
    {[...values.values()].map((peer) => (
  • {peer.name}, seen {new Date(peer.timestamp).toLocaleTimeString()}
  • ))}
); } ```
```vue ``` In Vue, `values` and `ready` are refs: access them with `.value` in script and directly in templates.
Channels live under your app topic; pass `topic2` to partition them further. Two components with different `topic2` values see fully independent channel maps: ```ts const cursors = useStatementChannel({ topic2: `doc-${docId}` }); ``` ## See also [#see-also] * [`useStatements`](/docs/statements/use-statements) is an accumulating feed instead of one value per channel. * [`usePublishStatement`](/docs/statements/use-publish-statement) for one-off publishes. ## API reference [#api-reference] ### React [#react] ```ts function useStatementChannel(options?): StatementChannel; ``` Last-write-wins channels (presence, live cursors, ephemeral app state). `ready` stays false standalone. ### Type Parameters [#type-parameters] | Type Parameter | | ---------------------- | | `T` *extends* `object` | ### Parameters [#parameters] | Parameter | Type | | ----------------- | ------------------------- | | `options?` | \{ `topic2?`: `string`; } | | `options.topic2?` | `string` | ### Returns [#returns] `StatementChannel`\<`T`> ### Type Parameters [#type-parameters-1] | Type Parameter | | -------------- | | `T` | ### Properties [#properties] #### ready [#ready] ```ts ready: boolean; ``` *** #### values [#values] ```ts values: ReadonlyMap; ``` Latest value per channel name (last-write-wins). *** #### write [#write] ```ts write: (channelName, value) => Promise; ``` ##### Parameters [#parameters-1] | Parameter | Type | | ------------- | -------- | | `channelName` | `string` | | `value` | `T` | ##### Returns [#returns-1] `Promise`\<`boolean`> ### Vue [#vue] ```ts function useStatementChannel(options?): StatementChannel; ``` Last-write-wins channels (presence, live cursors, ephemeral app state). `ready` stays false standalone. ### Type Parameters [#type-parameters-2] | Type Parameter | | ---------------------- | | `T` *extends* `object` | ### Parameters [#parameters-2] | Parameter | Type | | ----------------- | ------------------------- | | `options?` | \{ `topic2?`: `string`; } | | `options.topic2?` | `string` | ### Returns [#returns-2] `StatementChannel`\<`T`> ### Type Parameters [#type-parameters-3] | Type Parameter | | -------------- | | `T` | ### Properties [#properties-1] #### ready [#ready-1] ```ts ready: Ref; ``` *** #### values [#values-1] ```ts values: Ref>; ``` Latest value per channel name (last-write-wins). *** #### write [#write-1] ```ts write: (channelName, value) => Promise; ``` ##### Parameters [#parameters-3] | Parameter | Type | | ------------- | -------- | | `channelName` | `string` | | `value` | `T` | ##### Returns [#returns-3] `Promise`\<`boolean`> # useStatements (/docs/statements/use-statements) `useStatements` subscribes to ephemeral statements on your app's topic and accumulates them newest-last as a query result. Each entry is a `ReceivedStatement`: the JSON-decoded `data` payload (typed by the generic) plus metadata like `signerHex`, `topics` and `expiry`. The list is bounded: past `limit` (default `500`) the oldest entries are dropped. `clear()` empties the cached list without touching the subscription. Components watching the same `topic2` share a single statement-store subscription, detached when the last consumer unmounts. Statements are host-only. Standalone the hook is empty and inert: `data` settles to `[]`, nothing arrives, and no error is raised. ## Usage [#usage] ```tsx import { useStatements } from "@use-truapi/react"; interface Reaction { emoji: string; author: string; } function ReactionFeed() { const { data: reactions, clear } = useStatements(); return ( <>
    {reactions?.map((statement, i) => (
  • {statement.data.emoji} from {statement.data.author}
  • ))}
); } ```
```vue ```
Pass `topic2` to narrow the subscription to a room or document; only statements published with the same secondary topic arrive. In Vue it accepts a getter, so the subscription re-attaches when the value changes: ```ts // React const { data } = useStatements({ topic2: `room-${roomId}` }); // Vue const { data } = useStatements({ topic2: () => `room-${roomId.value}` }); ``` Statements are pushed, not polled. There is nothing to refetch. ## See also [#see-also] * [`usePublishStatement`](/docs/statements/use-publish-statement) is the publish side of the round trip. * [`useStatementChannel`](/docs/statements/use-statement-channel) keeps one value per channel instead of a feed. ## API reference [#api-reference] ### React [#react] ```ts function useStatements(options?): LiveListQueryResult>; ``` Live statements matching the app topic (and optional `topic2`), accumulated newest-last in the query cache. Empty and inert standalone. ### Type Parameters [#type-parameters] | Type Parameter | | -------------- | | `T` | ### Parameters [#parameters] | Parameter | Type | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------- | | `options?` | \{ `enabled?`: `boolean`; `limit?`: `number`; `query?`: `QueryOptions`\<`ReceivedStatement`\<`T`>\[]>; `topic2?`: `string`; } | | `options.enabled?` | `boolean` | | `options.limit?` | `number` | | `options.query?` | `QueryOptions`\<`ReceivedStatement`\<`T`>\[]> | | `options.topic2?` | `string` | ### Returns [#returns] `LiveListQueryResult`\<`ReceivedStatement`\<`T`>> ### Vue [#vue] ```ts function useStatements(options?): LiveListQueryResult>; ``` Live statements matching the app topic (and optional `topic2`), accumulated newest-last in the query cache. Empty and inert standalone. ### Type Parameters [#type-parameters-1] | Type Parameter | | -------------- | | `T` | ### Parameters [#parameters-1] | Parameter | Type | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `options?` | \{ `enabled?`: `MaybeGetter`\<`boolean`>; `limit?`: `number`; `query?`: `QueryOptions`\<`ReceivedStatement`\<`T`>\[]>; `topic2?`: `MaybeGetter`\<`string` \| `undefined`>; } | | `options.enabled?` | `MaybeGetter`\<`boolean`> | | `options.limit?` | `number` | | `options.query?` | `QueryOptions`\<`ReceivedStatement`\<`T`>\[]> | | `options.topic2?` | `MaybeGetter`\<`string` \| `undefined`> | ### Returns [#returns-1] `LiveListQueryResult`\<`ReceivedStatement`\<`T`>> # useCid (/docs/storage/use-cid) `useCid` fetches the content behind a CID via the host's preimage lookup. `data` is a `Uint8Array` by default; set `json: true` to parse the bytes as JSON instead, and type the result with the generic: `useCid(cid, { json: true })` yields `data` typed as `Note`. While `cid` is `undefined` the query stays disabled (`isPending` with no fetch), so you can pass the CID straight from an upload receipt or route params without guarding. Results are cached per (cid, json) pair, and since CID content is immutable the cache never goes stale. This is the read-side companion of [`useUpload`](/docs/storage/use-upload): upload bytes, keep `result.cid`, fetch it back anywhere with `useCid`. ## Usage [#usage] Fetching back a JSON document stored with `useUpload`: ```tsx import { useCid } from "@use-truapi/react"; interface Note { title: string; body: string } function StoredNote({ cid }: { cid?: string }) { const note = useCid(cid, { json: true }); if (!cid) return

No note stored yet.

; if (note.isPending) return

Fetching {cid}…

; if (note.error) return

Failed to fetch: {note.error.message}

; return (

{note.data.title}

{note.data.body}

); } ```
```vue ``` Pass a getter (`() => props.cid`) so the fetch re-runs when the CID changes.
Without `json`, `data` is the raw `Uint8Array`; decode it yourself: ```ts const fetched = useCid(cid); const text = fetched.data ? new TextDecoder().decode(fetched.data) : undefined; ``` ## See also [#see-also] * [`useUpload`](/docs/storage/use-upload) stores bytes and returns the CID this hook fetches. * [`useStorageAuthorization`](/docs/storage/use-storage-authorization) reports the account's storage quota. ## API reference [#api-reference] ### React [#react] ```ts function useCid(cid, options?): UseQueryResult; ``` Fetch CID content (host preimage lookup). Set `json` to parse. ### Type Parameters [#type-parameters] | Type Parameter | Default type | | -------------- | -------------------------------- | | `T` | `Uint8Array`\<`ArrayBufferLike`> | ### Parameters [#parameters] | Parameter | Type | | ---------------- | -------------------------------------------------------- | | `cid` | `string` \| `undefined` | | `options?` | \{ `json?`: `boolean`; `query?`: `QueryOptions`\<`T`>; } | | `options.json?` | `boolean` | | `options.query?` | `QueryOptions`\<`T`> | ### Returns [#returns] `UseQueryResult`\<`T`, `Error`> ### Vue [#vue] ```ts function useCid(cid, options?): QueryResult; ``` Fetch CID content (host preimage lookup). Set `json` to parse. ### Type Parameters [#type-parameters-1] | Type Parameter | Default type | | -------------- | -------------------------------- | | `T` | `Uint8Array`\<`ArrayBufferLike`> | ### Parameters [#parameters-1] | Parameter | Type | | ---------------- | -------------------------------------------------------- | | `cid` | `MaybeGetter`\<`string` \| `undefined`> | | `options?` | \{ `json?`: `boolean`; `query?`: `QueryOptions`\<`T`>; } | | `options.json?` | `boolean` | | `options.query?` | `QueryOptions`\<`T`> | ### Returns [#returns-1] `QueryResult`\<`T`> # useStorageAuthorization (/docs/storage/use-storage-authorization) `useStorageAuthorization` checks whether an account is authorized to store data on the Bulletin chain and how much quota remains. `data` is an `AuthorizationStatus`: `authorized`, plus the remaining transactions, the remaining bytes and the expiration block (all zero when not authorized). With no argument the hook checks the selected account; pass an address to check any other account. The result is cached per address. If neither an address nor a selected account is available the query errors, so gate the component behind a connect step or render `error.message`. Use it to warn before an [`useUpload`](/docs/storage/use-upload) that would fail: an unauthorized account or exhausted quota rejects the upload on chain. ## Usage [#usage] ```tsx import { useStorageAuthorization } from "@use-truapi/react"; function StorageQuota() { const { data: status, isPending, error } = useStorageAuthorization(); if (isPending) return

Checking quota…

; if (error) return

Quota check failed: {error.message}

; if (!status.authorized) return

This account is not authorized to store data.

; return (

Quota: {status.remainingTransactions} uploads /{" "} {status.remainingBytes.toString()} bytes, until block {status.expiration}

); } ```
```vue ``` Pass a getter (`() => account.value?.address`) when checking a reactive address, so the check re-runs when the value changes.
To check an account other than the selected one, pass its address: ```ts const status = useStorageAuthorization("5Grw…utQY"); ``` ## See also [#see-also] * [`useUpload`](/docs/storage/use-upload) performs the uploads this quota governs. * [`useCid`](/docs/storage/use-cid) fetches stored content back by CID. ## API reference [#api-reference] ### React [#react] ```ts function useStorageAuthorization(address?, options?): UseQueryResult; ``` Storage quota/authorization for the selected (or given) account. ### Parameters [#parameters] | Parameter | Type | | ---------------- | ------------------------------------------------------ | | `address?` | `string` | | `options?` | \{ `query?`: `QueryOptions`\<`AuthorizationStatus`>; } | | `options.query?` | `QueryOptions`\<`AuthorizationStatus`> | ### Returns [#returns] `UseQueryResult`\<`AuthorizationStatus`, `Error`> ### Vue [#vue] ```ts function useStorageAuthorization(address?, options?): QueryResult; ``` Storage quota/authorization for the selected (or given) account. ### Parameters [#parameters-1] | Parameter | Type | | ---------------- | ------------------------------------------------------ | | `address?` | `MaybeGetter`\<`string` \| `undefined`> | | `options?` | \{ `query?`: `QueryOptions`\<`AuthorizationStatus`>; } | | `options.query?` | `QueryOptions`\<`AuthorizationStatus`> | ### Returns [#returns-1] `QueryResult`\<`AuthorizationStatus`> # useUpload (/docs/storage/use-upload) `useUpload` stores raw bytes in Bulletin-backed cloud storage. Call `upload(data, options?)` with a `Uint8Array`; it resolves with a `StoreResult` receipt whose `cid` you later pass to [`useCid`](/docs/storage/use-cid) to read the content back. The receipt also carries `size` and, when known, the `blockNumber` and `extrinsicIndex` of the storing extrinsic. Mutation state (`data`, `error`, `isPending`, `reset`) tracks the last upload. Uploads are signed: the storage client is created lazily on first use and signs with the connected account, so the user must connect before the first upload (it rejects with a clear error otherwise). The app config must include `cloudStorage: { environment }`, or every storage hook rejects with a config error. ## Usage [#usage] Uploading a JSON document: encode it to bytes with `TextEncoder`, keep the returned CID to read it back. ```tsx import { useCid, useUpload } from "@use-truapi/react"; import { useState } from "react"; interface Note { title: string; body: string } function NoteUploader() { const [cid, setCid] = useState(undefined); const upload = useUpload({ mutation: { onSuccess: (result) => setCid(result.cid?.toString()) }, }); const stored = useCid(cid, { json: true }); function onSave(note: Note) { const data = new TextEncoder().encode(JSON.stringify(note)); void upload.upload(data).catch(() => {}); // failures land in upload.error } return ( <> {upload.error &&

{upload.error.message}

} {cid &&

Stored as {cid}, reads back: {stored.data?.title ?? "fetching…"}

} ); } ```
```vue ```
Large payloads can be chunked, with per-chunk progress reported through `onProgress`; both are per-upload options: ```ts const result = await upload.upload(fileBytes, { chunkSize: 256 * 1024, onProgress: (event) => console.log("upload progress", event), }); ``` ## See also [#see-also] * [`useCid`](/docs/storage/use-cid) fetches the stored content back by CID. * [`useStorageAuthorization`](/docs/storage/use-storage-authorization) checks the account's remaining quota before uploading. ## API reference [#api-reference] ### React [#react] ```ts function useUpload(options?): NamedMutation & object; ``` Upload bytes to Bulletin-backed cloud storage: `upload(data)` → CID receipt. ### Parameters [#parameters] | Parameter | Type | | ------------------- | ----------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`StoreResult`, `UploadVariables`>; } | | `options.mutation?` | `MutationOptions`\<`StoreResult`, `UploadVariables`> | ### Returns [#returns] `NamedMutation`\<`StoreResult`, `UploadVariables`> & `object` ### Vue [#vue] ```ts function useUpload(options?): NamedMutation & object; ``` Upload bytes to Bulletin-backed cloud storage: `upload(data)` → CID receipt. ### Parameters [#parameters-1] | Parameter | Type | | ------------------- | ----------------------------------------------------------------------- | | `options?` | \{ `mutation?`: `MutationOptions`\<`StoreResult`, `UploadVariables`>; } | | `options.mutation?` | `MutationOptions`\<`StoreResult`, `UploadVariables`> | ### Returns [#returns-1] `NamedMutation`\<`StoreResult`, `UploadVariables`> & `object` # useBatchTx (/docs/tx/use-batch-tx) `useBatchTx` is [`useTx`](/docs/tx/use-tx) for multiple calls at once: your `build` callback returns an array of calls built against the typed api, and `submit(build, options?)` wraps them in a single `Utility.batch_all` transaction. One signature, one fee payment, atomic by default. The lifecycle matches `useTx`, including `phase`: `"idle" → "signing" → "broadcasting" → "in-block" → "finalized" | "error"`. On first submit the hook requests the `ChainSubmit` permission and connects the signer if needed. The resolved `TxResult` reports dispatch failures as data: check `result.ok`. In the default atomic mode a single failing call reverts the whole batch and `dispatchError` carries the reason. ## Usage [#usage] ```tsx import { useBatchTx } from "@use-truapi/react"; import { MultiAddress } from "@polkadot-api/descriptors"; function PaySplit({ recipients, value }: { recipients: string[]; value: bigint }) { const { submit, phase, isPending, error } = useBatchTx(); async function onPayAll() { const result = await submit((api) => recipients.map((dest) => api.tx.Balances.transfer_keep_alive({ dest: MultiAddress.Id(dest), value, }), ), ); if (!result.ok) console.error("batch reverted", result.dispatchError); } return ( <> {error &&

{error.message}

} ); } ```
```vue ```
The calls don't have to target the same pallet; anything buildable against the typed api can go in the array: ```ts const result = await submit((api) => [ api.tx.Balances.transfer_keep_alive({ dest: MultiAddress.Id(dest), value }), api.tx.System.remark({ remark: Binary.fromText("paid invoice #42") }), ]); ``` `submit` accepts all of `useTx`'s submit options (`waitFor`, `onStatus`) plus `mode`, which picks the `Utility` wrapper: * `"batch_all"` (default): atomic, if any call fails all calls revert. * `"batch"`: stops at the first failure, earlier calls stay applied. * `"force_batch"`: keeps executing the remaining calls after failures. ```ts const result = await submit(buildCalls, { mode: "force_batch", waitFor: "finalized", }); ``` To build against a specific chain, pass its key; the `build` callback then receives that chain's typed api: ```ts const { submit } = useBatchTx({ chain: "people" }); ``` `reset()` clears the mutation state and returns `phase` to `"idle"`. ## See also [#see-also] * [`useTx`](/docs/tx/use-tx) submits a single transaction with the same lifecycle. ## API reference [#api-reference] ### React [#react] ```ts function useBatchTx(options?): UseBatchTxResult; ``` Like `useTx` but wraps the built calls in `Utility.batch_all` (atomic by default). ### Type Parameters [#type-parameters] | Type Parameter | Default type | | ---------------------- | ------------ | | `K` *extends* `string` | `string` | ### Parameters [#parameters] | Parameter | Type | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `options?` | \{ `chain?`: `K`; `mutation?`: `MutationOptions`\<`TxResult`, `TxVariables`\<`K`, `BatchableCall`\[], `SubmitOptions` & `object`>>; } | | `options.chain?` | `K` | | `options.mutation?` | `MutationOptions`\<`TxResult`, `TxVariables`\<`K`, `BatchableCall`\[], `SubmitOptions` & `object`>> | ### Returns [#returns] `UseBatchTxResult`\<`K`> ### Extends [#extends] * `Omit`\<`NamedMutation`\<`TxResult`, `TxVariables`\<`K`, `AnyBatchCall`\[], `SubmitOptions` & `object`>>, `"reset"`> ### Type Parameters [#type-parameters-1] | Type Parameter | | ------------------------ | | `K` *extends* `ChainKey` | ### Properties [#properties] #### context [#context] ```ts context: unknown; ``` ##### Inherited from [#inherited-from] ```ts Omit.context ``` *** #### data [#data] ```ts data: TxResult | undefined; ``` The last successfully resolved data for the mutation. ##### Inherited from [#inherited-from-1] ```ts Omit.data ``` *** #### error [#error] ```ts error: Error | null; ``` The error object for the mutation, if an error was encountered. * Defaults to `null`. ##### Inherited from [#inherited-from-2] ```ts Omit.error ``` *** #### failureCount [#failurecount] ```ts failureCount: number; ``` ##### Inherited from [#inherited-from-3] ```ts Omit.failureCount ``` *** #### failureReason [#failurereason] ```ts failureReason: Error | null; ``` ##### Inherited from [#inherited-from-4] ```ts Omit.failureReason ``` *** #### isError [#iserror] ```ts isError: boolean; ``` A boolean variable derived from `status`. * `true` if the last mutation attempt resulted in an error. ##### Inherited from [#inherited-from-5] ```ts Omit.isError ``` *** #### isIdle [#isidle] ```ts isIdle: boolean; ``` A boolean variable derived from `status`. * `true` if the mutation is in its initial state prior to executing. ##### Inherited from [#inherited-from-6] ```ts Omit.isIdle ``` *** #### isPaused [#ispaused] ```ts isPaused: boolean; ``` ##### Inherited from [#inherited-from-7] ```ts Omit.isPaused ``` *** #### isPending [#ispending] ```ts isPending: boolean; ``` A boolean variable derived from `status`. * `true` if the mutation is currently executing. ##### Inherited from [#inherited-from-8] ```ts Omit.isPending ``` *** #### isSuccess [#issuccess] ```ts isSuccess: boolean; ``` A boolean variable derived from `status`. * `true` if the last mutation attempt was successful. ##### Inherited from [#inherited-from-9] ```ts Omit.isSuccess ``` *** #### phase [#phase] ```ts phase: TxPhase; ``` *** #### reset [#reset] ```ts reset: () => void; ``` ##### Returns [#returns-1] `void` *** #### status [#status] ```ts status: "idle" | "success" | "error" | "pending"; ``` The status of the mutation. * Will be: * `idle` initial status prior to the mutation function executing. * `pending` if the mutation is currently executing. * `error` if the last mutation attempt resulted in an error. * `success` if the last mutation attempt was successful. ##### Inherited from [#inherited-from-10] ```ts Omit.status ``` *** #### submit [#submit] ```ts submit: (build, options?) => Promise; ``` ##### Parameters [#parameters-1] | Parameter | Type | | ---------- | --------------------------------------------------------------- | | `build` | (`api`) => `BatchableCall`\[] \| `Promise`\<`BatchableCall`\[]> | | `options?` | `SubmitOptions` & `object` | ##### Returns [#returns-2] `Promise`\<`TxResult`> *** #### submittedAt [#submittedat] ```ts submittedAt: number; ``` ##### Inherited from [#inherited-from-11] ```ts Omit.submittedAt ``` *** #### variables [#variables] ```ts variables: | TxVariables | undefined; ``` The variables object passed to the `mutationFn`. ##### Inherited from [#inherited-from-12] ```ts Omit.variables ``` ### Vue [#vue] ```ts function useBatchTx(options?): UseBatchTxResult; ``` Like `useTx` but wraps the built calls in `Utility.batch_all` (atomic by default). ### Type Parameters [#type-parameters-2] | Type Parameter | Default type | | ---------------------- | ------------ | | `K` *extends* `string` | `string` | ### Parameters [#parameters-2] | Parameter | Type | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `options?` | \{ `chain?`: `K`; `mutation?`: `MutationOptions`\<`TxResult`, `TxVariables`\<`K`, `BatchableCall`\[], `SubmitOptions` & `object`>>; } | | `options.chain?` | `K` | | `options.mutation?` | `MutationOptions`\<`TxResult`, `TxVariables`\<`K`, `BatchableCall`\[], `SubmitOptions` & `object`>> | ### Returns [#returns-3] `UseBatchTxResult`\<`K`> # useTx (/docs/tx/use-tx) `useTx` submits transactions built against the typed PAPI api. Call `submit(build, options?)`: it signs with the connected account, broadcasts, and resolves once the transaction reaches a best block (or finality with `waitFor: "finalized"`). `phase` reports where the transaction is: `"idle" → "signing" → "broadcasting" → "in-block" → "finalized" | "error"`. On first submit the hook requests the `ChainSubmit` permission and connects the signer if needed. No manual pre-flight. The resolved `TxResult` reports dispatch failures as data: check `result.ok`, a failed dispatch carries the decoded `dispatchError`. Thrown errors (`TxSigningRejectedError`, `TxError`) mean the transaction never made it into a block. ## Usage [#usage] ```tsx import { useTx } from "@use-truapi/react"; import { MultiAddress } from "@polkadot-api/descriptors"; function Transfer({ dest, value }: { dest: string; value: bigint }) { const { submit, phase, isPending, error } = useTx(); async function onSend() { const result = await submit((api) => api.tx.Balances.transfer_keep_alive({ dest: MultiAddress.Id(dest), value, }), ); if (!result.ok) console.error("dispatch failed", result.dispatchError); } return ( <> {error &&

{error.message}

} ); } ```
```vue ```
To wait for finality, or observe each phase transition: ```ts const result = await submit( (api) => api.tx.System.remark({ remark: Binary.fromText("hello") }), { waitFor: "finalized", onStatus: (status) => console.log("tx phase:", status), }, ); ``` To build against a specific chain, pass its key; the `build` callback then receives that chain's typed api: ```ts const { submit } = useTx({ chain: "people" }); ``` `reset()` clears the mutation state and returns `phase` to `"idle"`. ## See also [#see-also] * [`useBatchTx`](/docs/tx/use-batch-tx) submits several calls atomically via `Utility.batch_all`. * [`useContractTx`](/docs/contracts/use-contract-tx) is the same lifecycle for contract calls. ## API reference [#api-reference] ### React [#react] ```ts function useTx(options?): UseTxResult; ``` Transaction lifecycle as a TanStack mutation with a granular `phase`. Permission (`ChainSubmit`) and signer connection are handled automatically on first submit. ```ts const { submit, phase } = useTx(); await submit((api) => api.tx.Balances.transfer_keep_alive({ dest, value })); ``` ### Type Parameters [#type-parameters] | Type Parameter | Default type | | ---------------------- | ------------ | | `K` *extends* `string` | `string` | ### Parameters [#parameters] | Parameter | Type | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `options?` | \{ `chain?`: `K`; `mutation?`: `MutationOptions`\<`TxResult`, `TxVariables`\<`K`, `SubmittableTransaction`, `SubmitOptions`>>; } | | `options.chain?` | `K` | | `options.mutation?` | `MutationOptions`\<`TxResult`, `TxVariables`\<`K`, `SubmittableTransaction`, `SubmitOptions`>> | ### Returns [#returns] `UseTxResult`\<`K`> ### Extends [#extends] * `Omit`\<`NamedMutation`\<`TxResult`, `TxVariables`\<`K`, `AnyTx`, `SubmitOptions`>>, `"reset"`> ### Type Parameters [#type-parameters-1] | Type Parameter | | ------------------------ | | `K` *extends* `ChainKey` | ### Properties [#properties] #### context [#context] ```ts context: unknown; ``` ##### Inherited from [#inherited-from] ```ts Omit.context ``` *** #### data [#data] ```ts data: TxResult | undefined; ``` The last successfully resolved data for the mutation. ##### Inherited from [#inherited-from-1] ```ts Omit.data ``` *** #### error [#error] ```ts error: Error | null; ``` The error object for the mutation, if an error was encountered. * Defaults to `null`. ##### Inherited from [#inherited-from-2] ```ts Omit.error ``` *** #### failureCount [#failurecount] ```ts failureCount: number; ``` ##### Inherited from [#inherited-from-3] ```ts Omit.failureCount ``` *** #### failureReason [#failurereason] ```ts failureReason: Error | null; ``` ##### Inherited from [#inherited-from-4] ```ts Omit.failureReason ``` *** #### isError [#iserror] ```ts isError: boolean; ``` A boolean variable derived from `status`. * `true` if the last mutation attempt resulted in an error. ##### Inherited from [#inherited-from-5] ```ts Omit.isError ``` *** #### isIdle [#isidle] ```ts isIdle: boolean; ``` A boolean variable derived from `status`. * `true` if the mutation is in its initial state prior to executing. ##### Inherited from [#inherited-from-6] ```ts Omit.isIdle ``` *** #### isPaused [#ispaused] ```ts isPaused: boolean; ``` ##### Inherited from [#inherited-from-7] ```ts Omit.isPaused ``` *** #### isPending [#ispending] ```ts isPending: boolean; ``` A boolean variable derived from `status`. * `true` if the mutation is currently executing. ##### Inherited from [#inherited-from-8] ```ts Omit.isPending ``` *** #### isSuccess [#issuccess] ```ts isSuccess: boolean; ``` A boolean variable derived from `status`. * `true` if the last mutation attempt was successful. ##### Inherited from [#inherited-from-9] ```ts Omit.isSuccess ``` *** #### phase [#phase] ```ts phase: TxPhase; ``` "idle" → "signing" → "broadcasting" → "in-block" → "finalized" | "error". *** #### reset [#reset] ```ts reset: () => void; ``` ##### Returns [#returns-1] `void` *** #### status [#status] ```ts status: "idle" | "success" | "error" | "pending"; ``` The status of the mutation. * Will be: * `idle` initial status prior to the mutation function executing. * `pending` if the mutation is currently executing. * `error` if the last mutation attempt resulted in an error. * `success` if the last mutation attempt was successful. ##### Inherited from [#inherited-from-10] ```ts Omit.status ``` *** #### submit [#submit] ```ts submit: (build, options?) => Promise; ``` Build against the typed api and submit; resolves when the tx reaches best-block (or `waitFor: "finalized"`). ##### Parameters [#parameters-1] | Parameter | Type | | ---------- | --------------------------------------------------------------------------- | | `build` | (`api`) => `SubmittableTransaction` \| `Promise`\<`SubmittableTransaction`> | | `options?` | `SubmitOptions` | ##### Returns [#returns-2] `Promise`\<`TxResult`> *** #### submittedAt [#submittedat] ```ts submittedAt: number; ``` ##### Inherited from [#inherited-from-11] ```ts Omit.submittedAt ``` *** #### variables [#variables] ```ts variables: | TxVariables | undefined; ``` The variables object passed to the `mutationFn`. ##### Inherited from [#inherited-from-12] ```ts Omit.variables ``` ### Vue [#vue] ```ts function useTx(options?): UseTxResult; ``` Transaction lifecycle as a TanStack mutation with a granular `phase`. Permission (`ChainSubmit`) and signer connection are handled automatically on first submit. ### Type Parameters [#type-parameters-2] | Type Parameter | Default type | | ---------------------- | ------------ | | `K` *extends* `string` | `string` | ### Parameters [#parameters-2] | Parameter | Type | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `options?` | \{ `chain?`: `K`; `mutation?`: `MutationOptions`\<`TxResult`, `TxVariables`\<`K`, `SubmittableTransaction`, `SubmitOptions`>>; } | | `options.chain?` | `K` | | `options.mutation?` | `MutationOptions`\<`TxResult`, `TxVariables`\<`K`, `SubmittableTransaction`, `SubmitOptions`>> | ### Returns [#returns-3] `UseTxResult`\<`K`>