@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.
- package/README.md +38 -7
- package/dist/_types/auth/brandAssets.d.ts +2 -0
- package/dist/_types/auth/brandAssets.d.ts.map +1 -0
- package/dist/_types/auth/components/WalletSheet/index.d.ts +20 -0
- package/dist/_types/auth/components/WalletSheet/index.d.ts.map +1 -0
- package/dist/_types/auth/hooks/useWalletConnectPairing.d.ts +19 -0
- package/dist/_types/auth/hooks/useWalletConnectPairing.d.ts.map +1 -0
- package/dist/_types/auth/hooks/useWalletInfo.d.ts +32 -0
- package/dist/_types/auth/hooks/useWalletInfo.d.ts.map +1 -0
- package/dist/_types/auth/pages/SignUp/MoreWallets.d.ts.map +1 -1
- package/dist/_types/auth/pages/SignUp/Wallet.d.ts.map +1 -1
- package/dist/_types/auth/pages/SignUp/WalletConnect.d.ts +5 -0
- package/dist/_types/auth/pages/SignUp/WalletConnect.d.ts.map +1 -0
- package/dist/_types/auth/pages/SignUp/context.d.ts +4 -0
- package/dist/_types/auth/pages/SignUp/context.d.ts.map +1 -1
- package/dist/_types/auth/pages/SignUp/index.d.ts +2 -0
- package/dist/_types/auth/pages/SignUp/index.d.ts.map +1 -1
- package/dist/_types/auth/utils/isMobile.d.ts +2 -0
- package/dist/_types/auth/utils/isMobile.d.ts.map +1 -0
- package/dist/_types/auth/utils/isZeroDevWalletConnect.d.ts +6 -0
- package/dist/_types/auth/utils/isZeroDevWalletConnect.d.ts.map +1 -0
- package/dist/_types/auth/utils/walletDeepLink.d.ts +18 -0
- package/dist/_types/auth/utils/walletDeepLink.d.ts.map +1 -0
- package/dist/_types/auth/walletGuide.d.ts +21 -3
- package/dist/_types/auth/walletGuide.d.ts.map +1 -1
- package/dist/_types/index.d.ts +3 -0
- package/dist/_types/index.d.ts.map +1 -1
- package/dist/_types/zeroDevWalletConnect.d.ts +13 -0
- package/dist/_types/zeroDevWalletConnect.d.ts.map +1 -0
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1009 -659
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -5
- package/src/assets.d.ts +5 -0
- package/src/auth/brandAssets.ts +5 -0
- package/src/auth/components/WalletSheet/index.tsx +220 -0
- package/src/auth/hooks/useWalletConnectPairing.ts +134 -0
- package/src/auth/hooks/useWalletInfo.ts +145 -0
- package/src/auth/pages/SignUp/InstalledWallets.tsx +1 -1
- package/src/auth/pages/SignUp/MoreWallets.tsx +44 -9
- package/src/auth/pages/SignUp/Wallet.tsx +43 -13
- package/src/auth/pages/SignUp/WalletConnect.tsx +32 -0
- package/src/auth/pages/SignUp/context.tsx +4 -0
- package/src/auth/pages/SignUp/index.tsx +31 -0
- package/src/auth/utils/isMobile.ts +9 -0
- package/src/auth/utils/isZeroDevWalletConnect.ts +8 -0
- package/src/auth/utils/walletDeepLink.ts +23 -0
- package/src/auth/walletGuide.ts +42 -8
- package/src/index.ts +7 -2
- 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 {
|
|
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 {
|
|
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 } =
|
|
22
|
+
const { authPending, guardAgreement, setError, openWalletSheet } =
|
|
23
|
+
useSignUpContext()
|
|
21
24
|
const [open, setOpen] = useState(false)
|
|
22
25
|
const connectors = useConnectors()
|
|
23
|
-
const {
|
|
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
|
-
|
|
54
|
-
|
|
56
|
+
const wcEnabled = connectors.some(isZeroDevWalletConnect)
|
|
57
|
+
|
|
55
58
|
const guideTiles: WalletTileData[] = WALLET_GUIDE.map((wallet) => {
|
|
56
|
-
const
|
|
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
|
-
|
|
63
|
-
|
|
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 {
|
|
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 {
|
|
22
|
-
|
|
27
|
+
const {
|
|
28
|
+
authPending,
|
|
29
|
+
guardAgreement,
|
|
30
|
+
setError,
|
|
31
|
+
registerWallet,
|
|
32
|
+
openWalletSheet,
|
|
33
|
+
} = useSignUpContext()
|
|
23
34
|
const connectors = useConnectors()
|
|
24
|
-
const {
|
|
35
|
+
const { connect, isPending } = useConnect()
|
|
25
36
|
useReportPending(isPending)
|
|
26
37
|
useLayoutEffect(() => registerWallet(wallet.id), [registerWallet, wallet.id])
|
|
27
38
|
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
{...(
|
|
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,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
|
+
}
|
package/src/auth/walletGuide.ts
CHANGED
|
@@ -27,20 +27,53 @@ export type WalletGuideEntry = {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* A live
|
|
31
|
-
*
|
|
32
|
-
* `
|
|
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: {
|
|
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.
|
|
41
|
-
|
|
42
|
-
|
|
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'
|