Skip to content

Exploration: Wallet Discovery - UX/UI proposal - #2237

Draft
joel-da wants to merge 7 commits into
canton-network:mainfrom
joel-da:joel_wallet-discovery-v2
Draft

Exploration: Wallet Discovery - UX/UI proposal #2237
joel-da wants to merge 7 commits into
canton-network:mainfrom
joel-da:joel_wallet-discovery-v2

Conversation

@joel-da

@joel-da joel-da commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Wallet Discovery: in-page modal exploration

This is a UX/UI exploration for the wallet discovery flow. It is not intended to be merged as-is; it is meant to gather feedback on direction.

DC.demo.mov

Main changes

  • Adds an option for the discovery component to render as an in-page modal instead of a popup window, and makes it the dapp-sdk default.
  • Iterates on the UX/UI: transitions, animations (including a height-morphing modal shell that animates between views), refreshed color treatment, and softer shadows/spacing.
  • Adds loading / connecting states, including an inline WalletConnect QR view. The connecting view uses a logo-shaped progress ring.
  • Back during connecting: a back control in the connecting view aborts the in-flight attempt and returns to the wallet list.
  • Wallet list UX: ordered list (installed, then WalletConnect, then remote, then everything else), scrollable at ~4.5 rows, with scroll-aware top/bottom fade shadows that appear and disappear based on scroll position.
  • Suggested wallets: renders the bundled verified-wallet list inline (no separate "Suggested" section). Each not-yet-installed wallet shows a hover-revealed "Get for " label, and the whole row is the install link.
  • Light/Dark theming: a design-token color system with a dark variant via prefers-color-scheme, plus an explicit theme override so a dApp can force light or dark.

Supporting changes

  • New sdk.setWalletPicker() API so a dApp can inject a custom picker into the shared SDK singleton.
  • New setWalletPickerModalTheme('light' | 'dark' | 'auto') API (and a WalletPickerModalTheme type) to control the modal color scheme.
  • New setWalletPickerModalWalletConnectUri() to feed the WalletConnect pairing URI/QR into the modal's connecting view.
  • WalletConnect: pre-generates the pairing URI/QR while the wallet list is shown, so the QR is ready instantly (no loading flash), plus an openPopupForUri option to suppress the legacy popup. The QR view header reads "Scan with your phone" and includes an "Or" divider label above the copy-link action.
  • Treats remote wallets as a generic connection type usable by any wallet, exposed as an option the user can enable or disable in the picker.
  • The SDK routes connect / error / retry to whichever picker is active (modal or popup), so both keep working. Re-exports the popup picker so a dApp can opt back into it.
  • Ping example: removed its own custom modal and now uses the SDK default picker, plus adds Modal/Popup and Light/Dark toggles to compare.
  • Iconography: the Remote Wallet connector and saved remote wallets default to the Canton logo; a "Need a wallet?" footer links to the wallet directory.

Open questions

Wallet picker: modal vs popup (decision needed)

This PR adds a new in-page modal picker and makes it the dapp-sdk default. The old popup picker is still present. They are separate implementations with separate markup/CSS, so they look different. Both work today (the SDK routes connect/error/retry to whichever is active; the ping example has a toggle to compare).

Why not shared: the popup is not rendered normally. Its component is serialized with .toString() into a Blob and run in a separate window, so it cannot import shared modules or reference top-level constants (everything must be inlined). The modal has no such constraint. That is the blocker to a single shared component.

Possible options:

  1. Modal only (drop the popup): low effort, one implementation. Needs confirmation that no consumer depends on the separate window.
  2. Restyle the popup to match: medium effort, still two code paths to maintain.
  3. Re-architect the popup (load a real page instead of blob/toString()) and share one component: high effort.

Known gaps / caveats

  • e2e not updated: the shared test helper still drives the picker as a popup window, so the ping e2e connect path will not work against the in-page modal default. Needs updating (or running e2e in popup mode) before merge.
  • WalletConnect QR is modal-only: in Popup mode the QR does not render in the popup window.
  • Theme source: prefers-color-scheme follows the OS, not the host dApp's own theme; there is no wiring to the gateway theme variables yet.
  • Temporary test data: the bundled verified-wallet list currently has throwaway Wallet 1/2/3 entries used to test the list; remove before merge.
  • Build tooling: the dapp-sdk tsdown build fails locally with an unrun resolution error (environment/tooling, unrelated to these changes); the tsc type emit works.

joel-da added 7 commits August 4, 2026 12:28
Signed-off-by: Joel Lovera <joel.lovera@digitalasset.com>
Signed-off-by: Joel Lovera <joel.lovera@digitalasset.com>
Signed-off-by: Joel Lovera <joel.lovera@digitalasset.com>
Signed-off-by: Joel Lovera <joel.lovera@digitalasset.com>
- Add shared in-page modal picker (wallet-picker-modal) in
  core-wallet-ui-components, rendered in a Shadow DOM
- Wire it as the dapp-sdk default walletPicker (replaces popup); support
  connect/error/retry, back-to-list during connecting, and WalletConnect QR
- Render suggested wallets (from wallets.json) with hover-reveal install badge
- Ordering: installed, WalletConnect, remote, then the rest
- Scrollable list (~4.5 rows) with bottom shadow divider
- Design tokens + prefers-color-scheme dark mode; type/shape refinements
- Canton logo for Remote Wallet; Need a wallet link to wallet directory
- Migrate ping example to the SDK default picker (remove custom modal)
- Rename Send Connect -> Send; add test wallets

