@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,164 @@
1
+ import { useState } from 'react';
2
+ import { useStore } from '../store';
3
+ import { send } from '../utils/messaging';
4
+ import { INTERNAL_METHODS } from '../../shared/constants';
5
+ import RoseLogo96 from '../assets/iris-logo-96.svg';
6
+ import { ChevronLeftIcon } from '../components/icons/ChevronLeftIcon';
7
+ import { EyeIcon } from '../components/icons/EyeIcon';
8
+ import { EyeOffIcon } from '../components/icons/EyeOffIcon';
9
+ import { formatWalletError } from '../utils/formatWalletError';
10
+
11
+ export function KeySettingsPasswordScreen() {
12
+ const { navigate, setOnboardingMnemonic } = useStore();
13
+ const [password, setPassword] = useState('');
14
+ const [showPassword, setShowPassword] = useState(false);
15
+ const [error, setError] = useState('');
16
+ const [isLoading, setIsLoading] = useState(false);
17
+
18
+ function handleBack() {
19
+ navigate('settings');
20
+ }
21
+
22
+ async function handleConfirm() {
23
+ if (!password) {
24
+ setError('Please enter your password');
25
+ return;
26
+ }
27
+
28
+ setIsLoading(true);
29
+ setError('');
30
+
31
+ try {
32
+ const result = await send<{ ok?: boolean; mnemonic?: string; error?: string }>(
33
+ INTERNAL_METHODS.GET_MNEMONIC,
34
+ [password]
35
+ );
36
+
37
+ if (result?.error) {
38
+ setError(formatWalletError(result.error));
39
+ setPassword('');
40
+ } else if (result?.mnemonic) {
41
+ // Store mnemonic temporarily for viewing
42
+ setOnboardingMnemonic(result.mnemonic);
43
+ navigate('view-secret-phrase');
44
+ }
45
+ } catch (err) {
46
+ setError('Failed to retrieve secret phrase');
47
+ console.error('Failed to get mnemonic:', err);
48
+ } finally {
49
+ setIsLoading(false);
50
+ }
51
+ }
52
+
53
+ return (
54
+ <div
55
+ className="w-[357px] h-[600px] flex flex-col overflow-y-auto"
56
+ style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
57
+ >
58
+ {/* Header */}
59
+ <header
60
+ className="flex items-center justify-between px-4 py-3 min-h-[64px]"
61
+ style={{ backgroundColor: 'var(--color-bg)' }}
62
+ >
63
+ <button
64
+ type="button"
65
+ onClick={handleBack}
66
+ aria-label="Back"
67
+ className="w-8 h-8 p-2 flex items-center justify-center rounded-lg transition-colors focus:outline-none focus-visible:ring-2"
68
+ style={{ backgroundColor: 'transparent', color: 'var(--color-text-primary)' }}
69
+ onMouseEnter={e => (e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')}
70
+ onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
71
+ >
72
+ <ChevronLeftIcon className="w-5 h-5" />
73
+ </button>
74
+ <h1 className="m-0 text-base font-medium leading-[22px] tracking-[0.16px]">Key settings</h1>
75
+ <div className="w-8 h-8" />
76
+ </header>
77
+
78
+ <div className="flex flex-1 flex-col justify-between px-4 py-8">
79
+ {/* Content */}
80
+ <div className="flex flex-col items-center gap-3 w-full">
81
+ <img src={RoseLogo96} alt="Rose" className="w-24 h-24" />
82
+
83
+ <p
84
+ className="m-0 text-[13px] leading-[18px] tracking-[0.26px] text-center"
85
+ style={{ color: 'var(--color-text-muted)' }}
86
+ >
87
+ Please re-enter your password to see your keys
88
+ </p>
89
+ </div>
90
+
91
+ {/* Form */}
92
+ <div className="flex flex-col gap-4 w-full mt-8">
93
+ <div className="flex flex-col gap-[6px] w-full">
94
+ <label className="text-[13px] leading-[18px] tracking-[0.26px] font-medium">
95
+ Password
96
+ </label>
97
+
98
+ <div className="relative w-full">
99
+ <input
100
+ type={showPassword ? 'text' : 'password'}
101
+ className="w-full h-[52px] bg-transparent rounded-lg pl-3 pr-10 py-4 outline-none transition-colors text-sm leading-[18px] tracking-[0.14px] font-medium"
102
+ style={{
103
+ border: '1px solid var(--color-surface-700)',
104
+ color: 'var(--color-text-primary)',
105
+ }}
106
+ placeholder="Enter your password"
107
+ value={password}
108
+ onChange={e => {
109
+ setPassword(e.target.value);
110
+ setError('');
111
+ }}
112
+ onKeyDown={e => {
113
+ if (e.key === 'Enter') handleConfirm();
114
+ }}
115
+ onFocus={e => (e.currentTarget.style.borderColor = 'var(--color-primary)')}
116
+ onBlur={e => (e.currentTarget.style.borderColor = 'var(--color-surface-700)')}
117
+ disabled={isLoading}
118
+ />
119
+ <button
120
+ type="button"
121
+ onClick={() => setShowPassword(!showPassword)}
122
+ className="absolute right-3 top-1/2 -translate-y-1/2 p-1 flex items-center justify-center transition-colors"
123
+ style={{ color: 'var(--color-text-muted)' }}
124
+ onMouseEnter={e => (e.currentTarget.style.color = 'var(--color-text-primary)')}
125
+ onMouseLeave={e => (e.currentTarget.style.color = 'var(--color-text-muted)')}
126
+ aria-label={showPassword ? 'Hide password' : 'Show password'}
127
+ >
128
+ {showPassword ? (
129
+ <EyeIcon className="w-4 h-4" />
130
+ ) : (
131
+ <EyeOffIcon className="w-4 h-4" />
132
+ )}
133
+ </button>
134
+ </div>
135
+
136
+ {error && (
137
+ <p className="text-xs" style={{ color: 'var(--color-red)' }}>
138
+ {error}
139
+ </p>
140
+ )}
141
+ </div>
142
+
143
+ <button
144
+ onClick={handleConfirm}
145
+ disabled={isLoading}
146
+ className="w-full h-12 rounded-lg text-sm font-medium leading-[18px] tracking-[0.14px] transition-opacity hover:opacity-90 active:opacity-80 disabled:opacity-50"
147
+ style={{ backgroundColor: 'var(--color-primary)', color: '#000' }}
148
+ >
149
+ {isLoading ? 'Verifying...' : 'Confirm'}
150
+ </button>
151
+ </div>
152
+
153
+ {/* Warning */}
154
+ <p
155
+ className="m-0 text-[13px] leading-[18px] tracking-[0.26px] text-center"
156
+ style={{ color: 'var(--color-text-muted)' }}
157
+ >
158
+ Warning: Never disclose this key. Anyone with your private keys can steal any assets held
159
+ in your account.
160
+ </p>
161
+ </div>
162
+ </div>
163
+ );
164
+ }
@@ -0,0 +1,155 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useStore } from '../store';
3
+ import { send } from '../utils/messaging';
4
+ import { INTERNAL_METHODS } from '../../shared/constants';
5
+ import { ChevronLeftIcon } from '../components/icons/ChevronLeftIcon';
6
+
7
+ type LockTimeOption = '1min' | '5min' | '10min' | '15min' | '30min' | '1hour' | '4hours' | 'never';
8
+
9
+ // Conversion helpers
10
+ function minutesToOption(minutes: number): LockTimeOption {
11
+ if (minutes === 1) return '1min';
12
+ if (minutes === 5) return '5min';
13
+ if (minutes === 10) return '10min';
14
+ if (minutes === 15) return '15min';
15
+ if (minutes === 30) return '30min';
16
+ if (minutes === 60) return '1hour';
17
+ if (minutes === 240) return '4hours';
18
+ if (minutes === 0) return 'never';
19
+ return '15min'; // default
20
+ }
21
+
22
+ function optionToMinutes(option: LockTimeOption): number {
23
+ switch (option) {
24
+ case '1min':
25
+ return 1;
26
+ case '5min':
27
+ return 5;
28
+ case '10min':
29
+ return 10;
30
+ case '15min':
31
+ return 15;
32
+ case '30min':
33
+ return 30;
34
+ case '1hour':
35
+ return 60;
36
+ case '4hours':
37
+ return 240;
38
+ case 'never':
39
+ return 0;
40
+ default:
41
+ return 15;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * LockTimeScreen - Lock time settings screen
47
+ * Allows users to set the auto-lock timeout duration
48
+ */
49
+ export function LockTimeScreen() {
50
+ const { navigate } = useStore();
51
+ const [selectedTime, setSelectedTime] = useState<LockTimeOption>('15min');
52
+
53
+ // Load current lock time setting on mount
54
+ useEffect(() => {
55
+ (async () => {
56
+ const result = await send<{ minutes?: number }>(INTERNAL_METHODS.GET_AUTO_LOCK);
57
+ if (result?.minutes !== undefined) {
58
+ setSelectedTime(minutesToOption(result.minutes));
59
+ }
60
+ })();
61
+ }, []);
62
+
63
+ function handleBack() {
64
+ navigate('settings');
65
+ }
66
+
67
+ async function handleTimeSelect(time: LockTimeOption) {
68
+ setSelectedTime(time);
69
+
70
+ // Convert to minutes and persist to backend
71
+ const minutes = optionToMinutes(time);
72
+ await send(INTERNAL_METHODS.SET_AUTO_LOCK, [minutes]);
73
+ }
74
+
75
+ const timeOptions: { value: LockTimeOption; label: string }[] = [
76
+ { value: '1min', label: '1 minute' },
77
+ { value: '5min', label: '5 minutes' },
78
+ { value: '10min', label: '10 minutes' },
79
+ { value: '15min', label: '15 minutes' },
80
+ { value: '30min', label: '30 minutes' },
81
+ { value: '1hour', label: '1 hour' },
82
+ { value: '4hours', label: '4 hours' },
83
+ { value: 'never', label: 'Never' },
84
+ ];
85
+
86
+ return (
87
+ <div
88
+ className="w-[357px] h-[600px] flex flex-col overflow-y-auto"
89
+ style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
90
+ >
91
+ {/* Header */}
92
+ <header
93
+ className="flex items-center justify-between px-4 py-3 min-h-[64px]"
94
+ style={{ backgroundColor: 'var(--color-bg)' }}
95
+ >
96
+ <button
97
+ type="button"
98
+ onClick={handleBack}
99
+ aria-label="Back"
100
+ className="w-8 h-8 p-2 flex items-center justify-center rounded-lg transition-colors focus:outline-none focus-visible:ring-2"
101
+ style={{ color: 'var(--color-text-primary)' }}
102
+ onMouseEnter={e => (e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')}
103
+ onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
104
+ >
105
+ <ChevronLeftIcon className="w-5 h-5" />
106
+ </button>
107
+
108
+ <h1 className="m-0 text-base font-medium leading-[22px] tracking-[0.16px]">Lock time</h1>
109
+
110
+ <div className="w-8 h-8" />
111
+ </header>
112
+
113
+ {/* Time Options */}
114
+ <div className="flex flex-col gap-2 px-3 py-2">
115
+ {timeOptions.map(option => {
116
+ const selected = selectedTime === option.value;
117
+ return (
118
+ <button
119
+ key={option.value}
120
+ type="button"
121
+ onClick={() => handleTimeSelect(option.value)}
122
+ className="flex items-center justify-between p-3 rounded-lg text-left transition-colors focus:outline-none focus-visible:ring-2"
123
+ onMouseEnter={e =>
124
+ (e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')
125
+ }
126
+ onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
127
+ role="radio"
128
+ aria-checked={selected}
129
+ >
130
+ <span className="text-sm font-medium leading-[18px] tracking-[0.14px]">
131
+ {option.label}
132
+ </span>
133
+
134
+ {/* Radio */}
135
+ <span
136
+ className="w-5 h-5 rounded-full flex items-center justify-center transition-colors"
137
+ style={{
138
+ border: `2px solid ${selected ? 'var(--color-primary)' : 'var(--color-surface-700)'}`,
139
+ }}
140
+ aria-hidden="true"
141
+ >
142
+ {selected && (
143
+ <span
144
+ className="w-2.5 h-2.5 rounded-full"
145
+ style={{ backgroundColor: 'var(--color-primary)' }}
146
+ />
147
+ )}
148
+ </span>
149
+ </button>
150
+ );
151
+ })}
152
+ </div>
153
+ </div>
154
+ );
155
+ }
@@ -0,0 +1,149 @@
1
+ import { useState } from 'react';
2
+ import { useStore } from '../store';
3
+ import RoseLogo40 from '../assets/iris-logo-40.svg';
4
+ import { ChevronLeftIcon } from '../components/icons/ChevronLeftIcon';
5
+
6
+ export function ReceiveScreen() {
7
+ const { navigate, wallet } = useStore();
8
+ const [copySuccess, setCopySuccess] = useState(false);
9
+
10
+ // Get address from current account
11
+ const address = wallet.currentAccount?.address || wallet.address || '';
12
+ const addressStart = address.slice(0, 6);
13
+ const addressMiddle = address.slice(6, -5);
14
+ const addressEnd = address.slice(-5);
15
+
16
+ function handleBack() {
17
+ navigate('home');
18
+ }
19
+
20
+ async function handleCopyAddress() {
21
+ try {
22
+ await navigator.clipboard.writeText(address);
23
+ setCopySuccess(true);
24
+ setTimeout(() => setCopySuccess(false), 2000);
25
+ } catch (err) {
26
+ console.error('Failed to copy address:', err);
27
+ }
28
+ }
29
+
30
+ return (
31
+ <div
32
+ className="w-[357px] h-[600px] flex flex-col overflow-y-auto"
33
+ style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
34
+ >
35
+ {/* Header */}
36
+ <header
37
+ className="flex items-center justify-between px-4 py-3 min-h-[64px]"
38
+ style={{ backgroundColor: 'var(--color-bg)' }}
39
+ >
40
+ <button
41
+ type="button"
42
+ onClick={handleBack}
43
+ aria-label="Back"
44
+ className="w-8 h-8 p-2 flex items-center justify-center rounded-lg transition-colors focus:outline-none focus-visible:ring-2"
45
+ style={{ backgroundColor: 'transparent' }}
46
+ onMouseEnter={e => (e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')}
47
+ onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
48
+ >
49
+ <ChevronLeftIcon className="w-5 h-5" />
50
+ </button>
51
+ <h1 className="m-0 text-base font-medium leading-[22px] tracking-[0.16px]">Receive NOCK</h1>
52
+ <div className="w-8 h-8" />
53
+ </header>
54
+
55
+ {/* Content */}
56
+ <div className="flex flex-col gap-2 h-[536px]" style={{ backgroundColor: 'var(--color-bg)' }}>
57
+ <div className="flex flex-col gap-8 px-4 py-2">
58
+ {/* Intro */}
59
+ <div className="flex flex-col items-center gap-3 w-full">
60
+ <img src={RoseLogo40} alt="Rose" className="w-10 h-10" />
61
+
62
+ <h2
63
+ className="m-0 text-2xl font-medium leading-7 tracking-[-0.48px] text-center font-display"
64
+ style={{ color: 'var(--color-text-primary)' }}
65
+ >
66
+ Your address
67
+ </h2>
68
+ </div>
69
+
70
+ {/* Address + Copy */}
71
+ <div className="flex flex-col gap-4 w-full">
72
+ <div
73
+ className="rounded-lg px-3 pt-5 pb-3 flex flex-col items-center gap-5"
74
+ style={{ backgroundColor: 'var(--color-surface-800)' }}
75
+ >
76
+ <div className="text-sm leading-[18px] tracking-[0.14px] font-medium text-center break-words">
77
+ <span style={{ color: 'var(--color-text-primary)' }}>{addressStart}</span>
78
+ <span style={{ color: 'var(--color-text-muted)' }}>
79
+ {addressMiddle.substring(0, 18)}
80
+ </span>
81
+ <br />
82
+ <span style={{ color: 'var(--color-text-muted)' }}>
83
+ {addressMiddle.substring(18)}
84
+ </span>
85
+ <span style={{ color: 'var(--color-text-primary)' }}>{addressEnd}</span>
86
+ </div>
87
+
88
+ <button
89
+ type="button"
90
+ onClick={handleCopyAddress}
91
+ className="inline-flex items-center justify-center gap-[6px] py-[7px] pr-3 pl-4 bg-transparent rounded-full text-sm font-medium leading-[18px] tracking-[0.14px] transition active:opacity-80 focus:outline-none focus-visible:ring-2"
92
+ style={{
93
+ border: '1px solid var(--color-text-primary)',
94
+ color: 'var(--color-text-primary)',
95
+ }}
96
+ onMouseEnter={e =>
97
+ (e.currentTarget.style.backgroundColor = 'var(--color-surface-700)')
98
+ }
99
+ onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
100
+ >
101
+ <svg
102
+ width="16"
103
+ height="16"
104
+ viewBox="0 0 16 16"
105
+ fill="none"
106
+ xmlns="http://www.w3.org/2000/svg"
107
+ className="shrink-0"
108
+ >
109
+ <path
110
+ d="M10.6667 1.33334H2.66667C1.93334 1.33334 1.34 1.93334 1.34 2.66667L1.33334 11.3333C1.33334 12.0667 1.92667 12.6667 2.66 12.6667H10.6667C11.4 12.6667 12 12.0667 12 11.3333V2.66667C12 1.93334 11.4 1.33334 10.6667 1.33334ZM10.6667 11.3333H2.66667V2.66667H10.6667V11.3333ZM13.3333 5.33334V14C13.3333 14.7333 12.7333 15.3333 12 15.3333H4.66667C4.66667 15.3333 4.66667 14.6667 4.66667 14.6667H12.6667V5.33334C12.6667 5.33334 13.3333 5.33334 13.3333 5.33334Z"
111
+ fill="currentColor"
112
+ />
113
+ </svg>
114
+ {copySuccess ? 'Copied!' : 'Copy address'}
115
+ </button>
116
+ </div>
117
+
118
+ {/* Instructions */}
119
+ <div
120
+ className="rounded-lg p-3 flex flex-col gap-2.5"
121
+ style={{ border: '1px solid var(--color-divider)' }}
122
+ >
123
+ <h3
124
+ className="m-0 text-sm font-medium leading-[18px] tracking-[0.14px] font-display"
125
+ style={{ color: 'var(--color-text-primary)' }}
126
+ >
127
+ How to receive NOCK:
128
+ </h3>
129
+ <ul className="m-0 p-0 flex flex-col">
130
+ <li
131
+ className="text-[13px] leading-[18px] tracking-[0.26px] font-medium relative pl-0 before:content-['•'] before:mr-1 before:inline-block"
132
+ style={{ color: 'var(--color-text-primary)' }}
133
+ >
134
+ Share this address with the sender
135
+ </li>
136
+ <li
137
+ className="text-[13px] leading-[18px] tracking-[0.26px] font-medium relative pl-0 before:content-['•'] before:mr-1 before:inline-block"
138
+ style={{ color: 'var(--color-text-primary)' }}
139
+ >
140
+ Transactions will appear in your wallet
141
+ </li>
142
+ </ul>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ );
149
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Recovery Phrase Screen - View wallet's secret phrase
3
+ * Requires password confirmation for security
4
+ */
5
+
6
+ import { useState } from 'react';
7
+ import { useStore } from '../store';
8
+ import { ScreenContainer } from '../components/ScreenContainer';
9
+ import { Alert } from '../components/Alert';
10
+ import { PasswordInput } from '../components/PasswordInput';
11
+ import { send } from '../utils/messaging';
12
+ import { INTERNAL_METHODS } from '../../shared/constants';
13
+ import { ChevronLeftIcon } from '../components/icons/ChevronLeftIcon';
14
+ import { EyeIcon } from '../components/icons/EyeIcon';
15
+ import { formatWalletError } from '../utils/formatWalletError';
16
+
17
+ export function RecoveryPhraseScreen() {
18
+ const { navigate } = useStore();
19
+ const [password, setPassword] = useState('');
20
+ const [error, setError] = useState('');
21
+ const [mnemonic, setMnemonic] = useState<string | null>(null);
22
+ const [isRevealed, setIsRevealed] = useState(false);
23
+
24
+ async function handleReveal() {
25
+ setError('');
26
+
27
+ if (!password) {
28
+ setError('Please enter your password');
29
+ return;
30
+ }
31
+
32
+ const result = await send<{ ok?: boolean; mnemonic?: string; error?: string }>(
33
+ INTERNAL_METHODS.GET_MNEMONIC,
34
+ [password]
35
+ );
36
+
37
+ if (result?.error) {
38
+ setError(formatWalletError(result.error));
39
+ setPassword('');
40
+ } else {
41
+ setMnemonic(result.mnemonic || '');
42
+ setIsRevealed(true);
43
+ }
44
+ }
45
+
46
+ const words = mnemonic ? mnemonic.split(' ') : [];
47
+
48
+ // Password confirmation view
49
+ if (!isRevealed) {
50
+ return (
51
+ <div
52
+ className="w-[357px] h-[600px] flex flex-col p-4"
53
+ style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
54
+ >
55
+ {/* Header */}
56
+ <div className="flex items-center gap-3 mb-6">
57
+ <button
58
+ onClick={() => navigate('settings')}
59
+ className="transition-colors"
60
+ style={{ color: 'var(--color-text-primary)' }}
61
+ >
62
+ <ChevronLeftIcon />
63
+ </button>
64
+ <h2 className="text-xl font-semibold">View Secret Phrase</h2>
65
+ </div>
66
+
67
+ <div
68
+ className="mb-6 p-3 rounded-lg"
69
+ style={{ backgroundColor: 'var(--color-red-light)', color: 'var(--color-red)' }}
70
+ >
71
+ <strong>Warning:</strong> Never share your secret phrase with anyone. Anyone with access
72
+ to this phrase can access your funds.
73
+ </div>
74
+
75
+ <p className="text-sm mb-4" style={{ color: 'var(--color-text-muted)' }}>
76
+ Enter your password to reveal your 24-word secret phrase.
77
+ </p>
78
+
79
+ <input
80
+ type="password"
81
+ value={password}
82
+ onChange={e => {
83
+ setPassword(e.target.value);
84
+ setError('');
85
+ }}
86
+ placeholder="Password"
87
+ className="my-2 w-full rounded-lg px-4 py-3 outline-none"
88
+ style={{
89
+ border: '1px solid var(--color-surface-700)',
90
+ backgroundColor: 'var(--color-bg)',
91
+ color: 'var(--color-text-primary)',
92
+ }}
93
+ onKeyDown={e => e.key === 'Enter' && handleReveal()}
94
+ onFocus={e => (e.currentTarget.style.borderColor = 'var(--color-primary)')}
95
+ onBlur={e => (e.currentTarget.style.borderColor = 'var(--color-surface-700)')}
96
+ autoFocus
97
+ />
98
+
99
+ {error && (
100
+ <div
101
+ className="my-2 p-3 rounded-lg"
102
+ style={{ backgroundColor: 'var(--color-red-light)', color: 'var(--color-red)' }}
103
+ >
104
+ {error}
105
+ </div>
106
+ )}
107
+
108
+ <button
109
+ onClick={handleReveal}
110
+ className="my-2 w-full rounded-lg px-5 py-3.5 text-sm font-medium transition-opacity hover:opacity-90"
111
+ style={{ backgroundColor: 'var(--color-primary)', color: '#000' }}
112
+ >
113
+ <EyeIcon className="w-4 h-4 inline mr-2" />
114
+ Reveal Secret Phrase
115
+ </button>
116
+
117
+ <button
118
+ onClick={() => navigate('settings')}
119
+ className="my-2 w-full rounded-lg px-5 py-3.5 text-sm font-medium transition-colors"
120
+ style={{ backgroundColor: 'var(--color-surface-800)' }}
121
+ onMouseEnter={e => (e.currentTarget.style.backgroundColor = 'var(--color-surface-700)')}
122
+ onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')}
123
+ >
124
+ Cancel
125
+ </button>
126
+ </div>
127
+ );
128
+ }
129
+
130
+ // Secret phrase display view
131
+ return (
132
+ <div
133
+ className="w-[357px] h-[600px] flex flex-col p-4"
134
+ style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
135
+ >
136
+ {/* Header */}
137
+ <div className="flex items-center gap-3 mb-4">
138
+ <button
139
+ onClick={() => navigate('settings')}
140
+ className="transition-colors"
141
+ style={{ color: 'var(--color-text-primary)' }}
142
+ >
143
+ <ChevronLeftIcon />
144
+ </button>
145
+ <h2 className="text-xl font-semibold">Secret Phrase</h2>
146
+ </div>
147
+
148
+ <div
149
+ className="mb-4 p-3 rounded-lg"
150
+ style={{ backgroundColor: 'var(--color-red-light)', color: 'var(--color-red)' }}
151
+ >
152
+ <strong>Warning:</strong> Write down these 24 words in order and store them safely. Never
153
+ share them with anyone.
154
+ </div>
155
+
156
+ {/* Words grid */}
157
+ <div className="flex-1 overflow-y-auto mb-4">
158
+ <div className="grid grid-cols-2 gap-2">
159
+ {words.map((word, index) => (
160
+ <div
161
+ key={index}
162
+ className="rounded p-2 flex items-center gap-2"
163
+ style={{ backgroundColor: 'var(--color-surface-800)' }}
164
+ >
165
+ <span className="text-xs w-6" style={{ color: 'var(--color-text-muted)' }}>
166
+ {index + 1}.
167
+ </span>
168
+ <span className="text-sm font-mono">{word}</span>
169
+ </div>
170
+ ))}
171
+ </div>
172
+ </div>
173
+
174
+ <button
175
+ onClick={() => navigate('settings')}
176
+ className="w-full rounded-lg px-5 py-3.5 text-sm font-medium transition-opacity hover:opacity-90"
177
+ style={{ backgroundColor: 'var(--color-primary)', color: '#000' }}
178
+ >
179
+ Done
180
+ </button>
181
+ </div>
182
+ );
183
+ }