Capxul failed to start: {bootstrap.error?.message ?? "Unknown error"}
Email: {session.data.email}
User id: {session.data.authUserId}
{signIn.error.message}
: null} {verifyOtp.error ?{verifyOtp.error.message}
: null}Capxul bootstrap failed: {bootstrap.error?.message ?? "Unknown error"}
Email: {session.data.email}
User id: {session.data.authUserId}
{message}
: null} {signIn.error ?{signIn.error.message}
: null} {verifyOtp.error ?{verifyOtp.error.message}
: null}Setting up your account…
; } if (lifecycle.status === "failed") { return (Account setup failed: {lifecycle.error.message}
Your account is almost ready…
; } return children; } ``` Show balance and money-movement UI only behind `status === "ready"` with `canTransact` true. Which money hooks are live in the published alpha is tracked on [Capability status](https://capxul-sdk-docs.pages.dev/docs/capability-status). ## Signers [#signers] Activating an account involves signing, so `requirement: "deployed"` raises the question of who signs. The answer for browser apps is: **nobody you have to wire up**. ### Browser apps: omit `signer` [#browser-apps-omit-signer] With `requirement="deployed"` and no `signer` prop, the SDK provisions an embedded signer for the signed-in user automatically, configured from the same bootstrap the publishable key already resolves. This is the production path — the reference integration ships exactly `publishableKey` plus `requirement="deployed"` and nothing else. ```tsx{error?.message ?? "Account setup failed."}
{isSettingUp ? `Step: ${step}` : "Preparing…"}
{payments.error.message}
; } // … } ``` Remember the provider's defaults: queries retry twice before `isError` goes `true` (see [TanStack Query integration](https://capxul-sdk-docs.pages.dev/docs/guides/tanstack-query)). ## Errors from mutation hooks [#errors-from-mutation-hooks] Mutations surface the same error two ways: * `mutation.error` — for rendering inline, exactly like queries. * `mutateAsync` — **rejects** with the `CapxulError`, for imperative handling in submit handlers. (`mutate` does not throw; it routes the error to `mutation.error` and callbacks.) ```tsx "use client"; import { isCapxulError } from "@capxul/sdk"; import { useCapxulPay } from "@capxul/sdk-react"; function PayForm({ email }: { readonly email: string }) { const pay = useCapxulPay(); const handlePay = async () => { try { await pay.mutateAsync({ to: { kind: "email", email }, amount: { currency: "USD", value: "25.00", decimals: 6 }, }); } catch (cause) { if (isCapxulError(cause)) { switch (cause.code) { case "INSUFFICIENT_BALANCE": showTopUpPrompt(); return; case "RATE_LIMITED": { const retryAfterMs = cause.details?.retryAfterMs; scheduleRetry(typeof retryAfterMs === "number" ? retryAfterMs : 10_000); return; } default: showToast(cause.message); return; } } throw cause; } }; return ( ); } ``` Mutations never retry automatically (`retry: 0`) — a failed money movement is yours to re-drive deliberately. ## Errors from the raw client [#errors-from-the-raw-client] The core client never throws for operational failures. Every method returns a `CapxulResult{bootstrap.error?.message ?? "Capxul failed to start."}
Loading organizations…
; if (orgs.isError) return{orgs.error.message}
; return (Loading organization…
; } if (org.isError) return{org.error.message}
; if (org.data == null) returnOrganization not found.
; return ( <>Treasury: {treasury.data.balance.value} {treasury.data.balance.currency} (available:{" "} {treasury.data.available.value})
) : null}Loading payments…
; if (payments.isError) return{payments.error.message}
; return (Released {payment.data.released.value} / {payment.data.amount.value} — claimable:{" "} {payment.data.availableToClaim.value}
); } ``` A `Payment` is leak-safe by construction: recipient `kind` + `label`, amount, `status` (`pending`, `submitted`, `pending_claim`, `scheduled`, `streaming`, `settled`, `cancelled`, `redirected`, `expired`, `failed`), `timing`, `released` / `availableToClaim`, and document references. There is no transaction hash and no address on it. Money mutations invalidate the payments list and detail automatically — see [TanStack Query integration](https://capxul-sdk-docs.pages.dev/docs/guides/tanstack-query). ## Cancel a payment [#cancel-a-payment] Commitments are cancellable by their creator before they are fully claimed: cancelling returns the unvested remainder to you, while anything already vested stays claimable by the recipient. [`useCapxulCancelPayment`](https://capxul-sdk-docs.pages.dev/docs/hooks/payments/use-capxul-cancel-payment) is the mutation surface for this: ```tsx const cancelPayment = useCapxulCancelPayment(); await cancelPayment.mutateAsync(payment.id); ```Loading…
; if (profile.isError) return{profile.error.message}
; return (Loading…
; if (request.error) return{request.error.message}
; if (request.data === null) returnNo such request.
; return ({request.data.reference}: {request.data.amount.value}{" "} {request.data.amount.currency} ({request.data.status})
); } ``` The pieces that make this behave like a built-in hook: * **`enabled: client !== null`** — the query stays pending during bootstrap and wakes up on its own once the client resolves, exactly like the shipped hooks. * **`["capxul", ...]` query key** — the provider clears capxul-scoped queries when the client changes (re-bootstrap, retry). Keying your custom queries under `"capxul"` opts them into that hygiene so no data leaks across clients. * **Unwrapping** — client methods return a `CapxulResult`; throw `result.error` (a [`CapxulError`](https://capxul-sdk-docs.pages.dev/docs/guides/error-handling)) so it lands on the query's `error` field. See [Using the raw client](https://capxul-sdk-docs.pages.dev/docs/guides/raw-client) for the full pattern, including mutations and cache invalidation. ## Parameters [#parameters] The hook takes no parameters. ## Return type [#return-type] ```ts import type { CapxulClient } from "@capxul/sdk"; // CapxulClient | null ``` `null` while the provider is bootstrapping and when the bootstrap has failed (check [`useCapxul`](https://capxul-sdk-docs.pages.dev/docs/hooks/use-capxul) to tell those apart). In [injected client mode](https://capxul-sdk-docs.pages.dev/docs/hooks/capxul-provider#injected-client-mode-client) the injected client is returned from the first render. The hook never throws for "not ready" — it returns `null` instead. ## Errors [#errors] Calling the hook outside a `CapxulProvider` throws — missing provider is a wiring bug, not a pending state. # useCapxul (https://capxul-sdk-docs.pages.dev/docs/hooks/use-capxul) Reads the bootstrap state of the surrounding [`CapxulProvider`](https://capxul-sdk-docs.pages.dev/docs/hooks/capxul-provider): a `status`, the bootstrap `error` (if any), and a `retry` function. You rarely need it. Data hooks stay in `isPending` until the client is ready, so a plain app requires **zero bootstrap awareness** — render your normal loading states and everything resolves on its own. Reach for `useCapxul` only when you want an opt-in splash screen or a dedicated bootstrap-error panel with a retry button. ## Import [#import] ```ts import { useCapxul } from "@capxul/sdk-react"; ``` ## Usage [#usage] ```tsx "use client"; import type { ReactNode } from "react"; import { useCapxul } from "@capxul/sdk-react"; function BootstrapGate({ children }: { children: ReactNode }) { const { status, error, retry } = useCapxul(); if (status === "bootstrapping") returnStarting up…
; if (status === "error") { return ({error?.message}
Loading balance…
; if (balance.error) return{balance.error.message}
; const account = balance.data; return ({account.balance.value} {account.balance.currency}
Available: {account.available.value} {account.available.currency}
{error?.message ?? "Account setup failed."}
Step: {lifecycle.step}
:Preparing…
}Loading activity…
; if (activity.error) return{activity.error.message}
; return (Loading contact…
; if (entry.error) return{entry.error.message}
; if (entry.data === null) returnNo relationship with this contact yet.
; return ({entry.data.relationship.join(", ")}
Loading contacts…
; if (addressBook.error) return{addressBook.error.message}
; const visible = addressBook.data.filter((entry) => !entry.hidden); return (Loading…
; if (currentUser.error) return{currentUser.error.message}
; const { user, organizations } = currentUser.data; return ( ); } ``` This is a signed-in surface: it reads the current user, so gate it on [`useCapxulSession`](https://capxul-sdk-docs.pages.dev/docs/hooks/auth/use-capxul-session) (or pass `enabled: false`) while signed out. ## Parameters [#parameters] ### options [#options] `{ enabled?: boolean } | undefined` | Field | Type | Description | | --------- | ---------------------- | -------------------------------------------------------------------------------- | | `enabled` | `boolean \| undefined` | Set `false` to skip the read (for example while signed out). Defaults to `true`. | ## Return type [#return-type] ```ts import { type UseCapxulCurrentUserReturn } from "@capxul/sdk-react"; // UseQueryResultLoading profile…
; if (profile.error) return{profile.error.message}
; if (!profile.data) { // Fresh identity — signed in, but onboarding has not completed yet. return Finish setting up your account; } return ({profile.data.displayName ?? profile.data.email}
Country: {profile.data.country ?? "—"}
Loading…
; if (session.error) return{session.error.message}
; if (!session.data) return Sign in; return (Signed in as {session.data.email}
User id: {session.data.authUserId}
Loading inbox…
; if (inbox.error) return{inbox.error.message}
; const open = inbox.data.filter((item) => item.status === "open"); if (open.length === 0) returnNo requests waiting on you.
; return (Loading destinations…
; if (destinations.error) return{destinations.error.message}
; return (Getting quote…
; if (quote.error) return{quote.error.message}
; return (You receive {quote.data.receivedAmount.value}{" "} {quote.data.receivedAmount.currency} (fee {quote.data.fee.value}{" "} {quote.data.fee.currency})
); } ``` ## Parameters [#parameters] ### input [#input] `OfframpQuoteInput | undefined` The query stays disabled while `input` is `undefined`, so you can pass a destination id that arrives asynchronously without manual gating.Checking status…
; if (status.error) return{status.error.message}
; return (Offramp {status.data.id}: {status.data.status} {status.data.paymentId ? ` (payment ${status.data.paymentId})` : null}
); } ``` ## Parameters [#parameters] ### offrampId [#offrampid] `string | undefined` The offramp to track. The query stays disabled while `offrampId` is `undefined`, so you can pass an id that arrives asynchronously without manual gating. ### options [#options] `{ enabled?: boolean } | undefined` Pass `enabled: false` to skip the read until your UI is ready for it. ## Return type [#return-type] ```ts import { type UseCapxulOfframpStatusReturn } from "@capxul/sdk-react"; // UseQueryResultLoading history…
; if (history.error) return{history.error.message}
; return (Loading insights…
; if (insights.error) return{insights.error.message}
; const { pending, paidThisMonth, reconciliation } = insights.data; return (Loading roster…
; if (roster.error) return{roster.error.message}
; return ({runPayroll.data.length} payments created
: null} {runPayroll.error ?{runPayroll.error.message}
: null}{deployRoles.error.message}
: null}Loading members…
; if (members.error) return{members.error.message}
; if (members.data.length === 0) returnNo members yet.
; return (Loading roles…
; if (roles.error) return{roles.error.message}
; return (Loading treasury…
; if (treasury.error) return{treasury.error.message}
; return ({treasury.data.balance.value} {treasury.data.balance.currency}
); } ``` ## Parameters [#parameters] ### orgId [#orgid] `OrgId | undefined` The organization to read. The query stays disabled while `orgId` is `undefined`, so you can pass the result of another query directly — no manual gating needed. ### options [#options] `UseCapxulOrgTreasuryOptions | undefined`Loading organization…
; if (org.error) return{org.error.message}
; if (org.data === null) returnOrganization not found.
; return (@{org.data.handle} · your role: {org.data.role}
Loading account…
; if (account.error) return{account.error.message}
; return ({account.data.balance.value} {account.data.balance.currency}
Available: {account.data.available.value}{" "} {account.data.available.currency}
Loading audit log…
; if (auditLog.error) return{auditLog.error.message}
; return (Loading organizations…
; if (orgs.error) return{orgs.error.message}
; return ({cancelPayment.error.message}
: null}{pay.error.message}
: null} {pay.data ?Payment {pay.data.id} is {pay.data.status}.
: null}Loading payment…
; if (payment.error) return{payment.error.message}
; if (payment.data === null) returnPayment not found.
; return ({payment.data.amount.value} {payment.data.amount.currency} to{" "} {payment.data.recipient.label} — {payment.data.status}
); } ``` ## Parameters [#parameters] ### paymentId [#paymentid] `string | undefined` The payment to read. The query stays disabled while `paymentId` is `undefined`, so you can pass a value that arrives asynchronously (for example from route params or another query) without manual gating. ### options [#options] `{ enabled?: boolean } | undefined` Pass `enabled: false` to skip the read until your UI is ready for it. ## Return type [#return-type] ```ts import { type UseCapxulPaymentReturn } from "@capxul/sdk-react"; // UseQueryResultLoading payments…
; if (payments.error) return{payments.error.message}
; return (No wallet destination saved yet.
; return ({payout.error.message}
: null}{withdraw.error.message}
: null} {withdraw.data ? (Sent to {withdraw.data.recipient.label} — {withdraw.data.status}
) : null}{reconcile.error.message}
: null}Loading request…
; if (request.error) return{request.error.message}
; if (request.data === null) returnRequest not found.
; const { reference, amount, status, expiresAt } = request.data; return ({amount.value} {amount.currency} — {status}
{expiresAt !== null ? (Expires {new Date(expiresAt).toLocaleDateString()}
) : null}Loading requests…
; if (requests.error) return{requests.error.message}
; return (Loading envelopes…
; if (subAccounts.error) return{subAccounts.error.message}
; return (