diff --git a/canton-connect/README.md b/canton-connect/README.md index 4c62d0ff..dc676da2 100644 --- a/canton-connect/README.md +++ b/canton-connect/README.md @@ -116,6 +116,39 @@ 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' }), + ], +} +``` + +> [!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. + +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:devnet', + walletConnectProjectId: 'YOUR_REOWN_PROJECT_ID', +} +``` + +`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 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..7ae20a55 100644 --- a/canton-connect/architecture.md +++ b/canton-connect/architecture.md @@ -113,6 +113,52 @@ 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 })]`, 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. + +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). + +### WalletConnect + +`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. + +What differs over WalletConnect: + +- 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. 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 } // 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`. + ### The party type A party under the hosting participant's namespace is local, any other is external. A dApp cares @@ -131,8 +177,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..b5f3cf71 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. `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. | @@ -14,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..2499b8a5 100644 --- a/canton-connect/src/hooks/useLedger.ts +++ b/canton-connect/src/hooks/useLedger.ts @@ -27,7 +27,11 @@ export interface UseLedgerResult { * * @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 */ diff --git a/canton-connect/src/types.ts b/canton-connect/src/types.ts index 54e73d04..1bcf3f72 100644 --- a/canton-connect/src/types.ts +++ b/canton-connect/src/types.ts @@ -82,7 +82,22 @@ 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' + * 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 */