@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.
Files changed (205) hide show
  1. package/.github/workflows/artifacts.yml +33 -0
  2. package/.github/workflows/ci.yml +68 -0
  3. package/.github/workflows/publish-sdk.yml +35 -0
  4. package/.nvmrc +1 -0
  5. package/.prettierignore +5 -0
  6. package/.prettierrc +8 -0
  7. package/LICENSE +22 -0
  8. package/README.md +117 -0
  9. package/extension/background/index.ts +1500 -0
  10. package/extension/content/index.ts +59 -0
  11. package/extension/icons/rose.svg +27 -0
  12. package/extension/icons/rose128.png +0 -0
  13. package/extension/icons/rose16.png +0 -0
  14. package/extension/icons/rose256.png +0 -0
  15. package/extension/icons/rose32.png +0 -0
  16. package/extension/icons/rose48.png +0 -0
  17. package/extension/icons/rose512.png +0 -0
  18. package/extension/inpage/index.ts +86 -0
  19. package/extension/manifest.json +48 -0
  20. package/extension/popup/Popup.tsx +94 -0
  21. package/extension/popup/Router.tsx +121 -0
  22. package/extension/popup/assets/arrow-down-icon.svg +3 -0
  23. package/extension/popup/assets/arrow-left-icon.svg +3 -0
  24. package/extension/popup/assets/arrow-right-icon.svg +3 -0
  25. package/extension/popup/assets/arrow-up-icon.svg +3 -0
  26. package/extension/popup/assets/arrow-up-right-icon.svg +3 -0
  27. package/extension/popup/assets/checkmark-icon.svg +3 -0
  28. package/extension/popup/assets/checkmark-pencil-icon.svg +3 -0
  29. package/extension/popup/assets/checkmark-success-icon.svg +3 -0
  30. package/extension/popup/assets/clock-icon.svg +3 -0
  31. package/extension/popup/assets/close-x-icon.svg +3 -0
  32. package/extension/popup/assets/copy-icon.svg +6 -0
  33. package/extension/popup/assets/explorer-icon.svg +3 -0
  34. package/extension/popup/assets/eye-off-icon.svg +3 -0
  35. package/extension/popup/assets/eye-open-icon.svg +4 -0
  36. package/extension/popup/assets/feedback-icon.svg +3 -0
  37. package/extension/popup/assets/green-status-dot.svg +3 -0
  38. package/extension/popup/assets/info-icon.svg +3 -0
  39. package/extension/popup/assets/iris-logo-40.svg +27 -0
  40. package/extension/popup/assets/iris-logo-96.svg +27 -0
  41. package/extension/popup/assets/iris-logo-blue.svg +27 -0
  42. package/extension/popup/assets/iris-logo-no-eye.svg +27 -0
  43. package/extension/popup/assets/iris-logo-orange.svg +27 -0
  44. package/extension/popup/assets/iris-logo.svg +27 -0
  45. package/extension/popup/assets/key-icon.svg +3 -0
  46. package/extension/popup/assets/lock-icon-yellow.svg +3 -0
  47. package/extension/popup/assets/lock-icon.svg +3 -0
  48. package/extension/popup/assets/pencil-edit-icon.svg +3 -0
  49. package/extension/popup/assets/permissions-icon.svg +3 -0
  50. package/extension/popup/assets/receipt-icon.svg +5 -0
  51. package/extension/popup/assets/refresh-icon.svg +3 -0
  52. package/extension/popup/assets/settings-gear-icon.svg +8 -0
  53. package/extension/popup/assets/settings-icon.svg +3 -0
  54. package/extension/popup/assets/theme-icon.svg +3 -0
  55. package/extension/popup/assets/trash-bin-icon.svg +3 -0
  56. package/extension/popup/assets/trend-down-arrow.svg +5 -0
  57. package/extension/popup/assets/trend-up-arrow.svg +5 -0
  58. package/extension/popup/assets/user-account-icon.svg +3 -0
  59. package/extension/popup/assets/vector-bottom-left.svg +9 -0
  60. package/extension/popup/assets/vector-left.svg +9 -0
  61. package/extension/popup/assets/vector-right.svg +9 -0
  62. package/extension/popup/assets/vector-top-right-rotated.svg +8 -0
  63. package/extension/popup/assets/vector-top-right.svg +9 -0
  64. package/extension/popup/assets/wallet-dropdown-arrow.svg +5 -0
  65. package/extension/popup/assets/wallet-icon-style-1.svg +6 -0
  66. package/extension/popup/assets/wallet-icon-style-10.svg +8 -0
  67. package/extension/popup/assets/wallet-icon-style-11.svg +8 -0
  68. package/extension/popup/assets/wallet-icon-style-12.svg +8 -0
  69. package/extension/popup/assets/wallet-icon-style-13.svg +8 -0
  70. package/extension/popup/assets/wallet-icon-style-14.svg +8 -0
  71. package/extension/popup/assets/wallet-icon-style-15.svg +8 -0
  72. package/extension/popup/assets/wallet-icon-style-2.svg +8 -0
  73. package/extension/popup/assets/wallet-icon-style-3.svg +8 -0
  74. package/extension/popup/assets/wallet-icon-style-4.svg +8 -0
  75. package/extension/popup/assets/wallet-icon-style-5.svg +8 -0
  76. package/extension/popup/assets/wallet-icon-style-6.svg +8 -0
  77. package/extension/popup/assets/wallet-icon-style-7.svg +8 -0
  78. package/extension/popup/assets/wallet-icon-style-8.svg +8 -0
  79. package/extension/popup/assets/wallet-icon-style-9.svg +8 -0
  80. package/extension/popup/components/AccountIcon.tsx +78 -0
  81. package/extension/popup/components/AccountSelector.tsx +246 -0
  82. package/extension/popup/components/Alert.tsx +48 -0
  83. package/extension/popup/components/ConfirmModal.tsx +81 -0
  84. package/extension/popup/components/PasswordInput.tsx +49 -0
  85. package/extension/popup/components/ScreenContainer.tsx +17 -0
  86. package/extension/popup/components/SiteIcon.tsx +60 -0
  87. package/extension/popup/components/ThemeToggle.tsx +44 -0
  88. package/extension/popup/components/icons/ArrowDownLeftIcon.tsx +20 -0
  89. package/extension/popup/components/icons/ArrowUpRightIcon.tsx +20 -0
  90. package/extension/popup/components/icons/CheckIcon.tsx +20 -0
  91. package/extension/popup/components/icons/ChevronDownIcon.tsx +15 -0
  92. package/extension/popup/components/icons/ChevronLeftIcon.tsx +15 -0
  93. package/extension/popup/components/icons/ChevronRightIcon.tsx +15 -0
  94. package/extension/popup/components/icons/ChevronUpIcon.tsx +15 -0
  95. package/extension/popup/components/icons/CloseIcon.tsx +26 -0
  96. package/extension/popup/components/icons/CopyIcon.tsx +20 -0
  97. package/extension/popup/components/icons/EditIcon.tsx +20 -0
  98. package/extension/popup/components/icons/EyeIcon.tsx +13 -0
  99. package/extension/popup/components/icons/EyeOffIcon.tsx +13 -0
  100. package/extension/popup/components/icons/InfoIcon.tsx +20 -0
  101. package/extension/popup/components/icons/LockIcon.tsx +20 -0
  102. package/extension/popup/components/icons/PlusIcon.tsx +15 -0
  103. package/extension/popup/components/icons/ReceiveArrowIcon.tsx +14 -0
  104. package/extension/popup/components/icons/ReceiveCircleIcon.tsx +20 -0
  105. package/extension/popup/components/icons/SendPaperPlaneIcon.tsx +18 -0
  106. package/extension/popup/components/icons/SentArrowIcon.tsx +21 -0
  107. package/extension/popup/components/icons/SettingsIcon.tsx +26 -0
  108. package/extension/popup/components/icons/ShieldIcon.tsx +20 -0
  109. package/extension/popup/components/icons/UploadIcon.tsx +20 -0
  110. package/extension/popup/components/icons/WalletIcon.tsx +20 -0
  111. package/extension/popup/contexts/ThemeContext.tsx +105 -0
  112. package/extension/popup/hooks/useApprovalDetection.ts +128 -0
  113. package/extension/popup/hooks/useAutoFocus.ts +36 -0
  114. package/extension/popup/hooks/useAutoRejectOnClose.ts +25 -0
  115. package/extension/popup/hooks/useClickOutside.ts +33 -0
  116. package/extension/popup/hooks/useCopyToClipboard.ts +33 -0
  117. package/extension/popup/hooks/useFavicon.ts +64 -0
  118. package/extension/popup/hooks/useNumericInput.ts +93 -0
  119. package/extension/popup/index.html +13 -0
  120. package/extension/popup/index.tsx +24 -0
  121. package/extension/popup/screens/AboutScreen.tsx +118 -0
  122. package/extension/popup/screens/HomeScreen.tailwind.css +85 -0
  123. package/extension/popup/screens/HomeScreen.tsx +902 -0
  124. package/extension/popup/screens/KeySettingsPasswordScreen.tsx +164 -0
  125. package/extension/popup/screens/LockTimeScreen.tsx +155 -0
  126. package/extension/popup/screens/ReceiveScreen.tsx +149 -0
  127. package/extension/popup/screens/RecoveryPhraseScreen.tsx +183 -0
  128. package/extension/popup/screens/SendReviewScreen.tsx +308 -0
  129. package/extension/popup/screens/SendScreen.tsx +825 -0
  130. package/extension/popup/screens/SendSubmittedScreen.tsx +193 -0
  131. package/extension/popup/screens/SettingsScreen.tsx +116 -0
  132. package/extension/popup/screens/ThemeSettingsScreen.tsx +107 -0
  133. package/extension/popup/screens/TransactionDetailsScreen.tsx +346 -0
  134. package/extension/popup/screens/ViewSecretPhraseScreen.tsx +212 -0
  135. package/extension/popup/screens/WalletPermissionsScreen.tsx +123 -0
  136. package/extension/popup/screens/WalletSettingsScreen.tsx +381 -0
  137. package/extension/popup/screens/WalletStylingScreen.tsx +306 -0
  138. package/extension/popup/screens/approvals/ConnectApprovalScreen.tsx +136 -0
  139. package/extension/popup/screens/approvals/SignMessageScreen.tsx +140 -0
  140. package/extension/popup/screens/approvals/SignRawTxScreen.tsx +320 -0
  141. package/extension/popup/screens/approvals/TransactionApprovalScreen.tsx +167 -0
  142. package/extension/popup/screens/onboarding/BackupScreen.tsx +254 -0
  143. package/extension/popup/screens/onboarding/CreateScreen.tsx +273 -0
  144. package/extension/popup/screens/onboarding/ImportScreen.tsx +676 -0
  145. package/extension/popup/screens/onboarding/ImportScreenV0.tsx +678 -0
  146. package/extension/popup/screens/onboarding/ImportSuccessScreen.tsx +236 -0
  147. package/extension/popup/screens/onboarding/ResumeBackupScreen.tsx +166 -0
  148. package/extension/popup/screens/onboarding/StartScreen.tsx +142 -0
  149. package/extension/popup/screens/onboarding/SuccessScreen.tsx +193 -0
  150. package/extension/popup/screens/onboarding/VerifyScreen.tsx +220 -0
  151. package/extension/popup/screens/system/LockedScreen.tsx +288 -0
  152. package/extension/popup/screens/transactions/ReceiveScreen.tsx +84 -0
  153. package/extension/popup/screens/transactions/SentScreen.tsx +138 -0
  154. package/extension/popup/store.ts +482 -0
  155. package/extension/popup/styles.css +246 -0
  156. package/extension/popup/utils/format.ts +58 -0
  157. package/extension/popup/utils/formatWalletError.ts +36 -0
  158. package/extension/popup/utils/memo.ts +299 -0
  159. package/extension/popup/utils/messaging.ts +16 -0
  160. package/extension/shared/address-encoding.ts +69 -0
  161. package/extension/shared/balance-query.ts +123 -0
  162. package/extension/shared/constants.ts +386 -0
  163. package/extension/shared/currency.ts +128 -0
  164. package/extension/shared/first-name-derivation.ts +128 -0
  165. package/extension/shared/keyfile.ts +58 -0
  166. package/extension/shared/onboarding.ts +78 -0
  167. package/extension/shared/price-api.ts +79 -0
  168. package/extension/shared/rpc-client-browser.ts +315 -0
  169. package/extension/shared/transaction-builder.ts +443 -0
  170. package/extension/shared/types.ts +450 -0
  171. package/extension/shared/utxo-diff.ts +212 -0
  172. package/extension/shared/utxo-store.ts +548 -0
  173. package/extension/shared/utxo-sync.ts +343 -0
  174. package/extension/shared/validators.ts +26 -0
  175. package/extension/shared/vault.ts +1580 -0
  176. package/extension/shared/wallet-crypto.ts +77 -0
  177. package/extension/shared/wasm-utils.ts +76 -0
  178. package/extension/shared/webcrypto.ts +67 -0
  179. package/extension/types/wasm.d.ts +13 -0
  180. package/package.json +39 -0
  181. package/postcss.config.js +6 -0
  182. package/rose-extension-dist.zip +0 -0
  183. package/sdk/README.md +88 -0
  184. package/sdk/examples/app.ts +166 -0
  185. package/sdk/examples/index.html +51 -0
  186. package/sdk/examples/tsconfig.json +15 -0
  187. package/sdk/examples/tx-builder.html +532 -0
  188. package/sdk/examples/tx-builder.ts +1766 -0
  189. package/sdk/package-lock.json +424 -0
  190. package/sdk/package.json +68 -0
  191. package/sdk/src/constants.ts +28 -0
  192. package/sdk/src/errors.ts +74 -0
  193. package/sdk/src/hooks/index.ts +1 -0
  194. package/sdk/src/hooks/use-rose.ts +94 -0
  195. package/sdk/src/index.ts +12 -0
  196. package/sdk/src/provider.ts +396 -0
  197. package/sdk/src/transaction.ts +163 -0
  198. package/sdk/src/types/rose-wasm.d.ts +14 -0
  199. package/sdk/src/types.ts +97 -0
  200. package/sdk/src/wasm.ts +13 -0
  201. package/sdk/tsconfig.json +20 -0
  202. package/sdk/vite.config.examples.ts +32 -0
  203. package/tailwind.config.ts +38 -0
  204. package/tsconfig.json +20 -0
  205. package/vite.config.ts +60 -0
