Wallets
Canton CIP-0103, 5N Loop, EVM and Auth0 — the connect paths, and the SDK quirks each one forced.
Auth is exclusive: exactly one of Canton, EVM, or Auth0 is live at a time. Connecting one
disconnects the others. On rehydrate, priority is canton > evm > auth0.
Only the Canton paths produce a trading party.
Canton — CIP-0103
lib/auth/canton-wallet.ts, lazy-loaded. Three target kinds:
| Target | How |
|---|---|
{kind:'extension', id} | A wallet detected via canton:announceProvider window events |
{kind:'remote', url} | Gateway URL → SDK RemoteAdapter (supported by the store, not exposed in the modal) |
{kind:'walletconnect'} | SDK WalletConnectAdapter — mobile and hardware wallets |
Connection goes through DiscoveryClient.connect(providerId), not DappSDK.connect().
DappSDK.connect()'s failure path calls waitForWalletPickerRetrySelection(), which reads a
window opened by the SDK's own picker popup — one we never open. So every failed connect rejected
with "Wallet picker is not open" while the real cause went to notifyWalletPickerError(),
which posts into that same absent window and drops it.
Verified: a dead gateway reports Failed to fetch through DiscoveryClient, and Wallet picker is not open through DappSDK.connect(). One of those is debuggable.
Two Canton extensions can answer each other's requests. Extensions share the window.canton
slot. WindowTransport routes by target but matches the response on the JSON-RPC id alone —
so an untargeted adapter can be answered by the wrong wallet. The RPC client is therefore built
from the active session, carrying the announced target.
After connect
The wallet signs the canonical Session-Auth blob once, opening an engine trading session
(sessionService.authorize → POST /v1/session-auth). Failure is non-fatal — the user is
connected but cannot trade, and the order form shows "Enable trading" to retry.
connectCanton(target); // connect + session-auth
cancelCantonConnect(); // abort in-flight — the SDK never rejects an abandoned prompt
enableCantonTrading(); // re-run session-auth after a refused signature
disconnectCanton(); // drops session and wallet linkWalletConnect specifics
- The adapter is registered once via
sdk.init({ additionalAdapters })insidecreateInstance(); later connects reuseconfiguredAdapters. chainIdiscanton:${config.networkId}— the SDK defaults tocanton:devnet, which is wrong on mainnet builds.- The pairing URI reaches the modal through
setPairingUriHandler→cantonPairingUriin the store. The adapter's built-inshowUriInPopupwrites into a window only the SDK's picker opens, so the QR would otherwise be dropped — and it creates a strayabout:blanktab viawindow.open("", "wallet-popup"), whichwithoutSdkQrPopup()neuters for the duration of the connect.
Reown AppKit and the Canton adapter both initialise WalletConnect Core with the same project id,
so WC logs Core is already initialized… called 2 times. Harmless today; the fix if it ever
misbehaves is a separate project id for the Canton adapter.
5N Loop
Not CIP-0103. @fivenorth/loop-sdk runs its own popup, QR dialog and WebSocket session, so it
bypasses DiscoveryClient entirely — connectCantonWallet routes {kind:'loop'} to
lib/auth/loop-wallet.ts.
It signs Ed25519, so fingerprint binding and engine session-auth verification are unchanged.
Three quirks worth knowing:
loop.connect() resolves void; the provider only arrives through the onAccept callback passed
to init(). Hence the callback→promise bridge, and init() must run exactly once.
The RPC is SIGN_RAW_MESSAGE — the canonical blob goes over as literal text, never base64,
whatever VITE_SIGN_MESSAGE_ENCODING says.
Its QR is a full-screen <dialog open> opened by attribute, not showModal() — so Escape is
inert — with no close control, sitting on top of our own Cancel. installEscapeHatch() adds
Escape + backdrop-click, rejecting with LoopCancelledError and calling loop.logout(), whose
clearLocalSession() tears the dialog down.
The wallet catalog
lib/auth/wallet-catalog.ts is a hand-maintained list of the 10 known Canton wallets. The Canton
tab is a single searchable card grid — no accordion, no per-wallet rows.
connectivity | Card behaviour |
|---|---|
cip0103 | Connect when installed, otherwise an Install link |
walletconnect | Connect via the WC adapter |
vendorSdk | Disabled — "Coming soon" |
unsupported | Disabled, but shown |
Unsupported wallets are shown disabled rather than hidden. The user's real question is "is my wallet supported?", and an absent entry answers that ambiguously.
findCatalogWallet resolves an announcement by longest matching token — Console Wallet
announces as loop, so a first-match scan would steal a "5N Loop" announcement for Console.
EVM and Mail
| Method | Stack | Role |
|---|---|---|
| EVM | Reown AppKit + wagmi + viem, mirrored by <ReownWalletSync> | Identity / SIWE proof |
Auth0 email + passkey, mirrored by <Auth0WalletSync> | Identity |
Both are tenant-gated: with VITE_AUTH0_DOMAIN and VITE_AUTH0_CLIENT_ID empty, the Mail tab
renders Unavailable and no Auth0Provider mounts at all.