This walks you through detecting the Arkena wallet from a dApp, requesting connection, signing in with Canton (SIWC), and signing a test transaction. By the end you'll have all the pieces you need to wire Arkena into a real integration.
Detect the provider
The Arkena wallet injects
window.arkena(andwindow.canton) after page load. Injection is asynchronous, so detection needs to handle both already injected and not yet injected states.Codefunction getArkenaProvider(timeoutMs = 1000): Promise<typeof window.arkena | null> { if (typeof window === "undefined") return Promise.resolve(null); if (window.arkena) return Promise.resolve(window.arkena); return new Promise((resolve) => { const onReady = () => resolve(window.arkena); window.addEventListener("arkena#initialized", onReady, { once: true }); setTimeout(() => resolve(window.arkena ?? null), timeoutMs); }); }The
arkena#initializedevent is dispatched once on injection.Request connection
Codeconst provider = await getArkenaProvider(); if (!provider) throw new Error("Arkena wallet not detected"); const { accounts, network } = await provider.request({ method: "canton_connect", }); console.log("Connected as", accounts[0], "on", network);The user sees an approval popup with your origin and the requested permissions. If they reject, the request rejects with an
Errorwhosecodeis4001.Sign in with Canton
Most backend reads need a JWT scoped to the user's party. Get one with Sign-In with Canton:
Codeconst challenge = await fetch("/api/auth/nonce", { method: "POST", body: JSON.stringify({ partyId: accounts[0] }), headers: { "Content-Type": "application/json" }, }).then((r) => r.json()); const { signature } = await provider.request({ method: "canton_signMessage", params: { message: challenge.message }, }); const { token } = await fetch("/api/auth/verify", { method: "POST", body: JSON.stringify({ partyId: accounts[0], signature, nonce: challenge.nonce }), headers: { "Content-Type": "application/json" }, }).then((r) => r.json()); // Store `token` and attach as Authorization: Bearer <token> on subsequent calls.See Sign-In with Canton for the structured-message format and rotation rules.
Sign a test transaction
Most state-changing actions on Arkena follow a prepare → sign → execute loop. Here's the smallest example: transfer
0.1 CCto yourself (a no-op trade that exercises the full path).Code// 1. Prepare const prep = await fetch("/api/wallet/cc-transfer/prepare", { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: accounts[0], to: accounts[0], amount: "0.1", }), }).then((r) => r.json()); // 2. Sign const { signature: txSig } = await provider.request({ method: "canton_signTransaction", params: { hash: prep.hash, commandId: prep.commandId, hashingSchemeVersion: prep.hashingSchemeVersion, summary: prep.summary, }, }); // 3. Execute const result = await fetch("/api/wallet/cc-transfer/execute", { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: JSON.stringify({ commandId: prep.commandId, transaction: prep.transaction, signature: txSig, }), }).then((r) => r.json()); console.log("Settled:", result.commandId, result.status);Read The signing model for why this is three steps instead of one.
Where to go next
- Detect the Arkena provider — a reusable, SSR-safe helper.
- Connect and disconnect — the full lifecycle including mount-restore and graceful disconnect.
- Sign and submit transactions — every error case and how to handle it.
- Provider methods reference —
every
canton_*method, parameters, return types. - Connect Button recipe — a complete React component, copy-pasteable.