@nockchain/rose 0.1.4-nightly.5
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/.github/workflows/artifacts.yml +33 -0
- package/.github/workflows/ci.yml +68 -0
- package/.github/workflows/publish-sdk.yml +35 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +8 -0
- package/LICENSE +22 -0
- package/README.md +117 -0
- package/extension/background/index.ts +1500 -0
- package/extension/content/index.ts +59 -0
- package/extension/icons/rose.svg +27 -0
- package/extension/icons/rose128.png +0 -0
- package/extension/icons/rose16.png +0 -0
- package/extension/icons/rose256.png +0 -0
- package/extension/icons/rose32.png +0 -0
- package/extension/icons/rose48.png +0 -0
- package/extension/icons/rose512.png +0 -0
- package/extension/inpage/index.ts +86 -0
- package/extension/manifest.json +48 -0
- package/extension/popup/Popup.tsx +94 -0
- package/extension/popup/Router.tsx +121 -0
- package/extension/popup/assets/arrow-down-icon.svg +3 -0
- package/extension/popup/assets/arrow-left-icon.svg +3 -0
- package/extension/popup/assets/arrow-right-icon.svg +3 -0
- package/extension/popup/assets/arrow-up-icon.svg +3 -0
- package/extension/popup/assets/arrow-up-right-icon.svg +3 -0
- package/extension/popup/assets/checkmark-icon.svg +3 -0
- package/extension/popup/assets/checkmark-pencil-icon.svg +3 -0
- package/extension/popup/assets/checkmark-success-icon.svg +3 -0
- package/extension/popup/assets/clock-icon.svg +3 -0
- package/extension/popup/assets/close-x-icon.svg +3 -0
- package/extension/popup/assets/copy-icon.svg +6 -0
- package/extension/popup/assets/explorer-icon.svg +3 -0
- package/extension/popup/assets/eye-off-icon.svg +3 -0
- package/extension/popup/assets/eye-open-icon.svg +4 -0
- package/extension/popup/assets/feedback-icon.svg +3 -0
- package/extension/popup/assets/green-status-dot.svg +3 -0
- package/extension/popup/assets/info-icon.svg +3 -0
- package/extension/popup/assets/iris-logo-40.svg +27 -0
- package/extension/popup/assets/iris-logo-96.svg +27 -0
- package/extension/popup/assets/iris-logo-blue.svg +27 -0
- package/extension/popup/assets/iris-logo-no-eye.svg +27 -0
- package/extension/popup/assets/iris-logo-orange.svg +27 -0
- package/extension/popup/assets/iris-logo.svg +27 -0
- package/extension/popup/assets/key-icon.svg +3 -0
- package/extension/popup/assets/lock-icon-yellow.svg +3 -0
- package/extension/popup/assets/lock-icon.svg +3 -0
- package/extension/popup/assets/pencil-edit-icon.svg +3 -0
- package/extension/popup/assets/permissions-icon.svg +3 -0
- package/extension/popup/assets/receipt-icon.svg +5 -0
- package/extension/popup/assets/refresh-icon.svg +3 -0
- package/extension/popup/assets/settings-gear-icon.svg +8 -0
- package/extension/popup/assets/settings-icon.svg +3 -0
- package/extension/popup/assets/theme-icon.svg +3 -0
- package/extension/popup/assets/trash-bin-icon.svg +3 -0
- package/extension/popup/assets/trend-down-arrow.svg +5 -0
- package/extension/popup/assets/trend-up-arrow.svg +5 -0
- package/extension/popup/assets/user-account-icon.svg +3 -0
- package/extension/popup/assets/vector-bottom-left.svg +9 -0
- package/extension/popup/assets/vector-left.svg +9 -0
- package/extension/popup/assets/vector-right.svg +9 -0
- package/extension/popup/assets/vector-top-right-rotated.svg +8 -0
- package/extension/popup/assets/vector-top-right.svg +9 -0
- package/extension/popup/assets/wallet-dropdown-arrow.svg +5 -0
- package/extension/popup/assets/wallet-icon-style-1.svg +6 -0
- package/extension/popup/assets/wallet-icon-style-10.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-11.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-12.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-13.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-14.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-15.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-2.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-3.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-4.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-5.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-6.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-7.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-8.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-9.svg +8 -0
- package/extension/popup/components/AccountIcon.tsx +78 -0
- package/extension/popup/components/AccountSelector.tsx +246 -0
- package/extension/popup/components/Alert.tsx +48 -0
- package/extension/popup/components/ConfirmModal.tsx +81 -0
- package/extension/popup/components/PasswordInput.tsx +49 -0
- package/extension/popup/components/ScreenContainer.tsx +17 -0
- package/extension/popup/components/SiteIcon.tsx +60 -0
- package/extension/popup/components/ThemeToggle.tsx +44 -0
- package/extension/popup/components/icons/ArrowDownLeftIcon.tsx +20 -0
- package/extension/popup/components/icons/ArrowUpRightIcon.tsx +20 -0
- package/extension/popup/components/icons/CheckIcon.tsx +20 -0
- package/extension/popup/components/icons/ChevronDownIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronLeftIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronRightIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronUpIcon.tsx +15 -0
- package/extension/popup/components/icons/CloseIcon.tsx +26 -0
- package/extension/popup/components/icons/CopyIcon.tsx +20 -0
- package/extension/popup/components/icons/EditIcon.tsx +20 -0
- package/extension/popup/components/icons/EyeIcon.tsx +13 -0
- package/extension/popup/components/icons/EyeOffIcon.tsx +13 -0
- package/extension/popup/components/icons/InfoIcon.tsx +20 -0
- package/extension/popup/components/icons/LockIcon.tsx +20 -0
- package/extension/popup/components/icons/PlusIcon.tsx +15 -0
- package/extension/popup/components/icons/ReceiveArrowIcon.tsx +14 -0
- package/extension/popup/components/icons/ReceiveCircleIcon.tsx +20 -0
- package/extension/popup/components/icons/SendPaperPlaneIcon.tsx +18 -0
- package/extension/popup/components/icons/SentArrowIcon.tsx +21 -0
- package/extension/popup/components/icons/SettingsIcon.tsx +26 -0
- package/extension/popup/components/icons/ShieldIcon.tsx +20 -0
- package/extension/popup/components/icons/UploadIcon.tsx +20 -0
- package/extension/popup/components/icons/WalletIcon.tsx +20 -0
- package/extension/popup/contexts/ThemeContext.tsx +105 -0
- package/extension/popup/hooks/useApprovalDetection.ts +128 -0
- package/extension/popup/hooks/useAutoFocus.ts +36 -0
- package/extension/popup/hooks/useAutoRejectOnClose.ts +25 -0
- package/extension/popup/hooks/useClickOutside.ts +33 -0
- package/extension/popup/hooks/useCopyToClipboard.ts +33 -0
- package/extension/popup/hooks/useFavicon.ts +64 -0
- package/extension/popup/hooks/useNumericInput.ts +93 -0
- package/extension/popup/index.html +13 -0
- package/extension/popup/index.tsx +24 -0
- package/extension/popup/screens/AboutScreen.tsx +118 -0
- package/extension/popup/screens/HomeScreen.tailwind.css +85 -0
- package/extension/popup/screens/HomeScreen.tsx +902 -0
- package/extension/popup/screens/KeySettingsPasswordScreen.tsx +164 -0
- package/extension/popup/screens/LockTimeScreen.tsx +155 -0
- package/extension/popup/screens/ReceiveScreen.tsx +149 -0
- package/extension/popup/screens/RecoveryPhraseScreen.tsx +183 -0
- package/extension/popup/screens/SendReviewScreen.tsx +308 -0
- package/extension/popup/screens/SendScreen.tsx +825 -0
- package/extension/popup/screens/SendSubmittedScreen.tsx +193 -0
- package/extension/popup/screens/SettingsScreen.tsx +116 -0
- package/extension/popup/screens/ThemeSettingsScreen.tsx +107 -0
- package/extension/popup/screens/TransactionDetailsScreen.tsx +346 -0
- package/extension/popup/screens/ViewSecretPhraseScreen.tsx +212 -0
- package/extension/popup/screens/WalletPermissionsScreen.tsx +123 -0
- package/extension/popup/screens/WalletSettingsScreen.tsx +381 -0
- package/extension/popup/screens/WalletStylingScreen.tsx +306 -0
- package/extension/popup/screens/approvals/ConnectApprovalScreen.tsx +136 -0
- package/extension/popup/screens/approvals/SignMessageScreen.tsx +140 -0
- package/extension/popup/screens/approvals/SignRawTxScreen.tsx +320 -0
- package/extension/popup/screens/approvals/TransactionApprovalScreen.tsx +167 -0
- package/extension/popup/screens/onboarding/BackupScreen.tsx +254 -0
- package/extension/popup/screens/onboarding/CreateScreen.tsx +273 -0
- package/extension/popup/screens/onboarding/ImportScreen.tsx +676 -0
- package/extension/popup/screens/onboarding/ImportScreenV0.tsx +678 -0
- package/extension/popup/screens/onboarding/ImportSuccessScreen.tsx +236 -0
- package/extension/popup/screens/onboarding/ResumeBackupScreen.tsx +166 -0
- package/extension/popup/screens/onboarding/StartScreen.tsx +142 -0
- package/extension/popup/screens/onboarding/SuccessScreen.tsx +193 -0
- package/extension/popup/screens/onboarding/VerifyScreen.tsx +220 -0
- package/extension/popup/screens/system/LockedScreen.tsx +288 -0
- package/extension/popup/screens/transactions/ReceiveScreen.tsx +84 -0
- package/extension/popup/screens/transactions/SentScreen.tsx +138 -0
- package/extension/popup/store.ts +482 -0
- package/extension/popup/styles.css +246 -0
- package/extension/popup/utils/format.ts +58 -0
- package/extension/popup/utils/formatWalletError.ts +36 -0
- package/extension/popup/utils/memo.ts +299 -0
- package/extension/popup/utils/messaging.ts +16 -0
- package/extension/shared/address-encoding.ts +69 -0
- package/extension/shared/balance-query.ts +123 -0
- package/extension/shared/constants.ts +386 -0
- package/extension/shared/currency.ts +128 -0
- package/extension/shared/first-name-derivation.ts +128 -0
- package/extension/shared/keyfile.ts +58 -0
- package/extension/shared/onboarding.ts +78 -0
- package/extension/shared/price-api.ts +79 -0
- package/extension/shared/rpc-client-browser.ts +315 -0
- package/extension/shared/transaction-builder.ts +443 -0
- package/extension/shared/types.ts +450 -0
- package/extension/shared/utxo-diff.ts +212 -0
- package/extension/shared/utxo-store.ts +548 -0
- package/extension/shared/utxo-sync.ts +343 -0
- package/extension/shared/validators.ts +26 -0
- package/extension/shared/vault.ts +1580 -0
- package/extension/shared/wallet-crypto.ts +77 -0
- package/extension/shared/wasm-utils.ts +76 -0
- package/extension/shared/webcrypto.ts +67 -0
- package/extension/types/wasm.d.ts +13 -0
- package/package.json +39 -0
- package/postcss.config.js +6 -0
- package/rose-extension-dist.zip +0 -0
- package/sdk/README.md +88 -0
- package/sdk/examples/app.ts +166 -0
- package/sdk/examples/index.html +51 -0
- package/sdk/examples/tsconfig.json +15 -0
- package/sdk/examples/tx-builder.html +532 -0
- package/sdk/examples/tx-builder.ts +1766 -0
- package/sdk/package-lock.json +424 -0
- package/sdk/package.json +68 -0
- package/sdk/src/constants.ts +28 -0
- package/sdk/src/errors.ts +74 -0
- package/sdk/src/hooks/index.ts +1 -0
- package/sdk/src/hooks/use-rose.ts +94 -0
- package/sdk/src/index.ts +12 -0
- package/sdk/src/provider.ts +396 -0
- package/sdk/src/transaction.ts +163 -0
- package/sdk/src/types/rose-wasm.d.ts +14 -0
- package/sdk/src/types.ts +97 -0
- package/sdk/src/wasm.ts +13 -0
- package/sdk/tsconfig.json +20 -0
- package/sdk/vite.config.examples.ts +32 -0
- package/tailwind.config.ts +38 -0
- package/tsconfig.json +20 -0
- package/vite.config.ts +60 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding Import Screen - Import V0 wallet from mnemonic
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useState, useRef, useEffect } from 'react';
|
|
6
|
+
import { useStore } from '../../store';
|
|
7
|
+
import { Alert } from '../../components/Alert';
|
|
8
|
+
import { useAutoFocus } from '../../hooks/useAutoFocus';
|
|
9
|
+
import { markOnboardingComplete } from '../../../shared/onboarding';
|
|
10
|
+
import { INTERNAL_METHODS, UI_CONSTANTS, ERROR_CODES } from '../../../shared/constants';
|
|
11
|
+
import { send } from '../../utils/messaging';
|
|
12
|
+
import { formatWalletError } from '../../utils/formatWalletError';
|
|
13
|
+
import lockIcon from '../../assets/lock-icon.svg';
|
|
14
|
+
import { EyeIcon } from '../../components/icons/EyeIcon';
|
|
15
|
+
import { EyeOffIcon } from '../../components/icons/EyeOffIcon';
|
|
16
|
+
import { InfoIcon } from '../../components/icons/InfoIcon';
|
|
17
|
+
|
|
18
|
+
export function ImportScreenV0() {
|
|
19
|
+
const { navigate, wallet, syncWallet, onboardingMnemonicV0, setOnboardingMnemonicV0 } =
|
|
20
|
+
useStore();
|
|
21
|
+
const isOnboarding = !Boolean(wallet.currentAccount);
|
|
22
|
+
const [isImported, setIsImported] = useState<boolean | null>(null);
|
|
23
|
+
const [isClearingImport, setIsClearingImport] = useState(false);
|
|
24
|
+
|
|
25
|
+
// Clear any stale mnemonic state on mount to ensure fresh start
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (onboardingMnemonicV0) {
|
|
28
|
+
setOnboardingMnemonicV0(null);
|
|
29
|
+
}
|
|
30
|
+
}, []);
|
|
31
|
+
const [wordsV0, setWordsV0] = useState<string[]>(
|
|
32
|
+
Array(UI_CONSTANTS.MNEMONIC_WORD_COUNT).fill('')
|
|
33
|
+
);
|
|
34
|
+
const [password, setPassword] = useState('');
|
|
35
|
+
const [confirmPassword, setConfirmPassword] = useState('');
|
|
36
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
37
|
+
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
38
|
+
const [error, setError] = useState('');
|
|
39
|
+
const [step, setStep] = useState<'mnemonic' | 'password'>('mnemonic');
|
|
40
|
+
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
|
|
41
|
+
const firstInputRef = useAutoFocus<HTMLInputElement>();
|
|
42
|
+
|
|
43
|
+
// Check existing v0 seed stored in vault when in v0 flow
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
(async () => {
|
|
46
|
+
const res = await send<{ ok?: boolean; has?: boolean; error?: unknown }>(
|
|
47
|
+
INTERNAL_METHODS.HAS_V0_MNEMONIC,
|
|
48
|
+
[]
|
|
49
|
+
);
|
|
50
|
+
if (res?.ok) {
|
|
51
|
+
setIsImported(Boolean(res.has));
|
|
52
|
+
}
|
|
53
|
+
})();
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
function handleWordChange(index: number, value: string) {
|
|
57
|
+
const trimmedValue = value.trim().toLowerCase();
|
|
58
|
+
wordsV0[index] = trimmedValue;
|
|
59
|
+
setWordsV0(wordsV0);
|
|
60
|
+
|
|
61
|
+
setError('');
|
|
62
|
+
|
|
63
|
+
// Auto-advance to next field on space
|
|
64
|
+
if (value.endsWith(' ')) {
|
|
65
|
+
const nextIndex = index + 1;
|
|
66
|
+
if (nextIndex < UI_CONSTANTS.MNEMONIC_WORD_COUNT) {
|
|
67
|
+
inputRefs.current[nextIndex]?.focus();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Handle paste in first field to auto-fill all words
|
|
73
|
+
function handlePaste(index: number, e: React.ClipboardEvent<HTMLInputElement>) {
|
|
74
|
+
if (index === 0) {
|
|
75
|
+
const pasteData = e.clipboardData.getData('text');
|
|
76
|
+
const pastedWords = pasteData.trim().toLowerCase().split(/\s+/);
|
|
77
|
+
|
|
78
|
+
if (pastedWords.length === UI_CONSTANTS.MNEMONIC_WORD_COUNT) {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
setWordsV0(pastedWords);
|
|
81
|
+
setError('');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function handleKeyDown(index: number, e: React.KeyboardEvent<HTMLInputElement>) {
|
|
87
|
+
// Backspace on empty field goes to previous
|
|
88
|
+
if (e.key === 'Backspace' && !wordsV0[index] && index > 0) {
|
|
89
|
+
inputRefs.current[index - 1]?.focus();
|
|
90
|
+
}
|
|
91
|
+
// Enter advances to next field
|
|
92
|
+
if (e.key === 'Enter') {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
const nextIndex = index + 1;
|
|
95
|
+
if (nextIndex < UI_CONSTANTS.MNEMONIC_WORD_COUNT) {
|
|
96
|
+
inputRefs.current[nextIndex]?.focus();
|
|
97
|
+
} else {
|
|
98
|
+
await handleContinue();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function handleContinue() {
|
|
104
|
+
const mnemonicV0 = wordsV0.join(' ').trim();
|
|
105
|
+
if (isOnboarding) {
|
|
106
|
+
if (wordsV0.some(w => !w)) {
|
|
107
|
+
setError('Please enter all 24 words');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
setOnboardingMnemonicV0(mnemonicV0);
|
|
112
|
+
setStep('password');
|
|
113
|
+
} else {
|
|
114
|
+
handleImport();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function handleImport() {
|
|
118
|
+
// Use stored mnemonic (set either by manual entry or keyfile import)
|
|
119
|
+
const mnemonicV0 = onboardingMnemonicV0 || wordsV0.join(' ').trim();
|
|
120
|
+
|
|
121
|
+
let result:
|
|
122
|
+
| {
|
|
123
|
+
ok?: boolean;
|
|
124
|
+
address?: string;
|
|
125
|
+
mnemonic?: string;
|
|
126
|
+
error?: string;
|
|
127
|
+
}
|
|
128
|
+
| undefined;
|
|
129
|
+
|
|
130
|
+
// Onboarding flow - setup new wallet
|
|
131
|
+
if (isOnboarding) {
|
|
132
|
+
// Validate password
|
|
133
|
+
if (!password) {
|
|
134
|
+
setError('Please enter a password');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (password.length < UI_CONSTANTS.MIN_PASSWORD_LENGTH) {
|
|
139
|
+
setError(`Password must be at least ${UI_CONSTANTS.MIN_PASSWORD_LENGTH} characters`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (password !== confirmPassword) {
|
|
144
|
+
setError('Passwords do not match');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
result = await send<{
|
|
149
|
+
ok?: boolean;
|
|
150
|
+
address?: string;
|
|
151
|
+
mnemonic?: string;
|
|
152
|
+
error?: string;
|
|
153
|
+
}>(INTERNAL_METHODS.SETUP, [password, '', mnemonicV0]);
|
|
154
|
+
|
|
155
|
+
if (result?.error) {
|
|
156
|
+
if (result.error === ERROR_CODES.INVALID_V0_MNEMONIC) {
|
|
157
|
+
setError('Invalid secret phrase. Please check your words and try again.');
|
|
158
|
+
} else {
|
|
159
|
+
setError(formatWalletError(result.error));
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
// Successfully imported - mark onboarding complete (user already has their seed)
|
|
163
|
+
await markOnboardingComplete();
|
|
164
|
+
|
|
165
|
+
const firstAccount = {
|
|
166
|
+
name: 'Wallet 1',
|
|
167
|
+
address: result.address || '',
|
|
168
|
+
index: 0,
|
|
169
|
+
};
|
|
170
|
+
syncWallet({
|
|
171
|
+
locked: false,
|
|
172
|
+
address: result.address || null,
|
|
173
|
+
accounts: [firstAccount],
|
|
174
|
+
currentAccount: firstAccount,
|
|
175
|
+
balance: 0,
|
|
176
|
+
availableBalance: 0,
|
|
177
|
+
spendableBalance: 0,
|
|
178
|
+
accountBalances: {},
|
|
179
|
+
accountSpendableBalances: {},
|
|
180
|
+
accountBalanceDetails: {},
|
|
181
|
+
});
|
|
182
|
+
setOnboardingMnemonicV0(null);
|
|
183
|
+
navigate('onboarding-import-success');
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
// Import flow - import existing wallet
|
|
187
|
+
result = await send<{
|
|
188
|
+
ok?: boolean;
|
|
189
|
+
error?: string;
|
|
190
|
+
}>(INTERNAL_METHODS.SET_V0_MNEMONIC, [mnemonicV0]);
|
|
191
|
+
|
|
192
|
+
if (result?.error) {
|
|
193
|
+
setError(formatWalletError(result.error));
|
|
194
|
+
} else {
|
|
195
|
+
navigate('onboarding-import-success');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function handleBack() {
|
|
201
|
+
if (step === 'password') {
|
|
202
|
+
setStep('mnemonic');
|
|
203
|
+
} else {
|
|
204
|
+
if (isOnboarding) {
|
|
205
|
+
navigate('onboarding-start');
|
|
206
|
+
} else {
|
|
207
|
+
navigate('home');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function handleClearV0Mnemonic() {
|
|
213
|
+
setOnboardingMnemonicV0(null);
|
|
214
|
+
setWordsV0(Array(UI_CONSTANTS.MNEMONIC_WORD_COUNT).fill(''));
|
|
215
|
+
setError('');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function handleClearStoredV0() {
|
|
219
|
+
setIsClearingImport(true);
|
|
220
|
+
setError('');
|
|
221
|
+
try {
|
|
222
|
+
const res = await send<{ ok?: boolean; error?: unknown }>(
|
|
223
|
+
INTERNAL_METHODS.CLEAR_V0_MNEMONIC,
|
|
224
|
+
[]
|
|
225
|
+
);
|
|
226
|
+
if ((res as any)?.error) {
|
|
227
|
+
setError(formatWalletError((res as any).error));
|
|
228
|
+
} else {
|
|
229
|
+
setIsImported(false);
|
|
230
|
+
handleClearV0Mnemonic();
|
|
231
|
+
}
|
|
232
|
+
} catch {
|
|
233
|
+
setError('Failed to remove stored v0 seedphrase');
|
|
234
|
+
} finally {
|
|
235
|
+
setIsClearingImport(false);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Password setup step
|
|
240
|
+
if (step === 'password') {
|
|
241
|
+
return (
|
|
242
|
+
<div className="relative w-[357px] h-[600px] bg-[var(--color-bg)]">
|
|
243
|
+
{/* Header with back button */}
|
|
244
|
+
<div className="flex items-center justify-between h-16 px-4 py-3 border-b border-[var(--color-divider)]">
|
|
245
|
+
<button
|
|
246
|
+
onClick={handleBack}
|
|
247
|
+
className="p-2 -ml-2 hover:opacity-70 transition-opacity"
|
|
248
|
+
aria-label="Go back"
|
|
249
|
+
>
|
|
250
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
251
|
+
<path
|
|
252
|
+
d="M10 12L6 8L10 4"
|
|
253
|
+
stroke="var(--color-text-primary)"
|
|
254
|
+
strokeWidth="2"
|
|
255
|
+
strokeLinecap="round"
|
|
256
|
+
strokeLinejoin="round"
|
|
257
|
+
/>
|
|
258
|
+
</svg>
|
|
259
|
+
</button>
|
|
260
|
+
<h2
|
|
261
|
+
className="font-sans font-medium text-[var(--color-text-primary)]"
|
|
262
|
+
style={{
|
|
263
|
+
fontSize: 'var(--font-size-lg)',
|
|
264
|
+
lineHeight: 'var(--line-height-normal)',
|
|
265
|
+
letterSpacing: '0.01em',
|
|
266
|
+
}}
|
|
267
|
+
>
|
|
268
|
+
Encrypt your wallet
|
|
269
|
+
</h2>
|
|
270
|
+
<div className="w-8" />
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
{/* Main content */}
|
|
274
|
+
<div className="flex flex-col justify-between h-[536px]">
|
|
275
|
+
<div className="px-4 py-2 flex flex-col gap-6">
|
|
276
|
+
{/* Icon and heading */}
|
|
277
|
+
<div className="flex flex-col items-center gap-3">
|
|
278
|
+
<div className="w-10 h-10">
|
|
279
|
+
<img src={lockIcon} alt="" className="w-full h-full" />
|
|
280
|
+
</div>
|
|
281
|
+
<div className="flex flex-col gap-2 items-center text-center w-full">
|
|
282
|
+
<h1
|
|
283
|
+
className="font-serif font-medium text-[var(--color-text-primary)]"
|
|
284
|
+
style={{
|
|
285
|
+
fontSize: 'var(--font-size-xl)',
|
|
286
|
+
lineHeight: 'var(--line-height-relaxed)',
|
|
287
|
+
letterSpacing: '-0.02em',
|
|
288
|
+
}}
|
|
289
|
+
>
|
|
290
|
+
Choose a strong password
|
|
291
|
+
</h1>
|
|
292
|
+
<p
|
|
293
|
+
className="font-sans text-[var(--color-text-muted)]"
|
|
294
|
+
style={{
|
|
295
|
+
fontSize: 'var(--font-size-sm)',
|
|
296
|
+
lineHeight: 'var(--line-height-snug)',
|
|
297
|
+
letterSpacing: '0.02em',
|
|
298
|
+
}}
|
|
299
|
+
>
|
|
300
|
+
This password encrypts your wallet
|
|
301
|
+
</p>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
{/* Password fields */}
|
|
306
|
+
<div className="flex flex-col gap-6 w-full">
|
|
307
|
+
{/* Password input */}
|
|
308
|
+
<div className="flex flex-col gap-1.5">
|
|
309
|
+
<label
|
|
310
|
+
htmlFor="password"
|
|
311
|
+
className="font-sans font-medium text-[var(--color-text-primary)]"
|
|
312
|
+
style={{
|
|
313
|
+
fontSize: 'var(--font-size-sm)',
|
|
314
|
+
lineHeight: 'var(--line-height-snug)',
|
|
315
|
+
letterSpacing: '0.02em',
|
|
316
|
+
}}
|
|
317
|
+
>
|
|
318
|
+
Create password
|
|
319
|
+
</label>
|
|
320
|
+
<div className="relative">
|
|
321
|
+
<input
|
|
322
|
+
id="password"
|
|
323
|
+
type={showPassword ? 'text' : 'password'}
|
|
324
|
+
value={password}
|
|
325
|
+
onChange={e => {
|
|
326
|
+
setPassword(e.target.value);
|
|
327
|
+
setError('');
|
|
328
|
+
}}
|
|
329
|
+
className="w-full h-[52px] px-3 py-4 bg-transparent border border-[var(--color-surface-700)] rounded-lg font-sans font-medium text-[var(--color-text-primary)] placeholder:text-[var(--color-text-secondary)] focus:outline-none focus:border-[var(--color-primary)]"
|
|
330
|
+
style={{
|
|
331
|
+
fontSize: 'var(--font-size-base)',
|
|
332
|
+
lineHeight: 'var(--line-height-snug)',
|
|
333
|
+
letterSpacing: '0.01em',
|
|
334
|
+
}}
|
|
335
|
+
autoFocus
|
|
336
|
+
/>
|
|
337
|
+
<button
|
|
338
|
+
type="button"
|
|
339
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
340
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] transition-colors"
|
|
341
|
+
tabIndex={-1}
|
|
342
|
+
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
|
343
|
+
>
|
|
344
|
+
{showPassword ? (
|
|
345
|
+
<EyeIcon className="w-4 h-4" />
|
|
346
|
+
) : (
|
|
347
|
+
<EyeOffIcon className="w-4 h-4" />
|
|
348
|
+
)}
|
|
349
|
+
</button>
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
|
|
353
|
+
{/* Confirm password input */}
|
|
354
|
+
<div className="flex flex-col gap-1.5">
|
|
355
|
+
<label
|
|
356
|
+
htmlFor="confirmPassword"
|
|
357
|
+
className="font-sans font-medium text-[var(--color-text-primary)]"
|
|
358
|
+
style={{
|
|
359
|
+
fontSize: 'var(--font-size-sm)',
|
|
360
|
+
lineHeight: 'var(--line-height-snug)',
|
|
361
|
+
letterSpacing: '0.02em',
|
|
362
|
+
}}
|
|
363
|
+
>
|
|
364
|
+
Confirm password
|
|
365
|
+
</label>
|
|
366
|
+
<div className="relative">
|
|
367
|
+
<input
|
|
368
|
+
id="confirmPassword"
|
|
369
|
+
type={showConfirmPassword ? 'text' : 'password'}
|
|
370
|
+
value={confirmPassword}
|
|
371
|
+
onChange={e => {
|
|
372
|
+
setConfirmPassword(e.target.value);
|
|
373
|
+
setError('');
|
|
374
|
+
}}
|
|
375
|
+
onKeyDown={e => e.key === 'Enter' && handleImport()}
|
|
376
|
+
className="w-full h-[52px] px-3 py-4 bg-transparent border border-[var(--color-surface-700)] rounded-lg font-sans font-medium text-[var(--color-text-primary)] placeholder:text-[var(--color-text-secondary)] focus:outline-none focus:border-[var(--color-primary)]"
|
|
377
|
+
style={{
|
|
378
|
+
fontSize: 'var(--font-size-base)',
|
|
379
|
+
lineHeight: 'var(--line-height-snug)',
|
|
380
|
+
letterSpacing: '0.01em',
|
|
381
|
+
}}
|
|
382
|
+
/>
|
|
383
|
+
<button
|
|
384
|
+
type="button"
|
|
385
|
+
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
|
386
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] transition-colors"
|
|
387
|
+
tabIndex={-1}
|
|
388
|
+
aria-label={showConfirmPassword ? 'Hide password' : 'Show password'}
|
|
389
|
+
>
|
|
390
|
+
{showConfirmPassword ? (
|
|
391
|
+
<EyeIcon className="w-4 h-4" />
|
|
392
|
+
) : (
|
|
393
|
+
<EyeOffIcon className="w-4 h-4" />
|
|
394
|
+
)}
|
|
395
|
+
</button>
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
{/* Info box */}
|
|
401
|
+
<div className="bg-[var(--color-surface-900)] rounded-lg p-3">
|
|
402
|
+
<p
|
|
403
|
+
className="font-sans font-medium text-center text-[var(--color-text-muted)]"
|
|
404
|
+
style={{
|
|
405
|
+
fontSize: 'var(--font-size-xs)',
|
|
406
|
+
lineHeight: 'var(--line-height-tight)',
|
|
407
|
+
letterSpacing: '0.02em',
|
|
408
|
+
}}
|
|
409
|
+
>
|
|
410
|
+
This password encrypts your wallet on this device. Choose something strong but
|
|
411
|
+
memorable. Your private keys never leave your browser.
|
|
412
|
+
</p>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
{/* Error message */}
|
|
416
|
+
{error && <Alert type="error">{error}</Alert>}
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
{/* Bottom buttons */}
|
|
420
|
+
<div className="border-t border-[var(--color-surface-800)] px-4 py-3">
|
|
421
|
+
<div className="flex gap-3">
|
|
422
|
+
<button
|
|
423
|
+
onClick={handleBack}
|
|
424
|
+
className="flex-1 h-12 px-5 py-[15px] bg-[var(--color-surface-800)] text-[var(--color-text-primary)] rounded-lg flex items-center justify-center transition-opacity hover:opacity-90"
|
|
425
|
+
style={{
|
|
426
|
+
fontFamily: 'var(--font-sans)',
|
|
427
|
+
fontSize: 'var(--font-size-base)',
|
|
428
|
+
fontWeight: 500,
|
|
429
|
+
lineHeight: 'var(--line-height-snug)',
|
|
430
|
+
letterSpacing: '0.01em',
|
|
431
|
+
}}
|
|
432
|
+
>
|
|
433
|
+
Back
|
|
434
|
+
</button>
|
|
435
|
+
<button
|
|
436
|
+
onClick={handleImport}
|
|
437
|
+
className="flex-1 h-12 px-5 py-[15px] btn-primary text-[#000000] rounded-lg flex items-center justify-center transition-opacity hover:opacity-90"
|
|
438
|
+
style={{
|
|
439
|
+
fontFamily: 'var(--font-sans)',
|
|
440
|
+
fontSize: 'var(--font-size-base)',
|
|
441
|
+
fontWeight: 500,
|
|
442
|
+
lineHeight: 'var(--line-height-snug)',
|
|
443
|
+
letterSpacing: '0.01em',
|
|
444
|
+
}}
|
|
445
|
+
>
|
|
446
|
+
Import v0 wallet
|
|
447
|
+
</button>
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
</div>
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Mnemonic entry step
|
|
456
|
+
return (
|
|
457
|
+
<div className="relative w-[357px] h-[600px] bg-[var(--color-bg)]">
|
|
458
|
+
{/* Header with back button */}
|
|
459
|
+
<div className="flex items-center justify-between h-16 px-4 py-3 border-b border-[var(--color-divider)]">
|
|
460
|
+
<button
|
|
461
|
+
onClick={handleBack}
|
|
462
|
+
className="p-2 -ml-2 hover:opacity-70 transition-opacity"
|
|
463
|
+
aria-label="Go back"
|
|
464
|
+
>
|
|
465
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
466
|
+
<path
|
|
467
|
+
d="M10 12L6 8L10 4"
|
|
468
|
+
stroke="var(--color-text-primary)"
|
|
469
|
+
strokeWidth="2"
|
|
470
|
+
strokeLinecap="round"
|
|
471
|
+
strokeLinejoin="round"
|
|
472
|
+
/>
|
|
473
|
+
</svg>
|
|
474
|
+
</button>
|
|
475
|
+
<h2
|
|
476
|
+
className="font-sans font-medium text-[var(--color-text-primary)]"
|
|
477
|
+
style={{
|
|
478
|
+
fontSize: 'var(--font-size-lg)',
|
|
479
|
+
lineHeight: 'var(--line-height-normal)',
|
|
480
|
+
letterSpacing: '0.01em',
|
|
481
|
+
}}
|
|
482
|
+
>
|
|
483
|
+
{isImported === true ? 'Upgrade v0 Wallet' : 'Import v0 Wallet'}
|
|
484
|
+
</h2>
|
|
485
|
+
<div className="w-8" />
|
|
486
|
+
</div>
|
|
487
|
+
|
|
488
|
+
{/* Main content - scrollable */}
|
|
489
|
+
<div className="h-[536px] flex flex-col">
|
|
490
|
+
<div className="flex-1 overflow-y-auto no-scrollbar">
|
|
491
|
+
<div className="px-4 py-2 flex flex-col gap-6">
|
|
492
|
+
{isImported === true && (
|
|
493
|
+
<>
|
|
494
|
+
{/* migration hint box */}
|
|
495
|
+
<div
|
|
496
|
+
className="flex items-start gap-2 p-3 rounded-lg"
|
|
497
|
+
style={{
|
|
498
|
+
backgroundColor: 'var(--color-surface-800)',
|
|
499
|
+
border: '1px solid var(--color-surface-700)',
|
|
500
|
+
}}
|
|
501
|
+
>
|
|
502
|
+
<InfoIcon className="w-5 h-5 flex-shrink-0 mt-0.5 m-auto" />
|
|
503
|
+
<div className="flex-1">
|
|
504
|
+
<p
|
|
505
|
+
className="font-sans text-center text-[var(--color-text-muted)]"
|
|
506
|
+
style={{
|
|
507
|
+
fontSize: 'var(--font-size-sm)',
|
|
508
|
+
lineHeight: 'var(--line-height-snug)',
|
|
509
|
+
letterSpacing: '0.01em',
|
|
510
|
+
}}
|
|
511
|
+
>
|
|
512
|
+
Hint: Upgrade your v0 notes with the upgrade tool.
|
|
513
|
+
<br />
|
|
514
|
+
<a
|
|
515
|
+
href="https://nocknames.com/upgrade"
|
|
516
|
+
target="_blank"
|
|
517
|
+
rel="noopener noreferrer"
|
|
518
|
+
className="underline hover:opacity-70 text-[#37f] font-bold"
|
|
519
|
+
>
|
|
520
|
+
nocknames.com/upgrade
|
|
521
|
+
</a>
|
|
522
|
+
</p>
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
<div
|
|
526
|
+
className="flex flex-col gap-2 p-3 rounded-lg border"
|
|
527
|
+
style={{
|
|
528
|
+
borderColor: 'var(--color-surface-700)',
|
|
529
|
+
backgroundColor: 'var(--color-surface-900)',
|
|
530
|
+
}}
|
|
531
|
+
>
|
|
532
|
+
<p
|
|
533
|
+
className="font-sans text-[var(--color-text-muted)] text-sm leading-[18px] m-0 text-center"
|
|
534
|
+
style={{ letterSpacing: '0.01em' }}
|
|
535
|
+
>
|
|
536
|
+
A v0 seedphrase is already stored in your vault. Delete it to enter a new one.
|
|
537
|
+
</p>
|
|
538
|
+
<button
|
|
539
|
+
onClick={handleClearStoredV0}
|
|
540
|
+
className="btn-secondary h-10 rounded-lg font-sans font-medium text-sm transition-opacity hover:opacity-90"
|
|
541
|
+
disabled={isClearingImport}
|
|
542
|
+
>
|
|
543
|
+
{isClearingImport ? 'Deleting…' : 'Delete v0 seed and enter a new one'}
|
|
544
|
+
</button>
|
|
545
|
+
</div>
|
|
546
|
+
</>
|
|
547
|
+
)}
|
|
548
|
+
{isImported !== true && (
|
|
549
|
+
<>
|
|
550
|
+
{/* Icon and instructions */}
|
|
551
|
+
<div className="flex flex-col items-center gap-3">
|
|
552
|
+
<div className="w-10 h-10">
|
|
553
|
+
<img src={lockIcon} alt="" className="w-full h-full" />
|
|
554
|
+
</div>
|
|
555
|
+
<p
|
|
556
|
+
className="font-sans font-medium text-center text-[var(--color-text-primary)]"
|
|
557
|
+
style={{
|
|
558
|
+
fontSize: 'var(--font-size-base)',
|
|
559
|
+
lineHeight: 'var(--line-height-snug)',
|
|
560
|
+
letterSpacing: '0.01em',
|
|
561
|
+
}}
|
|
562
|
+
>
|
|
563
|
+
Enter your 24-word secret phrase.
|
|
564
|
+
<br />
|
|
565
|
+
Paste into first field to auto-fill all words.
|
|
566
|
+
</p>
|
|
567
|
+
</div>
|
|
568
|
+
{/* migration hint box */}
|
|
569
|
+
<div
|
|
570
|
+
className="flex items-start gap-2 p-3 rounded-lg"
|
|
571
|
+
style={{
|
|
572
|
+
backgroundColor: 'var(--color-surface-800)',
|
|
573
|
+
border: '1px solid var(--color-surface-700)',
|
|
574
|
+
}}
|
|
575
|
+
>
|
|
576
|
+
<InfoIcon className="w-5 h-5 flex-shrink-0 mt-0.5 m-auto" />
|
|
577
|
+
<div className="flex-1">
|
|
578
|
+
<p
|
|
579
|
+
className="font-sans text-center text-[var(--color-text-muted)]"
|
|
580
|
+
style={{
|
|
581
|
+
fontSize: 'var(--font-size-sm)',
|
|
582
|
+
lineHeight: 'var(--line-height-snug)',
|
|
583
|
+
letterSpacing: '0.01em',
|
|
584
|
+
}}
|
|
585
|
+
>
|
|
586
|
+
Hint: Use the upgrade tool to move your notes after import.
|
|
587
|
+
<br />
|
|
588
|
+
<a
|
|
589
|
+
href="https://nocknames.com/upgrade"
|
|
590
|
+
target="_blank"
|
|
591
|
+
rel="noopener noreferrer"
|
|
592
|
+
className="underline hover:opacity-70 text-[#37f] font-bold"
|
|
593
|
+
>
|
|
594
|
+
nocknames.com/upgrade
|
|
595
|
+
</a>
|
|
596
|
+
</p>
|
|
597
|
+
</div>
|
|
598
|
+
</div>
|
|
599
|
+
|
|
600
|
+
{/* 24-word input grid */}
|
|
601
|
+
<div className="flex flex-col gap-2 w-full pb-4">
|
|
602
|
+
{Array.from({ length: 12 }).map((_, rowIndex) => (
|
|
603
|
+
<div key={rowIndex} className="flex gap-2 w-full">
|
|
604
|
+
{[0, 1].map(col => {
|
|
605
|
+
const index = rowIndex * 2 + col;
|
|
606
|
+
return (
|
|
607
|
+
<div
|
|
608
|
+
key={col}
|
|
609
|
+
className="flex-1 min-w-0 bg-[var(--color-bg)] border border-[var(--color-surface-700)] rounded-lg p-2 flex items-center gap-2.5 h-11"
|
|
610
|
+
>
|
|
611
|
+
<span
|
|
612
|
+
className="bg-[var(--color-surface-700)] rounded w-7 h-7 flex items-center justify-center font-sans font-medium text-[var(--color-text-primary)] flex-shrink-0"
|
|
613
|
+
style={{
|
|
614
|
+
fontSize: 'var(--font-size-base)',
|
|
615
|
+
lineHeight: 'var(--line-height-snug)',
|
|
616
|
+
letterSpacing: '0.01em',
|
|
617
|
+
}}
|
|
618
|
+
>
|
|
619
|
+
{index + 1}
|
|
620
|
+
</span>
|
|
621
|
+
<input
|
|
622
|
+
ref={el => {
|
|
623
|
+
inputRefs.current[index] = el;
|
|
624
|
+
if (index === 0) {
|
|
625
|
+
// @ts-ignore - Assign to auto-focus ref
|
|
626
|
+
firstInputRef.current = el;
|
|
627
|
+
}
|
|
628
|
+
}}
|
|
629
|
+
type="text"
|
|
630
|
+
value={wordsV0[index]}
|
|
631
|
+
onChange={e => handleWordChange(index, e.target.value)}
|
|
632
|
+
onKeyDown={e => handleKeyDown(index, e)}
|
|
633
|
+
onPaste={e => handlePaste(index, e)}
|
|
634
|
+
placeholder="word"
|
|
635
|
+
autoComplete="off"
|
|
636
|
+
spellCheck="false"
|
|
637
|
+
className="flex-1 min-w-0 bg-transparent font-sans font-medium text-[var(--color-text-primary)] placeholder:text-[var(--color-text-secondary)] outline-none"
|
|
638
|
+
style={{
|
|
639
|
+
fontSize: 'var(--font-size-base)',
|
|
640
|
+
lineHeight: 'var(--line-height-snug)',
|
|
641
|
+
letterSpacing: '0.01em',
|
|
642
|
+
}}
|
|
643
|
+
/>
|
|
644
|
+
</div>
|
|
645
|
+
);
|
|
646
|
+
})}
|
|
647
|
+
</div>
|
|
648
|
+
))}
|
|
649
|
+
</div>
|
|
650
|
+
</>
|
|
651
|
+
)}
|
|
652
|
+
|
|
653
|
+
{/* Error message */}
|
|
654
|
+
{error && <Alert type="error">{error}</Alert>}
|
|
655
|
+
</div>
|
|
656
|
+
</div>
|
|
657
|
+
|
|
658
|
+
{/* Bottom button */}
|
|
659
|
+
<div className="border-t border-[var(--color-surface-800)] px-4 py-3">
|
|
660
|
+
<button
|
|
661
|
+
onClick={handleContinue}
|
|
662
|
+
disabled={wordsV0.some(w => !w)}
|
|
663
|
+
className="w-full h-12 px-5 py-[15px] btn-primary text-[#000000] rounded-lg flex items-center justify-center transition-opacity hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
664
|
+
style={{
|
|
665
|
+
fontFamily: 'var(--font-sans)',
|
|
666
|
+
fontSize: 'var(--font-size-base)',
|
|
667
|
+
fontWeight: 500,
|
|
668
|
+
lineHeight: 'var(--line-height-snug)',
|
|
669
|
+
letterSpacing: '0.01em',
|
|
670
|
+
}}
|
|
671
|
+
>
|
|
672
|
+
Import v0 wallet
|
|
673
|
+
</button>
|
|
674
|
+
</div>
|
|
675
|
+
</div>
|
|
676
|
+
</div>
|
|
677
|
+
);
|
|
678
|
+
}
|