Canton dApp quickstart: zero to a working wallet connection
Get a full wallet connection flow working in your React app in 3 steps. By the end of this guide, your users will be able to connect any Canton wallet.
Coming from Ethereum? The connect layer below will feel familiar, and wagmi for Canton maps the hooks you already know onto these ones. Read it before you port a read path, because that is the part that does not translate.
Which Canton SDK do you need?
Searching for a Canton Network SDK returns several packages that do different jobs, and picking the wrong one costs a day. Here is the honest layout, with each package described by what its own publisher says it is.
| Package | Use it when |
|---|---|
| @canton-network/wallet-sdk | You are building a wallet, an exchange or a custody service and need to talk to the ledger directly: allocate parties, authenticate to synchronizers, sign and submit. Published as a Node and browser SDK for integrating with Canton Network. Not what a dApp frontend needs. |
| @canton-network/dapp-sdk | You are building a dApp frontend and want the first-party CIP-0103 implementation, published as a browser SDK for dApp development on the Canton Network. It ships its own discovery and wallet-picker component. If you want the reference implementation with no third-party layer, use this. |
| @partylayer/react | You are building a dApp frontend in React and want per-wallet quirks handled for you, a registry-backed wallet list, and a themeable connect UI. That is this page. It speaks CIP-0103 too, and adds adapters for wallets that do not. |
@canton-network/dapp-sdk is the shorter path and you should take it. PartyLayer earns its place when you need several wallets at once, including ones that are not CIP-0103 native and need an adapter, and when you would rather not discover each wallet's quirks yourself. The wallet directory lists which is which, with the evidence for each.Before you start
- React 18 or 19 and Node 18 or newer. Every code block below has a Next.js and a Vite tab. The code is plain React, so another bundler will work, but those two are the ones actually shown.
- A wallet to connect to. On devnet you do not need one installed to see the flow: the modal lists registry wallets and shows install prompts for the ones you lack. To complete a real connection you need one of the wallets from the directory that supports your target network.
- No ledger credentials, no participant node, no Daml. Connecting a wallet and reading the connected party needs none of that. You need them when you start submitting transactions, which is Token transfers.
Step 1: Install
Add the PartyLayer packages to your existing React project. If you're starting fresh with Vite, run npm create vite@latest my-dapp -- --template react-ts first.
Step 2: Wrap Your App
Add PartyLayerKit at the root of your component tree. It handles wallet discovery, session management, and theming automatically.
PartyLayerKit automatically registers all built-in wallet adapters (Console, Loop, Cantor8, Nightly), fetches the wallet registry, and sets up session persistence. Send is discovered through the CIP-0103 announce path, so it appears in the picker without being registered.@partylayer/react/query entrypoint) are built on TanStack Query, so they need a QueryClient in context. The base connect flow below (PartyLayerKit, ConnectButton, useAccount) works without it, but wrapping in QueryClientProvider now means the data hooks are ready when you reach for them. See the Hooks reference.Step 3: Add ConnectButton
Drop ConnectButton anywhere in your app. It renders a connect button when disconnected and shows the connected address with a disconnect dropdown when connected.
That's it! Your app now has a complete wallet connection flow with a polished modal, wallet auto-discovery, and session management.
Complete Example
Here's the full setup in a single file:
What's Happening Under the Hood?
When PartyLayerKit mounts, it:
- Creates a PartyLayerClient: the core SDK instance that manages all wallet operations
- Registers built-in adapters: Console, Loop, Cantor8, and Nightly wallet adapters (Send is served through the CIP-0103 announce path)
- Fetches the wallet registry: verified wallet metadata from
registry.partylayer.xyz - Groups CIP-0103 native wallets: those flagged
cip0103.native: truein the registry render in a dedicated picker section - Restores existing sessions: if a user was previously connected, the session is restored automatically
Using Session Data
Once connected, read the session reactively from any component with useAccount:
Verify it actually works
Three checks, in order. Each one fails differently, so knowing which passed tells you where to look.
- The modal opens and lists wallets. If it opens empty, the registry did not load. Check the browser console for a registry fetch error; the SDK falls back to adapter-only discovery, so an empty list means neither the registry nor any adapter produced a wallet.
- Selecting a wallet starts its flow. An extension wallet prompts, a QR wallet opens a window. If nothing happens for a relay wallet, you probably have not wired its pairing URI; see the per-wallet notes on the wallet directory.
useAccount()reports a party id. This is the one that matters. A connection that resolves without a party id is not a usable session, and the SDK now throws rather than inventing one, so you will see an error rather than a silent half-state.
If it does not work
| Symptom | Cause and fix |
|---|---|
| Modal lists no wallets | The registry fetch failed and no adapter was registered. PartyLayerKit registers the built-in set by default; if you passed your own adapters prop you have replaced it, not added to it. |
| A wallet is missing from the list | Some adapters need configuration and are skipped without it. Bron needs OAuth config and WalletConnect needs a project id, and both are simply absent when unconfigured rather than shown as broken. |
| Hydration mismatch in Next.js | Wallet detection reads window. Render the connect UI on the client. The ConnectButton handles this; a custom picker built on useWallets has to. |
| useSignMessage rejects on some wallets | Not every adapter implements it. Three registry wallets declare signMessage: false, so an app that gates login on a signed message has no path through them. Check the capability matrix before offering a wallet. |
| Signature does not verify against my backend | Console Wallet base64-encodes the message bytes before signing. If your backend verifies raw bytes the signatures will not match. |
Next Steps
Now that you have basic connectivity, explore more:
- PartyLayerKit: Configuration options (network, adapters, theme)
- ConnectButton: Customize the button appearance and behavior
- React Hooks: Use
useSignMessage,useSubmitTransaction, and the/querydata hooks (useDamlContract,useChoice, cost hooks) - Pattern Cookbook: Copy-paste recipes for the data hooks, optimistic updates, and Suspense
- Theming: Switch between light, dark, and custom themes
- Wallets & Adapters: Add custom wallet adapters or the Bron enterprise wallet