@sigilry/react
@sigilry/react provides React hooks and context for interacting with the Sigilry provider. It builds on @sigilry/dapp and uses @tanstack/react-query for async state.
Install
Section titled “Install”yarn add @sigilry/react @sigilry/dapp @sigilry/canton-json-api @tanstack/react-queryProvider setup
Section titled “Provider setup”import { CantonReactProvider } from "@sigilry/react";import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
export function AppRoot() { return ( <QueryClientProvider client={queryClient}> <CantonReactProvider> <App /> </CantonReactProvider> </QueryClientProvider> );}Provider discovery
Section titled “Provider discovery”When multiple Canton wallets may be installed, let the user pick one. useDiscovery() returns the live list of discovered wallets; render them with WalletPicker; pass the selected provider to CantonReactProvider via its controlled provider prop. See Provider Discovery for the underlying model.
import { useState } from "react";import { CantonReactProvider, useDiscovery, WalletPicker, type DiscoveredWallet,} from "@sigilry/react";import { QueryClient, QueryClientProvider } from "@tanstack/react-query";import type { SpliceProvider } from "@sigilry/dapp";
const queryClient = new QueryClient();
function WalletGate({ children }: { children: React.ReactNode }) { const wallets = useDiscovery(); const [provider, setProvider] = useState<SpliceProvider | null>(null);
if (!provider) { return ( <WalletPicker wallets={wallets} onSelect={(w: DiscoveredWallet) => setProvider(w.getProvider())} emptyState={<p>No Canton wallet detected.</p>} /> ); }
// The controlled `provider` prop binds every hook below to the chosen wallet. return <CantonReactProvider provider={provider}>{children}</CantonReactProvider>;}
export function AppRoot() { return ( <QueryClientProvider client={queryClient}> <WalletGate> <App /> </WalletGate> </QueryClientProvider> );}Without a provider prop, CantonReactProvider falls back to the injected window.canton (the single-wallet path), so adopting discovery is opt-in per consumer.
Push event subscriptions
Section titled “Push event subscriptions”useCanton() exposes subscription helpers for the CIP-103 push events — onAccountsChanged and onStatusChanged are the §4.2.2 sync events; onConnected is the login-flow event. Each returns an unsubscribe function and is safe to use inside an effect.
import { useEffect } from "react";import { useCanton } from "@sigilry/react";
export function StatusWatcher() { const { onStatusChanged, onConnected, onAccountsChanged } = useCanton();
useEffect(() => { const offStatus = onStatusChanged((status) => console.log("status", status)); const offConnected = onConnected((status) => console.log("connected", status)); const offAccounts = onAccountsChanged((accounts) => console.log("accounts", accounts)); return () => { offStatus(); offConnected(); offAccounts(); }; }, [onStatusChanged, onConnected, onAccountsChanged]);
return null;}Most apps don’t need these directly — useAccounts, useSession, and useConnect already react to the same events internally. Reach for the raw subscriptions to observe transitions, e.g. log “session expired” when onAccountsChanged fires with an empty array.
Connect and read accounts
Section titled “Connect and read accounts”import { useAccounts, useActiveAccount, useConnect } from "@sigilry/react";
export function WalletStatus() { const { connect, isPending } = useConnect(); const { data: accounts, isConnected } = useAccounts(); const { data: activeAccount } = useActiveAccount();
if (!isConnected) { return ( <button onClick={connect} disabled={isPending}> {isPending ? "Connecting..." : "Connect"} </button> ); }
return ( <div> <p>Active: {activeAccount?.hint ?? "Unknown"}</p> <ul> {accounts.map((account) => ( <li key={account.partyId}>{account.partyId}</li> ))} </ul> </div> );}Ledger API request
Section titled “Ledger API request”import { useLedgerApi } from "@sigilry/react";import { zGetLedgerEndResponse } from "@sigilry/canton-json-api";
export function LedgerEndButton() { const { requestAsync, data, isPending } = useLedgerApi({ parse: zGetLedgerEndResponse.parse, });
const fetchLedgerEnd = async () => { await requestAsync({ requestMethod: "get", resource: "/v2/state/ledger-end", }); };
return ( <div> <button onClick={fetchLedgerEnd} disabled={isPending}> Fetch ledger end </button> {data && <span>Offset: {String(data.data.offset)}</span>} </div> );}Session awareness
Section titled “Session awareness”import { useSession } from "@sigilry/react";
export function SessionBadge() { const { sessionState, timeRemainingFormatted, reauthenticate } = useSession();
return ( <div> <span>Status: {sessionState.status}</span> <span>Time remaining: {timeRemainingFormatted}</span> {sessionState.status === "expiring_soon" && ( <button onClick={reauthenticate}>Refresh session</button> )} </div> );}Key exports
Section titled “Key exports”CantonReactProvider,useCantonuseConnect,useDisconnectuseAccounts,useActiveAccountuseLedgerApi,useLedgerEnd,useLedgerUpdatesuseContractStream,useActiveContracts,useExerciseChoice,useSubmitCommanduseSession,parseJwtuseDiscovery,WalletPicker(+DiscoveredWallet,SpliceProviderInfotypes)
Connection state
Section titled “Connection state”useCanton() exposes a discriminated union connectionState:
disconnectedconnectingconnected(includesaccountsandnetworkId)session_expirederror