@zerodev/wallet-react-ui 0.0.11 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +38 -7
  2. package/dist/_types/auth/brandAssets.d.ts +2 -0
  3. package/dist/_types/auth/brandAssets.d.ts.map +1 -0
  4. package/dist/_types/auth/components/WalletSheet/index.d.ts +20 -0
  5. package/dist/_types/auth/components/WalletSheet/index.d.ts.map +1 -0
  6. package/dist/_types/auth/hooks/useWalletConnectPairing.d.ts +19 -0
  7. package/dist/_types/auth/hooks/useWalletConnectPairing.d.ts.map +1 -0
  8. package/dist/_types/auth/hooks/useWalletInfo.d.ts +32 -0
  9. package/dist/_types/auth/hooks/useWalletInfo.d.ts.map +1 -0
  10. package/dist/_types/auth/pages/SignUp/MoreWallets.d.ts.map +1 -1
  11. package/dist/_types/auth/pages/SignUp/Wallet.d.ts.map +1 -1
  12. package/dist/_types/auth/pages/SignUp/WalletConnect.d.ts +5 -0
  13. package/dist/_types/auth/pages/SignUp/WalletConnect.d.ts.map +1 -0
  14. package/dist/_types/auth/pages/SignUp/context.d.ts +4 -0
  15. package/dist/_types/auth/pages/SignUp/context.d.ts.map +1 -1
  16. package/dist/_types/auth/pages/SignUp/index.d.ts +2 -0
  17. package/dist/_types/auth/pages/SignUp/index.d.ts.map +1 -1
  18. package/dist/_types/auth/utils/isMobile.d.ts +2 -0
  19. package/dist/_types/auth/utils/isMobile.d.ts.map +1 -0
  20. package/dist/_types/auth/utils/isZeroDevWalletConnect.d.ts +6 -0
  21. package/dist/_types/auth/utils/isZeroDevWalletConnect.d.ts.map +1 -0
  22. package/dist/_types/auth/utils/walletDeepLink.d.ts +18 -0
  23. package/dist/_types/auth/utils/walletDeepLink.d.ts.map +1 -0
  24. package/dist/_types/auth/walletGuide.d.ts +21 -3
  25. package/dist/_types/auth/walletGuide.d.ts.map +1 -1
  26. package/dist/_types/index.d.ts +3 -0
  27. package/dist/_types/index.d.ts.map +1 -1
  28. package/dist/_types/zeroDevWalletConnect.d.ts +13 -0
  29. package/dist/_types/zeroDevWalletConnect.d.ts.map +1 -0
  30. package/dist/index.cjs +3 -3
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.mjs +1009 -659
  33. package/dist/index.mjs.map +1 -1
  34. package/dist/styles.css +1 -1
  35. package/package.json +6 -5
  36. package/src/assets.d.ts +5 -0
  37. package/src/auth/brandAssets.ts +5 -0
  38. package/src/auth/components/WalletSheet/index.tsx +220 -0
  39. package/src/auth/hooks/useWalletConnectPairing.ts +134 -0
  40. package/src/auth/hooks/useWalletInfo.ts +145 -0
  41. package/src/auth/pages/SignUp/InstalledWallets.tsx +1 -1
  42. package/src/auth/pages/SignUp/MoreWallets.tsx +44 -9
  43. package/src/auth/pages/SignUp/Wallet.tsx +43 -13
  44. package/src/auth/pages/SignUp/WalletConnect.tsx +32 -0
  45. package/src/auth/pages/SignUp/context.tsx +4 -0
  46. package/src/auth/pages/SignUp/index.tsx +31 -0
  47. package/src/auth/utils/isMobile.ts +9 -0
  48. package/src/auth/utils/isZeroDevWalletConnect.ts +8 -0
  49. package/src/auth/utils/walletDeepLink.ts +23 -0
  50. package/src/auth/walletGuide.ts +42 -8
  51. package/src/index.ts +7 -2
  52. package/src/zeroDevWalletConnect.ts +95 -0
