From 23722598a7d2b2b78d332222b09f426fd1b3a504 Mon Sep 17 00:00:00 2001 From: 8bitpal Date: Tue, 26 May 2026 17:39:43 +0200 Subject: [PATCH 1/4] Add App Development module for dApp SDK and Wallet Gateway (#579) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the curriculum module requested in issue #579 and refreshes the integrations mirror against the latest splice-wallet-kernel main (c85656f). New curriculum module pages (appdev/modules/, Module 4 group): - m4-dapp-sdk.mdx — Introduces the dApp SDK in an app-development context: where it fits, the high-level vs Provider API split, window.canton discovery, the typical init/connect/listAccounts/ prepareExecute flow. - m4-wallet-gateway.mdx — Introduces the Wallet Gateway: why a mediator exists, what it provides (dApp API + User API + UI), the discovery and connection flow, when a team needs to run one, signing provider options, and operational realities relevant to dApp developers. Refreshed mirrors from upstream c85656f: - integrations/wallet-gateway/apis.mdx (hash c581db48) - integrations/wallet-gateway/signing-providers.mdx (hash 0e02eba4) Newly ported upstream pages (not previously in our docs): - integrations/dapp-sdk/overview.mdx — dApp SDK landing/overview - integrations/dapp-sdk/api-reference.mdx — Method reference - integrations/wallet-gateway/overview.mdx — Wallet Gateway landing - integrations/wallet-gateway/configuration-schema.mdx — Full JSON schema for the wallet-gateway-remote config file - integrations/wallet-gateway/deployment.mdx — Production deployment guide - integrations/dapp-building-examples.mdx — Ping and Portfolio sample dApps Navigation: - Added the two new curriculum pages to Module 4: Full-Stack Application Development. - Added the six new integration pages to their respective groups (Overview, dApp SDK, Wallet Gateway) under Integrations. Notes: - The 9 previously mirrored integration pages (download/usage/ best-practices/wallet-provider-integration/adapters-and-discovery for dApp SDK; configuration/usage/troubleshooting/download for Wallet Gateway, plus dapp-building-overview) are already in sync with upstream c85656f — no refresh needed. - Relative `.md` links in the ported pages (e.g. `(configuration/index.md)`) were rewritten to absolute Mintlify slugs. - The upstream `> [!IMPORTANT]` GitHub-flavored callout in wallet-gateway/index.md was converted to a `` block. --- docs-main/appdev/modules/m4-dapp-sdk.mdx | 118 +++ .../appdev/modules/m4-wallet-gateway.mdx | 96 +++ docs-main/docs.json | 12 +- .../integrations/dapp-building-examples.mdx | 39 + .../integrations/dapp-sdk/api-reference.mdx | 114 +++ docs-main/integrations/dapp-sdk/overview.mdx | 54 ++ .../integrations/wallet-gateway/apis.mdx | 6 +- .../wallet-gateway/configuration-schema.mdx | 779 ++++++++++++++++++ .../wallet-gateway/deployment.mdx | 199 +++++ .../integrations/wallet-gateway/overview.mdx | 45 + .../wallet-gateway/signing-providers.mdx | 11 +- 11 files changed, 1463 insertions(+), 10 deletions(-) create mode 100644 docs-main/appdev/modules/m4-dapp-sdk.mdx create mode 100644 docs-main/appdev/modules/m4-wallet-gateway.mdx create mode 100644 docs-main/integrations/dapp-building-examples.mdx create mode 100644 docs-main/integrations/dapp-sdk/api-reference.mdx create mode 100644 docs-main/integrations/dapp-sdk/overview.mdx create mode 100644 docs-main/integrations/wallet-gateway/configuration-schema.mdx create mode 100644 docs-main/integrations/wallet-gateway/deployment.mdx create mode 100644 docs-main/integrations/wallet-gateway/overview.mdx diff --git a/docs-main/appdev/modules/m4-dapp-sdk.mdx b/docs-main/appdev/modules/m4-dapp-sdk.mdx new file mode 100644 index 00000000..a9b1f921 --- /dev/null +++ b/docs-main/appdev/modules/m4-dapp-sdk.mdx @@ -0,0 +1,118 @@ +--- +title: "dApp SDK" +description: "Connect a Canton Network dApp to user wallets using the dApp SDK — a TypeScript library implementing CIP-103." +--- + +The dApp SDK (`@canton-network/dapp-sdk`) is a TypeScript library that lets a dApp connect to user wallets on Canton Network. It implements the dApp API defined by [CIP-103](https://github.com/canton-foundation/cips/blob/main/cip-0103/cip-0103.md) — a vendor-neutral JSON-RPC 2.0 protocol that any compliant wallet can speak — so your dApp interoperates with any Canton-compatible wallet rather than being tied to a single vendor. + +This page introduces what the SDK does, where it fits in a dApp's architecture, and what the typical usage flow looks like. For step-by-step API details and migration notes, see the [dApp SDK integration docs](/integrations/dapp-sdk/overview). + +## Where it fits + +A typical Canton Network dApp has three external parties to talk to: the user's wallet, a Canton validator (for the Ledger API), and a signing provider that holds the user's keys. The dApp SDK abstracts the first of those three. + +``` +┌─────────────┐ dApp API (CIP-103) ┌──────────────────┐ Ledger API ┌──────────────────┐ +│ Your dApp │ ◄─────────────────────────► │ Wallet Gateway │ ◄─────────────────► │ Canton Validator │ +│ (dApp SDK) │ (HTTP / postMessage) │ │ │ │ +└─────────────┘ └──────────────────┘ └──────────────────┘ + │ + ▼ + ┌──────────────────┐ + │ Signing Provider │ + └──────────────────┘ +``` + +- Your dApp uses the dApp SDK to call the **dApp API** (`connect`, `listAccounts`, `prepareExecute`, `signMessage`, `ledgerApi`, etc.). +- The wallet — typically a [Wallet Gateway](/appdev/modules/m4-wallet-gateway) — implements the dApp API, manages user sessions, and forwards requests to the validator and signing provider. +- The SDK abstracts the transport (HTTP for remote gateways, `postMessage` for browser-extension wallets) so the same dApp code works against either. + +## Two levels of API + +The SDK exposes two interfaces over the same protocol: + +- **High-level dApp SDK** — `sdk.connect()`, `sdk.listAccounts()`, `sdk.prepareExecute(...)`. Recommended for most applications. +- **Provider API** — Low-level [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)-style interface: `provider.request({ method, params })`. Useful when you need direct protocol access or are integrating with existing provider-based infrastructure. + +You can mix the two: `sdk.getConnectedProvider()` returns the active CIP-103 provider so you can drop down to `provider.request(...)` when needed. + +## Provider discovery (`window.canton`) + +For browser-extension wallets, the SDK exposes an EIP-1193-style provider at `window.canton`, mirroring the pattern Ethereum dApps use. This is how a dApp running in a browser discovers a wallet that's been installed as an extension. The SDK handles registration, the EIP-1193 events, and the user-facing wallet picker. For details and customization, see [Adapter registration & wallet discovery](/integrations/dapp-sdk/adapters-and-discovery). + +## Typical flow + +A common dApp lifecycle: + +1. **Initialize** — Call `sdk.init()` once at app startup. This registers adapters and attempts to restore any previously connected session, without opening the picker. + + ```typescript + import * as sdk from '@canton-network/dapp-sdk' + + await sdk.init() + ``` + +2. **Connect** — When the user clicks *Connect Wallet*, call `sdk.connect()`. If multiple wallets are registered, the SDK shows the picker; otherwise it proceeds directly. The wallet may redirect the user to its UI to log in (OAuth or self-signed JWT). + + ```typescript + const result = await sdk.connect() + if (result.isConnected) { + // session established; dApp API calls now work + } + ``` + +3. **Read accounts** — List the parties (accounts) the wallet has for this user and find the primary one. + + ```typescript + const accounts = await sdk.listAccounts() + const primary = accounts.find(a => a.primary) + ``` + +4. **Submit a transaction** — `prepareExecute` runs the full prepare → user-approval → sign → submit lifecycle. The wallet shows the user the transaction details and asks for approval; if granted, the signing provider produces the signature and the wallet submits to the validator. + + ```typescript + await sdk.prepareExecute({ + commands: [ + { + CreateCommand: { + templateId: '#AdminWorkflows:Canton.Internal.Ping:Ping', + createArguments: { + id: `ping-${Date.now()}`, + initiator: primary.partyId, + responder: primary.partyId, + }, + }, + }, + ], + }) + ``` + +5. **React to events** — The SDK emits `statusChanged`, `accountsChanged`, and `txChanged` so your UI stays in sync with the wallet's state. Remember to remove listeners when components unmount. + + ```typescript + sdk.onTxChanged(tx => { + if (tx.status === 'executed') { + console.log('update id:', tx.payload.updateId) + } + }) + ``` + +For the full method surface — including `signMessage`, `ledgerApi` proxying, `getActiveNetwork`, and the event subscription API — see the [API reference](/integrations/dapp-sdk/api-reference). + +## When to drop down to the Provider API + +You typically reach for `provider.request(...)` directly when: + +- The method you need isn't yet wrapped by the high-level SDK +- You're integrating with existing CIP-103-aware code that expects EIP-1193 semantics +- You're writing a wallet provider yourself and want a thin client for testing + +The Provider API and the high-level SDK call the same dApp API under the hood — there's no protocol-level difference. + +## Where to go next + +- [Installation](/integrations/dapp-sdk/download) — install the SDK from npm +- [Usage guide](/integrations/dapp-sdk/usage) — every method shown side-by-side in SDK and Provider API form +- [Best practices](/integrations/dapp-sdk/best-practices) — recommended patterns for production dApps +- [Wallet Gateway module](/appdev/modules/m4-wallet-gateway) — the server side of the dApp API +- [Examples](/integrations/dapp-building-examples) — Ping and Portfolio sample dApps diff --git a/docs-main/appdev/modules/m4-wallet-gateway.mdx b/docs-main/appdev/modules/m4-wallet-gateway.mdx new file mode 100644 index 00000000..7116a31a --- /dev/null +++ b/docs-main/appdev/modules/m4-wallet-gateway.mdx @@ -0,0 +1,96 @@ +--- +title: "Wallet Gateway" +description: "The server that mediates between Canton Network dApps, validator nodes, and signing providers — and how to think about it when building a dApp." +--- + +The Wallet Gateway is a JavaScript/TypeScript server that sits between a dApp and the Canton Network. It exposes the dApp API (the CIP-103 JSON-RPC protocol that the [dApp SDK](/appdev/modules/m4-dapp-sdk) speaks) and translates calls into Ledger API requests against a Canton validator and signing requests to a configured signing provider. + +This page covers what the Wallet Gateway is, the role it plays in a dApp deployment, and when you need to run your own versus rely on one a user already has. For setup, configuration, and operational details, see the [Wallet Gateway integration docs](/integrations/wallet-gateway/overview). + +## Why a mediator exists + +Canton's privacy model means a validator node only sees the subset of ledger state relevant to its parties — there is no single global state shared across all nodes. A dApp can't just "talk to the chain"; it has to talk to a validator that hosts the specific party the user wants to act as, and it needs that validator to accept commands authorized by the right signing key. + +The Wallet Gateway centralizes that responsibility. From the dApp's perspective, the Gateway looks like a wallet: a single JSON-RPC endpoint that knows the user's accounts, can sign on their behalf (via a signing provider), and submits transactions to the right validator. From the validator's perspective, the Gateway is an authenticated client of the Ledger API. + +## What it provides + +- **dApp API** — The CIP-103 JSON-RPC surface dApps call. Methods like `connect`, `listAccounts`, `prepareExecute`, `signMessage`, and `ledgerApi` proxying. +- **User API** — A separate JSON-RPC surface for users and automation: session management, identity-provider configuration, wallet creation, network configuration, and direct signing. +- **User UI** — A web interface served by the Gateway that uses the User API. Users log in, create and manage wallets, approve dApp transactions, and change settings. Custom integrations can call the User API directly instead. +- **Network configuration** — A Gateway can be configured for multiple Canton networks (DevNet, TestNet, MainNet, custom synchronizers). Users pick which network a wallet lives on. +- **Signing provider integration** — Pluggable backends for signing: a Canton participant (for in-band signing), the Gateway itself (test only), Fireblocks, Blockdaemon, etc. +- **Storage backends** — In-memory (dev), SQLite, or PostgreSQL for sessions, wallet metadata, and transaction history. + +## Where it fits + +``` +┌─────────────┐ dApp API (CIP-103) ┌──────────────────┐ Ledger API ┌──────────────────┐ +│ Your dApp │ ◄────────────────────────► │ Wallet Gateway │ ◄─────────────────► │ Canton Validator │ +│ (dApp SDK) │ (HTTP / postMessage) │ ┌────────────┐ │ │ │ +└─────────────┘ │ │ User API │ │ Signing └──────────────────┘ + │ │ │ User UI │ │ ┌──────────────────┐ + │ User interactions │ └────────────┘ │ ◄──►│ Signing Provider │ + └──────────────────────────────────►│ │ │ (Participant, │ + (User UI / User API) └──────────────────┘ │ Fireblocks, …) │ + └──────────────────┘ +``` + +- **dApp → Gateway**: CIP-103 JSON-RPC over HTTP or `postMessage`. +- **User → Gateway**: User UI (browser) or User API (programmatic). +- **Gateway → Canton**: Authenticated requests to a validator's Ledger API. +- **Gateway → Signing Provider**: Signing requests to whichever provider the user's wallet is configured for. + +## Discovery and connection flow + +A typical user-facing flow: + +1. **Discovery** — A dApp finds available Wallet Gateways via well-known URLs, a registry, or a browser extension that injects `window.canton`. Each Gateway publishes its base URL and supported networks. +2. **Connect** — The user picks a Gateway. The dApp calls `sdk.connect()`. The Gateway redirects the user to its User UI to log in if they don't have a session yet (OAuth or self-signed JWT). +3. **Session** — After login the Gateway returns a JWT. The dApp SDK uses it to authenticate subsequent dApp API calls. +4. **Transaction** — When the dApp calls `prepareExecute`, the Gateway prepares the transaction with the validator, shows the user the details in the User UI for approval, asks the signing provider to sign, then submits to the validator. The dApp receives the result and `txChanged` events. + +## Do you need to run one? + +For most dApps the answer is no — users bring their own Wallet Gateway. There are typical scenarios: + +| Scenario | Who runs the Gateway | +|---|---| +| Public dApp, retail users | Users connect via a browser-extension wallet or a hosted Gateway run by a wallet provider. | +| Enterprise dApp, known users | The dApp operator or a partner runs a Gateway shared by all users. | +| Internal tools and automation | The team runs a Gateway in their own infrastructure, often paired with an internal signing provider. | +| Development | You run a local Gateway alongside the validator, typically with the in-Gateway signing provider for testing. | + +If you're building a dApp, you mainly care about the **client-facing dApp API** — that's what the dApp SDK calls. The Gateway is the production target on the other end of those calls. + +If you're operating Canton infrastructure or running a dApp at scale, you'll likely run a Gateway too. The [Getting Started](/integrations/wallet-gateway/download) guide walks through standing one up. + +## Signing providers + +The Wallet Gateway does not sign transactions itself in production. It delegates to a configured signing provider: + +- **Canton participant** — A participant node that holds the party's signing key in its key store. The Gateway authenticates to the participant's admin API and requests signatures over the Ledger API. This is the default for SV-operated and enterprise deployments. +- **Internal (Gateway-managed)** — The Gateway generates and stores the key itself. **Test and development only** — not suitable for production. +- **Fireblocks** — Signing requests are forwarded to a Fireblocks workspace. Keys live in Fireblocks' MPC infrastructure. +- **Blockdaemon** — Same pattern with Blockdaemon's key management. + +A single Gateway can host wallets across different signing providers; the provider is configured per wallet. + +For configuration details, see [Signing Providers](/integrations/wallet-gateway/signing-providers). + +## What a dApp developer should know + +Even if you'll never operate a Gateway, two operational realities affect how you build a dApp: + +- **The user can pick the network at the Gateway level.** Your dApp should not assume `da-mainnet` — call `sdk.getActiveNetwork()` and react to `accountsChanged`. +- **The signing provider determines transaction latency.** A local participant signs in milliseconds; an external HSM-backed provider can take seconds. Don't lock the UI on `prepareExecute`; subscribe to `txChanged` and let the user keep working. + +## Where to go next + +- [Wallet Gateway overview](/integrations/wallet-gateway/overview) — what it is, what it provides, and what's in the integration docs +- [Getting Started](/integrations/wallet-gateway/download) — install and run a Gateway +- [Configuration](/integrations/wallet-gateway/configuration) — full config-file reference +- [APIs](/integrations/wallet-gateway/apis) — dApp API and User API method reference +- [Signing Providers](/integrations/wallet-gateway/signing-providers) — how to wire up each backend +- [dApp SDK module](/appdev/modules/m4-dapp-sdk) — the client side of the dApp API +- [Examples](/integrations/dapp-building-examples) — Ping and Portfolio sample dApps that talk to a Wallet Gateway diff --git a/docs-main/docs.json b/docs-main/docs.json index 989b31af..a2980ef4 100644 --- a/docs-main/docs.json +++ b/docs-main/docs.json @@ -200,6 +200,8 @@ "appdev/quickstart/running-the-demo", "appdev/modules/m4-backend-dev", "appdev/modules/m4-frontend-dev", + "appdev/modules/m4-dapp-sdk", + "appdev/modules/m4-wallet-gateway", "appdev/modules/m4-json-api-tutorial", "appdev/modules/m4-canton-coin", "appdev/modules/m4-featured-app-activity-marker", @@ -505,12 +507,14 @@ "pages": [ "integrations/overview", "integrations/integration-patterns", - "integrations/dapp-building-overview" + "integrations/dapp-building-overview", + "integrations/dapp-building-examples" ] }, { "group": "dApp SDK", "pages": [ + "integrations/dapp-sdk/overview", "integrations/dapp-sdk/download", { "group": "dApp SDK Integration Guide", @@ -518,7 +522,8 @@ "integrations/dapp-sdk/usage", "integrations/dapp-sdk/adapters-and-discovery", "integrations/dapp-sdk/wallet-provider-integration", - "integrations/dapp-sdk/best-practices" + "integrations/dapp-sdk/best-practices", + "integrations/dapp-sdk/api-reference" ] } ] @@ -526,8 +531,11 @@ { "group": "Wallet Gateway", "pages": [ + "integrations/wallet-gateway/overview", "integrations/wallet-gateway/download", "integrations/wallet-gateway/configuration", + "integrations/wallet-gateway/configuration-schema", + "integrations/wallet-gateway/deployment", { "group": "Wallet Gateway Integration Guide", "pages": [ diff --git a/docs-main/integrations/dapp-building-examples.mdx b/docs-main/integrations/dapp-building-examples.mdx new file mode 100644 index 00000000..7bf4a71f --- /dev/null +++ b/docs-main/integrations/dapp-building-examples.mdx @@ -0,0 +1,39 @@ +--- +title: "Examples" +description: "Sample dApps (Ping, Portfolio) demonstrating dApp SDK and Wallet Gateway integration." +--- + +{/* COPIED_START source="wallet-gateway:docs/dapp-building/examples/index.md" hash="6a5aa740" */} + +The following example dApps demonstrate how to use the dApp SDK and Wallet Gateway. You can find them in the [`/examples`](https://github.com/canton-network/wallet/tree/main/examples) directory of the repository. + +## Ping + +A minimal example dApp that imports the dApp SDK and communicates with a Wallet Gateway. Built with React + TypeScript using the Vite template, this is the best starting point for understanding the basics of dApp SDK integration. + +**Running:** + +```bash +yarn install +yarn dev +``` + +[Source code](https://github.com/canton-network/wallet/tree/main/examples/ping) + +## Portfolio + +A more complete example dApp that showcases a minimal but functional portfolio application using the dApp SDK and Wallet Gateway. Built with React + TypeScript using Vite, it demonstrates real-world patterns including holdings, allocations, transactions, and transfer workflows. + +**Building and running:** + +Because this example has a number of dependencies, it is recommended to use the scripts in the root repository for development: + +```bash +yarn build:all +yarn start:all # this service lives at http://localhost:8081 +yarn stop:all +``` + +[Source code](https://github.com/canton-network/wallet/tree/main/examples/portfolio) + +{/* COPIED_END */} diff --git a/docs-main/integrations/dapp-sdk/api-reference.mdx b/docs-main/integrations/dapp-sdk/api-reference.mdx new file mode 100644 index 00000000..2cc2f6c6 --- /dev/null +++ b/docs-main/integrations/dapp-sdk/api-reference.mdx @@ -0,0 +1,114 @@ +--- +title: "API Reference" +description: "Method-by-method reference for the dApp SDK and the underlying CIP-103 Provider API." +--- + +{/* COPIED_START source="wallet-gateway:docs/dapp-building/dapp-sdk/api-reference.md" hash="48132304" */} + +The dApp SDK implements the **dApp API** as defined in [CIP-0103](https://github.com/canton-foundation/cips/blob/main/cip-0103/cip-0103.md). The API provides a standardized, transport-agnostic interface that enables decentralized applications (dApps) to securely interact with the Canton Network via Wallets. + +The dApp API exists in two variants, each targeting a different deployment model and interaction pattern. + +## OpenRPC Specifications + +The machine-readable API specifications are maintained in the repository: + +- **Sync API**: [openrpc-dapp-api.json](https://github.com/canton-network/wallet/blob/main/api-specs/openrpc-dapp-api.json) +- **Async API**: [openrpc-dapp-remote-api.json](https://github.com/canton-network/wallet/blob/main/api-specs/openrpc-dapp-remote-api.json) + +## Synchronous dApp API (Sync API) + +The **Synchronous dApp API** is intended for clients that have direct access to a Wallet, such as browser extensions or desktop applications. +In this model, methods and events are executed in a standard request-response fashion, allowing dApps to perform ledger reads, transaction preparation, and signing operations immediately. + +This variant is the canonical choice for end-user applications, providing a familiar synchronous programming model and minimizing latency between user actions and dApp responses. + +**Methods:** + +| Method | Output | Description | +| ------------------- | ------------- | --------------------------------------------------------------- | +| `connect` | ConnectResult | Establishes a connection to the Wallet | +| `disconnect` | void | Closes the session between the client and the provider | +| `isConnected` | ConnectResult | Indicates connectivity to the Wallet | +| `status` | StatusEvent | Contains information regarding the connected Wallet and Network | +| `getActiveNetwork` | Network | Details of the connected network | +| `listAccounts` | Account[] | Lists all accounts the user has access to | +| `getPrimaryAccount` | Account | Returns the single account set as primary | +| `signMessage` | string | Signs an arbitrary string message | +| `prepareExecute` | void | Prepares, signs, and executes Daml commands | +| `ledgerApi` | string | Proxies requests to the JSON Ledger API | + +**Events:** + +| Event | Payload | Description | +| ----------------- | -------------------- | ------------------------------------------------------------------------------------- | +| `accountsChanged` | AccountsChangedEvent | Emitted when accounts change; contains all accounts and indicates the primary account | +| `statusChanged` | StatusEvent | Emitted when provider status changes (authentication, network connectivity) | +| `txChanged` | TxChangedEvent | Announces changes to the lifecycle of initiated transactions | + +## Asynchronous dApp API (Async API) + +The **Asynchronous dApp API** is designed for server-side Wallets or Wallets deployed as remote services, where synchronous interactions are not feasible due to transport limitations (e.g., HTTP request timeouts) or the need for multi-step user authorization. + +This variant mirrors the Sync API but decomposes operations that require user interaction into multi-phase workflows. For such operations, the API returns a `userUrl` that directs the user to complete the required action (e.g., login or transaction approval), and emits the corresponding event once the action is finalized. + +**Key Differences from Sync API:** + +| Method | Output | Difference | +| ---------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `connect` | { ...ConnectResult, userUrl: string } | If no prior connection exists, returns a `userUrl` pointing to a login facility. After successful login, a `connected` event is emitted. | +| `prepareExecute` | { userUrl: string } | Returns a `userUrl` pointing to a review facility where the user can approve or decline signing. A `txChanged` event is emitted after the prepare phase completes. | + +**Additional Event:** + +| Event | Payload | Description | +| ----------- | ----------- | -------------------------------------------------------------------------- | +| `connected` | StatusEvent | Same payload as `statusChanged` but only emitted as part of the login flow | + +## Choosing Between Sync and Async API + +| Consideration | Sync API | Async API | +| -------------------- | ---------------------------------------- | ------------------------------------ | +| **Deployment Model** | Client-side (browser extension, desktop) | Server-side, remote custody services | +| **User Interaction** | Direct, real-time | Via `userUrl` redirects | +| **Blocking Calls** | Supported | Not supported | +| **Transport** | postMessage, in-app bridges | HTTPS, SSE | + +Clients such as frontends that work against the Sync API **should** also work against a server exposing the Async API. The dApp SDK provides this compatibility by implementing the Sync API interface while relaying calls to an Async API server internally. + +## Provider Interface + +Both API variants are accessed through a **Provider** interface, following the pattern established by [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193): + +```typescript +interface Provider { + request(args: { + method: string + params?: unknown[] | Record + }): Promise + on(event: string, listener: (...args: T[]) => void): Provider + emit(event: string, ...args: T[]): boolean + removeListener(event: string, listener: (...args: T[]) => void): Provider +} +``` + +## Error Codes + +The dApp API adopts standardized error codes from [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) and [EIP-1474](https://eips.ethereum.org/EIPS/eip-1474): + +| Code | Message | Meaning | +| ------ | --------------------- | ---------------------------------------------------- | +| 4001 | User Rejected Request | The user rejected the request | +| 4100 | Unauthorized | The requested method/account has not been authorized | +| 4200 | Unsupported Method | The Provider does not support the requested method | +| 4900 | Disconnected | The Provider is disconnected from all chains | +| 4901 | Chain Disconnected | The Provider is not connected to the requested chain | +| -32700 | Parse error | Invalid JSON | +| -32600 | Invalid request | JSON is not a valid request object | +| -32601 | Method not found | Method does not exist | +| -32602 | Invalid params | Invalid method parameters | +| -32603 | Internal error | Internal JSON-RPC error | + +For the complete specification, refer to [CIP-0103](https://github.com/canton-foundation/cips/blob/main/cip-0103/cip-0103.md). + +{/* COPIED_END */} diff --git a/docs-main/integrations/dapp-sdk/overview.mdx b/docs-main/integrations/dapp-sdk/overview.mdx new file mode 100644 index 00000000..838be34d --- /dev/null +++ b/docs-main/integrations/dapp-sdk/overview.mdx @@ -0,0 +1,54 @@ +--- +title: "Overview" +description: "Introduction to the dApp SDK — a TypeScript library for connecting Canton Network dApps to wallets via CIP-103." +--- + +{/* COPIED_START source="wallet-gateway:docs/dapp-building/dapp-sdk/index.md" hash="a7fcee8d" */} + +The dApp SDK is a TypeScript library for building decentralized applications on the Canton Network. It provides a high-level interface for connecting to Canton wallets, managing accounts, signing messages, and executing transactions. + +The SDK implements the dApp API as defined in [CIP-0103](https://github.com/canton-foundation/cips/blob/main/cip-0103/cip-0103.md), establishing a vendor-neutral standard that enables any dApp to interoperate with any Canton-compatible wallet. + +**Overview** + +The dApp SDK decouples your application from wallet implementations, allowing users to choose their preferred wallet and signing provider. The SDK handles: + +- **Wallet Connectivity** - Connect, disconnect, and monitor connection status +- **Account Management** - List accounts, get the primary account, and respond to account changes +- **Transaction Signing** - Request user approval and signatures for Daml transactions +- **Message Signing** - Sign arbitrary messages for authentication or verification +- **Ledger API Access** - Proxy authenticated requests to the Canton JSON Ledger API +- **Real-time Events** - Subscribe to status changes, account changes, and transaction lifecycle events + +**Architecture** + +The SDK provides two levels of abstraction: + +1. **dApp SDK** (recommended) - High-level methods like `sdk.connect()`, `sdk.listAccounts()`, and `sdk.prepareExecute()` for common operations +2. **Provider API** - Low-level [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)-compatible interface using `provider.request({ method, params })` for direct API access + +Both interfaces work with the same underlying dApp API, so you can mix and match as needed. + +**Transport Support** + +The SDK supports multiple transport mechanisms to connect to different wallet types: + +- **postMessage** - For browser extension wallets that inject a provider into the page +- **HTTP/SSE** - For remote wallets and server-side wallet gateways + +The SDK automatically handles the transport layer, allowing your dApp to work with both local and remote wallets without code changes. + +**Provider Discovery** + +When using browser extension wallets, the SDK exposes an EIP-1193-compatible provider at `window.canton`, following the pattern established by Ethereum wallets. This enables wallet discovery and connection in browser environments. For extension authors, the SDK also supports namespace scanning, EIP-6963-style announcement events, and targeted `postMessage` routing. See the docs section [Wallet providers](/integrations/dapp-sdk/wallet-provider-integration). + +## Contents + +- [Installation](/integrations/dapp-sdk/download) +- [Usage](/integrations/dapp-sdk/usage) +- [Adapters & Discovery](/integrations/dapp-sdk/adapters-and-discovery) +- [API Reference](/integrations/dapp-sdk/api-reference) +- [Wallet providers](/integrations/dapp-sdk/wallet-provider-integration) +- [Best Practices](/integrations/dapp-sdk/best-practices) + +{/* COPIED_END */} diff --git a/docs-main/integrations/wallet-gateway/apis.mdx b/docs-main/integrations/wallet-gateway/apis.mdx index 64521620..72886abb 100644 --- a/docs-main/integrations/wallet-gateway/apis.mdx +++ b/docs-main/integrations/wallet-gateway/apis.mdx @@ -3,7 +3,7 @@ title: "APIs" description: "Wallet Gateway dApp API and User API reference" --- -{/* COPIED_START source="wallet-gateway:docs/dapp-building/wallet-gateway/apis/index.md@82ec39c9" hash="dbda9001" */} +{/* COPIED_START source="wallet-gateway:docs/dapp-building/wallet-gateway/apis/index.md" hash="c581db48" */} The Wallet Gateway exposes two JSON-RPC 2.0 APIs: one for dApps interactions and one for user interactions. Both APIs use the same base URL but different paths. @@ -28,7 +28,7 @@ Authorization: Bearer **Full API Specification:** -The complete OpenRPC specification is available at [openrpc-dapp-api.json](https://github.com/canton-network/wallet-gateway/blob/main/api-specs/openrpc-dapp-api.json). +The complete OpenRPC specification is available at [openrpc-dapp-api.json](https://github.com/canton-network/wallet/blob/main/api-specs/openrpc-dapp-api.json). ## User API Reference @@ -68,7 +68,7 @@ Most User API methods require authentication via JWT token. However, the followi **Full API Specification:** -The complete OpenRPC specification is available at [openrpc-user-api.json](https://github.com/canton-network/wallet-gateway/blob/main/api-specs/openrpc-user-api.json). +The complete OpenRPC specification is available at [openrpc-user-api.json](https://github.com/canton-network/wallet/blob/main/api-specs/openrpc-user-api.json). ## Server-Sent Events (SSE) Support diff --git a/docs-main/integrations/wallet-gateway/configuration-schema.mdx b/docs-main/integrations/wallet-gateway/configuration-schema.mdx new file mode 100644 index 00000000..39bbb8b3 --- /dev/null +++ b/docs-main/integrations/wallet-gateway/configuration-schema.mdx @@ -0,0 +1,779 @@ +--- +title: "Configuration Schema" +description: "Full JSON schema for the Wallet Gateway configuration file, generated by wallet-gateway-remote --config-schema." +--- + +{/* COPIED_START source="wallet-gateway:docs/dapp-building/wallet-gateway/configuration/schema.md" hash="42878326" */} + +The contents of this file was generated by the following command: + +```shell +npx @canton-network/wallet-gateway-remote@latest --config-schema +``` + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "kernel": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "publicUrl": { + "description": "The public base URL of the gateway, if available (e.g. https://wallet.example.com). This determines what browsers will try to use to connect, and is useful when using reverse proxies. If omitted, this will be derived from the server configuration.", + "type": "string" + }, + "clientType": { + "anyOf": [ + { + "type": "string", + "const": "browser" + }, + { + "type": "string", + "const": "desktop" + }, + { + "type": "string", + "const": "mobile" + }, + { + "type": "string", + "const": "remote" + } + ] + } + }, + "required": ["id", "clientType"], + "additionalProperties": false + }, + "server": { + "type": "object", + "properties": { + "port": { + "default": 3030, + "description": "The port on which the NodeJS service will listen. Defaults to 3030.", + "type": "number" + }, + "dappPath": { + "default": "/api/v0/dapp", + "description": "The path serving the dapp API. Defaults /api/v0/dapp", + "type": "string" + }, + "userPath": { + "default": "/api/v0/user", + "description": "The path serving the user API. Defaults /api/v0/user", + "type": "string" + }, + "allowedOrigins": { + "default": "*", + "description": "Allowed CORS origins, typically corresponding to which external dApps are allowed to connect. Use \"*\" to allow all origins, or set an array of origin strings.", + "anyOf": [ + { + "type": "string", + "const": "*" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "host": { + "deprecated": true, + "description": "The host interface the server binds to. Deprecated as the service always binds to the local machine network interface. Will be removed in a future release.", + "type": "string" + }, + "tls": { + "deprecated": true, + "description": "Deprecated, this option no longer has any effect. Will be removed in a future release.", + "type": "boolean" + }, + "requestSizeLimit": { + "default": "1mb", + "description": "The maximum size of incoming requests. Defaults to 1mb.", + "type": "string" + }, + "requestRateLimit": { + "default": 10000, + "description": "The maximum number of requests per minute from a single IP address. Defaults to 10000.", + "type": "number" + }, + "trustProxy": { + "default": false, + "description": "Express trust proxy setting used to resolve client IP addresses when running behind reverse proxies/load balancers. Set this correctly in production (for example 1 for a single trusted proxy hop). Defaults to false.", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + { + "type": "string" + } + ] + }, + "admin": { + "description": "The JWT claim (e.g. \"sub\") identifying the admin user. If set, requests with a matching claim will be granted admin privileges.", + "type": "string" + } + }, + "required": [ + "port", + "dappPath", + "userPath", + "allowedOrigins", + "requestSizeLimit", + "requestRateLimit", + "trustProxy" + ], + "additionalProperties": false + }, + "logging": { + "type": "object", + "properties": { + "level": { + "description": "The log level for the gateway. If omitted, defaults to info.", + "type": "string", + "enum": ["trace", "debug", "info", "warn", "error", "fatal"] + }, + "format": { + "description": "The log format for the gateway. If omitted, defaults to pretty.", + "type": "string", + "enum": ["json", "pretty"] + } + }, + "additionalProperties": false, + "description": "Optional logging configuration. If omitted, defaults will be used." + }, + "store": { + "type": "object", + "properties": { + "connection": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "memory" + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "sqlite" + }, + "database": { + "type": "string" + } + }, + "required": ["type", "database"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "postgres" + }, + "host": { + "type": "string" + }, + "port": { + "type": "number" + }, + "user": { + "type": "string" + }, + "password": { + "type": "string" + }, + "database": { + "type": "string" + } + }, + "required": [ + "type", + "host", + "port", + "user", + "password", + "database" + ], + "additionalProperties": false + } + ] + } + }, + "required": ["connection"], + "additionalProperties": false + }, + "signingStore": { + "type": "object", + "properties": { + "connection": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "memory" + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "sqlite" + }, + "database": { + "type": "string" + } + }, + "required": ["type", "database"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "postgres" + }, + "host": { + "type": "string" + }, + "port": { + "type": "number" + }, + "user": { + "type": "string" + }, + "password": { + "type": "string" + }, + "database": { + "type": "string" + } + }, + "required": [ + "type", + "host", + "port", + "user", + "password", + "database" + ], + "additionalProperties": false + } + ] + } + }, + "required": ["connection"], + "additionalProperties": false + }, + "bootstrap": { + "type": "object", + "properties": { + "idps": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "self_signed" + }, + "issuer": { + "type": "string" + } + }, + "required": ["id", "type", "issuer"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "const": "oauth" + }, + "issuer": { + "type": "string" + }, + "configUrl": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "id", + "type", + "issuer", + "configUrl" + ], + "additionalProperties": false + } + ] + } + }, + "networks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "synchronizerId": { + "type": "string", + "minLength": 10, + "pattern": "::" + }, + "identityProviderId": { + "type": "string" + }, + "ledgerApi": { + "type": "object", + "properties": { + "baseUrl": { + "type": "string", + "format": "uri" + } + }, + "required": ["baseUrl"], + "additionalProperties": false + }, + "auth": { + "anyOf": [ + { + "oneOf": [ + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "authorization_code" + }, + "audience": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "clientId": { + "type": "string" + } + }, + "required": [ + "method", + "audience", + "scope", + "clientId" + ], + "additionalProperties": false, + "description": "Authorization code flow authentication configuration. This is used for browser-based application login." + }, + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "client_credentials" + }, + "audience": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "clientSecret": { + "type": "string" + } + }, + "required": [ + "method", + "audience", + "scope", + "clientId", + "clientSecret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "self_signed" + }, + "issuer": { + "type": "string" + }, + "audience": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "clientSecret": { + "type": "string" + } + }, + "required": [ + "method", + "issuer", + "audience", + "scope", + "clientId", + "clientSecret" + ], + "additionalProperties": false + } + ] + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "authorization_code" + }, + "audience": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "clientId": { + "type": "string" + } + }, + "required": [ + "method", + "audience", + "scope", + "clientId" + ], + "additionalProperties": false, + "description": "Authorization code flow authentication configuration. This is used for browser-based application login." + }, + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "client_credentials" + }, + "audience": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "clientSecretEnv": { + "type": "string" + } + }, + "required": [ + "method", + "audience", + "scope", + "clientId", + "clientSecretEnv" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "self_signed" + }, + "issuer": { + "type": "string" + }, + "audience": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "clientSecretEnv": { + "type": "string" + } + }, + "required": [ + "method", + "issuer", + "audience", + "scope", + "clientId", + "clientSecretEnv" + ], + "additionalProperties": false + } + ] + } + ] + }, + "adminAuth": { + "anyOf": [ + { + "oneOf": [ + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "authorization_code" + }, + "audience": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "clientId": { + "type": "string" + } + }, + "required": [ + "method", + "audience", + "scope", + "clientId" + ], + "additionalProperties": false, + "description": "Authorization code flow authentication configuration. This is used for browser-based application login." + }, + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "client_credentials" + }, + "audience": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "clientSecret": { + "type": "string" + } + }, + "required": [ + "method", + "audience", + "scope", + "clientId", + "clientSecret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "self_signed" + }, + "issuer": { + "type": "string" + }, + "audience": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "clientSecret": { + "type": "string" + } + }, + "required": [ + "method", + "issuer", + "audience", + "scope", + "clientId", + "clientSecret" + ], + "additionalProperties": false + } + ] + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "authorization_code" + }, + "audience": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "clientId": { + "type": "string" + } + }, + "required": [ + "method", + "audience", + "scope", + "clientId" + ], + "additionalProperties": false, + "description": "Authorization code flow authentication configuration. This is used for browser-based application login." + }, + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "client_credentials" + }, + "audience": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "clientSecretEnv": { + "type": "string" + } + }, + "required": [ + "method", + "audience", + "scope", + "clientId", + "clientSecretEnv" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "self_signed" + }, + "issuer": { + "type": "string" + }, + "audience": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "clientSecretEnv": { + "type": "string" + } + }, + "required": [ + "method", + "issuer", + "audience", + "scope", + "clientId", + "clientSecretEnv" + ], + "additionalProperties": false + } + ] + } + ] + } + }, + "required": [ + "id", + "name", + "description", + "identityProviderId", + "ledgerApi", + "auth" + ], + "additionalProperties": false + } + } + }, + "required": ["idps", "networks"], + "additionalProperties": false + } + }, + "required": ["kernel", "server", "store", "signingStore", "bootstrap"], + "additionalProperties": false +} +``` + +{/* COPIED_END */} diff --git a/docs-main/integrations/wallet-gateway/deployment.mdx b/docs-main/integrations/wallet-gateway/deployment.mdx new file mode 100644 index 00000000..fdecbb19 --- /dev/null +++ b/docs-main/integrations/wallet-gateway/deployment.mdx @@ -0,0 +1,199 @@ +--- +title: "Deployment" +description: "How to deploy the Wallet Gateway server in production." +--- + +{/* COPIED_START source="wallet-gateway:docs/dapp-building/wallet-gateway/deployment/index.md" hash="1e85642e" */} + +This section outlines some recommendations for production deployments of the Wallet Gateway service. + +The service is available in both Docker and Helm variants: + +- **Docker Registry**: `ghcr.io/digital-asset/wallet-gateway/docker/wallet-gateway:` +- **Helm Repository**: `ghcr.io/digital-asset/wallet-gateway/helm/wallet-gateway:` + +Replace `` with the version you want to deploy. We don't currently publish `latest` tags. To determine which version to use, check either + +- The latest version displayed on the GHCR repo: https://github.com/digital-asset/wallet-gateway/pkgs/container/wallet-gateway%2Fdocker%2Fwallet-gateway +- The version corresponding to the NPM package: https://www.npmjs.com/package/@canton-network/wallet-gateway-remote + +## Docker + +To run the Docker container, a configuration file must be supplied for the Wallet Gateway. If you don't have a configuration, you can generate a sample config to serve as a starting point: + +```shell +# via Docker +docker run --rm ghcr.io/digital-asset/wallet-gateway/docker/wallet-gateway: --config-example > config.json + +# alternatively, generate a sample config file via NPM +npx @canton-network/wallet-gateway-remote@ --config-example > config.json +``` + +With the default config file, start the service: + +```shell +docker run -p 3030:3030 \ + -v ${PWD}/config.json:/app/config.json:ro \ + ghcr.io/digital-asset/wallet-gateway/docker/wallet-gateway: +``` + +If all went well, the Wallet Gateway login page can be opened in a browser at http://localhost:3030. + +## Helm + +An official Helm chart is available for Kubernetes deployments. The full values.schema is [here](https://github.com/digital-asset/wallet-gateway/blob/main/charts/wallet-gateway/values.schema.json), but the important thing to note is that the Wallet Gateway is configured through the top-level `config:` key in `values.yaml`. + +The config is then specified as YAML, but otherwise uses the same schema as `config.json`. + +Signing providers can be configured directly in the chart values: + +```yaml +signing: + # optional, define to enable blockdaemon integration -- or omit + blockdaemon: + apiUrl: 'http://localhost:5080/api/cwp/canton' + apiKeyRef: + name: 'blockdaemon-creds' + key: 'api-key' + # optional, define to enable fireblocks integration -- or omit + fireblocks: + apiKeyRef: + name: 'fireblocks-creds' + key: 'fb-api-key' + secretRef: + name: 'fireblocks-creds' + key: 'fb-secret' +``` + +The chart also provides a helper for providing OAuth client secrets directly from Kubernetes secrets. This is done by specifying a mapping between a custom environment variable name and the secret reference: + +```yaml +oauthSecrets: + # map a kubernetes secret to a Wallet Gateway network auth config + MY_OAUTH2_CLIENT_SECRET: + secretRef: + name: 'my-oauth' + key: 'client-secret' +# ... + +config: + # ... + networks: + - id: 'my-network' + # ... + adminAuth: + # ... + # should correlate to a secret provided in oauthSecrets + clientSecretEnv: 'MY_OAUTH2_CLIENT_SECRET' +``` + +## Configuration + +The [Configuration](/integrations/wallet-gateway/configuration) section contains a complete breakdown of the options. Please read through that page first, then return here. + +The following config is incomplete, but highlights specific fields of note to consider for a production deployment: + +```yaml +kernel: + # Set the publically accessible URL that users would use to connect to the deployed Wallet Gateway. + # Subpath routing is also supported + publicUrl: 'https://wallet.example.com/subpath' +server: + # In a Helm/k8s setup, we recommend leaving the port set to the default `3030` value, + # and routing the service internally from your Ingress/LoadBalancer (exposed on 443) to the pod's port. + + # We strongly recommend TLS termination of your cluster, so that the Wallet Gateway is accessible to clients over `https`. + port: 3030 + + # Set this to an array of origins corresponding to the set of web dApps that are allowed to call the dApp API. + allowedOrigins: + - 'https://dapp1.example.com' + - 'https://dapp2.example.com' + + # The default value (`5mb`) may need to be bumped for heavy use (large contract payloads may exceed this). + requestSizeLimit: '5mb' + + # Default is `10000` requests / second / IP address. Bump if encountering HTTP 429 errors during regular use. + requestRateLimit: 10000 +bootstrap: + networks: + - adminAuth: + # For PRODUCTION, we recommend providing OAuth secrets for network admins via the environment. + # This allows the secret to be stored in a secure secrets manager, and injected into the container at runtime. + # This field defines the name of the environment variable to use for this network. + clientSecretEnv: 'OAUTH2_CLIENT_SECRET' +``` + +### Environment Variables + +Aside from the dynamic environment variables supported in config (`networks[].adminAuth.clientSecretEnv`), there are a few static environment variables available to configure external signing providers: + +**Fireblocks** + +- `FIREBLOCKS_API_KEY`: The API key for the Fireblocks integration +- `FIREBLOCKS_SECRET`: The secret for the Fireblocks integration + +**Blockdaemon** + +- `BLOCKDAEMON_API_KEY`: The API key for the Blockdaemon integration +- `BLOCKDAEMON_API_URL`: The URL for the Blockdaemon API + +See [Signing Providers](/integrations/wallet-gateway/signing-providers) for more information. + +## Database Persistence + +### SQLite + +The default config uses `sqlite` as a persistent data store for the Wallet Gateway. To prevent the data from being reset across container restarts, the sqlite files must be mounted to a volume. + +First, configure the stores to point somewhere in the container: + +```yaml +# config YAML for helm, or equivalent config.json +signingStore: + connection: + type: "sqlite", + database: "/data/signing_store.sqlite" +store: + connection: + type: "sqlite", + database: "/data/store.sqlite" +``` + +Then start the container with the volume mount + +```shell +docker run -p 3030:3030 \ + -v ${PWD}/config.json:/app/config.json:ro \ + -v ${PWD}/data:/data \ + ghcr.io/digital-asset/wallet-gateway/docker/wallet-gateway: +``` + +### PostgreSQL + +Alternatively, the Wallet Gateway can be configured to use a separate PostgreSQL connection: + +```yaml +# config YAML for helm, or equivalent config.json +store: + connection: + type: "postgres", + host: "", + port: 5432, + database: "", + user: "", + password: "" +``` + +## Logging + +JSON logging can be enabled via the `--log-format` CLI flag (values: `"pretty" (default) | "json"`): + +```shell +docker run -p 3030:3030 \ + -v ${PWD}/config.json:/app/config.json:ro \ + ghcr.io/digital-asset/wallet-gateway/docker/wallet-gateway: \ + --log-format=json +``` + +{/* COPIED_END */} diff --git a/docs-main/integrations/wallet-gateway/overview.mdx b/docs-main/integrations/wallet-gateway/overview.mdx new file mode 100644 index 00000000..3ccc16ce --- /dev/null +++ b/docs-main/integrations/wallet-gateway/overview.mdx @@ -0,0 +1,45 @@ +--- +title: "Overview" +description: "Introduction to the Wallet Gateway — a server that mediates between dApps, Canton validators, and signing providers." +--- + +{/* COPIED_START source="wallet-gateway:docs/dapp-building/wallet-gateway/index.md" hash="112a68f0" */} + +The Wallet Gateway is a JavaScript/TypeScript-based server that facilitates secure communication between decentralized applications (dApps), Canton Validator nodes, and Wallet Providers. It acts as a mediator, enabling seamless transaction signing and ledger interactions while maintaining the privacy and security guarantees of the Canton protocol. + + +This guide is under active development, not all sections are complete and sections will be added and adjusted over time. + + +**What is Wallet Gateway?** + +The Wallet Gateway enables transparent interaction between a dApp, Validator Node, and a Wallet Provider. Unlike public permissionless blockchains where a total state is shared amongst all nodes, Canton's unique approach to security and privacy results in fractured states shared amongst selected Validator nodes. Simply showing ownership of an associated private key does not reveal your entire financial data to a counter-party (such as a dApp). + +**Wallet Gateway aims to** + +- Maintain the high-level of security and trust inherent in the Canton Protocol +- Enable seamless communication between a dApp, Validator Node, and Signature Provider, similar in experience to other blockchains +- Provide transparency against malicious dApps, Validator Nodes, or Signature Providers +- Create a standardized communication framework that allows anybody to extend or integrate with the Wallet Gateway + +**Key Features** + +- **JSON-RPC APIs**: Two distinct APIs for dApp and user interactions +- **Multiple Signing Providers**: Support for participant-based signing, internal signing, Dfns, Fireblocks, and Blockdaemon +- **Flexible Identity Providers**: Support for OAuth 2.0 and self-signed JWT tokens +- **Network Management**: Configure and manage multiple Canton networks +- **Session Management**: Secure session handling with JWT authentication +- **Web UI**: User-friendly web interface for wallet management +- **Multiple Storage Backends**: Support for in-memory, SQLite, and PostgreSQL storage + +## Contents + +- [Getting Started](/integrations/wallet-gateway/download) +- [Configuration](/integrations/wallet-gateway/configuration) +- [Usage](/integrations/wallet-gateway/usage) +- [APIs](/integrations/wallet-gateway/apis) +- [Signing Providers](/integrations/wallet-gateway/signing-providers) +- [Deployment](/integrations/wallet-gateway/deployment) +- [Troubleshooting](/integrations/wallet-gateway/troubleshooting) + +{/* COPIED_END */} diff --git a/docs-main/integrations/wallet-gateway/signing-providers.mdx b/docs-main/integrations/wallet-gateway/signing-providers.mdx index 09cf2211..8b7ebfd5 100644 --- a/docs-main/integrations/wallet-gateway/signing-providers.mdx +++ b/docs-main/integrations/wallet-gateway/signing-providers.mdx @@ -1,9 +1,9 @@ --- title: "Signing Providers" -description: "Wallet Gateway signing provider integrations" +description: "Wallet Gateway signing provider integrations (participant, internal, Fireblocks, Blockdaemon)" --- -{/* COPIED_START source="wallet-gateway:docs/dapp-building/wallet-gateway/signing-providers/index.md@82ec39c9" hash="ce58d241" */} +{/* COPIED_START source="wallet-gateway:docs/dapp-building/wallet-gateway/signing-providers/index.md" hash="0e02eba4" */} The Wallet Gateway supports multiple signing providers that handle cryptographic key management and transaction signing. Each provider has different use cases and security characteristics. @@ -25,8 +25,9 @@ This provider is automatically available when a `signingStore` is configured in **Security Considerations:** -> [!IMPORTANT] -> Private keys are stored in the database. If the database is compromised, all keys are at risk. Use only in non-production environments. + +Private keys are stored in the database. If the database is compromised, all keys are at risk. Use only in non-production environments. + ## Participant-Based Signing @@ -52,7 +53,7 @@ Fireblocks is a third-party crypto custody service provider that offers enterpri **Setup:** -1. Complete steps 1-3 from the [Fireblocks signing documentation](https://github.com/canton-network/wallet-gateway/tree/main/core/signing-fireblocks) +1. Complete steps 1-3 from the [Fireblocks signing documentation](https://github.com/canton-network/wallet/tree/main/core/signing-fireblocks) 2. Supply an environment variable named `FIREBLOCKS_API_KEY` containing your Fireblocks API key (from the `API User (ID)` column in the Fireblocks API users table). From 259d6f33b2a58429610f0bbffc9b1c31e4d7125e Mon Sep 17 00:00:00 2001 From: 8bitpal Date: Tue, 26 May 2026 17:48:06 +0200 Subject: [PATCH 2/4] Tighten prose on m4-dapp-sdk and m4-wallet-gateway MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Editorial pass on the two new curriculum pages to match the project's writing style: - Removed em dashes throughout. Most became commas, colons, or sentence breaks; a few "X — Y" bullet pairs became "X: Y" or got rewritten as proper prose. - Replaced the "Term — definition" bullet pattern in "What it provides" (m4-wallet-gateway) and the "Signing providers" section with prose paragraphs that embed the terms inline. Bullets stay where they're genuinely list-shaped (arrow flows, next-steps). - Dropped the "This page covers..." meta intros from both files. The lead paragraph already names the topic; the second paragraph now goes straight to the "see also" pointer. - Replaced the "From the dApp's perspective" / "From the validator's perspective" parallel construction with "To the dApp" / "To the validator". - Removed minor marketing flourishes (e.g., "rather than being tied to a single vendor" → just describing the interoperability concretely). - Frontmatter description punctuation tightened (no em dashes). Content unchanged. The numbered "Typical flow" steps still have the same code samples and the same five-step structure. --- docs-main/appdev/modules/m4-dapp-sdk.mdx | 50 ++++++-------- .../appdev/modules/m4-wallet-gateway.mdx | 67 +++++++++---------- 2 files changed, 52 insertions(+), 65 deletions(-) diff --git a/docs-main/appdev/modules/m4-dapp-sdk.mdx b/docs-main/appdev/modules/m4-dapp-sdk.mdx index a9b1f921..be265c02 100644 --- a/docs-main/appdev/modules/m4-dapp-sdk.mdx +++ b/docs-main/appdev/modules/m4-dapp-sdk.mdx @@ -1,15 +1,15 @@ --- title: "dApp SDK" -description: "Connect a Canton Network dApp to user wallets using the dApp SDK — a TypeScript library implementing CIP-103." +description: "Connect a Canton Network dApp to user wallets using the dApp SDK, a TypeScript library that implements CIP-103." --- -The dApp SDK (`@canton-network/dapp-sdk`) is a TypeScript library that lets a dApp connect to user wallets on Canton Network. It implements the dApp API defined by [CIP-103](https://github.com/canton-foundation/cips/blob/main/cip-0103/cip-0103.md) — a vendor-neutral JSON-RPC 2.0 protocol that any compliant wallet can speak — so your dApp interoperates with any Canton-compatible wallet rather than being tied to a single vendor. +The dApp SDK (`@canton-network/dapp-sdk`) is a TypeScript library that lets a dApp connect to user wallets on Canton Network. It implements [CIP-103](https://github.com/canton-foundation/cips/blob/main/cip-0103/cip-0103.md), a vendor-neutral JSON-RPC 2.0 protocol for the dApp API. Any wallet that implements CIP-103 works with any dApp built on the SDK. -This page introduces what the SDK does, where it fits in a dApp's architecture, and what the typical usage flow looks like. For step-by-step API details and migration notes, see the [dApp SDK integration docs](/integrations/dapp-sdk/overview). +For step-by-step API details, see the [dApp SDK integration docs](/integrations/dapp-sdk/overview). ## Where it fits -A typical Canton Network dApp has three external parties to talk to: the user's wallet, a Canton validator (for the Ledger API), and a signing provider that holds the user's keys. The dApp SDK abstracts the first of those three. +A Canton Network dApp talks to three external systems: the user's wallet, a Canton validator (for the Ledger API), and a signing provider that holds the user's keys. The dApp SDK covers the wallet side. ``` ┌─────────────┐ dApp API (CIP-103) ┌──────────────────┐ Ledger API ┌──────────────────┐ @@ -23,28 +23,26 @@ A typical Canton Network dApp has three external parties to talk to: the user's └──────────────────┘ ``` -- Your dApp uses the dApp SDK to call the **dApp API** (`connect`, `listAccounts`, `prepareExecute`, `signMessage`, `ledgerApi`, etc.). -- The wallet — typically a [Wallet Gateway](/appdev/modules/m4-wallet-gateway) — implements the dApp API, manages user sessions, and forwards requests to the validator and signing provider. -- The SDK abstracts the transport (HTTP for remote gateways, `postMessage` for browser-extension wallets) so the same dApp code works against either. +The dApp uses the SDK to call the dApp API (`connect`, `listAccounts`, `prepareExecute`, `signMessage`, `ledgerApi`, and others). The wallet on the other end is typically a [Wallet Gateway](/appdev/modules/m4-wallet-gateway), which implements the dApp API, manages user sessions, and forwards requests to the validator and signing provider. The SDK abstracts the transport: HTTP for remote gateways, `postMessage` for browser-extension wallets. The same dApp code runs against either. ## Two levels of API The SDK exposes two interfaces over the same protocol: -- **High-level dApp SDK** — `sdk.connect()`, `sdk.listAccounts()`, `sdk.prepareExecute(...)`. Recommended for most applications. -- **Provider API** — Low-level [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)-style interface: `provider.request({ method, params })`. Useful when you need direct protocol access or are integrating with existing provider-based infrastructure. +- **High-level dApp SDK**: `sdk.connect()`, `sdk.listAccounts()`, `sdk.prepareExecute(...)`. Recommended for most applications. +- **Provider API**: low-level [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)-style interface using `provider.request({ method, params })`. Useful for direct protocol access or for integrating with existing provider-based code. -You can mix the two: `sdk.getConnectedProvider()` returns the active CIP-103 provider so you can drop down to `provider.request(...)` when needed. +The two can be mixed. `sdk.getConnectedProvider()` returns the active CIP-103 provider, so you can drop down to `provider.request(...)` whenever the high-level API doesn't cover what you need. ## Provider discovery (`window.canton`) -For browser-extension wallets, the SDK exposes an EIP-1193-style provider at `window.canton`, mirroring the pattern Ethereum dApps use. This is how a dApp running in a browser discovers a wallet that's been installed as an extension. The SDK handles registration, the EIP-1193 events, and the user-facing wallet picker. For details and customization, see [Adapter registration & wallet discovery](/integrations/dapp-sdk/adapters-and-discovery). +Browser-extension wallets are discovered through `window.canton`, an EIP-1193-style provider that the SDK injects. A dApp running in the browser finds the wallet there. The SDK handles registration, EIP-1193 events, and the user-facing wallet picker. See [Adapter registration & wallet discovery](/integrations/dapp-sdk/adapters-and-discovery) for customization. ## Typical flow A common dApp lifecycle: -1. **Initialize** — Call `sdk.init()` once at app startup. This registers adapters and attempts to restore any previously connected session, without opening the picker. +1. **Initialize.** Call `sdk.init()` once at app startup. This registers adapters and attempts to restore any previously connected session without opening the picker. ```typescript import * as sdk from '@canton-network/dapp-sdk' @@ -52,7 +50,7 @@ A common dApp lifecycle: await sdk.init() ``` -2. **Connect** — When the user clicks *Connect Wallet*, call `sdk.connect()`. If multiple wallets are registered, the SDK shows the picker; otherwise it proceeds directly. The wallet may redirect the user to its UI to log in (OAuth or self-signed JWT). +2. **Connect.** When the user clicks *Connect Wallet*, call `sdk.connect()`. If multiple wallets are registered, the SDK shows the picker; otherwise it proceeds directly. The wallet may redirect the user to its UI to log in (OAuth or self-signed JWT). ```typescript const result = await sdk.connect() @@ -61,14 +59,14 @@ A common dApp lifecycle: } ``` -3. **Read accounts** — List the parties (accounts) the wallet has for this user and find the primary one. +3. **Read accounts.** List the parties (accounts) the wallet has for this user and find the primary one. ```typescript const accounts = await sdk.listAccounts() const primary = accounts.find(a => a.primary) ``` -4. **Submit a transaction** — `prepareExecute` runs the full prepare → user-approval → sign → submit lifecycle. The wallet shows the user the transaction details and asks for approval; if granted, the signing provider produces the signature and the wallet submits to the validator. +4. **Submit a transaction.** `prepareExecute` runs the full prepare → user-approval → sign → submit lifecycle. The wallet shows the user the transaction details and asks for approval; if granted, the signing provider produces the signature and the wallet submits to the validator. ```typescript await sdk.prepareExecute({ @@ -87,7 +85,7 @@ A common dApp lifecycle: }) ``` -5. **React to events** — The SDK emits `statusChanged`, `accountsChanged`, and `txChanged` so your UI stays in sync with the wallet's state. Remember to remove listeners when components unmount. +5. **React to events.** The SDK emits `statusChanged`, `accountsChanged`, and `txChanged` so your UI stays in sync with the wallet. Remove listeners when components unmount. ```typescript sdk.onTxChanged(tx => { @@ -97,22 +95,18 @@ A common dApp lifecycle: }) ``` -For the full method surface — including `signMessage`, `ledgerApi` proxying, `getActiveNetwork`, and the event subscription API — see the [API reference](/integrations/dapp-sdk/api-reference). +For the full method surface (including `signMessage`, `ledgerApi` proxying, `getActiveNetwork`, and the event subscription API), see the [API reference](/integrations/dapp-sdk/api-reference). ## When to drop down to the Provider API -You typically reach for `provider.request(...)` directly when: +Reach for `provider.request(...)` directly when the method you need isn't yet wrapped by the high-level SDK, when you're integrating with existing CIP-103-aware code that expects EIP-1193 semantics, or when you're writing a wallet provider yourself and want a thin client for testing. -- The method you need isn't yet wrapped by the high-level SDK -- You're integrating with existing CIP-103-aware code that expects EIP-1193 semantics -- You're writing a wallet provider yourself and want a thin client for testing - -The Provider API and the high-level SDK call the same dApp API under the hood — there's no protocol-level difference. +Both interfaces speak the same protocol, so the choice is ergonomic, not functional. ## Where to go next -- [Installation](/integrations/dapp-sdk/download) — install the SDK from npm -- [Usage guide](/integrations/dapp-sdk/usage) — every method shown side-by-side in SDK and Provider API form -- [Best practices](/integrations/dapp-sdk/best-practices) — recommended patterns for production dApps -- [Wallet Gateway module](/appdev/modules/m4-wallet-gateway) — the server side of the dApp API -- [Examples](/integrations/dapp-building-examples) — Ping and Portfolio sample dApps +- [Installation](/integrations/dapp-sdk/download): install the SDK from npm. +- [Usage guide](/integrations/dapp-sdk/usage): every method shown side-by-side in SDK and Provider API form. +- [Best practices](/integrations/dapp-sdk/best-practices): recommended patterns for production dApps. +- [Wallet Gateway module](/appdev/modules/m4-wallet-gateway): the server side of the dApp API. +- [Examples](/integrations/dapp-building-examples): Ping and Portfolio sample dApps. diff --git a/docs-main/appdev/modules/m4-wallet-gateway.mdx b/docs-main/appdev/modules/m4-wallet-gateway.mdx index 7116a31a..ffa69cf0 100644 --- a/docs-main/appdev/modules/m4-wallet-gateway.mdx +++ b/docs-main/appdev/modules/m4-wallet-gateway.mdx @@ -1,26 +1,23 @@ --- title: "Wallet Gateway" -description: "The server that mediates between Canton Network dApps, validator nodes, and signing providers — and how to think about it when building a dApp." +description: "The server that mediates between Canton Network dApps, validator nodes, and signing providers, and how to think about it when building a dApp." --- -The Wallet Gateway is a JavaScript/TypeScript server that sits between a dApp and the Canton Network. It exposes the dApp API (the CIP-103 JSON-RPC protocol that the [dApp SDK](/appdev/modules/m4-dapp-sdk) speaks) and translates calls into Ledger API requests against a Canton validator and signing requests to a configured signing provider. +The Wallet Gateway is a JavaScript/TypeScript server that sits between a dApp and the Canton Network. It implements the dApp API (the CIP-103 JSON-RPC protocol the [dApp SDK](/appdev/modules/m4-dapp-sdk) speaks) and translates the calls into Ledger API requests against a Canton validator and signing requests to a configured signing provider. -This page covers what the Wallet Gateway is, the role it plays in a dApp deployment, and when you need to run your own versus rely on one a user already has. For setup, configuration, and operational details, see the [Wallet Gateway integration docs](/integrations/wallet-gateway/overview). +For setup, configuration, and operational details, see the [Wallet Gateway integration docs](/integrations/wallet-gateway/overview). ## Why a mediator exists -Canton's privacy model means a validator node only sees the subset of ledger state relevant to its parties — there is no single global state shared across all nodes. A dApp can't just "talk to the chain"; it has to talk to a validator that hosts the specific party the user wants to act as, and it needs that validator to accept commands authorized by the right signing key. +Canton's privacy model means a validator node only sees the subset of ledger state relevant to its parties. There is no single global state shared across all nodes. A dApp can't just "talk to the chain"; it has to find a validator that hosts the user's party, and the validator has to accept commands signed by the right key. -The Wallet Gateway centralizes that responsibility. From the dApp's perspective, the Gateway looks like a wallet: a single JSON-RPC endpoint that knows the user's accounts, can sign on their behalf (via a signing provider), and submits transactions to the right validator. From the validator's perspective, the Gateway is an authenticated client of the Ledger API. +The Wallet Gateway centralizes that responsibility. To the dApp, the Gateway looks like a wallet: a single JSON-RPC endpoint that knows the user's accounts, signs on their behalf via the configured signing provider, and submits transactions to the right validator. To the validator, the Gateway is an authenticated Ledger API client. ## What it provides -- **dApp API** — The CIP-103 JSON-RPC surface dApps call. Methods like `connect`, `listAccounts`, `prepareExecute`, `signMessage`, and `ledgerApi` proxying. -- **User API** — A separate JSON-RPC surface for users and automation: session management, identity-provider configuration, wallet creation, network configuration, and direct signing. -- **User UI** — A web interface served by the Gateway that uses the User API. Users log in, create and manage wallets, approve dApp transactions, and change settings. Custom integrations can call the User API directly instead. -- **Network configuration** — A Gateway can be configured for multiple Canton networks (DevNet, TestNet, MainNet, custom synchronizers). Users pick which network a wallet lives on. -- **Signing provider integration** — Pluggable backends for signing: a Canton participant (for in-band signing), the Gateway itself (test only), Fireblocks, Blockdaemon, etc. -- **Storage backends** — In-memory (dev), SQLite, or PostgreSQL for sessions, wallet metadata, and transaction history. +The Gateway exposes two JSON-RPC surfaces. The **dApp API** is what dApps call (`connect`, `listAccounts`, `prepareExecute`, `signMessage`, and a `ledgerApi` proxy). The **User API** is for users and automation: session management, identity-provider configuration, wallet creation, network configuration, and direct signing. A web **User UI** built on top of the User API handles the human-facing flows (login, wallet management, transaction approval). Custom integrations can call the User API directly instead. + +A Gateway can be configured for multiple Canton networks (DevNet, TestNet, MainNet, or a custom synchronizer); users pick a network when they create a wallet. Signing is delegated to a pluggable backend: a Canton participant, the Gateway itself (test only), Fireblocks, Blockdaemon, or a similar provider. Sessions, wallet metadata, and transaction history live in one of the supported storage backends (in-memory for dev, SQLite, or PostgreSQL). ## Where it fits @@ -38,21 +35,21 @@ The Wallet Gateway centralizes that responsibility. From the dApp's perspective, - **dApp → Gateway**: CIP-103 JSON-RPC over HTTP or `postMessage`. - **User → Gateway**: User UI (browser) or User API (programmatic). -- **Gateway → Canton**: Authenticated requests to a validator's Ledger API. -- **Gateway → Signing Provider**: Signing requests to whichever provider the user's wallet is configured for. +- **Gateway → Canton**: authenticated requests to a validator's Ledger API. +- **Gateway → Signing Provider**: signing requests to whichever provider the user's wallet is configured for. ## Discovery and connection flow A typical user-facing flow: -1. **Discovery** — A dApp finds available Wallet Gateways via well-known URLs, a registry, or a browser extension that injects `window.canton`. Each Gateway publishes its base URL and supported networks. -2. **Connect** — The user picks a Gateway. The dApp calls `sdk.connect()`. The Gateway redirects the user to its User UI to log in if they don't have a session yet (OAuth or self-signed JWT). -3. **Session** — After login the Gateway returns a JWT. The dApp SDK uses it to authenticate subsequent dApp API calls. -4. **Transaction** — When the dApp calls `prepareExecute`, the Gateway prepares the transaction with the validator, shows the user the details in the User UI for approval, asks the signing provider to sign, then submits to the validator. The dApp receives the result and `txChanged` events. +1. **Discovery.** A dApp finds available Wallet Gateways via well-known URLs, a registry, or a browser extension that injects `window.canton`. Each Gateway publishes its base URL and supported networks. +2. **Connect.** The user picks a Gateway. The dApp calls `sdk.connect()`. The Gateway redirects the user to its User UI to log in if they don't have a session yet (OAuth or self-signed JWT). +3. **Session.** After login the Gateway returns a JWT. The dApp SDK uses it to authenticate subsequent dApp API calls. +4. **Transaction.** When the dApp calls `prepareExecute`, the Gateway prepares the transaction with the validator, shows the user the details in the User UI for approval, asks the signing provider to sign, then submits to the validator. The dApp receives the result and `txChanged` events. ## Do you need to run one? -For most dApps the answer is no — users bring their own Wallet Gateway. There are typical scenarios: +For most dApps the answer is no; users bring their own Wallet Gateway. Typical scenarios: | Scenario | Who runs the Gateway | |---|---| @@ -61,36 +58,32 @@ For most dApps the answer is no — users bring their own Wallet Gateway. There | Internal tools and automation | The team runs a Gateway in their own infrastructure, often paired with an internal signing provider. | | Development | You run a local Gateway alongside the validator, typically with the in-Gateway signing provider for testing. | -If you're building a dApp, you mainly care about the **client-facing dApp API** — that's what the dApp SDK calls. The Gateway is the production target on the other end of those calls. +If you're building a dApp, you mainly care about the client-facing dApp API. That's what the dApp SDK calls; the Gateway is the production target on the other end of those calls. If you're operating Canton infrastructure or running a dApp at scale, you'll likely run a Gateway too. The [Getting Started](/integrations/wallet-gateway/download) guide walks through standing one up. ## Signing providers -The Wallet Gateway does not sign transactions itself in production. It delegates to a configured signing provider: - -- **Canton participant** — A participant node that holds the party's signing key in its key store. The Gateway authenticates to the participant's admin API and requests signatures over the Ledger API. This is the default for SV-operated and enterprise deployments. -- **Internal (Gateway-managed)** — The Gateway generates and stores the key itself. **Test and development only** — not suitable for production. -- **Fireblocks** — Signing requests are forwarded to a Fireblocks workspace. Keys live in Fireblocks' MPC infrastructure. -- **Blockdaemon** — Same pattern with Blockdaemon's key management. +The Wallet Gateway does not sign transactions itself in production. It delegates to a configured provider. -A single Gateway can host wallets across different signing providers; the provider is configured per wallet. +A **Canton participant** signs in-band: the participant node holds the party's key in its key store, and the Gateway requests signatures over the participant's admin API. This is the default for SV-operated and enterprise deployments. **Internal** signing means the Gateway generates and stores the key itself; that mode is for test and development only. **Fireblocks** and **Blockdaemon** are external providers that hold the key in their own MPC or HSM infrastructure and produce signatures via API. -For configuration details, see [Signing Providers](/integrations/wallet-gateway/signing-providers). +A single Gateway can host wallets across different signing providers; the provider is configured per wallet. See [Signing Providers](/integrations/wallet-gateway/signing-providers) for configuration. ## What a dApp developer should know -Even if you'll never operate a Gateway, two operational realities affect how you build a dApp: +Two operational realities affect how a dApp behaves, even if you'll never operate a Gateway. + +The user picks the network at the Gateway level, so your dApp should not assume `da-mainnet`. Call `sdk.getActiveNetwork()` and react to `accountsChanged`. -- **The user can pick the network at the Gateway level.** Your dApp should not assume `da-mainnet` — call `sdk.getActiveNetwork()` and react to `accountsChanged`. -- **The signing provider determines transaction latency.** A local participant signs in milliseconds; an external HSM-backed provider can take seconds. Don't lock the UI on `prepareExecute`; subscribe to `txChanged` and let the user keep working. +The signing provider determines transaction latency. A local participant signs in milliseconds; an external HSM-backed provider can take seconds. Don't lock the UI on `prepareExecute`; subscribe to `txChanged` and let the user keep working. ## Where to go next -- [Wallet Gateway overview](/integrations/wallet-gateway/overview) — what it is, what it provides, and what's in the integration docs -- [Getting Started](/integrations/wallet-gateway/download) — install and run a Gateway -- [Configuration](/integrations/wallet-gateway/configuration) — full config-file reference -- [APIs](/integrations/wallet-gateway/apis) — dApp API and User API method reference -- [Signing Providers](/integrations/wallet-gateway/signing-providers) — how to wire up each backend -- [dApp SDK module](/appdev/modules/m4-dapp-sdk) — the client side of the dApp API -- [Examples](/integrations/dapp-building-examples) — Ping and Portfolio sample dApps that talk to a Wallet Gateway +- [Wallet Gateway overview](/integrations/wallet-gateway/overview): what it is, what it provides, and what's in the integration docs. +- [Getting Started](/integrations/wallet-gateway/download): install and run a Gateway. +- [Configuration](/integrations/wallet-gateway/configuration): full config-file reference. +- [APIs](/integrations/wallet-gateway/apis): dApp API and User API method reference. +- [Signing Providers](/integrations/wallet-gateway/signing-providers): how to wire up each backend. +- [dApp SDK module](/appdev/modules/m4-dapp-sdk): the client side of the dApp API. +- [Examples](/integrations/dapp-building-examples): Ping and Portfolio sample dApps that talk to a Wallet Gateway. From 1544837ee0bf1958e75b9472469d3750845840d5 Mon Sep 17 00:00:00 2001 From: 8bitpal Date: Tue, 26 May 2026 18:43:09 +0200 Subject: [PATCH 3/4] Convert ASCII diagrams to mermaid in m4-dapp-sdk and m4-wallet-gateway Replaces the two box-drawing ASCII diagrams in the curriculum modules with mermaid flowcharts. m4-dapp-sdk: four-node LR flowchart (dApp <-> Wallet Gateway, Gateway <-> Canton Validator, Gateway <-> Signing Provider) with the dApp API (CIP-103) transport labels preserved. m4-wallet-gateway: same four-node skeleton plus a User node and a subgraph that shows the Gateway's internal pieces (dApp API, User API, User UI). The User node connects to User UI (browser) and User API (programmatic) separately, which is clearer than the ASCII version's single 'User interactions' arrow. Mermaid renders consistently with other diagrams in the project (m1-mental-models, ledger-model, pqs/operate). --- docs-main/appdev/modules/m4-dapp-sdk.mdx | 19 +++++++------ .../appdev/modules/m4-wallet-gateway.mdx | 27 ++++++++++++------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/docs-main/appdev/modules/m4-dapp-sdk.mdx b/docs-main/appdev/modules/m4-dapp-sdk.mdx index be265c02..7591cb32 100644 --- a/docs-main/appdev/modules/m4-dapp-sdk.mdx +++ b/docs-main/appdev/modules/m4-dapp-sdk.mdx @@ -11,16 +11,15 @@ For step-by-step API details, see the [dApp SDK integration docs](/integrations/ A Canton Network dApp talks to three external systems: the user's wallet, a Canton validator (for the Ledger API), and a signing provider that holds the user's keys. The dApp SDK covers the wallet side. -``` -┌─────────────┐ dApp API (CIP-103) ┌──────────────────┐ Ledger API ┌──────────────────┐ -│ Your dApp │ ◄─────────────────────────► │ Wallet Gateway │ ◄─────────────────► │ Canton Validator │ -│ (dApp SDK) │ (HTTP / postMessage) │ │ │ │ -└─────────────┘ └──────────────────┘ └──────────────────┘ - │ - ▼ - ┌──────────────────┐ - │ Signing Provider │ - └──────────────────┘ +```mermaid +flowchart LR + D["Your dApp
(dApp SDK)"] + G[Wallet Gateway] + V[Canton Validator] + S[Signing Provider] + D <-->|dApp API (CIP-103)
HTTP / postMessage| G + G <-->|Ledger API| V + G <-->|Signing| S ``` The dApp uses the SDK to call the dApp API (`connect`, `listAccounts`, `prepareExecute`, `signMessage`, `ledgerApi`, and others). The wallet on the other end is typically a [Wallet Gateway](/appdev/modules/m4-wallet-gateway), which implements the dApp API, manages user sessions, and forwards requests to the validator and signing provider. The SDK abstracts the transport: HTTP for remote gateways, `postMessage` for browser-extension wallets. The same dApp code runs against either. diff --git a/docs-main/appdev/modules/m4-wallet-gateway.mdx b/docs-main/appdev/modules/m4-wallet-gateway.mdx index ffa69cf0..c6e41176 100644 --- a/docs-main/appdev/modules/m4-wallet-gateway.mdx +++ b/docs-main/appdev/modules/m4-wallet-gateway.mdx @@ -21,16 +21,23 @@ A Gateway can be configured for multiple Canton networks (DevNet, TestNet, MainN ## Where it fits -``` -┌─────────────┐ dApp API (CIP-103) ┌──────────────────┐ Ledger API ┌──────────────────┐ -│ Your dApp │ ◄────────────────────────► │ Wallet Gateway │ ◄─────────────────► │ Canton Validator │ -│ (dApp SDK) │ (HTTP / postMessage) │ ┌────────────┐ │ │ │ -└─────────────┘ │ │ User API │ │ Signing └──────────────────┘ - │ │ │ User UI │ │ ┌──────────────────┐ - │ User interactions │ └────────────┘ │ ◄──►│ Signing Provider │ - └──────────────────────────────────►│ │ │ (Participant, │ - (User UI / User API) └──────────────────┘ │ Fireblocks, …) │ - └──────────────────┘ +```mermaid +flowchart LR + D["Your dApp
(dApp SDK)"] + U[User] + subgraph WG[Wallet Gateway] + direction TB + DA[dApp API] + UA[User API] + UI[User UI] + end + V[Canton Validator] + S["Signing Provider
(Participant, Fireblocks, …)"] + D <-->|CIP-103 over
HTTP / postMessage| DA + U -->|browser| UI + U -->|programmatic| UA + WG <-->|Ledger API| V + WG <-->|Signing| S ``` - **dApp → Gateway**: CIP-103 JSON-RPC over HTTP or `postMessage`. From 4ea782979f0f4cba30ed0d7040b5253774cbed64 Mon Sep 17 00:00:00 2001 From: 8bitpal Date: Tue, 26 May 2026 18:49:05 +0200 Subject: [PATCH 4/4] Convert dapp-building-overview ASCII diagram to mermaid The High-Level Architecture diagram in dapp-building-overview.mdx is inside a COPIED block (mirrors splice-wallet-kernel:docs/dapp-building/ overview/index.md). Converting the ASCII box-drawing to mermaid means deliberate divergence from upstream verbatim. Added a LOCAL_MODIFICATION comment after the COPIED_END marker explaining the deviation. The surrounding prose stays in sync with upstream; only the diagram block was changed. Mermaid form mirrors the m4-wallet-gateway diagram: dApp <-> Wallet Gateway (subgraph containing dApp API, User API, User UI), User connecting to UI (browser) and User API (programmatic) separately, plus Gateway <-> Validator and Gateway <-> Signing Provider on the backend. The dApp transport label keeps the upstream wording ('HTTP / WebSocket') rather than the m4 module's 'HTTP / postMessage' to preserve upstream's framing. --- .../integrations/dapp-building-overview.mdx | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/docs-main/integrations/dapp-building-overview.mdx b/docs-main/integrations/dapp-building-overview.mdx index 7a93b4b0..3f54a926 100644 --- a/docs-main/integrations/dapp-building-overview.mdx +++ b/docs-main/integrations/dapp-building-overview.mdx @@ -19,17 +19,23 @@ A typical setup involves: ## High-Level Architecture -``` -┌─────────────┐ dApp API ┌──────────────────┐ Ledger API ┌─────────────────┐ -│ Your dApp │ ◄──────────────────► │ Wallet Gateway │ ◄─────────────────► │ Canton Validator│ -│ (dApp SDK) │ (HTTP / WebSocket) │ │ │ │ -└─────────────┘ │ ┌────────────┐ │ Signing └─────────────────┘ - │ │ │ User API │ │ - │ User interactions │ │ User UI │ │ ┌─────────────────┐ - └────────────────────────────►│ └────────────┘ │ ◄──►│ Signing Provider│ - (User UI / User API) │ │ │ (Participant, │ - └──────────────────┘ │ Fireblocks…) │ - └─────────────────┘ +```mermaid +flowchart LR + D["Your dApp
(dApp SDK)"] + U[User] + subgraph WG[Wallet Gateway] + direction TB + DA[dApp API] + UA[User API] + UI[User UI] + end + V[Canton Validator] + S["Signing Provider
(Participant, Fireblocks, …)"] + D <-->|dApp API
HTTP / WebSocket| DA + U -->|browser| UI + U -->|programmatic| UA + WG <-->|Ledger API| V + WG <-->|Signing| S ``` - **dApp → Wallet Gateway**: Your dApp uses the dApp SDK to call the **dApp API** (connect, list accounts, prepare and execute transactions). The SDK can use HTTP (remote Wallet Gateway) or `postMessage` (browser extension). @@ -65,3 +71,5 @@ See [Usage](/integrations/wallet-gateway/usage) and [APIs](/integrations/wallet- - **Using the User UI or User API?** → See [Usage](/integrations/wallet-gateway/usage) for typical workflows and when to use which interface. {/* COPIED_END */} + +{/* LOCAL_MODIFICATION: the "High-Level Architecture" diagram was converted from ASCII box-drawing to mermaid for consistency with other diagrams in the project. The diagram is the only divergence from the upstream source; the surrounding prose is verbatim. */}