@partylayer/react
v3.1.2
Published
Canton Network wallet connection kit for React: CIP-0103 integrations, included wallet runtimes, hooks, themes and a ready-made Connect Wallet button.
Maintainers
Readme
@partylayer/react
The ready-made Canton wallet connection kit for React
Overview
@partylayer/react provides React hooks and components for integrating Canton Network wallet connectivity into your React application. Built on top of @partylayer/sdk, it offers a declarative API with built-in state management and CIP-0103 wallet integrations.
Coming from RainbowKit? Read the Canton React wallet guide. PartyLayer is independent from RainbowKit and uses Canton party identity and transaction semantics. Get integration help.
This guide covers 3.1.0+. See release validation for acceptance scope and release status.
Features
- React Hooks:
useSession,useWallets,useConnect,useSignMessage, and more - Ready-to-Use Components: a pre-built wallet modal (with wallet search and a "New to Canton wallets?" explainer) and a
ConnectButtonwith a deterministic account avatar and a copy-address dropdown - Themeable: callable
lightTheme/darkThemewith an accent color, radius, blur, and font stack, ready-madeaccentPresets, and a dynamic light/dark option - State Management: Automatic session state synchronization
- TanStack Query data layer: a
@partylayer/react/queryentrypoint for DAML reads/writes (useDamlContract,useChoice), CIP-0104 cost (useTransactionCostEstimate,usePaidTrafficCost), Suspense twins, and optimistic updates - TypeScript: Full type safety for all hooks and components
- SSR Compatible: Works with Next.js and other SSR frameworks (cookieStorage hydration, Server Components compatible)
One installation, shared wallet configuration
npm install @partylayer/react @tanstack/react-queryimport { PartyLayerKit, ConnectButton } from '@partylayer/react';
<PartyLayerKit network="devnet" appName="My dApp">
<ConnectButton />
</PartyLayerKit>The SDK and reviewed official wallet SDKs are included. Wallets are filtered by network
and channel. Use channel="beta" on Devnet for Vala. Enable WalletConnect with
walletConnect={{ projectId: 'your-project-id' }} and Bron with bron={oauthAndApiConfig}.
Keep configuration objects stable (outside the component or with useMemo). No wallet
SDK imports or per-wallet installs are required. adapters replaces defaults;
additionalAdapters extends them. Hosted wallets still require the user's own login.
Supported Wallets
This generated stable-catalog table describes effective PartyLayer capabilities. Check the picker
and session.capabilitiesSnapshot at runtime; capability metadata is not live acceptance evidence.
Vala is an additional Devnet beta entry. Wallet SDKs are included; WalletConnect and Bron require
application-owned configuration. Send is Mainnet-only in the catalog.
| Wallet | Networks | Capabilities | Opt-in | Additional setup | |--------|----------|--------------|--------|---------| | Console Wallet | devnet, testnet, mainnet | signMessage, submitTransaction, restore | No | Included | | 5N Loop | devnet, mainnet | signMessage, submitTransaction, ledgerApi, restore, events | No | Included | | Cantor8 | devnet, mainnet | submitTransaction, events | No | Included | | Bron | devnet, testnet, mainnet | signMessage, signTransaction, ledgerApi, restore | Yes | Your OAuth/API configuration | | Nightly | devnet, testnet, mainnet | signMessage, submitTransaction, ledgerApi, restore, events | No | Included | | Cauri Wallet | devnet, mainnet | signMessage, submitTransaction | No | Included | | OneSwap V2 Wallet | devnet, mainnet | signMessage, submitTransaction | No | Included | | Send | mainnet | signMessage, submitTransaction, ledgerApi, restore, events | No | Included | | Walley | mainnet, testnet, devnet | signMessage, submitTransaction | No | Included | | WalletConnect | devnet, testnet, mainnet | signMessage, submitTransaction, ledgerApi, restore, events | Yes | Your project ID |
Try it live: PartyLayer Studio: runnable, editable scenarios · Pattern Cookbook: copy-paste recipes.
Installation
npm install @partylayer/react @tanstack/react-query@tanstack/react-query is a required peer dependency. The data hooks in the
@partylayer/react/query entrypoint are built on TanStack Query, and you supply the
QueryClient via QueryClientProvider (the same model wagmi uses). The base session
and connect surface (the main entrypoint: useSession, useAccount, useConnect,
ConnectButton, WalletModal, PartyLayerKit) works without it; the
QueryClientProvider is required only for the /query data hooks.
Quick Start
Use the kit and the default button in an existing React application:
// App.tsx — use a 'use client' boundary in Next.js.
import { PartyLayerKit, ConnectButton } from '@partylayer/react';
export default function App() {
return (
<PartyLayerKit network="devnet" appName="My dApp">
<ConnectButton />
</PartyLayerKit>
);
}PartyLayerKit owns discovery, client cleanup, theme and session context. The button
opens wallet selection, handles connection states and displays the account menu.
The modal shows Powered by PartyLayer by default. Custom UI and attribution
configuration are available when needed; no stylesheet import is required.
Use the complete quick start for Vite and Next.js App Router, including the root layout. Starting a new app?
npm create partylayer-app@latest my-dapp -- --template react-vite --pm npmRead the account
import { useAccount } from '@partylayer/react';
export function Account() {
const { isConnected, status, party, networkId } = useAccount();
if (!isConnected) return <p>Wallet: {status}</p>;
return <p>{party} · {networkId}</p>;
}Render this inside the kit. Connection is not application authentication: use the authentication guide for backend-verified sign-in.
Add optional data hooks
For @partylayer/react/query, wrap the kit with QueryClientProvider. In a Next.js
client provider, use a per-instance stable QueryClient:
'use client';
import { useState, type ReactNode } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { PartyLayerKit } from '@partylayer/react';
export function Providers({ children }: { children: ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
return (
<QueryClientProvider client={queryClient}>
<PartyLayerKit network="devnet" appName="My dApp">
{children}
</PartyLayerKit>
</QueryClientProvider>
);
}The base connect UI does not require QueryClientProvider. Do not create a new
client on each render or share an SSR QueryClient across requests.
Hooks Reference
useSession()
Reactive session state + actions (UseSessionReturn). Re-renders on every
session change.
const { status, account, networkId, isConnected, connect, disconnect, on } = useSession();
if (isConnected) {
console.log('Party ID:', account?.partyId);
console.log('Network:', networkId);
}In v2,
useSession()returns the reactive session store (UseSessionReturn). The legacy SDKSessiongetter ({ sessionId, walletId, … }) is preserved asuseClientSession()(deprecated). See the v1 to v2 migration guide.
useWallets()
Returns the list of available wallets and loading state.
const { wallets, isLoading, error } = useWallets();
return (
<ul>
{wallets.map((wallet) => (
<li key={wallet.walletId}>
<img src={wallet.icon} alt={wallet.name} />
{wallet.name}
</li>
))}
</ul>
);useConnect()
Hook for connecting to a wallet.
const { connect, isConnecting, error } = useConnect();
// Connect to first available wallet
await connect();
// Connect to specific wallet
await connect({ walletId: 'console' });
// With options
await connect({
walletId: 'loop',
timeoutMs: 60000,
requiredCapabilities: ['signMessage', 'signTransaction'],
});useDisconnect()
Hook for disconnecting from the current wallet.
const { disconnect, isDisconnecting } = useDisconnect();
<button onClick={disconnect} disabled={isDisconnecting}>
Disconnect
</button>useSignMessage()
Hook for signing messages.
const { signMessage, isSigning, error } = useSignMessage();
const handleSign = async () => {
const result = await signMessage({ message: 'Hello, Canton!' });
console.log('Signature:', result.signature);
};usePartyLayer()
Returns the underlying SDK client for advanced usage.
const client = usePartyLayer();
// Access SDK methods directly
const wallets = await client.listWallets();useRegistryStatus()
Returns the wallet registry status.
const status = useRegistryStatus();
if (status?.stale) {
console.log('Registry data may be outdated');
}Data hooks (@partylayer/react/query)
v2 adds a TanStack Query powered entrypoint, @partylayer/react/query, for reading and
writing ledger data and for cost estimation. These hooks require a QueryClientProvider
(see Installation). PartyLayer does not own ledger transport: you supply
the fetcher, and the hook wraps it in useQuery / useMutation for caching, loading
state, and invalidation. (The entrypoint also exports query-backed variants of the wallet
hooks: useConnect, useWallets, useDisconnect, useSignMessage, useSubmitTransaction.)
DAML read and write (Model 2)
import { useDamlContract, useChoice } from '@partylayer/react/query';
// read: generic over the contract type; you supply the `read` fetcher.
const { contract, isLoading } = useDamlContract<MyContract>({ read: fetchContract });
// write: generic over result/variables; exposes exerciseChoice / exerciseChoiceAsync.
const { exerciseChoice, exerciseChoiceAsync } = useChoice<MyResult, MyVars>({ exercise });null is a valid resolved value (the contract is absent), not an error.
CIP-0104 cost
import { useTransactionCostEstimate, usePaidTrafficCost } from '@partylayer/react/query';
const { costEstimate } = useTransactionCostEstimate({ estimate: fetchEstimate });
const { paidTrafficCost } = usePaidTrafficCost({ fetch: fetchPaid });useTransactionCostEstimate: the pre-submissionCostEstimation.usePaidTrafficCost: the post-execution paid cost.
Suspense twins
The query hooks have useSuspense* twins (useSuspenseTransactionCostEstimate,
useSuspensePaidTrafficCost, useSuspenseWallets) for declarative loading inside a
React <Suspense> boundary: the value is always present (no loading flag), and the
boundary shows the fallback while it resolves.
Optimistic updates
optimisticMutationOptions wires an optimistic cache update with automatic rollback on
error into useChoice (the wagmi-style onMutate/rollback pattern). The partyLayerKeys
factory (also exported here) produces the hierarchical cache keys, so manual cache
reads/writes line up with the hooks.
import { useChoice, optimisticMutationOptions } from '@partylayer/react/query';
const { exerciseChoice } = useChoice({
exercise,
mutation: optimisticMutationOptions({ queryClient, queryKey, update }),
});See the v1 to v2 migration guide and the Pattern Cookbook for full recipes, optimistic patterns, and the SSR (cookieStorage) and Server Components details.
Components
PartyLayerProvider
The context provider that must wrap your application.
<PartyLayerProvider client={client}>
{children}
</PartyLayerProvider>Props:
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| client | PartyLayerClient | Yes | The SDK client instance |
| children | ReactNode | Yes | Child components |
WalletModal
A pre-built modal for wallet selection.
<WalletModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
// optional: the modal self-closes via onClose and the session is observable
// via useSession()/useAccount(); pass it only to get the session id directly.
onConnect={(sessionId) => console.log('Connected:', sessionId)}
/>Props:
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| isOpen | boolean | Yes | Whether the modal is visible |
| onClose | () => void | Yes | Called when modal should close |
| onConnect | (sessionId: string) => void | No | Optional; called with the new session id after a successful connection |
| walletIcons | Record<string, string> | No | Override wallet logos by wallet id |
| walletOrder | readonly string[] | No | Wallet ids in display order; unlisted wallets fall to the end |
| showAttribution | boolean | No | Show the muted "Powered by PartyLayer" footer line (default true) |
| disclaimer | ReactNode | No | Optional legal line (e.g. Terms / Privacy) shown above the footer |
| showWalletGuide | boolean | No | Show the "New to Canton wallets?" explainer row (default true) |
The modal also shows a search field once the wallet list is long enough, and a clean transport label under each wallet (for example "Browser Extension" or "Scan to connect").
ConnectButton
A drop-in button that opens the wallet modal, then, once connected, shows a deterministic account avatar with the truncated party id and a dropdown with a copy-address action and disconnect.
import { ConnectButton } from '@partylayer/react';
<ConnectButton accountStatus="full" />Props:
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| label | string | No | Text when disconnected (default "Connect Wallet") |
| accountStatus | 'avatar' \| 'address' \| 'full' | No | What the connected button shows (default 'full' = avatar + id) |
| connectedLabel | 'address' \| 'wallet' \| 'custom' | No | Which identity to display when connected |
| formatAddress | (partyId: string) => string | No | Custom formatter (with connectedLabel="custom") |
| showDisconnect | boolean | No | Show the dropdown with copy and disconnect (default true) |
| className / style | string / CSSProperties | No | Styling overrides |
Keyboard behavior: The wallet dialog moves focus inside on open, wraps Tab and
Shift+Tab, and restores the opener after closing. The connected account menu
supports Enter/Space, Arrow Up/Down, Home/End and Escape; Tab leaves the menu.
Avatar-only buttons have an accessible account name. Actions use type="button"
so embedding the component in a form does not submit the form. Vendor approval
surfaces keep their own keyboard handling.
UI language and direction
PartyLayerKit supports English (default) and Turkish. Set locale="tr" or
locale="tr-TR"; unsupported language tags fall back to English. Set
direction="rtl" when embedding the wallet UI in a right-to-left layout.
<PartyLayerKit network="devnet" appName="My dApp" locale="tr-TR" direction="ltr">
<ConnectButton />
</PartyLayerKit>The setting covers PartyLayer's connection dialog, account menu, accessible labels,
CostPreview, TransactionToast and SynchronizerSwitcher. Changing language or
direction does not recreate the SDK client or disconnect the wallet. The server and
client use the same explicit setting; PartyLayer does not guess the browser language
or mutate the application's document language.
Advanced PartyLayerProvider compositions can wrap their UI with
PartyLayerLocaleProvider, which accepts the same locale and direction props.
Application copy, custom button labels, wallet names, identifiers, provider error
details and wallet-owned approval screens remain under their respective owners.
Your app controls cost/number formatting through the existing formatting props.
PartyAvatar
A small deterministic avatar generated from a Canton party id (the same id always yields the same avatar). Reused inside ConnectButton, and exported so you can render the same identity mark elsewhere.
import { PartyAvatar } from '@partylayer/react';
<PartyAvatar id={partyId} size={24} />| Prop | Type | Required | Description |
|------|------|----------|-------------|
| id | string | Yes | The party id the avatar is generated from |
| size | number | No | Diameter in px (default 20) |
| className / style | string / CSSProperties | No | Styling overrides |
Theming
lightTheme and darkTheme are callable: call them with a few friendly overrides to customize, or pass them as-is. The default look is unchanged.
import { PartyLayerKit, darkTheme, accentPresets } from '@partylayer/react';
// A custom accent, larger radius, and a stronger backdrop blur:
<PartyLayerKit
network="mainnet"
appName="My dApp"
theme={darkTheme({ accentColor: '#7B3FE4', borderRadius: 'large', overlayBlur: 'large' })}
>
{children}
</PartyLayerKit>
// Or a ready-made preset:
<PartyLayerKit network="mainnet" appName="My dApp" theme={darkTheme({ ...accentPresets.purple })}>
{children}
</PartyLayerKit>ThemeOverrides (accepted by lightTheme(...) / darkTheme(...) / createTheme(base, overrides)):
| Field | Type | Description |
|------|------|-------------|
| accentColor | string | Sets the primary accent (a hover shade is derived) |
| accentColorForeground | string | Text/icon color on the accent (auto-derived if omitted) |
| borderRadius | 'none' \| 'small' \| 'medium' \| 'large' \| string | Corner radius keyword or a raw CSS length |
| overlayBlur | 'none' \| 'small' \| 'large' \| string | Modal backdrop blur keyword or a raw CSS length |
| fontStack | 'system' \| 'rounded' \| string | Font family keyword or a raw CSS font-family |
| colors | Partial<PartyLayerTheme['colors']> | Deep color overrides |
accentPresets ships partyYellow, blue, green, purple, orange, pink, and red. Pass a static theme, or a dynamic { lightMode, darkMode } object that follows the OS preference. The showAttribution and disclaimer props are also available on PartyLayerKit and apply to the modal footer.
Next.js Integration
Put PartyLayerKit in a client provider component and render it from your Server
Component root layout. The complete Next.js example
includes both files. The next-ssr CLI template additionally demonstrates an
explicit cookie-based UI session projection.
Cookie state is a display hint, not proof of wallet authorization or server login. Protect server routes with independently verified backend sessions. Native wallet restoration checks the live party/network and may require one reconnect for older caches. See protocol migration.
TypeScript
All hooks and components are fully typed:
import type { Session, WalletInfo, UseSessionReturn } from '@partylayer/react';
// Reactive session: state + actions
const session: UseSessionReturn = useSession();
// Legacy SDK session getter (deprecated alias)
const legacy: Session | null = useClientSession();
// Wallet info type
const { wallets }: { wallets: WalletInfo[] } = useWallets();Custom UI
The default ConnectButton is the recommended integration. For a custom picker,
use WalletModal, useWallets, useConnect, useDisconnect and useAccount
inside the same kit. Start popup connection from a user gesture, preserve keyboard
focus and inspect session capabilities before exposing an operation.
See the hook reference and WalletModal. Custom UI owns its loading, rejection, timeout, accessibility and account-state presentation.
Related Packages
| Package | Description | |---------|-------------| | @partylayer/sdk | SDK included as a dependency | | @partylayer/core | Core types and abstractions |
Links
License
MIT
Session hooks
useSession / useAccount / useAccountEffect are thin React bindings over the
@partylayer/session store, created by PartyLayerProvider / PartyLayerKit.
SSR-safe (no window/BroadcastChannel at module or hook scope).
const { status, account, networkId, connect, disconnect, on } = useSession();
const { address, isConnected, chain } = useAccount();
useAccountEffect({
onConnect: ({ account }) => {/* … */},
onDisconnect: () => {/* … */},
onPartyChanged: ({ previous, current }) => {/* invalidate caches */},
});Adopt the session layer via PartyLayerKit:
<PartyLayerKit … sessionOptions={{
storage: createEncryptedIndexedDBStorage(), // @partylayer/session
persistSnapshot: true,
reconnect: DEFAULT_RETRY_POLICY,
broadcast: true, // multi-tab sync
}}>…</PartyLayerKit>Migrating from v1
In v2, useSession() is the reactive session-store hook (UseSessionReturn: live
SessionState plus connect/disconnect/restore/on). The previous SDK-layer getter
((): Session | null) is preserved verbatim as useClientSession() (deprecated). v2 also
moves the data layer to TanStack Query (the @partylayer/react/query entrypoint), which
adds the QueryClientProvider setup shown above.
| Before (v1) | After (v2) |
|---|---|
| const session = useSession(); session?.partyId | const session = useClientSession(); session?.partyId |
| (no reactive session) | const { status, account, connect } = useSession() |
For the full upgrade (renames, the query peer, and the provider change), see the v1 to v2 migration guide.
Vue parity
The Vue bindings ship as @partylayer/vue
with the shared connection/account composables below. The new authentication and
transaction-journal React components/hooks do not imply complete Vue feature parity.
| React (@partylayer/react) | Vue (@partylayer/vue) |
|---|---|
| useSession() | useSession() |
| useAccount() | useAccount() |
| useAccountEffect() | useAccountEffect() |
| useClientSession() (legacy) | None (not ported) |
The recommended UI is PartyLayer's ConnectButton. Its connection modal includes
“Powered by PartyLayer” by default, as do the generated starters. Custom UI remains
available for advanced use. For authentication challenges, include nonce and domain in
the message being signed; separate nonce/domain metadata is not a cryptographic binding.
For application sign-in, pass a stable authentication backend integration to
PartyLayerKit. The same ConnectButton then offers explicit sign-in and app
sign-out. useAuthentication() exposes backend session status, pending/error state,
canSignIn and refresh. Custom buttons wait for canSignIn and invoke signIn()
directly in their click handler to preserve hosted-wallet popup activation.
Implement server challenge storage, CSRF/browser binding, exact message
signature verification and independent party-key authorization before enabling it.
See the authentication guide; a wallet connection
or client-side status does not authorize backend requests.
Use useTransactionFlow to submit once and independently reconcile ledger outcomes.
Transactions from this hook appear in the default account panel automatically;
useRecentTransactions and RecentTransactions support custom activity views.
The journal is private in-memory UI activity, scoped by wallet/party/network, not
a full ledger indexer. An unknown outcome never triggers automatic submission.
All ledger-write mutation hooks ignore automatic retry settings; check ledger
state before an explicit new attempt. See Transaction lifecycle.
Pass authorized, party/network-bound holdings to ConnectButton to show balances
in the same account panel, or use TokenBalances for a custom view. Exact-decimal
summaries separate instrument admin/id and reported locks. Data comes from your
authorized backend or a supported wallet ledger proxy; unavailable reads do not
become zero balances. See Wallet balances.
