create-openfort 0.1.10 → 1.0.1
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/CHANGELOG.md +16 -0
- package/dist/index.js +24 -23
- package/package.json +1 -1
- package/template/openfort-templates/firebase/AGENTS.md +1 -1
- package/template/openfort-templates/firebase/package.json +3 -3
- package/template/openfort-templates/firebase/src/App.tsx +2 -1
- package/template/openfort-templates/firebase/src/integrations/openfort/providers.tsx +36 -35
- package/template/openfort-templates/firebase/src/lib/contracts.ts +34 -0
- package/template/openfort-templates/firebase/src/ui/openfort/blockchain/ActionsCard.tsx +25 -13
- package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletCreation.tsx +108 -63
- package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletListCard.tsx +211 -41
- package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletPasswordSheets.tsx +45 -21
- package/template/openfort-templates/headless/AGENTS.md +1 -1
- package/template/openfort-templates/headless/package.json +2 -2
- package/template/openfort-templates/headless/src/components/cards/actions.tsx +30 -21
- package/template/openfort-templates/headless/src/components/cards/profile.tsx +0 -2
- package/template/openfort-templates/headless/src/components/cards/wallets.tsx +230 -67
- package/template/openfort-templates/headless/src/components/createWallet.tsx +115 -73
- package/template/openfort-templates/headless/src/components/passwordRecovery.tsx +48 -23
- package/template/openfort-templates/headless/src/components/providers.tsx +30 -25
- package/template/openfort-templates/headless/src/lib/contracts.ts +43 -0
- package/template/openfort-templates/openfort-ui/AGENTS.md +1 -1
- package/template/openfort-templates/openfort-ui/package.json +3 -3
- package/template/openfort-templates/openfort-ui/src/components/cards/actions.tsx +30 -15
- package/template/openfort-templates/openfort-ui/src/components/cards/auth.tsx +1 -1
- package/template/openfort-templates/openfort-ui/src/components/cards/wallets.tsx +232 -67
- package/template/openfort-templates/openfort-ui/src/components/createWallet.tsx +115 -73
- package/template/openfort-templates/openfort-ui/src/components/passwordRecovery.tsx +48 -23
- package/template/openfort-templates/openfort-ui/src/components/providers.tsx +34 -30
- package/template/openfort-templates/openfort-ui/src/lib/contracts.ts +35 -0
- package/template/openfort-templates/solana-headless/biome.json +70 -0
- package/template/openfort-templates/solana-headless/index.html +16 -0
- package/template/openfort-templates/solana-headless/package.json +34 -0
- package/template/openfort-templates/solana-headless/public/githubLogo.svg +5 -0
- package/template/openfort-templates/solana-headless/public/openfort.svg +13 -0
- package/template/openfort-templates/solana-headless/public/solanaLogo.svg +11 -0
- package/template/openfort-templates/solana-headless/src/App.tsx +7 -0
- package/template/openfort-templates/solana-headless/src/components/cards/auth.tsx +167 -0
- package/template/openfort-templates/solana-headless/src/components/cards/head.tsx +359 -0
- package/template/openfort-templates/solana-headless/src/components/cards/history.tsx +134 -0
- package/template/openfort-templates/solana-headless/src/components/cards/main.tsx +140 -0
- package/template/openfort-templates/solana-headless/src/components/cards/profile.tsx +80 -0
- package/template/openfort-templates/solana-headless/src/components/cards/send.tsx +242 -0
- package/template/openfort-templates/solana-headless/src/components/cards/sign.tsx +48 -0
- package/template/openfort-templates/solana-headless/src/components/cards/wallets.tsx +199 -0
- package/template/openfort-templates/solana-headless/src/components/createWallet.tsx +117 -0
- package/template/openfort-templates/solana-headless/src/components/passwordRecovery.tsx +167 -0
- package/template/openfort-templates/solana-headless/src/components/providers.tsx +23 -0
- package/template/openfort-templates/solana-headless/src/components/ui/Sheet.tsx +47 -0
- package/template/openfort-templates/solana-headless/src/components/ui/Tabs.tsx +111 -0
- package/template/openfort-templates/solana-headless/src/components/ui/TruncateData.tsx +31 -0
- package/template/openfort-templates/solana-headless/src/hooks/useSolanaMessageSigner.ts +37 -0
- package/template/openfort-templates/solana-headless/src/index.css +180 -0
- package/template/openfort-templates/solana-headless/src/lib/errors.ts +4 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/balance.ts +17 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/index.ts +4 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/kora.ts +137 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/transaction.ts +146 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/transactionHistory.ts +39 -0
- package/template/openfort-templates/solana-headless/src/main.tsx +13 -0
- package/template/openfort-templates/solana-headless/src/vite-env.d.ts +1 -0
- package/template/openfort-templates/solana-headless/tsconfig.app.json +24 -0
- package/template/openfort-templates/solana-headless/tsconfig.json +7 -0
- package/template/openfort-templates/solana-headless/tsconfig.node.json +22 -0
- package/template/openfort-templates/solana-headless/vite.config.ts +8 -0
|
@@ -2,13 +2,17 @@ import {
|
|
|
2
2
|
FingerPrintIcon,
|
|
3
3
|
KeyIcon,
|
|
4
4
|
LockClosedIcon,
|
|
5
|
+
WalletIcon,
|
|
5
6
|
} from '@heroicons/react/24/outline'
|
|
6
|
-
import {
|
|
7
|
+
import { AccountTypeEnum, RecoveryMethod } from '@openfort/react'
|
|
8
|
+
import { useEthereumEmbeddedWallet } from '@openfort/react/ethereum'
|
|
7
9
|
import { useState } from 'react'
|
|
8
10
|
|
|
9
11
|
import { Sheet } from '../../../components/ui/Sheet'
|
|
10
12
|
import { CreateWalletPasswordSheet } from './WalletPasswordSheets'
|
|
11
13
|
|
|
14
|
+
type CreateStep = 'choose-account-type' | 'choose-recovery-method'
|
|
15
|
+
|
|
12
16
|
type CreateWalletSheetProps = {
|
|
13
17
|
open: boolean
|
|
14
18
|
onClose: () => void
|
|
@@ -23,9 +27,7 @@ export function CreateWalletSheet({
|
|
|
23
27
|
return (
|
|
24
28
|
<Sheet
|
|
25
29
|
open={open}
|
|
26
|
-
onClose={
|
|
27
|
-
onClose()
|
|
28
|
-
}}
|
|
30
|
+
onClose={onClose}
|
|
29
31
|
title="Create Wallet"
|
|
30
32
|
description="Please choose a recovery method for your new wallet."
|
|
31
33
|
>
|
|
@@ -44,83 +46,126 @@ export function CreateWallet({
|
|
|
44
46
|
}: {
|
|
45
47
|
onWalletCreated?: () => void
|
|
46
48
|
}) {
|
|
49
|
+
const { create, status } = useEthereumEmbeddedWallet()
|
|
50
|
+
const [error, setError] = useState<string | null>(null)
|
|
51
|
+
const [step, setStep] = useState<CreateStep>('choose-account-type')
|
|
52
|
+
const [accountType, setAccountType] = useState<AccountTypeEnum>(AccountTypeEnum.SMART_ACCOUNT)
|
|
47
53
|
const [passwordSheetOpen, setPasswordSheetOpen] = useState(false)
|
|
48
54
|
|
|
49
|
-
const
|
|
50
|
-
onSuccess: () => {
|
|
51
|
-
onWalletCreated?.()
|
|
52
|
-
},
|
|
53
|
-
})
|
|
55
|
+
const isCreating = status === 'creating'
|
|
54
56
|
|
|
55
57
|
if (isCreating) {
|
|
56
58
|
return <div>Creating wallet...</div>
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
const handleCreate = async (recoveryMethod: RecoveryMethod, password?: string) => {
|
|
62
|
+
try {
|
|
63
|
+
await create({ recoveryMethod, accountType, ...(password && { password }) })
|
|
64
|
+
onWalletCreated?.()
|
|
65
|
+
} catch (err) {
|
|
66
|
+
setError(err instanceof Error ? err.message : 'Failed to create wallet')
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
59
70
|
return (
|
|
60
71
|
<>
|
|
61
|
-
|
|
62
|
-
<
|
|
63
|
-
className="
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
className="wallet-option
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
72
|
+
{step === 'choose-account-type' && (
|
|
73
|
+
<div className="flex flex-col gap-4 wallet-option-group mb-4">
|
|
74
|
+
<p className="text-sm text-zinc-400">Select account type:</p>
|
|
75
|
+
{Object.values(AccountTypeEnum).map((type) => (
|
|
76
|
+
<button
|
|
77
|
+
key={type}
|
|
78
|
+
type="button"
|
|
79
|
+
className="wallet-option cursor-pointer"
|
|
80
|
+
onClick={() => {
|
|
81
|
+
setAccountType(type)
|
|
82
|
+
setStep('choose-recovery-method')
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<WalletIcon className="h-6 w-6 shrink-0" />
|
|
86
|
+
<div className="flex flex-col text-start">
|
|
87
|
+
<h4>{type}</h4>
|
|
88
|
+
<p className="text-sm hover-description">
|
|
89
|
+
{type === AccountTypeEnum.EOA && 'A standard externally owned account.'}
|
|
90
|
+
{type === AccountTypeEnum.SMART_ACCOUNT &&
|
|
91
|
+
'A smart contract wallet (also creates an EOA owner).'}
|
|
92
|
+
{type === AccountTypeEnum.DELEGATED_ACCOUNT && 'A delegated smart account.'}
|
|
93
|
+
</p>
|
|
94
|
+
</div>
|
|
95
|
+
</button>
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
{step === 'choose-recovery-method' && (
|
|
101
|
+
<>
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
className="text-xs text-zinc-400 mb-4 hover:text-zinc-200 flex items-center gap-1"
|
|
105
|
+
onClick={() => setStep('choose-account-type')}
|
|
106
|
+
>
|
|
107
|
+
← Back ·{' '}
|
|
108
|
+
<span className="text-zinc-300">{accountType}</span>
|
|
109
|
+
</button>
|
|
110
|
+
<div className="flex flex-col gap-4 wallet-option-group mb-4">
|
|
111
|
+
<p className="text-sm text-zinc-400">Select recovery method:</p>
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
className="wallet-option cursor-pointer"
|
|
115
|
+
onClick={() => handleCreate(RecoveryMethod.PASSKEY)}
|
|
116
|
+
>
|
|
117
|
+
<FingerPrintIcon />
|
|
118
|
+
<div className="flex flex-col text-start">
|
|
119
|
+
<h4>Passkey</h4>
|
|
120
|
+
<p className="text-sm hover-description">
|
|
121
|
+
Secure your wallet with biometric authentication.
|
|
122
|
+
</p>
|
|
123
|
+
</div>
|
|
124
|
+
</button>
|
|
125
|
+
<button
|
|
126
|
+
type="button"
|
|
127
|
+
className="wallet-option cursor-pointer"
|
|
128
|
+
onClick={() => handleCreate(RecoveryMethod.AUTOMATIC)}
|
|
129
|
+
>
|
|
130
|
+
<LockClosedIcon />
|
|
131
|
+
<div className="flex flex-col text-start">
|
|
132
|
+
<h4>Automatic recovery</h4>
|
|
133
|
+
<p className="text-sm hover-description">
|
|
134
|
+
Uses encryption session to recover your wallet.
|
|
135
|
+
</p>
|
|
136
|
+
</div>
|
|
137
|
+
</button>
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
className="wallet-option cursor-pointer"
|
|
141
|
+
onClick={() => setPasswordSheetOpen(true)}
|
|
142
|
+
>
|
|
143
|
+
<KeyIcon />
|
|
144
|
+
<div className="flex flex-col text-start">
|
|
145
|
+
<h4>Password</h4>
|
|
146
|
+
<p className="text-sm hover-description">
|
|
147
|
+
Create a strong password to secure your wallet.
|
|
148
|
+
</p>
|
|
149
|
+
</div>
|
|
150
|
+
</button>
|
|
108
151
|
</div>
|
|
109
|
-
|
|
110
|
-
</div>
|
|
111
|
-
{error && (
|
|
112
|
-
<p className="text-red-500 text-sm mb-2">Error: {error.message}</p>
|
|
152
|
+
</>
|
|
113
153
|
)}
|
|
154
|
+
|
|
155
|
+
{error && <p className="text-red-500 text-sm mb-2">Error: {error}</p>}
|
|
156
|
+
|
|
114
157
|
<p className="mb-4 text-xs text-zinc-400">
|
|
115
158
|
Disclaimer: This is a demo of Openfort recovery methods. In production,
|
|
116
159
|
it's best to choose one method for a smoother user experience.
|
|
117
160
|
</p>
|
|
161
|
+
|
|
118
162
|
<CreateWalletPasswordSheet
|
|
119
163
|
open={passwordSheetOpen}
|
|
120
164
|
onClose={() => setPasswordSheetOpen(false)}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
165
|
+
create={create}
|
|
166
|
+
status={status}
|
|
167
|
+
accountType={accountType}
|
|
168
|
+
onCreateWallet={onWalletCreated}
|
|
124
169
|
/>
|
|
125
170
|
</>
|
|
126
171
|
)
|
|
@@ -1,21 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CheckIcon,
|
|
3
|
+
ChevronDownIcon,
|
|
4
|
+
ChevronUpIcon,
|
|
5
|
+
ClipboardDocumentIcon,
|
|
2
6
|
FingerPrintIcon,
|
|
3
7
|
KeyIcon,
|
|
4
8
|
LockClosedIcon,
|
|
5
9
|
} from '@heroicons/react/24/outline'
|
|
6
10
|
import {
|
|
11
|
+
AccountTypeEnum,
|
|
7
12
|
RecoveryMethod,
|
|
8
|
-
type
|
|
13
|
+
type ConnectedEmbeddedEthereumWallet,
|
|
9
14
|
useSignOut,
|
|
10
15
|
useUser,
|
|
11
|
-
useWallets,
|
|
12
16
|
} from '@openfort/react'
|
|
13
|
-
import {
|
|
17
|
+
import { useEthereumEmbeddedWallet } from '@openfort/react/ethereum'
|
|
18
|
+
import { useMemo, useState } from 'react'
|
|
14
19
|
import { useAccount } from 'wagmi'
|
|
15
20
|
import { CreateWallet, CreateWalletSheet } from './WalletCreation'
|
|
16
21
|
import { WalletRecoverPasswordSheet } from './WalletPasswordSheets'
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
const ACCOUNT_TYPE_LABELS: Record<AccountTypeEnum, string> = {
|
|
24
|
+
[AccountTypeEnum.EOA]: 'EOA',
|
|
25
|
+
[AccountTypeEnum.SMART_ACCOUNT]: 'SM',
|
|
26
|
+
[AccountTypeEnum.DELEGATED_ACCOUNT]: 'DE',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const VISIBLE_WALLET_COUNT = 4
|
|
30
|
+
|
|
31
|
+
function WalletRecoveryBadge({ wallet }: { wallet: ConnectedEmbeddedEthereumWallet }) {
|
|
19
32
|
let Icon = LockClosedIcon
|
|
20
33
|
let label = 'Unknown'
|
|
21
34
|
|
|
@@ -42,28 +55,128 @@ function WalletRecoveryBadge({ wallet }: { wallet: UserWallet }) {
|
|
|
42
55
|
)
|
|
43
56
|
}
|
|
44
57
|
|
|
58
|
+
function WalletItem({
|
|
59
|
+
wallet,
|
|
60
|
+
isActive,
|
|
61
|
+
isConnecting,
|
|
62
|
+
nested,
|
|
63
|
+
onClick,
|
|
64
|
+
}: {
|
|
65
|
+
wallet: ConnectedEmbeddedEthereumWallet
|
|
66
|
+
isActive: boolean
|
|
67
|
+
isConnecting: boolean
|
|
68
|
+
nested?: boolean
|
|
69
|
+
onClick: () => void
|
|
70
|
+
}) {
|
|
71
|
+
return (
|
|
72
|
+
<div className={nested ? 'ml-5 flex items-center gap-1' : undefined}>
|
|
73
|
+
{nested && <span className="text-zinc-500 text-sm">↳</span>}
|
|
74
|
+
<button
|
|
75
|
+
key={wallet.id + wallet.address}
|
|
76
|
+
className="px-4 py-3 border data-[active=true]:border-zinc-300 border-zinc-700 rounded data-[active=false]:cursor-pointer data-[active=false]:hover:bg-zinc-700/20 hover:border-zinc-300 transition-colors flex-1 text-sm w-full"
|
|
77
|
+
onClick={onClick}
|
|
78
|
+
data-active={isActive}
|
|
79
|
+
disabled={isActive || isConnecting}
|
|
80
|
+
type="button"
|
|
81
|
+
>
|
|
82
|
+
{isConnecting && isActive ? (
|
|
83
|
+
<p>Connecting...</p>
|
|
84
|
+
) : (
|
|
85
|
+
<div className="flex justify-between items-center">
|
|
86
|
+
<p className="font-medium mr-2">
|
|
87
|
+
{`${wallet.address.substring(0, 6)}...${wallet.address.substring(wallet.address.length - 4)}`}
|
|
88
|
+
</p>
|
|
89
|
+
<div className="flex items-center gap-2">
|
|
90
|
+
{wallet.accountType && (
|
|
91
|
+
<span className="text-[10px] font-semibold leading-none border border-zinc-600 rounded px-1 py-0.5 opacity-70">
|
|
92
|
+
{ACCOUNT_TYPE_LABELS[wallet.accountType]}
|
|
93
|
+
</span>
|
|
94
|
+
)}
|
|
95
|
+
<WalletRecoveryBadge wallet={wallet} />
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
45
104
|
export function WalletListCard() {
|
|
46
105
|
const {
|
|
47
106
|
wallets,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
} =
|
|
107
|
+
status,
|
|
108
|
+
activeWallet,
|
|
109
|
+
setActive,
|
|
110
|
+
exportPrivateKey,
|
|
111
|
+
} = useEthereumEmbeddedWallet()
|
|
112
|
+
const isLoadingWallets = status === 'fetching-wallets'
|
|
113
|
+
const isConnecting = status === 'connecting'
|
|
53
114
|
const { user, isAuthenticated } = useUser()
|
|
54
115
|
const { isConnected } = useAccount()
|
|
55
116
|
const { signOut } = useSignOut()
|
|
56
117
|
|
|
57
118
|
const [createWalletSheetOpen, setCreateWalletSheetOpen] = useState(false)
|
|
58
|
-
const [walletToRecover, setWalletToRecover] =
|
|
59
|
-
null
|
|
60
|
-
)
|
|
119
|
+
const [walletToRecover, setWalletToRecover] =
|
|
120
|
+
useState<ConnectedEmbeddedEthereumWallet | null>(null)
|
|
121
|
+
const [showAllWallets, setShowAllWallets] = useState(false)
|
|
122
|
+
const [exportedKey, setExportedKey] = useState<string | null>(null)
|
|
123
|
+
const [isExporting, setIsExporting] = useState(false)
|
|
124
|
+
const [exportError, setExportError] = useState<string | null>(null)
|
|
125
|
+
const [copied, setCopied] = useState(false)
|
|
126
|
+
|
|
127
|
+
// Group wallets: EOAs at top level, Smart/Delegated accounts nested under their owner
|
|
128
|
+
const { topLevel, childrenByOwner } = useMemo(() => {
|
|
129
|
+
const ownerAddresses = new Set(wallets.map((w) => w.address.toLowerCase()))
|
|
130
|
+
const childrenByOwner = new Map<string, ConnectedEmbeddedEthereumWallet[]>()
|
|
131
|
+
const topLevel: ConnectedEmbeddedEthereumWallet[] = []
|
|
132
|
+
|
|
133
|
+
for (const wallet of wallets) {
|
|
134
|
+
const owner = wallet.ownerAddress?.toLowerCase()
|
|
135
|
+
if (owner && ownerAddresses.has(owner) && owner !== wallet.address.toLowerCase()) {
|
|
136
|
+
const existing = childrenByOwner.get(owner) ?? []
|
|
137
|
+
existing.push(wallet)
|
|
138
|
+
childrenByOwner.set(owner, existing)
|
|
139
|
+
} else {
|
|
140
|
+
topLevel.push(wallet)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { topLevel, childrenByOwner }
|
|
145
|
+
}, [wallets])
|
|
146
|
+
|
|
147
|
+
const handleExportKey = async () => {
|
|
148
|
+
setIsExporting(true)
|
|
149
|
+
setExportError(null)
|
|
150
|
+
setExportedKey(null)
|
|
151
|
+
try {
|
|
152
|
+
const key = await exportPrivateKey()
|
|
153
|
+
setExportedKey(key)
|
|
154
|
+
} catch {
|
|
155
|
+
setExportError('Cannot export private key for this wallet.')
|
|
156
|
+
} finally {
|
|
157
|
+
setIsExporting(false)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const handleWalletClick = (wallet: ConnectedEmbeddedEthereumWallet) => {
|
|
162
|
+
const isActive =
|
|
163
|
+
activeWallet?.address.toLowerCase() === wallet.address.toLowerCase()
|
|
164
|
+
if (isActive || isConnecting) return
|
|
165
|
+
|
|
166
|
+
if (wallet.recoveryMethod === RecoveryMethod.PASSWORD) {
|
|
167
|
+
setWalletToRecover(wallet)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
setActive({ address: wallet.address })
|
|
172
|
+
}
|
|
61
173
|
|
|
174
|
+
if (!activeWallet && isConnecting) return <div>recovering ...</div>
|
|
62
175
|
if (isLoadingWallets || (!user && isAuthenticated)) {
|
|
63
176
|
return <div>Loading wallets...</div>
|
|
64
177
|
}
|
|
65
178
|
|
|
66
|
-
if (
|
|
179
|
+
if (wallets.length === 0) {
|
|
67
180
|
return (
|
|
68
181
|
<div className="flex gap-2 flex-col w-full">
|
|
69
182
|
<h1>Create a wallet</h1>
|
|
@@ -73,19 +186,9 @@ export function WalletListCard() {
|
|
|
73
186
|
)
|
|
74
187
|
}
|
|
75
188
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (wallet.recoveryMethod === RecoveryMethod.PASSWORD) {
|
|
80
|
-
setWalletToRecover(wallet)
|
|
81
|
-
return
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
setActiveWallet({
|
|
85
|
-
walletId: 'xyz.openfort',
|
|
86
|
-
address: wallet.address,
|
|
87
|
-
})
|
|
88
|
-
}
|
|
189
|
+
const visibleTopLevel = showAllWallets
|
|
190
|
+
? topLevel
|
|
191
|
+
: topLevel.slice(0, VISIBLE_WALLET_COUNT)
|
|
89
192
|
|
|
90
193
|
return (
|
|
91
194
|
<div className="flex flex-col w-full">
|
|
@@ -97,34 +200,101 @@ export function WalletListCard() {
|
|
|
97
200
|
<div className="space-y-4 pb-4">
|
|
98
201
|
<h2>Your Wallets</h2>
|
|
99
202
|
<div className="flex flex-col space-y-2">
|
|
100
|
-
{
|
|
203
|
+
{visibleTopLevel.map((wallet) => {
|
|
204
|
+
const isActive =
|
|
205
|
+
activeWallet?.address.toLowerCase() === wallet.address.toLowerCase()
|
|
206
|
+
const children = childrenByOwner.get(wallet.address.toLowerCase())
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<div key={wallet.id} className="space-y-1">
|
|
210
|
+
<WalletItem
|
|
211
|
+
wallet={wallet}
|
|
212
|
+
isActive={isActive}
|
|
213
|
+
isConnecting={isConnecting}
|
|
214
|
+
onClick={() => handleWalletClick(wallet)}
|
|
215
|
+
/>
|
|
216
|
+
{children?.map((child) => {
|
|
217
|
+
const isChildActive =
|
|
218
|
+
activeWallet?.address.toLowerCase() === child.address.toLowerCase()
|
|
219
|
+
return (
|
|
220
|
+
<WalletItem
|
|
221
|
+
key={child.id}
|
|
222
|
+
wallet={child}
|
|
223
|
+
isActive={isChildActive}
|
|
224
|
+
isConnecting={isConnecting}
|
|
225
|
+
nested
|
|
226
|
+
onClick={() => handleWalletClick(child)}
|
|
227
|
+
/>
|
|
228
|
+
)
|
|
229
|
+
})}
|
|
230
|
+
</div>
|
|
231
|
+
)
|
|
232
|
+
})}
|
|
233
|
+
|
|
234
|
+
{topLevel.length > VISIBLE_WALLET_COUNT && (
|
|
101
235
|
<button
|
|
102
|
-
|
|
103
|
-
className="
|
|
104
|
-
onClick={() =>
|
|
105
|
-
data-active={wallet.isActive}
|
|
106
|
-
disabled={wallet.isActive || isConnecting}
|
|
236
|
+
type="button"
|
|
237
|
+
className="flex items-center justify-center gap-1 text-xs text-zinc-400 hover:text-zinc-200 cursor-pointer transition-colors py-1"
|
|
238
|
+
onClick={() => setShowAllWallets((prev) => !prev)}
|
|
107
239
|
>
|
|
108
|
-
{
|
|
109
|
-
|
|
240
|
+
{showAllWallets ? (
|
|
241
|
+
<>
|
|
242
|
+
Show less <ChevronUpIcon className="h-4 w-4" />
|
|
243
|
+
</>
|
|
110
244
|
) : (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<WalletRecoveryBadge wallet={wallet} />
|
|
116
|
-
</div>
|
|
245
|
+
<>
|
|
246
|
+
Show {topLevel.length - VISIBLE_WALLET_COUNT} more{' '}
|
|
247
|
+
<ChevronDownIcon className="h-4 w-4" />
|
|
248
|
+
</>
|
|
117
249
|
)}
|
|
118
250
|
</button>
|
|
119
|
-
)
|
|
251
|
+
)}
|
|
120
252
|
|
|
121
253
|
<button
|
|
122
254
|
className="p-3 border border-zinc-700 rounded cursor-pointer hover:bg-zinc-700/20 hover:border-zinc-300 transition-colors flex-1"
|
|
123
255
|
onClick={() => setCreateWalletSheetOpen(true)}
|
|
256
|
+
type="button"
|
|
124
257
|
>
|
|
125
258
|
+ Create Wallet
|
|
126
259
|
</button>
|
|
127
260
|
</div>
|
|
261
|
+
|
|
262
|
+
{activeWallet && (
|
|
263
|
+
<div className="mt-4 space-y-2">
|
|
264
|
+
<button
|
|
265
|
+
type="button"
|
|
266
|
+
className="w-full p-3 border border-zinc-700 rounded cursor-pointer hover:bg-zinc-700/20 hover:border-zinc-300 transition-colors text-sm flex items-center justify-center gap-2"
|
|
267
|
+
onClick={handleExportKey}
|
|
268
|
+
disabled={isExporting}
|
|
269
|
+
>
|
|
270
|
+
<KeyIcon className="h-4 w-4" />
|
|
271
|
+
{isExporting ? 'Exporting...' : 'Export Private Key'}
|
|
272
|
+
</button>
|
|
273
|
+
{exportError && (
|
|
274
|
+
<p className="text-red-400 text-xs">{exportError}</p>
|
|
275
|
+
)}
|
|
276
|
+
{exportedKey && (
|
|
277
|
+
<div className="flex items-center gap-2 p-3 border border-zinc-700 rounded bg-zinc-800 text-xs break-all font-mono">
|
|
278
|
+
<span className="flex-1">{exportedKey}</span>
|
|
279
|
+
<button
|
|
280
|
+
type="button"
|
|
281
|
+
className="shrink-0 p-1 rounded hover:bg-zinc-700 transition-colors cursor-pointer"
|
|
282
|
+
onClick={() => {
|
|
283
|
+
navigator.clipboard.writeText(exportedKey)
|
|
284
|
+
setCopied(true)
|
|
285
|
+
setTimeout(() => setCopied(false), 2000)
|
|
286
|
+
}}
|
|
287
|
+
>
|
|
288
|
+
{copied ? (
|
|
289
|
+
<CheckIcon className="h-4 w-4 text-green-400" />
|
|
290
|
+
) : (
|
|
291
|
+
<ClipboardDocumentIcon className="h-4 w-4 text-zinc-400" />
|
|
292
|
+
)}
|
|
293
|
+
</button>
|
|
294
|
+
</div>
|
|
295
|
+
)}
|
|
296
|
+
</div>
|
|
297
|
+
)}
|
|
128
298
|
</div>
|
|
129
299
|
|
|
130
300
|
<WalletRecoverPasswordSheet
|