@partylayer/react
v2.1.0
Published
React hooks and components for PartyLayer
Readme
@partylayer/react
React hooks and components for PartyLayer
Overview
@partylayer/react provides React hooks and components for seamlessly 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.
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 (v2): 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)
Supported Wallets
| Wallet | Networks | Capabilities | Opt-in | Adapter |
|--------|----------|--------------|--------|---------|
| Console Wallet | devnet, testnet, mainnet | signMessage, signTransaction, submitTransaction, transactionStatus | No | @partylayer/adapter-console |
| 5N Loop | devnet, testnet, mainnet | signMessage, submitTransaction, transactionStatus | No | @partylayer/adapter-loop |
| Cantor8 | devnet, testnet, mainnet | signMessage, signTransaction | No | @partylayer/adapter-cantor8 |
| Bron | devnet, testnet, mainnet | signMessage, signTransaction | Yes | @partylayer/adapter-bron |
| Nightly | devnet, testnet, mainnet | signMessage, submitTransaction | No | @partylayer/adapter-nightly |
| Send | mainnet | signMessage, submitTransaction, transactionStatus | No | @partylayer/adapter-send |
| Walley | mainnet, testnet, devnet | signMessage, submitTransaction, transactionStatus | No | @k2flabs/walley-dapp-sdk |
| WalletConnect | devnet, testnet, mainnet | signMessage, submitTransaction | Yes | @partylayer/adapter-walletconnect |
Try it live: PartyLayer Studio: runnable, editable scenarios · Pattern Cookbook: copy-paste recipes.
Installation
npm install @partylayer/sdk @partylayer/react @tanstack/react-query@tanstack/react-query is a required peer dependency of v2. 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
1. Set Up the Provider
Wrap your app in QueryClientProvider (you own the QueryClient; PartyLayer does not
create one) and PartyLayerProvider. PartyLayerKit is the zero-config alternative to
PartyLayerProvider; the nesting is the same.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createPartyLayer } from '@partylayer/sdk';
import { PartyLayerProvider } from '@partylayer/react';
const client = createPartyLayer({
network: 'devnet',
app: { name: 'My dApp' },
});
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<PartyLayerProvider client={client}>
<MyApp />
</PartyLayerProvider>
</QueryClientProvider>
);
}The base session and connect hooks (
useSession,useAccount,useConnect, etc.) work withoutQueryClientProvider. It is required for the@partylayer/react/querydata hooks (DAML reads/writes, cost estimation). Migrating from v1? See the v1 to v2 migration guide.
2. Use Hooks in Your Components
import { useAccount, useConnect, useDisconnect } from '@partylayer/react';
function WalletButton() {
const { isConnected, party } = useAccount();
const { connect, isConnecting } = useConnect();
const { disconnect } = useDisconnect();
if (isConnected) {
return (
<div>
<p>Connected: {party}</p>
<button onClick={disconnect}>Disconnect</button>
</div>
);
}
return (
<button onClick={() => connect()} disabled={isConnecting}>
{isConnecting ? 'Connecting...' : 'Connect Wallet'}
</button>
);
}3. Use the Wallet Modal (Optional)
import { useState } from 'react';
import { WalletModal, useAccount } from '@partylayer/react';
function ConnectButton() {
const [isOpen, setIsOpen] = useState(false);
const { isConnected, party } = useAccount();
if (isConnected) {
return <p>Connected: {party}</p>;
}
return (
<>
<button onClick={() => setIsOpen(true)}>Connect Wallet</button>
<WalletModal isOpen={isOpen} onClose={() => setIsOpen(false)} />
</>
);
}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 |
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
For Next.js applications, initialize the client on the client side:
'use client';
import { useEffect, useState } from 'react';
import { createPartyLayer, PartyLayerClient } from '@partylayer/sdk';
import { PartyLayerProvider } from '@partylayer/react';
export function Providers({ children }: { children: React.ReactNode }) {
const [client, setClient] = useState<PartyLayerClient | null>(null);
useEffect(() => {
const cantonClient = createPartyLayer({
network: 'devnet',
app: { name: 'My Next.js App' },
});
setClient(cantonClient);
return () => cantonClient.destroy();
}, []);
if (!client) {
return <div>Loading...</div>;
}
return (
<PartyLayerProvider client={client}>
{children}
</PartyLayerProvider>
);
}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();Complete Example
import { useState } from 'react';
import { createPartyLayer } from '@partylayer/sdk';
import {
PartyLayerProvider,
useAccount,
useWallets,
useConnect,
useDisconnect,
useSignMessage,
WalletModal,
} from '@partylayer/react';
const client = createPartyLayer({
network: 'devnet',
app: { name: 'My dApp' },
});
function WalletStatus() {
const { isConnected, party, networkId } = useAccount();
const { wallets, isLoading } = useWallets();
const { connect, isConnecting } = useConnect();
const { disconnect } = useDisconnect();
const { signMessage, isSigning } = useSignMessage();
const [modalOpen, setModalOpen] = useState(false);
if (isConnected) {
return (
<div>
<h2>Connected</h2>
<p>Party: {party}</p>
<p>Network: {networkId}</p>
<button
onClick={() => signMessage({ message: 'Test' })}
disabled={isSigning}
>
{isSigning ? 'Signing...' : 'Sign Message'}
</button>
<button onClick={disconnect}>Disconnect</button>
</div>
);
}
return (
<div>
<h2>Not Connected</h2>
<button onClick={() => setModalOpen(true)}>
Connect Wallet
</button>
<WalletModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
/>
</div>
);
}
export default function App() {
return (
<PartyLayerProvider client={client}>
<WalletStatus />
</PartyLayerProvider>
);
}Related Packages
| Package | Description | |---------|-------------| | @partylayer/sdk | Core SDK (required) | | @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 same surface as composables.
| React (@partylayer/react) | Vue (@partylayer/vue) |
|---|---|
| useSession() | useSession() |
| useAccount() | useAccount() |
| useAccountEffect() | useAccountEffect() |
| useClientSession() (legacy) | None (not ported) |
