Skip to content

@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.

Terminal window
yarn add @sigilry/react @sigilry/dapp @sigilry/canton-json-api @tanstack/react-query
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>
);
}

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.

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.

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>
);
}
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>
);
}
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>
);
}
  • CantonReactProvider, useCanton
  • useConnect, useDisconnect
  • useAccounts, useActiveAccount
  • useLedgerApi, useLedgerEnd, useLedgerUpdates
  • useContractStream, useActiveContracts, useExerciseChoice, useSubmitCommand
  • useSession, parseJwt
  • useDiscovery, WalletPicker (+ DiscoveredWallet, SpliceProviderInfo types)

useCanton() exposes a discriminated union connectionState:

  • disconnected
  • connecting
  • connected (includes accounts and networkId)
  • session_expired
  • error