@@ -23,7 +23,7 @@ export function SignUpInstalledWallets({
23
23
  const { authPending, guardAgreement, setError, registeredWallets } =
24
24
  useSignUpContext()
25
25
  const connectors = useConnectors()
26
- const { mutate: connect, isPending } = useConnect()
26
+ const { connect, isPending } = useConnect()
27
27
  useReportPending(isPending)
28
28
 
29
29
  // Same rule that earns the INSTALLED badge elsewhere: only a 6963
@@ -1,13 +1,15 @@
1
1
  import { ListItem, ListItemChevron, ListItemIcon } from '@zerodev/react-ui'
2
2
  import { useState } from 'react'
3
3
  import { useConnect, useConnectors } from 'wagmi'
4
+ import { walletConnectLogo } from '../../brandAssets'
4
5
  import {
5
6
  WalletGridSheet,
6
7
  type WalletTileData,
7
8
  } from '../../components/WalletGridSheet'
8
9
  import { useAuth } from '../../hooks/useAuth'
9
10
  import { isCancellationError } from '../../utils/isCancellationError'
10
- import { matchesWallet, WALLET_GUIDE } from '../../walletGuide'
11
+ import { isZeroDevWalletConnect } from '../../utils/isZeroDevWalletConnect'
12
+ import { announcesWallet, matchesWallet, WALLET_GUIDE } from '../../walletGuide'
11
13
  import { useReportPending, useSignUpContext } from './context'
12
14
 
13
15
  /** "More wallets" row — opens the wallet grid sheet. */
@@ -17,10 +19,11 @@ export function SignUpMoreWallets({
17
19
  title?: string
18
20
  }) {
19
21
  const { goToStep } = useAuth()
20
- const { authPending, guardAgreement, setError } = useSignUpContext()
22
+ const { authPending, guardAgreement, setError, openWalletSheet } =
23
+ useSignUpContext()
21
24
  const [open, setOpen] = useState(false)
22
25
  const connectors = useConnectors()
23
- const { mutate: connect, isPending } = useConnect()
26
+ const { connect, isPending } = useConnect()
24
27
  useReportPending(isPending)
25
28
 
26
29
  // Our own connector is the embedded wallet, and walletConnect-type
@@ -50,17 +53,33 @@ export function SignUpMoreWallets({
50
53
  )
51
54
  }
52
55
 
53
- // Claimed by a connector → connect it; otherwise the tile opens the
54
- // vendor download page.
56
+ const wcEnabled = connectors.some(isZeroDevWalletConnect)
57
+
55
58
  const guideTiles: WalletTileData[] = WALLET_GUIDE.map((wallet) => {
56
- const installed = walletConnectors.find((c) => matchesWallet(c, wallet))
59
+ const announced = walletConnectors.find((c) => announcesWallet(c, wallet))
57
60
  return {
58
61
  key: wallet.id,
59
62
  name: wallet.name,
60
63
  icon: wallet.icon,
61
64
  onSelect: () => {
62
- if (installed) {
63
- startConnect(installed)
65
+ // Announced = the wallet is live on this page (extension or its own
66
+ // in-app browser) — connect directly instead of a WC handoff.
67
+ if (announced) {
68
+ startConnect(announced)
69
+ return
70
+ }
71
+ if (wcEnabled) {
72
+ if (authPending) return
73
+ if (!guardAgreement()) return
74
+ setOpen(false)
75
+ openWalletSheet(wallet)
76
+ return
77
+ }
78
+ // No WC handoff available — a configured connector that claims the
79
+ // wallet (e.g. a vendor SDK) is the last way to connect.
80
+ const claimed = walletConnectors.find((c) => matchesWallet(c, wallet))
81
+ if (claimed) {
82
+ startConnect(claimed)
64
83
  return
65
84
  }
66
85
  setOpen(false)
@@ -80,6 +99,22 @@ export function SignUpMoreWallets({
80
99
  onSelect: () => startConnect(connector),
81
100
  }))
82
101
 
102
+ const walletConnectTiles: WalletTileData[] = wcEnabled
103
+ ? [
104
+ {
105
+ key: 'walletconnect',
106
+ name: 'WalletConnect',
107
+ icon: walletConnectLogo,
108
+ onSelect: () => {
109
+ if (authPending) return
110
+ if (!guardAgreement()) return
111
+ setOpen(false)
112
+ openWalletSheet()
113
+ },
114
+ },
115
+ ]
116
+ : []
117
+
83
118
  const handleClick = () => {
84
119
  if (authPending) return
85
120
  if (!guardAgreement()) return
@@ -98,7 +133,7 @@ export function SignUpMoreWallets({
98
133
  <WalletGridSheet
99
134
  open={open}
100
135
  onOpenChange={setOpen}
101
- tiles={[...guideTiles, ...connectorTiles]}
136
+ tiles={[...walletConnectTiles, ...guideTiles, ...connectorTiles]}
102
137
  />
103
138
  </>
104
139
  )
@@ -3,7 +3,13 @@ import { useLayoutEffect } from 'react'
3
3
  import { useConnect, useConnectors } from 'wagmi'
4
4
  import { useAuth } from '../../hooks/useAuth'
5
5
  import { isCancellationError } from '../../utils/isCancellationError'
6
- import { matchesWallet, WALLET_GUIDE, type WalletId } from '../../walletGuide'
6
+ import { isZeroDevWalletConnect } from '../../utils/isZeroDevWalletConnect'
7
+ import {
8
+ announcesWallet,
9
+ matchesWallet,
10
+ WALLET_GUIDE,
11
+ type WalletId,
12
+ } from '../../walletGuide'
7
13
  import { useReportPending, useSignUpContext } from './context'
8
14
 
9
15
  /** Dedicated row for a single guide wallet: connects it directly when a live
@@ -18,19 +24,21 @@ export function SignUpWallet({ walletId }: { walletId: WalletId }) {
18
24
  }
19
25
 
20
26
  const { goToStep } = useAuth()
21
- const { authPending, guardAgreement, setError, registerWallet } =
22
- useSignUpContext()
27
+ const {
28
+ authPending,
29
+ guardAgreement,
30
+ setError,
31
+ registerWallet,
32
+ openWalletSheet,
33
+ } = useSignUpContext()
23
34
  const connectors = useConnectors()
24
- const { mutate: connect, isPending } = useConnect()
35
+ const { connect, isPending } = useConnect()
25
36
  useReportPending(isPending)
26
37
  useLayoutEffect(() => registerWallet(wallet.id), [registerWallet, wallet.id])
27
38
 
28
- // Same claim rule as the MoreWallets grid: a 6963 announcement or a
29
- // configured SDK connector both make the row connectable. Only an
30
- // announcement (id === rdns) proves a live extension and earns the badge.
31
- const installed = connectors.find((c) => matchesWallet(c, wallet))
32
- const isAnnounced =
33
- !!wallet.rdns && connectors.some((c) => c.id === wallet.rdns)
39
+ // Only a 6963 announcement proves a live extension (or the wallet's own
40
+ // in-app browser) it earns the badge and a direct connect.
41
+ const announced = connectors.find((c) => announcesWallet(c, wallet))
34
42
 
35
43
  const shared = {
36
44
  title: wallet.name,
@@ -38,7 +46,29 @@ export function SignUpWallet({ walletId }: { walletId: WalletId }) {
38
46
  trailing: <ListItemChevron />,
39
47
  }
40
48
 
41
- if (!installed) {
49
+ // An announcement means the wallet is right here (extension, or its own
50
+ // in-app browser) — connect it directly; WC pairing would bounce out of
51
+ // the very wallet the user is standing in.
52
+ if (!announced && connectors.some(isZeroDevWalletConnect)) {
53
+ return (
54
+ <ListItem
55
+ {...shared}
56
+ disabled={authPending}
57
+ onClick={() => {
58
+ if (authPending) return
59
+ if (!guardAgreement()) return
60
+ openWalletSheet(wallet)
61
+ }}
62
+ />
63
+ )
64
+ }
65
+
66
+ // Same claim rule as the MoreWallets grid: announced, or — without a WC
67
+ // handoff — a configured connector (e.g. a vendor SDK) that claims the
68
+ // wallet.
69
+ const claimed = announced ?? connectors.find((c) => matchesWallet(c, wallet))
70
+
71
+ if (!claimed) {
42
72
  return (
43
73
  <ListItem {...shared} asChild>
44
74
  {/* biome-ignore lint/a11y/useAnchorContent: the row layout (incl. the title text) is injected into the anchor via Slot */}
@@ -56,7 +86,7 @@ export function SignUpWallet({ walletId }: { walletId: WalletId }) {
56
86
  if (!guardAgreement()) return
57
87
  setError(null)
58
88
  connect(
59
- { connector: installed },
89
+ { connector: claimed },
60
90
  {
61
91
  // The external wallet is now the active wagmi connection — the
62
92
  // embedded-wallet flow is done, so close it (mirrors SignUp.MoreWallets).
@@ -73,7 +103,7 @@ export function SignUpWallet({ walletId }: { walletId: WalletId }) {
73
103
  return (
74
104
  <ListItem
75
105
  {...shared}
76
- {...(isAnnounced && { subtitle: <Badge text="INSTALLED" /> })}
106
+ {...(!!announced && { subtitle: <Badge text="INSTALLED" /> })}
77
107
  disabled={authPending}
78
108
  onClick={handleClick}
79
109
  />
@@ -0,0 +1,32 @@
1
+ import { Badge, ListItem, ListItemChevron } from '@zerodev/react-ui'
2
+ import { useConnectors } from 'wagmi'
3
+ import { walletConnectLogo } from '../../brandAssets'
4
+ import { isZeroDevWalletConnect } from '../../utils/isZeroDevWalletConnect'
5
+ import { useSignUpContext } from './context'
6
+
7
+ /** Generic WalletConnect row — opens the pairing sheet with a raw-URI QR any
8
+ * wallet's scanner can claim. Renders nothing unless a `zeroDevWalletConnect`
9
+ * connector is in the wagmi config. */
10
+ export function SignUpWalletConnect() {
11
+ const { authPending, guardAgreement, openWalletSheet } = useSignUpContext()
12
+ const connectors = useConnectors()
13
+
14
+ if (!connectors.some(isZeroDevWalletConnect)) return null
15
+
16
+ const handleClick = () => {
17
+ if (authPending) return
18
+ if (!guardAgreement()) return
19
+ openWalletSheet()
20
+ }
21
+
22
+ return (
23
+ <ListItem
24
+ title="WalletConnect"
25
+ icon={<img src={walletConnectLogo} alt="" className="zd:w-6 zd:h-6" />}
26
+ subtitle={<Badge text="QR CODE" />}
27
+ trailing={<ListItemChevron />}
28
+ disabled={authPending}
29
+ onClick={handleClick}
30
+ />
31
+ )
32
+ }
@@ -1,5 +1,6 @@
1
1
  import { createContext, useContext, useEffect } from 'react'
2
2
  import type { EmailAuthMethod } from '../../types'
3
+ import type { WalletGuideEntry } from '../../walletGuide'
3
4
 
4
5
  export type SignUpContextValue = {
5
6
  /** True while any method's auth attempt is in flight — used to disable
@@ -21,6 +22,9 @@ export type SignUpContextValue = {
21
22
  registeredWallets: readonly string[]
22
23
  /** Returns the unregister function. */
23
24
  registerWallet: (walletId: string) => () => void
25
+ /** Opens the root's single WalletSheet — a wallet's connection paths, or
26
+ * the generic WalletConnect pairing when called without one. */
27
+ openWalletSheet: (wallet?: WalletGuideEntry) => void
24
28
  }
25
29
 
26
30
  export const SignUpContext = createContext<SignUpContextValue | null>(null)
@@ -2,7 +2,10 @@ import { Button, Text } from '@zerodev/react-ui'
2
2
  import { type ReactNode, useCallback, useState } from 'react'
3
3
  import { SignUpFooter } from '../../../shared/components/SignUpFooter'
4
4
  import { BlobAnimation } from '../../components/BlobAnimation'
5
+ import { WalletSheet } from '../../components/WalletSheet'
6
+ import { useWalletConnectPairing } from '../../hooks/useWalletConnectPairing'
5
7
  import type { EmailAuthMethod } from '../../types'
8
+ import type { WalletGuideEntry } from '../../walletGuide'
6
9
  import { SignUpContext } from './context'
7
10
  import { SignUpEmail } from './Email'
8
11
  import { SignUpGoogle } from './Google'
@@ -10,6 +13,7 @@ import { SignUpInstalledWallets } from './InstalledWallets'
10
13
  import { SignUpMoreWallets } from './MoreWallets'
11
14
  import { SignUpPasskey } from './Passkey'
12
15
  import { SignUpWallet } from './Wallet'
16
+ import { SignUpWalletConnect } from './WalletConnect'
13
17
 
14
18
  type SignUpRootProps = {
15
19
  children: ReactNode
@@ -51,6 +55,17 @@ function SignUpRoot({
51
55
  }
52
56
  }, [])
53
57
 
58
+ // The page's single WalletSheet — every wallet surface opens it via
59
+ // `openWalletSheet` instead of mounting a sheet of its own. The box
60
+ // distinguishes open-without-a-wallet (generic pairing) from closed.
61
+ const [walletSheet, setWalletSheet] = useState<{
62
+ wallet?: WalletGuideEntry | undefined
63
+ } | null>(null)
64
+ // Pairing preloads at page mount so the sheet's QR is ready on open and the
65
+ // mobile deep link below fires synchronously inside the tap — iOS only
66
+ // hands a universal link to the app for gesture-qualified navigations.
67
+ const pairing = useWalletConnectPairing()
68
+
54
69
  const requiresAgreement = !!(termsAndConditionsUrl || privacyPolicyUrl)
55
70
  const needsAgreement = requiresAgreement && !agreedToTerms
56
71
 
@@ -71,6 +86,13 @@ function SignUpRoot({
71
86
  setError,
72
87
  registeredWallets,
73
88
  registerWallet,
89
+ openWalletSheet: (wallet) => {
90
+ const deepLink = wallet && pairing.deepLinkFor(wallet)
91
+ if (deepLink) window.location.href = deepLink
92
+ // Sheet opens either way — it's the fallback surface when the
93
+ // redirect doesn't take (and the only surface on desktop).
94
+ setWalletSheet({ wallet })
95
+ },
74
96
  }}
75
97
  >
76
98
  {error !== null && (
@@ -124,6 +146,14 @@ function SignUpRoot({
124
146
  />
125
147
  </div>
126
148
  </div>
149
+ <WalletSheet
150
+ pairing={pairing}
151
+ open={walletSheet !== null}
152
+ onOpenChange={(open) => {
153
+ if (!open) setWalletSheet(null)
154
+ }}
155
+ wallet={walletSheet?.wallet}
156
+ />
127
157
  </SignUpContext.Provider>
128
158
  )
129
159
  }
@@ -158,6 +188,7 @@ export const SignUp = Object.assign(SignUpRoot, {
158
188
  Google: SignUpGoogle,
159
189
  Email: SignUpEmail,
160
190
  Wallet: SignUpWallet,
191
+ WalletConnect: SignUpWalletConnect,
161
192
  InstalledWallets: SignUpInstalledWallets,
162
193
  MoreWallets: SignUpMoreWallets,
163
194
  Divider: SignUpDivider,
@@ -0,0 +1,9 @@
1
+ export function isMobile() {
2
+ if (typeof window === 'undefined') return false
3
+ return (
4
+ window.matchMedia?.('(pointer: coarse)')?.matches ||
5
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|Opera Mini/u.test(
6
+ navigator.userAgent,
7
+ )
8
+ )
9
+ }
@@ -0,0 +1,8 @@
1
+ import type { Connector } from 'wagmi'
2
+
3
+ /** True for connectors created by `zeroDevWalletConnect` — the only ones the
4
+ * kit pairs through. A raw `walletConnect()` may have `showQrModal` enabled
5
+ * (its default), which can't be read back, so it's ignored. */
6
+ export function isZeroDevWalletConnect(connector: Connector) {
7
+ return connector.type === 'walletConnect' && 'zdWalletConnect' in connector
8
+ }
@@ -0,0 +1,23 @@
1
+ import { matchesWallet, type WalletGuideEntry } from '../walletGuide'
2
+
3
+ /**
4
+ * Wrapped deep link for the one-tap mobile redirect into `wallet`'s app, or
5
+ * null when the tap should just open the sheet: desktop, a claiming installed
6
+ * connector (direct connect wins), or no pairing URI.
7
+ */
8
+ export function walletDeepLink(params: {
9
+ wallet: WalletGuideEntry
10
+ connectors: readonly {
11
+ id: string
12
+ name?: string
13
+ type?: string
14
+ rdns?: string | readonly string[] | undefined
15
+ }[]
16
+ uri: string | null
17
+ mobile: boolean
18
+ }): string | null {
19
+ const { wallet, connectors, uri, mobile } = params
20
+ if (!wallet.mobileLink || !mobile || !uri) return null
21
+ if (connectors.some((c) => matchesWallet(c, wallet))) return null
22
+ return `${wallet.mobileLink}${encodeURIComponent(uri)}`
23
+ }
@@ -27,20 +27,53 @@ export type WalletGuideEntry = {
27
27
  }
28
28
 
29
29
  /**
30
- * A live connector "claims" a guide wallet by rdns: announced (6963)
31
- * connectors carry `id === rdns`, explicit ones (e.g. `metaMask()`) declare
32
- * `rdns` as a string or array.
30
+ * A live 6963 announcement claiming a guide wallet. Announced connectors
31
+ * carry `id === rdns`; wallets' in-app browsers announce a variant rdns
32
+ * (MetaMask mobile is `io.metamask.mobile`) but keep the wallet's exact
33
+ * name, so an announced connector matching the guide name also counts —
34
+ * the same fallback Reown AppKit and Dynamic use. The generic `injected()`
35
+ * connector (id "injected") and our embedded connector also claim type
36
+ * "injected" without being announcements.
37
+ */
38
+ export function announcesWallet(
39
+ connector: {
40
+ id: string
41
+ name?: string
42
+ type?: string
43
+ rdns?: string | readonly string[] | undefined
44
+ },
45
+ wallet: WalletGuideEntry,
46
+ ): boolean {
47
+ if (!!wallet.rdns && connector.id === wallet.rdns) return true
48
+ return (
49
+ !!wallet.rdns &&
50
+ connector.type === 'injected' &&
51
+ connector.id !== 'injected' &&
52
+ connector.id !== 'zerodev-wallet' &&
53
+ connector.name === wallet.name
54
+ )
55
+ }
56
+
57
+ /**
58
+ * A live connector "claims" a guide wallet: it announces it (see
59
+ * `announcesWallet`), or is an explicit connector (e.g. `metaMask()`)
60
+ * declaring the wallet's rdns as a string or array.
33
61
  */
34
62
  export function matchesWallet(
35
- connector: { id: string; rdns?: string | readonly string[] | undefined },
63
+ connector: {
64
+ id: string
65
+ name?: string
66
+ type?: string
67
+ rdns?: string | readonly string[] | undefined
68
+ },
36
69
  wallet: WalletGuideEntry,
37
70
  ): boolean {
71
+ if (announcesWallet(connector, wallet)) return true
38
72
  return (
39
73
  !!wallet.rdns &&
40
- (connector.id === wallet.rdns ||
41
- (Array.isArray(connector.rdns)
42
- ? connector.rdns.includes(wallet.rdns)
43
- : connector.rdns === wallet.rdns))
74
+ (Array.isArray(connector.rdns)
75
+ ? connector.rdns.includes(wallet.rdns)
76
+ : connector.rdns === wallet.rdns)
44
77
  )
45
78
  }
46
79
 
@@ -81,6 +114,7 @@ const guide = [
81
114
  name: 'Rabby Wallet',
82
115
  rdns: 'io.rabby',
83
116
  icon: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2028%2028%22%3E%3Cg%20clip-path%3D%22url(%23a)%22%3E%3Cpath%20fill%3D%22%238697FF%22%20d%3D%22M28%200H0v28h28V0Z%22%2F%3E%3Cpath%20fill%3D%22url(%23b)%22%20d%3D%22M22.54%2015.078c.677-1.514-2.673-5.744-5.874-7.506-2.017-1.365-4.12-1.178-4.545-.579-.935%201.316%203.094%202.43%205.788%203.731-.58.252-1.125.703-1.446%201.28-1.004-1.096-3.209-2.04-5.796-1.28-1.743.513-3.191%201.721-3.751%203.546a1.097%201.097%200%201%200-.445%202.1c.112%200%20.463-.075.463-.075l5.612.041c-2.244%203.56-4.018%204.081-4.018%204.698s1.697.45%202.335.22c3.05-1.1%206.327-4.531%206.89-5.519%202.36.295%204.345.33%204.786-.657Z%22%2F%3E%3Cpath%20fill%3D%22url(%23c)%22%20fill-rule%3D%22evenodd%22%20d%3D%22m17.885%2010.713.025.01c.125-.049.105-.233.07-.378-.078-.333-1.438-1.676-2.715-2.277-1.743-.82-3.025-.777-3.212-.398.356.726%201.998%201.408%203.714%202.12.723.3%201.46.606%202.118.923Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3Cpath%20fill%3D%22url(%23d)%22%20fill-rule%3D%22evenodd%22%20d%3D%22M15.701%2018.036a10.296%2010.296%200%200%200-1.2-.37c.482-.862.583-2.138.128-2.945-.639-1.133-1.44-1.736-3.304-1.736-1.024%200-3.783.346-3.832%202.648-.005.242%200%20.464.017.667l5.036.037a17.264%2017.264%200%200%201-1.871%202.483c.669.172%201.221.316%201.728.448.48.125.92.24%201.38.357a21.003%2021.003%200%200%200%201.918-1.59Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3Cpath%20fill%3D%22url(%23e)%22%20d%3D%22M6.848%2016.063c.206%201.75%201.2%202.435%203.232%202.638%202.032.203%203.197.067%204.749.208%201.296.118%202.453.778%202.882.55.386-.205.17-.947-.347-1.423-.67-.617-1.597-1.046-3.229-1.199.325-.89.234-2.138-.27-2.817-.731-.982-2.079-1.426-3.785-1.232-1.782.202-3.49%201.08-3.232%203.275Z%22%2F%3E%3C%2Fg%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22b%22%20x1%3D%2210.464%22%20x2%3D%2222.394%22%20y1%3D%2213.737%22%20y2%3D%2217.12%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%23fff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23fff%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22c%22%20x1%3D%2220.386%22%20x2%3D%2211.779%22%20y1%3D%2213.509%22%20y2%3D%224.879%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%237258DC%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23797DEA%22%20stop-opacity%3D%220%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22d%22%20x1%3D%2215.94%22%20x2%3D%227.673%22%20y1%3D%2218.337%22%20y2%3D%2213.584%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%237461EA%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23BFC2FF%22%20stop-opacity%3D%220%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22e%22%20x1%3D%2211.177%22%20x2%3D%2216.765%22%20y1%3D%2213.648%22%20y2%3D%2220.749%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%23fff%22%2F%3E%3Cstop%20offset%3D%22.984%22%20stop-color%3D%22%23D5CEFF%22%2F%3E%3C%2FlinearGradient%3E%3CclipPath%20id%3D%22a%22%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M0%200h28v28H0z%22%2F%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E',
117
+ mobileLink: 'rabby://wc?uri=',
84
118
  downloadUrl: 'https://rabby.io',
85
119
  },
86
120
  {
package/src/index.ts CHANGED
@@ -6,17 +6,21 @@
6
6
  // Auth
7
7
  export { ConnectWallet } from './auth'
8
8
  export { useAuth } from './auth/hooks/useAuth'
9
+ // Connected wallet identity (call-compatible with AppKit's useWalletInfo)
10
+ export type {
11
+ WalletInfo,
12
+ WalletSource,
13
+ } from './auth/hooks/useWalletInfo.js'
14
+ export { useWalletInfo } from './auth/hooks/useWalletInfo.js'
9
15
  export { SignUp } from './auth/pages/SignUp'
10
16
  export type { AuthMethod, AuthStep, EmailAuthMethod } from './auth/types'
11
17
  export type { WalletId } from './auth/walletGuide'
12
-
13
18
  // Connector
14
19
  export type {
15
20
  // SigningConfig,
16
21
  ZeroDevKitConnectorParams,
17
22
  } from './connector.js'
18
23
  export { zeroDevWallet } from './connector.js'
19
-
20
24
  // History
21
25
  export {
22
26
  TxHistory,
@@ -24,6 +28,7 @@ export {
24
28
  type TxHistoryStep,
25
29
  } from './history/pages'
26
30
  export type { TxHistoryEntry } from './history/types'
31
+ export { zeroDevWalletConnect } from './zeroDevWalletConnect.js'
27
32
 
28
33
  // Signing
29
34
  // export type { SignatureRequestProps } from './signing'
@@ -0,0 +1,95 @@
1
+ import type { CreateConnectorFn } from 'wagmi'
2
+ import { type WalletConnectParameters, walletConnect } from 'wagmi/connectors'
3
+ import { isMobile } from './auth/utils/isMobile'
4
+
5
+ /**
6
+ * localStorage key `@walletconnect/sign-client` reads before publishing each
7
+ * session request — when set, the SDK deep-links back into the chosen wallet
8
+ * app (`{href}/wc?requestId=…&sessionTopic=…`) so the approval prompt is in
9
+ * front of the user. Same key AppKit writes; the SDK no-ops when it's absent.
10
+ */
11
+ const DEEPLINK_CHOICE_KEY = 'WALLETCONNECT_DEEPLINK_CHOICE'
12
+
13
+ /** The slice of `@walletconnect/ethereum-provider` this module touches. */
14
+ type WcSessionProvider = {
15
+ on: (event: 'connect' | 'disconnect', listener: () => void) => unknown
16
+ session?: {
17
+ peer?: {
18
+ metadata?: { name?: string; redirect?: { native?: string } }
19
+ }
20
+ }
21
+ }
22
+
23
+ const watched = new WeakSet<object>()
24
+
25
+ /**
26
+ * Arm the SDK's redirect-on-request with the connected wallet's own
27
+ * registered scheme (`session.peer.metadata.redirect.native`) — authoritative
28
+ * for whichever wallet actually claimed the pairing. Desktop sessions and
29
+ * wallets that register no native redirect stay unarmed.
30
+ */
31
+ function storeDeepLinkChoice(provider: WcSessionProvider) {
32
+ const metadata = provider.session?.peer?.metadata
33
+ const href = metadata?.redirect?.native
34
+ if (!href || !isMobile()) {
35
+ clearDeepLinkChoice()
36
+ return
37
+ }
38
+ try {
39
+ localStorage.setItem(
40
+ DEEPLINK_CHOICE_KEY,
41
+ JSON.stringify({ href, name: metadata?.name ?? '' }),
42
+ )
43
+ } catch {
44
+ // Storage unavailable — requests still work, minus the app hop.
45
+ }
46
+ }
47
+
48
+ function clearDeepLinkChoice() {
49
+ try {
50
+ localStorage.removeItem(DEEPLINK_CHOICE_KEY)
51
+ } catch {
52
+ // Nothing to clear if storage is unavailable.
53
+ }
54
+ }
55
+
56
+ function watchSession(provider: WcSessionProvider) {
57
+ if (watched.has(provider)) return
58
+ watched.add(provider)
59
+ provider.on('connect', () => storeDeepLinkChoice(provider))
60
+ // A stale choice would deep-link a future session to the wrong app.
61
+ provider.on('disconnect', clearDeepLinkChoice)
62
+ // Session restored from storage (page reload) — re-arm without an event.
63
+ if (provider.session) storeDeepLinkChoice(provider)
64
+ }
65
+
66
+ /**
67
+ * WalletConnect connector preconfigured for the kit. `showQrModal: false` is
68
+ * required — the kit renders its own pairing UI, and WalletConnect's bundled
69
+ * Reown modal would pop over it otherwise. All other WalletConnect
70
+ * parameters (e.g. `metadata`) are forwarded as-is.
71
+ *
72
+ * The kit only pairs through connectors created here — a raw `walletConnect()`
73
+ * in the config is ignored, since its `showQrModal` can't be read back.
74
+ */
75
+ export function zeroDevWalletConnect(
76
+ params: Omit<WalletConnectParameters, 'showQrModal'>,
77
+ ): CreateConnectorFn {
78
+ if (!params.projectId) {
79
+ throw new Error('zeroDevWalletConnect requires a WalletConnect projectId')
80
+ }
81
+ const create = walletConnect({ ...params, showQrModal: false })
82
+ return (config) => {
83
+ // Stamp the connector instance so the kit's discovery can tell a
84
+ // correctly-configured connector from a raw walletConnect(), where
85
+ // showQrModal defaults to true.
86
+ const connector = Object.assign(create(config), { zdWalletConnect: true })
87
+ const getProvider = connector.getProvider.bind(connector)
88
+ connector.getProvider = async (...args: Parameters<typeof getProvider>) => {
89
+ const provider = await getProvider(...args)
90
+ watchSession(provider as WcSessionProvider)
91
+ return provider
92
+ }
93
+ return connector
94
+ }
95
+ }