PartyLayerDocs
Try Demo

React Hooks

PartyLayer provides React hooks for accessing wallet state, performing operations, and managing sessions. The hooks below are the main entrypoint (@partylayer/react): they use useSyncExternalStore and must be used within a PartyLayerKit or PartyLayerProvider. v2 also adds a TanStack Query powered @partylayer/react/query entrypoint for ledger data and cost, documented in Data hooks at the end of this page.

Core Hooks

usePartyLayer

Access the underlying PartyLayerClient instance directly.

tsx
import { usePartyLayer } from '@partylayer/react';

function Advanced() {
  const client = usePartyLayer();

  // Access any client method
  const wallets = await client.listWallets();
  const provider = client.asProvider();
}
ℹ️ Note
Use this hook when you need direct access to the SDK client for operations not covered by the other hooks (e.g., asProvider(), registerAdapter()).

useSession

Reactive session state and actions. Re-renders on every session change. This returns UseSessionReturn (the reactive store), not the legacy SDK session getter.

tsx
import { useSession } from '@partylayer/react';

function Profile() {
  const { status, account, networkId, isConnected, disconnect } = useSession();

  if (!isConnected) return <p>Not connected ({status})</p>;

  return (
    <div>
      <p>Party ID: {account?.partyId}</p>
      <p>Network: {networkId}</p>
      <button onClick={() => disconnect()}>Disconnect</button>
    </div>
  );
}

Return type: UseSessionReturn, the reactive SessionState (status, account, accounts, networkId, lastError) plus isConnected/isConnecting/isReconnecting/isDisconnected and the actions connect, disconnect, restore, on.

⚠️ Warning
Migration: useSession() was re-pointed from the SDK-layer session getter (Session | null) to the reactive store. The old getter is preserved VERBATIM as useClientSession() (deprecated): it still returns the Session object (sessionId, walletId, partyId, network, …). Migrate useSession()useClientSession() if you need that shape.

useWallets

Fetch and list all available wallets (from both the registry and registered adapters).

tsx
import { useWallets } from '@partylayer/react';

function WalletList() {
  const { wallets, isLoading, error } = useWallets();

  if (isLoading) return <p>Loading wallets...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {wallets.map(w => (
        <li key={w.walletId}>
          {w.name}: {w.capabilities.join(', ')}
        </li>
      ))}
    </ul>
  );
}

Return type: { wallets: WalletInfo[], isLoading: boolean, error: Error | null }

Connection Hooks

useConnect

Connect to a wallet programmatically.

tsx
import { useConnect } from '@partylayer/react';

function CustomConnect() {
  const { connect, isConnecting, error, reset } = useConnect();

  const handleConnect = async () => {
    const session = await connect({ walletId: 'console' });
    if (session) {
      console.log('Connected:', session.partyId);
    }
  };

  return (
    <div>
      <button onClick={handleConnect} disabled={isConnecting}>
        {isConnecting ? 'Connecting...' : 'Connect Console'}
      </button>
      {error && (
        <div>
          <p>Error: {error.message}</p>
          <button onClick={reset}>Reset</button>
        </div>
      )}
    </div>
  );
}

Return type: { connect: (options?) => Promise<Session | null>, isConnecting: boolean, error: Error | null, reset: () => void }

The options parameter accepts: walletId (optional: if omitted, opens the modal).


useDisconnect

Disconnect the active wallet session.

tsx
import { useDisconnect } from '@partylayer/react';

function DisconnectButton() {
  const { disconnect, isDisconnecting, error } = useDisconnect();

  return (
    <button onClick={() => disconnect()} disabled={isDisconnecting}>
      {isDisconnecting ? 'Disconnecting...' : 'Disconnect'}
    </button>
  );
}

Return type: { disconnect: () => Promise<void>, isDisconnecting: boolean, error: Error | null }

Signing Hooks

useSignMessage

Sign an arbitrary message with the connected wallet.

tsx
import { useSignMessage } from '@partylayer/react';

function SignDemo() {
  const { signMessage, isSigning, error } = useSignMessage();

  const handleSign = async () => {
    const result = await signMessage({
      message: 'Hello from PartyLayer!',
      nonce: crypto.randomUUID(),
    });

    if (result) {
      console.log('Signature:', result.signature);
      console.log('Signed by:', result.partyId);
    }
  };

  return (
    <button onClick={handleSign} disabled={isSigning}>
      {isSigning ? 'Signing...' : 'Sign Message'}
    </button>
  );
}

Return type: { signMessage: (params) => Promise<SignedMessage | null>, isSigning: boolean, error: Error | null }

SignedMessage includes: signature, partyId, message, nonce, domain.


useSignTransaction

Sign a transaction without submitting it.

tsx
import { useSignTransaction } from '@partylayer/react';

function SignTx() {
  const { signTransaction, isSigning, error } = useSignTransaction();

  const handleSign = async () => {
    const result = await signTransaction({
      tx: { templateId: '...', choiceId: '...', argument: { /* ... */ } },
    });

    if (result) {
      console.log('Transaction hash:', result.transactionHash);
      console.log('Signed payload:', result.signedTx);
    }
  };

  return <button onClick={handleSign}>{isSigning ? 'Signing...' : 'Sign Transaction'}</button>;
}

Return type: { signTransaction: (params) => Promise<SignedTransaction | null>, isSigning: boolean, error: Error | null }


useSubmitTransaction

Sign and submit a transaction to the ledger in one step.

tsx
import { useSubmitTransaction } from '@partylayer/react';

function SubmitTx() {
  const { submitTransaction, isSubmitting, error } = useSubmitTransaction();

  const handleSubmit = async () => {
    const receipt = await submitTransaction({
      signedTx: signedPayload,  // Pass the signed transaction
    });

    if (receipt) {
      console.log('TX Hash:', receipt.transactionHash);
      console.log('Submitted at:', new Date(receipt.submittedAt));
      console.log('Command ID:', receipt.commandId);
    }
  };

  return <button onClick={handleSubmit}>{isSubmitting ? 'Submitting...' : 'Submit'}</button>;
}

Return type: { submitTransaction: (params) => Promise<TxReceipt | null>, isSubmitting: boolean, error: Error | null }


useLedgerApi

Call the Canton Ledger API through the connected wallet.

tsx
import { useLedgerApi, useAccount } from '@partylayer/react';

function BalanceQuery() {
  const { isConnected, party } = useAccount();
  const { ledgerApi, isLoading, error } = useLedgerApi();

  const fetchBalance = async () => {
    if (!isConnected || !party) return;

    const result = await ledgerApi({
      requestMethod: 'POST',
      resource: '/v2/state/active-contracts',
      body: JSON.stringify({
        filter: {
          filtersByParty: {
            [party]: {
              inclusive: {
                templateFilters: [{ templateId: 'Splice.Amulet:Amulet' }],
              },
            },
          },
        },
      }),
    });

    if (result) {
      const { activeContracts = [] } = JSON.parse(result.response);
      console.log('Contracts:', activeContracts.length);
    }
  };

  return (
    <button onClick={fetchBalance} disabled={isLoading}>
      {isLoading ? 'Loading...' : 'Fetch Balance'}
    </button>
  );
}

Return type: { ledgerApi: (params) => Promise<LedgerApiResult | null>, isLoading: boolean, error: Error | null }

Requires a wallet with ledgerApi capability, see Capability Matrix. Throws CapabilityNotSupportedError for wallets that don't support it (e.g. Cantor8).

Utility Hooks

useRegistryStatus

Get the current wallet registry status and refresh it.

tsx
import { useRegistryStatus } from '@partylayer/react';

function RegistryInfo() {
  const { status, refresh } = useRegistryStatus();

  if (!status) return <p>No registry data</p>;

  return (
    <div>
      <p>Source: {status.source}</p>
      <p>Verified: {status.verified ? 'Yes' : 'No'}</p>
      <p>Channel: {status.channel}</p>
      <p>Stale: {status.stale ? 'Yes' : 'No'}</p>
      <button onClick={refresh}>Refresh</button>
    </div>
  );
}

Return type: { status: RegistryStatus | null, refresh: () => Promise<void> }


useWalletIcons

Access the wallet icon overrides provided by PartyLayerKit.

tsx
import { useWalletIcons, resolveWalletIcon } from '@partylayer/react';

function WalletIcon({ walletId, registryIcon }: { walletId: string; registryIcon?: string }) {
  const walletIcons = useWalletIcons();
  const iconUrl = resolveWalletIcon(walletId, walletIcons, registryIcon);

  if (!iconUrl) return <div className="fallback-icon" />;
  return <img src={iconUrl} alt={walletId} width={32} height={32} />;
}

Return type: Record<string, string>


useTheme

Access the current PartyLayer theme.

tsx
import { useTheme } from '@partylayer/react';

function ThemedComponent() {
  const theme = useTheme();

  return (
    <div style={{
      background: theme.colors.background,
      color: theme.colors.text,
      fontFamily: theme.fontFamily,
    }}>
      Current mode: {theme.mode}
    </div>
  );
}

Return type: PartyLayerTheme, see Theming for the full interface.

Data hooks (@partylayer/react/query)

v2 adds a TanStack Query powered entrypoint for reading and writing ledger data and for cost estimation. These hooks import from @partylayer/react/query and require a QueryClientProvider (see Quick Start). PartyLayer does not own ledger transport: you supply the fetcher, and the hook wraps it in useQuery / useMutation.

ℹ️ Requires QueryClientProvider
The hooks above (the main entrypoint) work without TanStack Query. The data hooks here need a QueryClient in context, so wrap your app in QueryClientProvider (in addition to PartyLayerKit / PartyLayerProvider). The entrypoint also exports query-backed variants of useConnect, useWallets, useDisconnect, useSignMessage, and useSubmitTransaction.

useDamlContract / useChoice (DAML, Model 2)

Read a contract (generic over its type) and exercise a choice. You supply the fetcher.

tsx
import { useDamlContract, useChoice } from '@partylayer/react/query';

// read: you supply the `read` fetcher; null is a valid (absent) value, not an error.
const { contract, isLoading } = useDamlContract<MyContract>({ read: fetchContract });

// write: exposes exerciseChoice / exerciseChoiceAsync.
const { exerciseChoice, exerciseChoiceAsync } = useChoice<MyResult, MyVars>({ exercise });

useTransactionCostEstimate / usePaidTrafficCost (CIP-0104)

tsx
import { useTransactionCostEstimate, usePaidTrafficCost } from '@partylayer/react/query';

const { costEstimate } = useTransactionCostEstimate({ estimate: fetchEstimate });
const { paidTrafficCost } = usePaidTrafficCost({ fetch: fetchPaid });

useTransactionCostEstimate is the pre-submission CostEstimation; usePaidTrafficCost is 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 partyLayerKeys factory (also exported here) produces the hierarchical cache keys, so manual cache reads/writes line up with the hooks. See the Pattern Cookbook for full recipes.

PreviousThemingNextVanilla JS