@zerodev/wallet-react-ui 0.0.10 → 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
@@ -0,0 +1,145 @@
1
+ import { useEffect, useMemo, useState } from 'react'
2
+ import { useAccount } from 'wagmi'
3
+ import {
4
+ matchesWallet,
5
+ WALLET_GUIDE,
6
+ type WalletGuideEntry,
7
+ type WalletId,
8
+ } from '../walletGuide'
9
+
10
+ /** How the active connection reaches the wallet. */
11
+ export type WalletSource = 'injected' | 'walletconnect' | 'embedded' | 'other'
12
+
13
+ export type WalletInfo = {
14
+ /** Human-readable wallet name ('MetaMask', 'Trust Wallet', …). */
15
+ name?: string | undefined
16
+ /** Wallet icon URL or data: URI, when one is known. */
17
+ icon?: string | undefined
18
+ /** Guide id when the wallet matches the kit's wallet guide. */
19
+ walletId?: WalletId | undefined
20
+ source: WalletSource
21
+ }
22
+
23
+ /** The slice of `@walletconnect/ethereum-provider` this hook reads. */
24
+ type WcPeerProvider = {
25
+ session?: {
26
+ peer?: {
27
+ metadata?: { name?: string; icons?: readonly string[] }
28
+ }
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Guide entry for a WalletConnect peer, matched by name. Peers report their
34
+ * own branding ('MetaMask Wallet', 'Trust Wallet', …), so match by
35
+ * case-insensitive containment in either direction against the guide names.
36
+ */
37
+ function guideEntryForPeerName(name: string): WalletGuideEntry | undefined {
38
+ const peer = name.toLowerCase()
39
+ return WALLET_GUIDE.find((wallet) => {
40
+ const guide = wallet.name.toLowerCase()
41
+ return peer.includes(guide) || guide.includes(peer)
42
+ })
43
+ }
44
+
45
+ function guideEntryForConnector(connector: {
46
+ id: string
47
+ name?: string
48
+ type?: string
49
+ rdns?: string | readonly string[] | undefined
50
+ }): WalletGuideEntry | undefined {
51
+ return WALLET_GUIDE.find((wallet) => matchesWallet(connector, wallet))
52
+ }
53
+
54
+ function useResolvedWalletInfo(): WalletInfo | undefined {
55
+ const { connector, isConnected } = useAccount()
56
+ const [peerMetadata, setPeerMetadata] = useState<
57
+ { name?: string; icons?: readonly string[] } | undefined
58
+ >(undefined)
59
+
60
+ const isWalletConnect = !!connector && connector.type === 'walletConnect'
61
+
62
+ useEffect(() => {
63
+ if (!connector || !isWalletConnect) {
64
+ setPeerMetadata(undefined)
65
+ return
66
+ }
67
+ let cancelled = false
68
+ connector
69
+ .getProvider()
70
+ .then((provider) => {
71
+ if (cancelled) return
72
+ const metadata = (provider as WcPeerProvider).session?.peer?.metadata
73
+ setPeerMetadata(metadata)
74
+ })
75
+ .catch(() => {
76
+ // Provider unavailable (torn down mid-flight) — stay unresolved.
77
+ })
78
+ return () => {
79
+ cancelled = true
80
+ }
81
+ }, [connector, isWalletConnect])
82
+
83
+ // Memoized so the identity is reference-stable across renders: consumers
84
+ // put `walletInfo` in effect deps (analytics on wallet change), and a fresh
85
+ // object every render would fire those on every render instead.
86
+ return useMemo((): WalletInfo | undefined => {
87
+ if (!isConnected || !connector) return undefined
88
+
89
+ if (connector.id === 'zerodev-wallet') {
90
+ return {
91
+ name: connector.name,
92
+ icon: connector.icon,
93
+ source: 'embedded',
94
+ }
95
+ }
96
+
97
+ if (isWalletConnect) {
98
+ const entry = peerMetadata?.name
99
+ ? guideEntryForPeerName(peerMetadata.name)
100
+ : undefined
101
+ return {
102
+ name: peerMetadata?.name ?? undefined,
103
+ icon: peerMetadata?.icons?.[0] ?? entry?.icon,
104
+ walletId: entry?.id as WalletId | undefined,
105
+ // Identity is about the session, so a raw walletConnect() connector
106
+ // resolves here too — unlike pairing, which is kit-connector-only.
107
+ source: 'walletconnect',
108
+ }
109
+ }
110
+
111
+ const entry = guideEntryForConnector(connector)
112
+
113
+ return {
114
+ name: connector.name,
115
+ icon: connector.icon ?? entry?.icon,
116
+ walletId: entry?.id as WalletId | undefined,
117
+ source: connector.type === 'injected' ? 'injected' : 'other',
118
+ }
119
+ }, [connector, isConnected, isWalletConnect, peerMetadata])
120
+ }
121
+
122
+ /**
123
+ * Identity of the wallet behind the active wagmi connection, for
124
+ * wallet-specific handling and analytics attribution. Call-compatible with
125
+ * AppKit's `useWalletInfo` — same name, same `{ walletInfo }` return shape,
126
+ * so migrating is an import swap; `walletId` and `source` are kit extras.
127
+ *
128
+ * wagmi's `useAccount().connector` already names injected wallets, but a
129
+ * WalletConnect connection only reports "WalletConnect" — the actual wallet
130
+ * on the other end (Trust, Rainbow, … on a phone) is in the session's peer
131
+ * metadata, which this hook reads from the provider. `walletInfo` is
132
+ * `undefined` while disconnected, and the WalletConnect case resolves
133
+ * asynchronously (briefly `name: undefined` after connect/reload).
134
+ *
135
+ * @param _namespace - Accepted for AppKit call-compatibility ('eip155');
136
+ * ignored — the kit is EVM-only.
137
+ */
138
+ export function useWalletInfo(_namespace?: string): {
139
+ walletInfo: WalletInfo | undefined
140
+ } {
141
+ const walletInfo = useResolvedWalletInfo()
142
+ // The wrapper is memoized too, so the returned object is stable for
143
+ // consumers that depend on it whole rather than destructuring.
144
+ return useMemo(() => ({ walletInfo }), [walletInfo])
145
+ }
@@ -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'