@@ -0,0 +1,167 @@
1
+ import { useStore } from '../../store';
2
+ import { ChevronRightIcon } from '../../components/icons/ChevronRightIcon';
3
+ import { AccountIcon } from '../../components/AccountIcon';
4
+ import { SiteIcon } from '../../components/SiteIcon';
5
+ import { truncateAddress } from '../../utils/format';
6
+ import { send } from '../../utils/messaging';
7
+ import {
8
+ INTERNAL_METHODS,
9
+ NOCK_TO_NICKS,
10
+ DEFAULT_TRANSACTION_FEE,
11
+ } from '../../../shared/constants';
12
+ import { formatNock, formatNick } from '../../../shared/currency';
13
+ import { useAutoRejectOnClose } from '../../hooks/useAutoRejectOnClose';
14
+
15
+ export function TransactionApprovalScreen() {
16
+ const { navigate, pendingTransactionRequest, setPendingTransactionRequest, wallet } = useStore();
17
+
18
+ if (!pendingTransactionRequest) {
19
+ navigate('home');
20
+ return null;
21
+ }
22
+
23
+ const { id, origin, to, amount } = pendingTransactionRequest;
24
+ const fee = DEFAULT_TRANSACTION_FEE;
25
+ const total = amount + fee;
26
+ const displayOrigin = origin.includes('://') ? new URL(origin).hostname : origin;
27
+
28
+ useAutoRejectOnClose(id, INTERNAL_METHODS.REJECT_TRANSACTION);
29
+
30
+ async function handleReject() {
31
+ await send(INTERNAL_METHODS.REJECT_TRANSACTION, [id]);
32
+ setPendingTransactionRequest(null);
33
+ window.close();
34
+ }
35
+
36
+ async function handleApprove() {
37
+ await send(INTERNAL_METHODS.APPROVE_TRANSACTION, [id]);
38
+ setPendingTransactionRequest(null);
39
+ window.close();
40
+ }
41
+
42
+ const bg = 'var(--color-bg)';
43
+ const surface = 'var(--color-surface-800)';
44
+ const textPrimary = 'var(--color-text-primary)';
45
+ const textMuted = 'var(--color-text-muted)';
46
+ const divider = 'var(--color-divider)';
47
+
48
+ return (
49
+ <div className="h-screen flex items-center justify-center" style={{ backgroundColor: bg }}>
50
+ <div
51
+ className="w-full h-full flex flex-col"
52
+ style={{ backgroundColor: bg, maxWidth: '357px', maxHeight: '600px' }}
53
+ >
54
+ {/* Header */}
55
+ <div className="flex items-center justify-center px-4 py-4 shrink-0">
56
+ <h2 className="text-xl font-semibold" style={{ color: textPrimary }}>
57
+ Approve Transaction
58
+ </h2>
59
+ </div>
60
+
61
+ {/* Content */}
62
+ <div className="flex-1 min-h-0 overflow-y-auto">
63
+ <div className="px-4 pb-2">
64
+ {/* Site Badge */}
65
+ <div
66
+ className="flex items-center justify-center gap-2 px-3 py-1.5 rounded-lg mb-3"
67
+ style={{ backgroundColor: surface }}
68
+ >
69
+ <span className="text-xs" style={{ color: textMuted }}>
70
+ From
71
+ </span>
72
+ <SiteIcon origin={origin} domain={displayOrigin} size="sm" />
73
+ <span
74
+ className="text-sm font-semibold truncate max-w-[160px]"
75
+ style={{ color: textPrimary }}
76
+ >
77
+ {displayOrigin}
78
+ </span>
79
+ </div>
80
+
81
+ {/* Amount */}
82
+ <div className="text-center mb-4">
83
+ <div className="font-[Lora] text-[32px] font-semibold leading-none">
84
+ {formatNock(amount / NOCK_TO_NICKS)} <span style={{ color: textMuted }}>NOCK</span>
85
+ </div>
86
+ <div className="text-[10px] mt-1" style={{ color: textMuted }}>
87
+ {formatNick(amount)} nicks
88
+ </div>
89
+ </div>
90
+
91
+ <div className="space-y-2">
92
+ {/* From/To */}
93
+ <div
94
+ className="rounded-lg p-3 flex items-center gap-2"
95
+ style={{ backgroundColor: surface }}
96
+ >
97
+ <div className="flex-1">
98
+ <div className="text-xs mb-1" style={{ color: textMuted }}>
99
+ From
100
+ </div>
101
+ <div className="flex items-center gap-1.5">
102
+ <AccountIcon
103
+ styleId={wallet.currentAccount?.iconStyleId}
104
+ color={wallet.currentAccount?.iconColor}
105
+ className="w-4 h-4"
106
+ />
107
+ <span className="text-sm">
108
+ {truncateAddress(wallet.currentAccount?.address)}
109
+ </span>
110
+ </div>
111
+ </div>
112
+ <ChevronRightIcon className="w-4 h-4 shrink-0" />
113
+ <div className="flex-1">
114
+ <div className="text-xs mb-1" style={{ color: textMuted }}>
115
+ To
116
+ </div>
117
+ <span className="text-sm">{truncateAddress(to)}</span>
118
+ </div>
119
+ </div>
120
+
121
+ {/* Fee & Total */}
122
+ <div className="rounded-lg p-3 space-y-2" style={{ backgroundColor: surface }}>
123
+ <div className="flex justify-between text-sm">
124
+ <span>Network fee</span>
125
+ <div className="text-right">
126
+ <div>{formatNock(fee / NOCK_TO_NICKS)} NOCK</div>
127
+ <div className="text-[10px]" style={{ color: textMuted }}>
128
+ {formatNick(fee)} nicks
129
+ </div>
130
+ </div>
131
+ </div>
132
+ <div className="h-px" style={{ backgroundColor: 'var(--color-surface-700)' }} />
133
+ <div className="flex justify-between text-sm font-semibold">
134
+ <span>Total</span>
135
+ <div className="text-right">
136
+ <div>{formatNock(total / NOCK_TO_NICKS)} NOCK</div>
137
+ <div className="text-[10px] font-normal" style={{ color: textMuted }}>
138
+ {formatNick(total)} nicks
139
+ </div>
140
+ </div>
141
+ </div>
142
+ </div>
143
+
144
+ {/* Balance After */}
145
+ <div className="text-center text-xs py-2" style={{ color: textMuted }}>
146
+ Balance after: {formatNock(wallet.balance - total / NOCK_TO_NICKS)} NOCK
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </div>
151
+
152
+ {/* Footer Buttons */}
153
+ <div
154
+ className="px-4 py-2.5 shrink-0 flex gap-3"
155
+ style={{ borderTop: `1px solid ${divider}` }}
156
+ >
157
+ <button onClick={handleReject} className="btn-secondary flex-1">
158
+ Reject
159
+ </button>
160
+ <button onClick={handleApprove} className="btn-primary flex-1">
161
+ Approve
162
+ </button>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ );
167
+ }
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Onboarding Backup Screen - Display mnemonic for user to write down
3
+ */
4
+
5
+ import { useState } from 'react';
6
+ import { useStore } from '../../store';
7
+ import { Alert } from '../../components/Alert';
8
+ import { CheckIcon } from '../../components/icons/CheckIcon';
9
+ import lockIcon from '../../assets/lock-icon.svg';
10
+
11
+ export function BackupScreen() {
12
+ const { onboardingMnemonic, navigate } = useStore();
13
+ const [isRevealed, setIsRevealed] = useState(false);
14
+ const [hasConfirmed, setHasConfirmed] = useState(false);
15
+ const [copiedAll, setCopiedAll] = useState(false);
16
+
17
+ async function handleCopyAll() {
18
+ if (onboardingMnemonic) {
19
+ try {
20
+ await navigator.clipboard.writeText(onboardingMnemonic);
21
+ setCopiedAll(true);
22
+ setTimeout(() => setCopiedAll(false), 2000);
23
+ } catch (err) {
24
+ console.error('Failed to copy secret phrase:', err);
25
+ }
26
+ }
27
+ }
28
+
29
+ if (!onboardingMnemonic) {
30
+ // Should never happen, but handle gracefully
31
+ return (
32
+ <div className="w-[357px] h-[600px] bg-[var(--color-bg)] flex items-center justify-center p-4">
33
+ <Alert type="error">No mnemonic found. Please restart onboarding.</Alert>
34
+ </div>
35
+ );
36
+ }
37
+
38
+ const words = onboardingMnemonic.split(' ');
39
+
40
+ function handleContinue() {
41
+ if (hasConfirmed) {
42
+ navigate('onboarding-verify');
43
+ }
44
+ }
45
+
46
+ return (
47
+ <div className="w-[357px] h-[600px] flex flex-col bg-[var(--color-bg)]">
48
+ {/* Header with back button */}
49
+ <div className="flex items-center justify-between h-16 px-4 py-3 border-b border-[var(--color-divider)] shrink-0">
50
+ <button
51
+ onClick={() => navigate('onboarding-create')}
52
+ className="p-2 -ml-2 hover:opacity-70 transition-opacity"
53
+ aria-label="Go back"
54
+ >
55
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
56
+ <path
57
+ d="M10 12L6 8L10 4"
58
+ stroke="var(--color-text-primary)"
59
+ strokeWidth="2"
60
+ strokeLinecap="round"
61
+ strokeLinejoin="round"
62
+ />
63
+ </svg>
64
+ </button>
65
+ <h2
66
+ className="font-sans font-medium text-[var(--color-text-primary)]"
67
+ style={{
68
+ fontSize: 'var(--font-size-lg)',
69
+ lineHeight: 'var(--line-height-normal)',
70
+ letterSpacing: '0.01em',
71
+ }}
72
+ >
73
+ Save your secret phrase
74
+ </h2>
75
+ <div className="w-8" /> {/* Spacer for centering */}
76
+ </div>
77
+
78
+ {/* Main content - scrollable */}
79
+ <div className="flex-1 overflow-y-auto no-scrollbar">
80
+ <div className="px-4 py-2 pb-4 flex flex-col gap-6">
81
+ {/* Icon and heading */}
82
+ <div className="flex flex-col items-center gap-3">
83
+ <div className="w-10 h-10">
84
+ <img src={lockIcon} alt="" className="w-full h-full" />
85
+ </div>
86
+ <h1
87
+ className="font-serif font-medium text-center text-[var(--color-text-primary)]"
88
+ style={{
89
+ fontSize: 'var(--font-size-xl)',
90
+ lineHeight: 'var(--line-height-relaxed)',
91
+ letterSpacing: '-0.02em',
92
+ }}
93
+ >
94
+ Write these words
95
+ <br />
96
+ down in order.
97
+ </h1>
98
+ </div>
99
+
100
+ {/* Secret phrase section */}
101
+ <div className="flex flex-col gap-3">
102
+ {/* Warning box */}
103
+ <div className="bg-[var(--color-surface-900)] rounded-lg p-3">
104
+ <p
105
+ className="font-sans font-medium text-center text-[var(--color-text-muted)]"
106
+ style={{
107
+ fontSize: 'var(--font-size-xs)',
108
+ lineHeight: 'var(--line-height-tight)',
109
+ letterSpacing: '0.02em',
110
+ }}
111
+ >
112
+ This is your ONLY way to recover your wallet.
113
+ </p>
114
+ </div>
115
+
116
+ {/* Secret phrase grid */}
117
+ <div className="relative">
118
+ <div className="grid grid-cols-2 gap-2">
119
+ {words.map((word, index) => (
120
+ <div
121
+ key={index}
122
+ className="bg-[var(--color-bg)] border border-[var(--color-surface-900)] rounded-lg p-2 flex items-center gap-2.5 h-11"
123
+ >
124
+ <span
125
+ className="bg-[var(--color-surface-900)] rounded min-w-[28px] h-7 flex items-center justify-center font-sans font-medium text-[var(--color-text-primary)]"
126
+ style={{
127
+ fontSize: 'var(--font-size-base)',
128
+ lineHeight: 'var(--line-height-snug)',
129
+ letterSpacing: '0.01em',
130
+ }}
131
+ >
132
+ {index + 1}
133
+ </span>
134
+ <span
135
+ className="font-sans font-medium text-[var(--color-text-primary)]"
136
+ style={{
137
+ fontSize: 'var(--font-size-base)',
138
+ lineHeight: 'var(--line-height-snug)',
139
+ letterSpacing: '0.01em',
140
+ }}
141
+ >
142
+ {word}
143
+ </span>
144
+ </div>
145
+ ))}
146
+ </div>
147
+
148
+ {/* Blur overlay when not revealed */}
149
+ {!isRevealed && (
150
+ <div
151
+ className="absolute inset-0 backdrop-blur-[6px] rounded-lg"
152
+ style={{
153
+ backgroundColor: 'var(--color-popover)',
154
+ border: '1px solid var(--color-surface-900)',
155
+ }}
156
+ />
157
+ )}
158
+ </div>
159
+ </div>
160
+
161
+ {/* Checkbox confirmation */}
162
+ <div className="bg-[var(--color-surface-900)] rounded-lg p-3 flex items-center gap-2.5">
163
+ <button
164
+ onClick={() => setHasConfirmed(!hasConfirmed)}
165
+ className={`w-7 h-7 rounded-md flex items-center justify-center transition-colors ${
166
+ hasConfirmed
167
+ ? 'btn-primary'
168
+ : 'bg-transparent border-2 border-[var(--color-surface-700)]'
169
+ }`}
170
+ aria-label="Confirm you've written down the secret phrase"
171
+ >
172
+ {hasConfirmed && (
173
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
174
+ <path
175
+ d="M16 6L7.5 14.5L4 11"
176
+ stroke="#000000"
177
+ strokeWidth="2"
178
+ strokeLinecap="round"
179
+ strokeLinejoin="round"
180
+ />
181
+ </svg>
182
+ )}
183
+ </button>
184
+ <p
185
+ className="flex-1 font-sans font-medium text-[var(--color-text-muted)]"
186
+ style={{
187
+ fontSize: 'var(--font-size-xs)',
188
+ lineHeight: 'var(--line-height-tight)',
189
+ letterSpacing: '0.02em',
190
+ }}
191
+ >
192
+ I've securely written down all 24 words of my secret phrase
193
+ </p>
194
+ </div>
195
+ </div>
196
+ </div>
197
+
198
+ {/* Bottom buttons - Pinned to bottom */}
199
+ <div className="border-t border-[var(--color-surface-800)] px-4 py-3 bg-[var(--color-bg)] shrink-0">
200
+ {!isRevealed ? (
201
+ <button
202
+ onClick={() => setIsRevealed(true)}
203
+ className="w-full h-12 px-5 py-[15px] btn-secondary text-[var(--color-bg)] rounded-lg flex items-center justify-center transition-opacity hover:opacity-90"
204
+ style={{
205
+ fontFamily: 'var(--font-sans)',
206
+ fontSize: 'var(--font-size-base)',
207
+ fontWeight: 500,
208
+ lineHeight: 'var(--line-height-snug)',
209
+ letterSpacing: '0.01em',
210
+ }}
211
+ >
212
+ Show secret phrase
213
+ </button>
214
+ ) : (
215
+ <div className="flex gap-3">
216
+ <button
217
+ onClick={handleCopyAll}
218
+ disabled={copiedAll}
219
+ className="flex-1 h-12 px-5 py-[15px] rounded-lg flex items-center justify-center gap-2 transition-opacity bg-[var(--color-surface-900)] text-[var(--color-text-primary)] hover:opacity-90 disabled:opacity-100"
220
+ style={{
221
+ fontFamily: 'var(--font-sans)',
222
+ fontSize: 'var(--font-size-base)',
223
+ fontWeight: 500,
224
+ lineHeight: 'var(--line-height-snug)',
225
+ letterSpacing: '0.01em',
226
+ }}
227
+ >
228
+ {copiedAll && <CheckIcon className="w-5 h-5" />}
229
+ {copiedAll ? 'Copied!' : 'Copy all'}
230
+ </button>
231
+ <button
232
+ onClick={handleContinue}
233
+ disabled={!hasConfirmed}
234
+ className={`flex-1 h-12 px-5 py-[15px] rounded-lg flex items-center justify-center transition-opacity ${
235
+ hasConfirmed
236
+ ? 'btn-secondary text-[var(--color-bg)] hover:opacity-90'
237
+ : 'bg-[var(--color-surface-700)] text-[var(--color-text-muted)] cursor-not-allowed opacity-50'
238
+ }`}
239
+ style={{
240
+ fontFamily: 'var(--font-sans)',
241
+ fontSize: 'var(--font-size-base)',
242
+ fontWeight: 500,
243
+ lineHeight: 'var(--line-height-snug)',
244
+ letterSpacing: '0.01em',
245
+ }}
246
+ >
247
+ Continue
248
+ </button>
249
+ </div>
250
+ )}
251
+ </div>
252
+ </div>
253
+ );
254
+ }
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Onboarding Create Screen - Set password and create wallet
3
+ */
4
+
5
+ import { useState } from 'react';
6
+ import { INTERNAL_METHODS, UI_CONSTANTS } from '../../../shared/constants';
7
+ import { setOnboardingInProgress } from '../../../shared/onboarding';
8
+ import { useStore } from '../../store';
9
+ import { send } from '../../utils/messaging';
10
+ import { formatWalletError } from '../../utils/formatWalletError';
11
+ import { Alert } from '../../components/Alert';
12
+ import lockIcon from '../../assets/lock-icon.svg';
13
+ import { EyeIcon } from '../../components/icons/EyeIcon';
14
+ import { EyeOffIcon } from '../../components/icons/EyeOffIcon';
15
+
16
+ export function CreateScreen() {
17
+ const [password, setPassword] = useState('');
18
+ const [confirmPassword, setConfirmPassword] = useState('');
19
+ const [showPassword, setShowPassword] = useState(false);
20
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
21
+ const [error, setError] = useState('');
22
+ const { navigate, syncWallet, setOnboardingMnemonic } = useStore();
23
+
24
+ async function handleCreate() {
25
+ // Clear previous errors
26
+ setError('');
27
+
28
+ // Validate password
29
+ if (!password) {
30
+ setError('Please enter a password');
31
+ return;
32
+ }
33
+
34
+ if (password.length < UI_CONSTANTS.MIN_PASSWORD_LENGTH) {
35
+ setError(`Password must be at least ${UI_CONSTANTS.MIN_PASSWORD_LENGTH} characters`);
36
+ return;
37
+ }
38
+
39
+ if (password !== confirmPassword) {
40
+ setError('Passwords do not match');
41
+ return;
42
+ }
43
+
44
+ // Create wallet
45
+ const result = await send<{
46
+ ok?: boolean;
47
+ address?: string;
48
+ mnemonic?: string;
49
+ error?: string;
50
+ }>(INTERNAL_METHODS.SETUP, [password]);
51
+
52
+ if (result?.error) {
53
+ setError(formatWalletError(result.error));
54
+ } else {
55
+ // Mark onboarding as in-progress (backup not yet complete)
56
+ await setOnboardingInProgress();
57
+
58
+ // Store mnemonic temporarily for backup/verification flow
59
+ setOnboardingMnemonic(result.mnemonic || '');
60
+ // After setup, we have the first account (Wallet 1)
61
+ const firstAccount = {
62
+ name: 'Wallet 1',
63
+ address: result.address || '',
64
+ index: 0,
65
+ };
66
+ syncWallet({
67
+ locked: false,
68
+ address: result.address || null,
69
+ accounts: [firstAccount],
70
+ currentAccount: firstAccount,
71
+ balance: 0, // New wallet starts with 0 balance
72
+ availableBalance: 0,
73
+ spendableBalance: 0,
74
+ accountBalances: {},
75
+ accountSpendableBalances: {},
76
+ accountBalanceDetails: {},
77
+ });
78
+ navigate('onboarding-backup');
79
+ }
80
+ }
81
+
82
+ return (
83
+ <div className="relative w-[357px] h-[600px] bg-[var(--color-bg)]">
84
+ {/* Header with back button */}
85
+ <div className="flex items-center justify-between h-16 px-4 py-3 border-b border-[var(--color-divider)]">
86
+ <button
87
+ onClick={() => navigate('onboarding-start')}
88
+ className="p-2 -ml-2 hover:opacity-70 transition-opacity"
89
+ aria-label="Go back"
90
+ >
91
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
92
+ <path
93
+ d="M10 12L6 8L10 4"
94
+ stroke="var(--color-text-primary)"
95
+ strokeWidth="2"
96
+ strokeLinecap="round"
97
+ strokeLinejoin="round"
98
+ />
99
+ </svg>
100
+ </button>
101
+ <h2
102
+ className="font-sans font-medium text-[var(--color-text-primary)]"
103
+ style={{
104
+ fontSize: 'var(--font-size-lg)',
105
+ lineHeight: 'var(--line-height-normal)',
106
+ letterSpacing: '0.01em',
107
+ }}
108
+ >
109
+ Create new wallet
110
+ </h2>
111
+ <div className="w-8" /> {/* Spacer for centering */}
112
+ </div>
113
+
114
+ {/* Main content */}
115
+ <div className="flex flex-col justify-between h-[536px]">
116
+ <div className="px-4 py-2 flex flex-col gap-6">
117
+ {/* Icon and heading */}
118
+ <div className="flex flex-col items-center gap-3">
119
+ <div className="w-10 h-10">
120
+ <img src={lockIcon} alt="" className="w-full h-full" />
121
+ </div>
122
+ <h1
123
+ className="font-serif font-medium text-center text-[var(--color-text-primary)]"
124
+ style={{
125
+ fontSize: 'var(--font-size-xl)',
126
+ lineHeight: 'var(--line-height-relaxed)',
127
+ letterSpacing: '-0.02em',
128
+ }}
129
+ >
130
+ First, let's secure your
131
+ <br />
132
+ wallet with a password
133
+ </h1>
134
+ </div>
135
+
136
+ {/* Password inputs */}
137
+ <div className="flex flex-col gap-6">
138
+ {/* Create password */}
139
+ <div className="flex flex-col gap-1.5">
140
+ <label
141
+ htmlFor="password"
142
+ className="font-sans font-medium text-[var(--color-text-primary)]"
143
+ style={{
144
+ fontSize: 'var(--font-size-sm)',
145
+ lineHeight: 'var(--line-height-snug)',
146
+ letterSpacing: '0.02em',
147
+ }}
148
+ >
149
+ Create password
150
+ </label>
151
+ <div className="relative">
152
+ <input
153
+ id="password"
154
+ type={showPassword ? 'text' : 'password'}
155
+ value={password}
156
+ onChange={e => {
157
+ setPassword(e.target.value);
158
+ setError('');
159
+ }}
160
+ 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)]"
161
+ style={{
162
+ fontSize: 'var(--font-size-base)',
163
+ lineHeight: 'var(--line-height-snug)',
164
+ letterSpacing: '0.01em',
165
+ }}
166
+ autoFocus
167
+ />
168
+ <button
169
+ type="button"
170
+ onClick={() => setShowPassword(!showPassword)}
171
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] transition-colors"
172
+ tabIndex={-1}
173
+ aria-label={showPassword ? 'Hide password' : 'Show password'}
174
+ >
175
+ {showPassword ? (
176
+ <EyeIcon className="w-4 h-4" />
177
+ ) : (
178
+ <EyeOffIcon className="w-4 h-4" />
179
+ )}
180
+ </button>
181
+ </div>
182
+ </div>
183
+
184
+ {/* Confirm password */}
185
+ <div className="flex flex-col gap-1.5">
186
+ <label
187
+ htmlFor="confirmPassword"
188
+ className="font-sans font-medium text-[var(--color-text-primary)]"
189
+ style={{
190
+ fontSize: 'var(--font-size-sm)',
191
+ lineHeight: 'var(--line-height-snug)',
192
+ letterSpacing: '0.02em',
193
+ }}
194
+ >
195
+ Confirm password
196
+ </label>
197
+ <div className="relative">
198
+ <input
199
+ id="confirmPassword"
200
+ type={showConfirmPassword ? 'text' : 'password'}
201
+ value={confirmPassword}
202
+ onChange={e => {
203
+ setConfirmPassword(e.target.value);
204
+ setError('');
205
+ }}
206
+ onKeyDown={e => e.key === 'Enter' && handleCreate()}
207
+ 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)]"
208
+ style={{
209
+ fontSize: 'var(--font-size-base)',
210
+ lineHeight: 'var(--line-height-snug)',
211
+ letterSpacing: '0.01em',
212
+ }}
213
+ />
214
+ <button
215
+ type="button"
216
+ onClick={() => setShowConfirmPassword(!showConfirmPassword)}
217
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] transition-colors"
218
+ tabIndex={-1}
219
+ aria-label={showConfirmPassword ? 'Hide password' : 'Show password'}
220
+ >
221
+ {showConfirmPassword ? (
222
+ <EyeIcon className="w-4 h-4" />
223
+ ) : (
224
+ <EyeOffIcon className="w-4 h-4" />
225
+ )}
226
+ </button>
227
+ </div>
228
+ </div>
229
+ </div>
230
+
231
+ {/* Info box */}
232
+ <div className="bg-[var(--color-surface-900)] rounded-lg p-3">
233
+ <p
234
+ className="font-sans font-medium text-center text-[var(--color-text-muted)]"
235
+ style={{
236
+ fontSize: 'var(--font-size-xs)',
237
+ lineHeight: 'var(--line-height-tight)',
238
+ letterSpacing: '0.02em',
239
+ }}
240
+ >
241
+ This password encrypts your wallet on this device. Choose something strong but
242
+ memorable. Your private keys never leave your browser.
243
+ </p>
244
+ </div>
245
+
246
+ {/* Error message */}
247
+ {error && (
248
+ <Alert type="error" className="mt-2">
249
+ {error}
250
+ </Alert>
251
+ )}
252
+ </div>
253
+
254
+ {/* Bottom button */}
255
+ <div className="border-t border-[var(--color-surface-800)] px-4 py-3">
256
+ <button
257
+ onClick={handleCreate}
258
+ className="w-full h-12 px-5 py-[15px] btn-secondary text-[var(--color-bg)] rounded-lg flex items-center justify-center transition-opacity hover:opacity-90"
259
+ style={{
260
+ fontFamily: 'var(--font-sans)',
261
+ fontSize: 'var(--font-size-base)',
262
+ fontWeight: 500,
263
+ lineHeight: 'var(--line-height-snug)',
264
+ letterSpacing: '0.01em',
265
+ }}
266
+ >
267
+ Continue
268
+ </button>
269
+ </div>
270
+ </div>
271
+ </div>
272
+ );
273
+ }