Signed-off-by: Joel Lovera <joel.lovera@digitalasset.com>
…polish

- Fade hints at top and bottom of the list that appear/disappear based on
  scroll position (hidden at their edge and when the list is not scrollable)
- Remove footer divider line; tighten list-to-footer gap
- Add horizontal padding around the component for more breathing room

Signed-off-by: Joel Lovera <joel.lovera@digitalasset.com>
- Add Modal/Popup and Light/Dark toggles to the ping example
- setWalletPickerModalTheme (light/dark/auto) with data-swk-theme override;
  refactor modal colors into shared light/dark token blocks
- Route SDK connect notify/retry to the active picker (modal or popup) so the
  popup picker works when selected
- Re-export pickWallet, setWalletPickerModalTheme, WalletPickerModalTheme
- WalletConnect: header shows 'Scan with your phone'; add 'Or' divider label

Signed-off-by: Joel Lovera <joel.lovera@digitalasset.com>
Copilot AI lite review requested due to automatic review settings August 5, 2026 10:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Explores a new wallet discovery UX by adding an in-page modal wallet picker (Shadow DOM–based) and making it the dapp-sdk default, while keeping the legacy popup picker available for comparison and fallback.

Changes:

  • Adds an in-page wallet picker modal implementation (including WalletConnect QR/connect states and modal theme override hooks).
  • Updates DappSDK to default to the modal picker, adds setWalletPicker() override API, and routes connect/error/retry/back signals to the active picker.
  • Enhances WalletConnect adapter URI handling (QR generation + optional suppression of legacy popup) and updates the ping example to exercise modal vs popup and light vs dark.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
sdk/dapp-sdk/src/wallets.json Updates bundled suggested wallets list (includes temporary placeholder entries).
sdk/dapp-sdk/src/sdk.ts Defaults to modal picker, adds picker override API, and adds “back to abort” connect flow logic.
sdk/dapp-sdk/src/sdk.test.ts Adjusts mocks/assertions for modal picker notifications and back handling.
sdk/dapp-sdk/src/index.ts Re-exports wallet picker-related APIs and adds setWalletPicker to the public surface.
sdk/dapp-sdk/src/adapter/walletconnect-adapter.ts Adds QR/URI pre-generation and openPopupForUri control; expands onUri signature.
examples/ping/src/index.css Tweaks example styling (link/button colors).
examples/ping/src/hooks/useConnect.ts Updates ping to use SDK modal picker plumbing + WalletConnect URI forwarding.
examples/ping/src/App.tsx Adds UI toggles to compare modal vs popup and light vs dark modal theme.
examples/ping/src/App.css Minor styling tweak.
examples/ping/.env.example Documents optional WalletConnect chain ID override.
core/wallet-ui-components/src/windows/wallet-picker-modal.ts Introduces the new in-page modal wallet picker implementation and public control APIs.
core/wallet-ui-components/src/index.ts Exports the new modal picker module.
Suppressed comments (1)

sdk/dapp-sdk/src/sdk.ts:703

  • The module-level setWalletPicker wrapper should accept and forward the optional picker kind so callers can reliably select the popup notification path when providing a wrapped picker function.
export const setWalletPicker = (picker: WalletPickerFn | undefined): void =>
    sdk.setWalletPicker(picker)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread sdk/dapp-sdk/src/index.ts
// ── Asset exports (icons for wallet adapters) ──
export { CANTON_LOGO_PNG, WALLET_GATEWAY_ICON } from './assets'

// ── Default wallet picker (in-page modal) ──
if (!parsed) return
const normalizedUrl = parsed.toString()
this.select({
providerId: `custom:remote:${encodeURIComponent(normalizedUrl)}`,
Comment thread sdk/dapp-sdk/src/sdk.ts
Comment on lines +109 to +112
setWalletPicker(picker: WalletPickerFn | undefined): void {
this.walletPicker = picker ?? (pickWalletModal as WalletPickerFn)
this.pickerKind = this.resolvePickerKind(this.walletPicker)
}
Comment on lines 16 to 23
a {
font-weight: 500;
color: #646cff;
color: #000;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
color: #000;
}
Comment on lines +420 to +422
async prepareUri(): Promise<void> {
if (this.session || this.pendingApproval) return
if (this.prepareUriPromise) return this.prepareUriPromise
Comment on lines 158 to 170
vi.mock('@canton-network/core-wallet-ui-components', async (importOriginal) => {
const actual =
await importOriginal<
typeof import('@canton-network/core-wallet-ui-components')
>()
return {
...actual,
notifyWalletPickerConnected: mockNotifyWalletPickerConnected,
notifyWalletPickerError: mockNotifyWalletPickerError,
waitForWalletPickerRetrySelection:
notifyWalletPickerModalConnected: mockNotifyWalletPickerConnected,
notifyWalletPickerModalError: mockNotifyWalletPickerError,
waitForWalletPickerModalRetrySelection:
mockWaitForWalletPickerRetrySelection,
waitForWalletPickerModalBack: mockWaitForWalletPickerModalBack,
}
Comment on lines +15 to +27
{
"name": "Wallet 1",
"type": "browser",
"providerId": "browser:ext:test-wallet-1",
"description": "Connect via a browser extension wallet",
"icon": "https://info.send.it/img/favicon.svg",
"installUrls": [
{
"platform": "chrome",
"url": "https://chromewebstore.google.com/detail/send-connect/ldmohiccoioolenadmogclhoklmanpgi"
}
]
},
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants