From 26b3d5a0243a3ce643ef9b76e1ffeac2600670cf Mon Sep 17 00:00:00 2001 From: fernandomg Date: Wed, 9 Sep 2026 17:06:48 +0200 Subject: [PATCH 1/4] docs(connect): document the Wallet Gateway path - architecture: Remote gateway section, #2 leaves Deferred - README: configure a gateway, restore and lock contracts, local recipe - CantonConnectConfig: example with a RemoteAdapter - coming-from-wagmi: connectors row --- canton-connect/README.md | 25 ++++++++++++++++++++++ canton-connect/architecture.md | 33 +++++++++++++++++++++++++++-- canton-connect/coming-from-wagmi.md | 1 + canton-connect/src/types.ts | 11 ++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/canton-connect/README.md b/canton-connect/README.md index 4c62d0ff..0be618b4 100644 --- a/canton-connect/README.md +++ b/canton-connect/README.md @@ -116,6 +116,31 @@ and a wallet-side disconnect look the same here. `useLedger().isReady` covers both, and `useParty().party` is `undefined` for the duration: gate session content on the party, and use `isLocked` only to explain why it went away. +### Connecting through a Wallet Gateway + +```tsx +import { RemoteAdapter } from '@canton-network/dapp-sdk' + +const config = { + appName: 'My dApp', + additionalAdapters: [ + new RemoteAdapter({ name: 'Gateway', rpcUrl: 'http://localhost:3030/api/v0/dapp' }), + ], +} +``` + +Restore after a reload is silent only for a gateway configured here; a URL typed into the picker +at connect time has nothing to match at the next init and does not come back. A gateway has no +lock, so `useWalletStatus().isLocked` never turns true on one. `ledgerApi` reads must use the +templated Canton route with values in `path`; a concrete URL is refused. + +Run one locally: `npx @canton-network/wallet-gateway-remote@1.10.0 -c config.json`. The config +needs a `self_signed` IDP and `ledgerApi.baseUrl` pointed at the participant's JSON API; see the +package on [npm](https://www.npmjs.com/package/@canton-network/wallet-gateway-remote). + +The full seam, including the popup flow and the party read, is in +[architecture.md](https://github.com/BootNodeDev/canton-dappbooster/blob/main/canton-connect/architecture.md). + ## Reference Every hook and every config field is documented in JSDoc, which your editor surfaces at the call diff --git a/canton-connect/architecture.md b/canton-connect/architecture.md index 601cce86..c35b7cf1 100644 --- a/canton-connect/architecture.md +++ b/canton-connect/architecture.md @@ -113,6 +113,37 @@ init actor passes `defaultAdapters: []`, dropping the SDK's bundled `localhost:3 `networkId` (default `'canton:local'`) is both the WalletConnect `chainId` and the fallback `Party.networkId` for a wallet that reports none. +### Remote gateway + +Configured like any adapter: `additionalAdapters: [new RemoteAdapter({ name, rpcUrl })]` in +`CantonConnectConfig`, `RemoteAdapter` imported from `dapp-sdk`. Connect, restore, disconnect and +execute all reach a gateway with no change to this package. + +The SDK picker lists it by `name`; picking it opens the gateway's login page in the SDK popup, +where a self-signed IDP takes a client id and secret, the gateway pushes the connected status over +SSE, and the party arrives. `useExecute` opens the gateway's review page the same way; approval +there signs and executes (an admin-workflow `Ping` create came back `executed`, with an update id, +in about half a second). The popup-close guard behaves the same on a gateway as on an extension. + +Restore after a reload is silent only for a gateway registered through `additionalAdapters` at +init: `RemoteAdapter.restore()` requires the stored discovery URL to match a registered adapter's +`rpcUrl`, so a gateway URL typed into the picker has nothing to match at the next init and its +session does not come back. + +A gateway has no lock: its UI offers only Logout, which ends the gateway page's own session, not +the dApp's, so `useWalletStatus().isLocked` never turns true on one. Disconnect from the dApp does +work: status goes to not connected and the SDK clears its session and discovery keys, keeping the +picker's cache. + +The gateway checks every `ledgerApi` resource against the Canton JSON API route list exactly (the +templated route with values in `path`, never a concrete URL); extensions accept either. + +Run one locally: `npx @canton-network/wallet-gateway-remote@1.10.0 -c config.json`, with +`kernel.clientType: "remote"`, a `server.port` / `dappPath`, one `self_signed` entry in +`bootstrap.idps`, and one `bootstrap.networks` entry pointing `ledgerApi.baseUrl` at the +participant's JSON API with matching `self_signed` `auth` / `adminAuth`. Point the dApp's adapter +at `rpcUrl: 'http://localhost:3030/api/v0/dapp'`. + ### The party type A party under the hosting participant's namespace is local, any other is external. A dApp cares @@ -131,8 +162,6 @@ announce, detect and connect path. `createAutoPicker` answers the picker headles ## Deferred -- Remote / Wallet Gateway (OIDC) path: a configurable `RemoteAdapter` through `additionalAdapters` - and `CantonConnectConfig` (#2). - Themed in-page picker (#50): its PR (#63) was closed unmerged, so the SDK popup is still the only picker; a new attempt starts from the `walletPicker` seam. diff --git a/canton-connect/coming-from-wagmi.md b/canton-connect/coming-from-wagmi.md index 7bf2b2c3..e0f97166 100644 --- a/canton-connect/coming-from-wagmi.md +++ b/canton-connect/coming-from-wagmi.md @@ -6,6 +6,7 @@ The hook names follow wagmi, so a developer arriving from it knows which one to | wagmi | canton-connect | why | |---|---|---| +| `connectors: [injected(), walletConnect({ projectId })]` | `walletConnectProjectId`, `additionalAdapters: [new RemoteAdapter(...)]` for a gateway | Extensions are discovered automatically; there is no connector to list for them. | | none | `useConnect().cancelConnect` | Abandons a connect in flight, rejecting it with `ConnectCancelledError`; wagmi has no cancel. | | `useAccount().address` | `useParty().party.partyId` | A Canton identity is a party. | | `useAccount().addresses`, `.connector`, `.chain` | none | Not exposed yet. | diff --git a/canton-connect/src/types.ts b/canton-connect/src/types.ts index 54e73d04..812cd26f 100644 --- a/canton-connect/src/types.ts +++ b/canton-connect/src/types.ts @@ -84,6 +84,17 @@ export interface Party { * @example * const config: CantonConnectConfig = { appName: 'Vesting', networkId: 'canton:devnet' } * + * @example + * import type { CantonConnectConfig } from '#src/types' + * import { RemoteAdapter } from '@canton-network/dapp-sdk' + * + * const config: CantonConnectConfig = { + * appName: 'Vesting', + * additionalAdapters: [ + * new RemoteAdapter({ name: 'Gateway', rpcUrl: 'https://gateway.example.com/dapp' }), + * ], + * } + * * @category Configuration */ export interface CantonConnectConfig { From cb4bab43836c13b51c741e93978404aacf93fb13 Mon Sep 17 00:00:00 2001 From: fernandomg Date: Wed, 9 Sep 2026 20:04:09 +0200 Subject: [PATCH 2/4] docs(connect): document the WalletConnect path - architecture: WalletConnect section, networkId must match the wallet's chain - README: configure WalletConnect, network id and lock rules, pairing - CantonConnectConfig: first example shows networkId and walletConnectProjectId - coming-from-wagmi: networkId clause on the connectors row --- canton-connect/README.md | 16 ++++++++++++++++ canton-connect/architecture.md | 21 +++++++++++++++++++++ canton-connect/coming-from-wagmi.md | 2 +- canton-connect/src/types.ts | 6 +++++- 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/canton-connect/README.md b/canton-connect/README.md index 0be618b4..6ddfe1b6 100644 --- a/canton-connect/README.md +++ b/canton-connect/README.md @@ -141,6 +141,22 @@ package on [npm](https://www.npmjs.com/package/@canton-network/wallet-gateway-re The full seam, including the popup flow and the party read, is in [architecture.md](https://github.com/BootNodeDev/canton-dappbooster/blob/main/canton-connect/architecture.md). +### Connecting through WalletConnect + +```tsx +const config = { + appName: 'My dApp', + networkId: 'canton:localnet', + walletConnectProjectId: 'YOUR_REOWN_PROJECT_ID', +} +``` + +`networkId` must match what the wallet advertises or it rejects the session; our LocalNet +wallet-service advertises `canton:localnet`. A wallet lock never crosses the relay, so +`useWalletStatus().isLocked` never turns true on one, and restore after a reload is silent: the +sign client persists the session and picks it up at init. Pair by scanning the QR code or copying +the `wc:` URI the SDK popup shows. + ## Reference Every hook and every config field is documented in JSDoc, which your editor surfaces at the call diff --git a/canton-connect/architecture.md b/canton-connect/architecture.md index c35b7cf1..b996e0dd 100644 --- a/canton-connect/architecture.md +++ b/canton-connect/architecture.md @@ -144,6 +144,27 @@ Run one locally: `npx @canton-network/wallet-gateway-remote@1.10.0 -c config.jso participant's JSON API with matching `self_signed` `auth` / `adminAuth`. Point the dApp's adapter at `rpcUrl: 'http://localhost:3030/api/v0/dapp'`. +### WalletConnect + +Extensions need no configuration; `walletConnectProjectId` is what makes `buildAdditionalAdapters` build +the SDK's `WalletConnectAdapter` (Adapters above), whose `networkId` is the CAIP-2 chain the wallet must +serve. It has to equal what the wallet advertises, or the wallet rejects the session: our LocalNet +wallet-service advertises `canton:localnet`, so a dApp against it sets `networkId: 'canton:localnet'`. + +The SDK picker lists `WalletConnect`; picking it shows a QR code and a copyable `wc:` URI in the SDK +popup, the wallet pairs and approves the session, and the popup closes itself about a second after +the connected status arrives. The party follows `isConnected` shortly after, read over the relay. The +popup-close guard passes here too: three closes with nothing chosen re-enable the button, and closing +the popup after picking WalletConnect fails the connect as a cancel, with no error surfaced. + +Restore after a reload is silent: the session is persisted by the sign client and picked up at init. A +lock never crosses the relay either way, so `useWalletStatus().isLocked` stays false until the wallet +answers a request again. Disconnect from the dApp does work: it clears the discovery session key and +ends the session on the wallet's side too. + +`useExecute` travels as one relay request (`canton_prepareSignExecute`); approval happens on the wallet, +and the result comes back `executed`, with an update id, in under five seconds. + ### The party type A party under the hosting participant's namespace is local, any other is external. A dApp cares diff --git a/canton-connect/coming-from-wagmi.md b/canton-connect/coming-from-wagmi.md index e0f97166..474283c5 100644 --- a/canton-connect/coming-from-wagmi.md +++ b/canton-connect/coming-from-wagmi.md @@ -6,7 +6,7 @@ The hook names follow wagmi, so a developer arriving from it knows which one to | wagmi | canton-connect | why | |---|---|---| -| `connectors: [injected(), walletConnect({ projectId })]` | `walletConnectProjectId`, `additionalAdapters: [new RemoteAdapter(...)]` for a gateway | Extensions are discovered automatically; there is no connector to list for them. | +| `connectors: [injected(), walletConnect({ projectId })]` | `walletConnectProjectId`, `additionalAdapters: [new RemoteAdapter(...)]` for a gateway | Extensions are discovered automatically; there is no connector to list for them. `networkId` is the CAIP-2 chain the wallet must serve. | | none | `useConnect().cancelConnect` | Abandons a connect in flight, rejecting it with `ConnectCancelledError`; wagmi has no cancel. | | `useAccount().address` | `useParty().party.partyId` | A Canton identity is a party. | | `useAccount().addresses`, `.connector`, `.chain` | none | Not exposed yet. | diff --git a/canton-connect/src/types.ts b/canton-connect/src/types.ts index 812cd26f..1bcf3f72 100644 --- a/canton-connect/src/types.ts +++ b/canton-connect/src/types.ts @@ -82,7 +82,11 @@ export interface Party { * and that surface is yours. The app fields stay inert until `walletConnectProjectId` (Reown) is set. * * @example - * const config: CantonConnectConfig = { appName: 'Vesting', networkId: 'canton:devnet' } + * const config: CantonConnectConfig = { + * appName: 'Vesting', + * networkId: 'canton:devnet', + * walletConnectProjectId: 'REOWN_PROJECT_ID', + * } * * @example * import type { CantonConnectConfig } from '#src/types' From 96faa9bfd1bf60bbc8d8e0e017548b9ddbab268b Mon Sep 17 00:00:00 2001 From: fernandomg Date: Wed, 9 Sep 2026 20:44:33 +0200 Subject: [PATCH 3/4] docs(connect): state the templated route rule for ledgerApi - architecture: Ledger reads section, both spellings side by side - README: the rule once under Usage, gateway subsection points at it - useLedger: prose states the rule, example uses path - coming-from-wagmi: read hooks row notes the templated route --- canton-connect/README.md | 7 +++++-- canton-connect/architecture.md | 17 +++++++++++++++-- canton-connect/coming-from-wagmi.md | 2 +- canton-connect/src/hooks/useLedger.ts | 9 +++++++-- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/canton-connect/README.md b/canton-connect/README.md index 6ddfe1b6..b0525f78 100644 --- a/canton-connect/README.md +++ b/canton-connect/README.md @@ -116,6 +116,9 @@ and a wallet-side disconnect look the same here. `useLedger().isReady` covers both, and `useParty().party` is `undefined` for the duration: gate session content on the party, and use `isLocked` only to explain why it went away. +`ledgerApi` names its route the way Canton's JSON API OpenAPI does: templated, with the variable +parts in `path`, never written into the string; `query` and `body` carry the rest. + ### Connecting through a Wallet Gateway ```tsx @@ -131,8 +134,8 @@ const config = { Restore after a reload is silent only for a gateway configured here; a URL typed into the picker at connect time has nothing to match at the next init and does not come back. A gateway has no -lock, so `useWalletStatus().isLocked` never turns true on one. `ledgerApi` reads must use the -templated Canton route with values in `path`; a concrete URL is refused. +lock, so `useWalletStatus().isLocked` never turns true on one. `ledgerApi` reads follow the +templated form above; a concrete URL is refused here too. Run one locally: `npx @canton-network/wallet-gateway-remote@1.10.0 -c config.json`. The config needs a `self_signed` IDP and `ledgerApi.baseUrl` pointed at the participant's JSON API; see the diff --git a/canton-connect/architecture.md b/canton-connect/architecture.md index b996e0dd..f9ba3a94 100644 --- a/canton-connect/architecture.md +++ b/canton-connect/architecture.md @@ -135,8 +135,8 @@ the dApp's, so `useWalletStatus().isLocked` never turns true on one. Disconnect work: status goes to not connected and the SDK clears its session and discovery keys, keeping the picker's cache. -The gateway checks every `ledgerApi` resource against the Canton JSON API route list exactly (the -templated route with values in `path`, never a concrete URL); extensions accept either. +The gateway checks every `ledgerApi` resource against the Canton JSON API route list exactly; +extensions accept either. Ledger reads (below) covers the general rule this enforces. Run one locally: `npx @canton-network/wallet-gateway-remote@1.10.0 -c config.json`, with `kernel.clientType: "remote"`, a `server.port` / `dappPath`, one `self_signed` entry in @@ -165,6 +165,19 @@ ends the session on the wallet's side too. `useExecute` travels as one relay request (`canton_prepareSignExecute`); approval happens on the wallet, and the result comes back `executed`, with an update id, in under five seconds. +### Ledger reads + +`ledgerApi` names its route the way Canton's JSON API OpenAPI does: templated, with the variable +parts in `path`, never written into the string. The SDK's client sends this same shape on every +transport, which is why the Remote gateway's exact route check (above) applies to every wallet. + +```text +resource: '/v2/users/{user-id}/rights', path: { 'user-id': userId } // every wallet +resource: `/v2/users/${userId}/rights` // gateway refuses this +``` + +`query` holds query parameters, `body` the request body; neither goes into `resource`. + ### The party type A party under the hosting participant's namespace is local, any other is external. A dApp cares diff --git a/canton-connect/coming-from-wagmi.md b/canton-connect/coming-from-wagmi.md index 474283c5..b5f3cf71 100644 --- a/canton-connect/coming-from-wagmi.md +++ b/canton-connect/coming-from-wagmi.md @@ -15,5 +15,5 @@ The hook names follow wagmi, so a developer arriving from it knows which one to | `useWriteContract` then `useWaitForTransactionReceipt` | `useExecute().execute`, resolving after execution | The wallet submits and waits; one call covers both. | | none | `useExecute().lastTx` | The wallet pushes `pending`, `signed`, `executed`, `failed` as it goes; wagmi has no hook returning a stream. | | `useSignMessage().data`, a hex string | `useSignMessage().signature` | The name says the type. | -| `usePublicClient()`, a typed client | `useLedger().ledgerApi`, untyped, gated by `isReady` | The participant's JSON API, passed through the wallet's session. | +| `useReadContract`, `usePublicClient().request` | `useLedger().ledgerApi`, untyped, gated by `isReady` | The participant's JSON API; the route is templated, with values in `path`, never written into the string. | | `mutate`, `mutateAsync`, `status`, `variables`, `data` | none; `isPending`, `error`, `reset` carry over | No TanStack Query underneath. | diff --git a/canton-connect/src/hooks/useLedger.ts b/canton-connect/src/hooks/useLedger.ts index 5f0add60..d477574e 100644 --- a/canton-connect/src/hooks/useLedger.ts +++ b/canton-connect/src/hooks/useLedger.ts @@ -20,14 +20,19 @@ export interface UseLedgerResult { /** * Escape hatch for ledger reads `useExecute` and `useSignMessage` do not cover: the participant's - * JSON API, passed through untyped. + * JSON API, passed through untyped. `ledgerApi` names its route the way Canton's JSON API OpenAPI + * does: templated, with the variable parts in `path`, never written into the string. * * @throws with no {@link CantonConnectProvider} above it, and from `ledgerApi` itself where nothing * is connected, which `isReady` is there to check first. * * @example * const { ledgerApi } = useLedger() - * await ledgerApi({ requestMethod: 'get', resource: '/v2/state/ledger-end' }) + * await ledgerApi({ + * requestMethod: 'get', + * resource: '/v2/users/{user-id}/rights', + * path: { 'user-id': 'alice' }, + * }) * * @category Hooks */ From 8cb2269e0736676b12b1ac67ca4112e2f636111d Mon Sep 17 00:00:00 2001 From: fernandomg Date: Thu, 10 Sep 2026 14:39:53 +0200 Subject: [PATCH 4/4] docs(connect): trim the wallet path docs to setup and pointers - README: snippets, a typed-URL note, per-path links to architecture - architecture: only what this package does; Canton docs for the flow - useLedger: the example shows the templated route --- canton-connect/README.md | 27 +++------- canton-connect/architecture.md | 77 ++++++++++----------------- canton-connect/src/hooks/useLedger.ts | 3 +- 3 files changed, 38 insertions(+), 69 deletions(-) diff --git a/canton-connect/README.md b/canton-connect/README.md index b0525f78..dc676da2 100644 --- a/canton-connect/README.md +++ b/canton-connect/README.md @@ -116,9 +116,6 @@ and a wallet-side disconnect look the same here. `useLedger().isReady` covers both, and `useParty().party` is `undefined` for the duration: gate session content on the party, and use `isLocked` only to explain why it went away. -`ledgerApi` names its route the way Canton's JSON API OpenAPI does: templated, with the variable -parts in `path`, never written into the string; `query` and `body` carry the rest. - ### Connecting through a Wallet Gateway ```tsx @@ -132,33 +129,25 @@ const config = { } ``` -Restore after a reload is silent only for a gateway configured here; a URL typed into the picker -at connect time has nothing to match at the next init and does not come back. A gateway has no -lock, so `useWalletStatus().isLocked` never turns true on one. `ledgerApi` reads follow the -templated form above; a concrete URL is refused here too. - -Run one locally: `npx @canton-network/wallet-gateway-remote@1.10.0 -c config.json`. The config -needs a `self_signed` IDP and `ledgerApi.baseUrl` pointed at the participant's JSON API; see the -package on [npm](https://www.npmjs.com/package/@canton-network/wallet-gateway-remote). +> [!NOTE] +> The dapp-sdk wallet picker also lets a user paste any gateway URL and connect to it without the +> dApp listing it. That session does not survive a reload; one with a gateway listed here does. -The full seam, including the popup flow and the party read, is in -[architecture.md](https://github.com/BootNodeDev/canton-dappbooster/blob/main/canton-connect/architecture.md). +Details in [architecture.md](https://github.com/BootNodeDev/canton-dappbooster/blob/main/canton-connect/architecture.md#remote-gateway). ### Connecting through WalletConnect ```tsx const config = { appName: 'My dApp', - networkId: 'canton:localnet', + networkId: 'canton:devnet', walletConnectProjectId: 'YOUR_REOWN_PROJECT_ID', } ``` -`networkId` must match what the wallet advertises or it rejects the session; our LocalNet -wallet-service advertises `canton:localnet`. A wallet lock never crosses the relay, so -`useWalletStatus().isLocked` never turns true on one, and restore after a reload is silent: the -sign client persists the session and picks it up at init. Pair by scanning the QR code or copying -the `wc:` URI the SDK popup shows. +`networkId` is the Canton network the dApp targets, as a CAIP-2 chain id. + +Details in [architecture.md](https://github.com/BootNodeDev/canton-dappbooster/blob/main/canton-connect/architecture.md#walletconnect). ## Reference diff --git a/canton-connect/architecture.md b/canton-connect/architecture.md index f9ba3a94..7ae20a55 100644 --- a/canton-connect/architecture.md +++ b/canton-connect/architecture.md @@ -115,65 +115,46 @@ init actor passes `defaultAdapters: []`, dropping the SDK's bundled `localhost:3 ### Remote gateway -Configured like any adapter: `additionalAdapters: [new RemoteAdapter({ name, rpcUrl })]` in -`CantonConnectConfig`, `RemoteAdapter` imported from `dapp-sdk`. Connect, restore, disconnect and -execute all reach a gateway with no change to this package. - -The SDK picker lists it by `name`; picking it opens the gateway's login page in the SDK popup, -where a self-signed IDP takes a client id and secret, the gateway pushes the connected status over -SSE, and the party arrives. `useExecute` opens the gateway's review page the same way; approval -there signs and executes (an admin-workflow `Ping` create came back `executed`, with an update id, -in about half a second). The popup-close guard behaves the same on a gateway as on an extension. - -Restore after a reload is silent only for a gateway registered through `additionalAdapters` at -init: `RemoteAdapter.restore()` requires the stored discovery URL to match a registered adapter's -`rpcUrl`, so a gateway URL typed into the picker has nothing to match at the next init and its -session does not come back. - -A gateway has no lock: its UI offers only Logout, which ends the gateway page's own session, not -the dApp's, so `useWalletStatus().isLocked` never turns true on one. Disconnect from the dApp does -work: status goes to not connected and the SDK clears its session and discovery keys, keeping the -picker's cache. - -The gateway checks every `ledgerApi` resource against the Canton JSON API route list exactly; -extensions accept either. Ledger reads (below) covers the general rule this enforces. - -Run one locally: `npx @canton-network/wallet-gateway-remote@1.10.0 -c config.json`, with -`kernel.clientType: "remote"`, a `server.port` / `dappPath`, one `self_signed` entry in -`bootstrap.idps`, and one `bootstrap.networks` entry pointing `ledgerApi.baseUrl` at the -participant's JSON API with matching `self_signed` `auth` / `adminAuth`. Point the dApp's adapter -at `rpcUrl: 'http://localhost:3030/api/v0/dapp'`. +Configured like any adapter: `additionalAdapters: [new RemoteAdapter({ name, rpcUrl })]`, with +`RemoteAdapter` imported from `dapp-sdk`. Connect, restore, disconnect and execute reach a gateway +with no change to this package, and the popup-close guard behaves as it does on an extension. The +flow itself (login page, status push, review page) is the SDK's: +https://docs.canton.network/sdks-tools/sdks/dapp-sdk/wallet-providers/remote-wallet.md. -### WalletConnect +What differs on a gateway: + +- Restore after a reload works only for a gateway registered through `additionalAdapters` at init: + `RemoteAdapter.restore()` matches the stored discovery URL against a registered `rpcUrl`, so a URL + the user typed into the SDK picker has nothing to match next time. +- No lock: a gateway has none, so `useWalletStatus().isLocked` never turns true. +- `ledgerApi` must name the route as a template with the values in `path`; a route with the value + written in is refused. See [Ledger reads](#ledger-reads). -Extensions need no configuration; `walletConnectProjectId` is what makes `buildAdditionalAdapters` build -the SDK's `WalletConnectAdapter` (Adapters above), whose `networkId` is the CAIP-2 chain the wallet must -serve. It has to equal what the wallet advertises, or the wallet rejects the session: our LocalNet -wallet-service advertises `canton:localnet`, so a dApp against it sets `networkId: 'canton:localnet'`. +### WalletConnect -The SDK picker lists `WalletConnect`; picking it shows a QR code and a copyable `wc:` URI in the SDK -popup, the wallet pairs and approves the session, and the popup closes itself about a second after -the connected status arrives. The party follows `isConnected` shortly after, read over the relay. The -popup-close guard passes here too: three closes with nothing chosen re-enable the button, and closing -the popup after picking WalletConnect fails the connect as a cancel, with no error surfaced. +`walletConnectProjectId` is what makes `buildAdditionalAdapters` build the SDK's +`WalletConnectAdapter` (Adapters above). Execute, disconnect and the popup-close guard behave as on +an extension. Pairing and requests are the SDK's: +https://docs.canton.network/sdks-tools/sdks/dapp-sdk/wallet-providers/walletconnect.md. -Restore after a reload is silent: the session is persisted by the sign client and picked up at init. A -lock never crosses the relay either way, so `useWalletStatus().isLocked` stays false until the wallet -answers a request again. Disconnect from the dApp does work: it clears the discovery session key and -ends the session on the wallet's side too. +What differs over WalletConnect: -`useExecute` travels as one relay request (`canton_prepareSignExecute`); approval happens on the wallet, -and the result comes back `executed`, with an update id, in under five seconds. +- Restore after a reload is silent: the sign client persists the session. +- No lock reaches the dApp: `useWalletStatus().isLocked` never turns true, and a request sent to a + locked wallet waits. +- `networkId` is the CAIP-2 chain id of the network the dApp targets: a wallet on another network + cannot pair, and the default `canton:local` is a local participant, so a dApp on devnet or mainnet + sets it. ### Ledger reads `ledgerApi` names its route the way Canton's JSON API OpenAPI does: templated, with the variable -parts in `path`, never written into the string. The SDK's client sends this same shape on every -transport, which is why the Remote gateway's exact route check (above) applies to every wallet. +parts in `path`, never written into the string. It is the shape the SDK's own client sends, and the +only one a gateway accepts. ```text -resource: '/v2/users/{user-id}/rights', path: { 'user-id': userId } // every wallet -resource: `/v2/users/${userId}/rights` // gateway refuses this +resource: '/v2/users/{user-id}/rights', path: { 'user-id': userId } // the SDK's shape +resource: `/v2/users/${userId}/rights` // a gateway refuses this ``` `query` holds query parameters, `body` the request body; neither goes into `resource`. diff --git a/canton-connect/src/hooks/useLedger.ts b/canton-connect/src/hooks/useLedger.ts index d477574e..2499b8a5 100644 --- a/canton-connect/src/hooks/useLedger.ts +++ b/canton-connect/src/hooks/useLedger.ts @@ -20,8 +20,7 @@ export interface UseLedgerResult { /** * Escape hatch for ledger reads `useExecute` and `useSignMessage` do not cover: the participant's - * JSON API, passed through untyped. `ledgerApi` names its route the way Canton's JSON API OpenAPI - * does: templated, with the variable parts in `path`, never written into the string. + * JSON API, passed through untyped. * * @throws with no {@link CantonConnectProvider} above it, and from `ledgerApi` itself where nothing * is connected, which `isReady` is there to check first.