@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,346 @@
1
+ import React, { useState } from 'react';
2
+ import { useStore } from '../store';
3
+ import { ChevronLeftIcon } from '../components/icons/ChevronLeftIcon';
4
+ import { ChevronRightIcon } from '../components/icons/ChevronRightIcon';
5
+ import { CheckIcon } from '../components/icons/CheckIcon';
6
+ import RoseLogo40 from '../assets/iris-logo-40.svg';
7
+ import { truncateAddress, formatUTCTimestamp } from '../utils/format';
8
+ import { NOCK_TO_NICKS } from '../../shared/constants';
9
+
10
+ export function TransactionDetailsScreen() {
11
+ const {
12
+ navigate,
13
+ selectedTransaction,
14
+ wallet,
15
+ fetchWalletTransactions,
16
+ walletTransactions,
17
+ setSelectedTransaction,
18
+ } = useStore();
19
+
20
+ const [copiedTxId, setCopiedTxId] = useState(false);
21
+
22
+ // Fetch fresh transaction data on mount
23
+ React.useEffect(() => {
24
+ fetchWalletTransactions();
25
+ }, []);
26
+
27
+ // Sync selectedTransaction with updates from walletTransactions
28
+ React.useEffect(() => {
29
+ if (!selectedTransaction) return;
30
+
31
+ // Find the updated transaction by id
32
+ const updatedTx = walletTransactions.find(tx => tx.id === selectedTransaction.id);
33
+ if (updatedTx) {
34
+ // Update selectedTransaction with the latest data
35
+ setSelectedTransaction(updatedTx);
36
+ }
37
+ }, [walletTransactions, selectedTransaction?.id]);
38
+
39
+ // If no transaction selected, show error state
40
+ if (!selectedTransaction) {
41
+ return (
42
+ <div
43
+ className="w-[357px] h-[600px] flex items-center justify-center"
44
+ style={{ backgroundColor: 'var(--color-bg)' }}
45
+ >
46
+ <div className="text-center" style={{ color: 'var(--color-text-muted)' }}>
47
+ <p>No transaction selected</p>
48
+ <button
49
+ onClick={() => navigate('home')}
50
+ className="mt-4 px-4 py-2 rounded-lg"
51
+ style={{ backgroundColor: 'var(--color-primary)' }}
52
+ >
53
+ Go back
54
+ </button>
55
+ </div>
56
+ </div>
57
+ );
58
+ }
59
+
60
+ // Extract data from selected transaction
61
+ const transactionType = selectedTransaction.direction === 'outgoing' ? 'sent' : 'received';
62
+
63
+ // Convert amount from nicks to NOCK
64
+ const amountNock = (selectedTransaction.amount || 0) / NOCK_TO_NICKS;
65
+ const feeNock = (selectedTransaction.fee || 0) / NOCK_TO_NICKS;
66
+
67
+ const amount = amountNock.toLocaleString('en-US', {
68
+ minimumFractionDigits: 2,
69
+ maximumFractionDigits: 2,
70
+ });
71
+
72
+ // Only show USD value if we have historical price stored
73
+ const usdValue = selectedTransaction.priceUsdAtTime
74
+ ? `$${(amountNock * selectedTransaction.priceUsdAtTime).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
75
+ : null;
76
+
77
+ // Determine status display
78
+ let statusText: string;
79
+ let statusColor: string;
80
+
81
+ switch (selectedTransaction.status) {
82
+ case 'confirmed':
83
+ statusText = 'Confirmed';
84
+ statusColor = 'var(--color-green)';
85
+ break;
86
+ case 'failed':
87
+ statusText = 'Failed';
88
+ statusColor = 'var(--color-red)';
89
+ break;
90
+ case 'expired':
91
+ statusText = 'Expired';
92
+ statusColor = 'var(--color-red)';
93
+ break;
94
+ case 'broadcasted_unconfirmed':
95
+ case 'broadcast_pending':
96
+ case 'created':
97
+ statusText = 'Pending';
98
+ statusColor = '#C88414';
99
+ break;
100
+ default:
101
+ statusText = 'Unknown';
102
+ statusColor = 'var(--color-text-muted)';
103
+ }
104
+
105
+ const currentAddress = wallet.currentAccount?.address || '';
106
+ const counterpartyAddress =
107
+ selectedTransaction.direction === 'outgoing'
108
+ ? selectedTransaction.recipient
109
+ : selectedTransaction.sender;
110
+
111
+ const fromAddress =
112
+ selectedTransaction.direction === 'outgoing'
113
+ ? truncateAddress(currentAddress)
114
+ : counterpartyAddress
115
+ ? truncateAddress(counterpartyAddress)
116
+ : 'Unknown';
117
+ const toAddress =
118
+ selectedTransaction.direction === 'outgoing'
119
+ ? truncateAddress(counterpartyAddress || '')
120
+ : truncateAddress(currentAddress);
121
+
122
+ // For incoming transactions, we don't have fee info
123
+ const networkFee =
124
+ selectedTransaction.direction === 'outgoing'
125
+ ? `${feeNock.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} NOCK`
126
+ : '-';
127
+ const totalNock =
128
+ selectedTransaction.direction === 'outgoing' ? amountNock + feeNock : amountNock;
129
+ const total = `${totalNock.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} NOCK`;
130
+
131
+ // Only show total USD if we have historical price stored
132
+ const totalUsd = selectedTransaction.priceUsdAtTime
133
+ ? `$${(totalNock * selectedTransaction.priceUsdAtTime).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
134
+ : null;
135
+ const transactionId = selectedTransaction.txHash || selectedTransaction.id;
136
+ const transactionTimeUTC = formatUTCTimestamp(selectedTransaction.createdAt);
137
+
138
+ function handleBack() {
139
+ navigate('home');
140
+ }
141
+ function handleViewExplorer() {
142
+ // Open transaction on nockblocks.com (only if we have a txHash)
143
+ const txHash = selectedTransaction?.txHash;
144
+ if (txHash) {
145
+ window.open(`https://nockblocks.com/tx/${txHash}`, '_blank');
146
+ }
147
+ }
148
+ async function handleCopyTransactionId() {
149
+ try {
150
+ await navigator.clipboard.writeText(transactionId);
151
+ setCopiedTxId(true);
152
+ setTimeout(() => setCopiedTxId(false), 2000);
153
+ } catch (err) {
154
+ console.error('Failed to copy transaction ID:', err);
155
+ }
156
+ }
157
+
158
+ return (
159
+ <div
160
+ className="w-[357px] h-[600px] flex flex-col"
161
+ style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
162
+ >
163
+ {/* Header */}
164
+ <header
165
+ className="flex items-center justify-between px-4 py-3 min-h-[64px]"
166
+ style={{ backgroundColor: 'var(--color-bg)' }}
167
+ >
168
+ <button
169
+ type="button"
170
+ onClick={handleBack}
171
+ className="w-8 h-8 flex items-center justify-center p-2 rounded-lg transition-opacity focus:outline-none focus-visible:ring-2"
172
+ style={{ color: 'var(--color-text-primary)' }}
173
+ onMouseEnter={e => (e.currentTarget.style.opacity = '0.7')}
174
+ onMouseLeave={e => (e.currentTarget.style.opacity = '1')}
175
+ aria-label="Back"
176
+ >
177
+ <ChevronLeftIcon className="w-5 h-5" />
178
+ </button>
179
+ <h1 className="m-0 text-base font-medium leading-[22px] tracking-[0.16px]">
180
+ {transactionType === 'sent' ? 'Sent' : 'Received'}
181
+ </h1>
182
+ <div className="w-8 h-8" />
183
+ </header>
184
+
185
+ {/* Content */}
186
+ <div
187
+ className="flex flex-col gap-2 h-[536px] overflow-y-auto"
188
+ style={{ backgroundColor: 'var(--color-bg)' }}
189
+ >
190
+ <div className="flex flex-col gap-8 px-4 py-2">
191
+ {/* Amount Section */}
192
+ <div className="flex flex-col items-center gap-3">
193
+ <img src={RoseLogo40} alt="Rose" className="w-10 h-10" />
194
+ <div className="flex flex-col items-center gap-0.5 text-center">
195
+ <h2
196
+ className="m-0 font-[Lora] text-[36px] font-semibold leading-10 tracking-[-0.72px]"
197
+ style={{ color: 'var(--color-text-primary)' }}
198
+ >
199
+ {transactionType === 'sent' && '-'}
200
+ {amount} <span style={{ color: 'var(--color-text-muted)' }}>NOCK</span>
201
+ </h2>
202
+ {usdValue && (
203
+ <p
204
+ className="m-0 text-[13px] font-medium leading-[18px] tracking-[0.26px]"
205
+ style={{ color: 'var(--color-text-muted)' }}
206
+ >
207
+ {usdValue}
208
+ </p>
209
+ )}
210
+ </div>
211
+ </div>
212
+
213
+ {/* Transaction Details */}
214
+ <div className="flex flex-col gap-2">
215
+ {/* Status */}
216
+ <div
217
+ className="rounded-lg px-3 py-5"
218
+ style={{ backgroundColor: 'var(--color-surface-800)' }}
219
+ >
220
+ <div className="flex items-center justify-between text-sm font-medium leading-[18px] tracking-[0.14px]">
221
+ <div style={{ color: 'var(--color-text-primary)' }}>Status</div>
222
+ <div style={{ color: statusColor }}>
223
+ <span className="whitespace-nowrap">{statusText}</span>
224
+ </div>
225
+ </div>
226
+ </div>
227
+
228
+ {/* Transaction Time */}
229
+ <div
230
+ className="rounded-lg px-3 py-5"
231
+ style={{ backgroundColor: 'var(--color-surface-800)' }}
232
+ >
233
+ <div className="flex items-center justify-between text-sm font-medium leading-[18px] tracking-[0.14px]">
234
+ <div style={{ color: 'var(--color-text-primary)' }}>Time</div>
235
+ <div
236
+ className="text-right text-[13px] leading-[18px] tracking-[0.26px]"
237
+ style={{ color: 'var(--color-text-muted)' }}
238
+ >
239
+ {transactionTimeUTC}
240
+ </div>
241
+ </div>
242
+ </div>
243
+
244
+ {/* From / To */}
245
+ <div className="rounded-lg p-3" style={{ backgroundColor: 'var(--color-surface-800)' }}>
246
+ <div className="flex items-center gap-2.5">
247
+ <div className="flex-1 flex flex-col gap-1 min-w-0">
248
+ <div className="text-sm font-medium leading-[18px] tracking-[0.14px]">From</div>
249
+ <div
250
+ className="text-[13px] leading-[18px] tracking-[0.26px] truncate"
251
+ style={{ color: 'var(--color-text-muted)' }}
252
+ >
253
+ {fromAddress}
254
+ </div>
255
+ </div>
256
+ <div className="p-1 shrink-0">
257
+ <ChevronRightIcon className="w-4 h-4" />
258
+ </div>
259
+ <div className="flex-1 flex flex-col gap-1 min-w-0">
260
+ <div className="text-sm font-medium leading-[18px] tracking-[0.14px]">To</div>
261
+ <div
262
+ className="text-[13px] leading-[18px] tracking-[0.26px] truncate"
263
+ style={{ color: 'var(--color-text-muted)' }}
264
+ >
265
+ {toAddress}
266
+ </div>
267
+ </div>
268
+ </div>
269
+ </div>
270
+
271
+ {/* Fee and Total */}
272
+ <div
273
+ className="rounded-lg px-3 py-3 flex flex-col gap-3"
274
+ style={{ backgroundColor: 'var(--color-surface-800)' }}
275
+ >
276
+ <div className="flex items-center justify-between text-sm font-medium leading-[18px] tracking-[0.14px]">
277
+ <div style={{ color: 'var(--color-text-primary)', opacity: 0.7 }}>Network fee</div>
278
+ <div className="whitespace-nowrap" style={{ color: 'var(--color-text-muted)' }}>
279
+ {networkFee}
280
+ </div>
281
+ </div>
282
+ <div className="h-px w-full" style={{ backgroundColor: 'var(--color-divider)' }} />
283
+ <div className="flex items-center justify-between text-sm font-medium leading-[18px] tracking-[0.14px]">
284
+ <div style={{ color: 'var(--color-text-primary)' }}>Total</div>
285
+ <div className="flex flex-col items-end gap-1 w-[75px]">
286
+ <div className="whitespace-nowrap" style={{ color: 'var(--color-text-primary)' }}>
287
+ {total}
288
+ </div>
289
+ {totalUsd && (
290
+ <div
291
+ className="text-[13px] leading-[18px] tracking-[0.26px] whitespace-nowrap"
292
+ style={{ color: 'var(--color-text-muted)' }}
293
+ >
294
+ {totalUsd}
295
+ </div>
296
+ )}
297
+ </div>
298
+ </div>
299
+ </div>
300
+
301
+ {/* Actions */}
302
+ <div className="flex gap-2">
303
+ <button
304
+ type="button"
305
+ onClick={handleViewExplorer}
306
+ disabled={!selectedTransaction.txHash}
307
+ className="flex-1 py-[7px] px-3 bg-transparent rounded-full text-sm font-medium leading-[18px] tracking-[0.14px] transition-colors focus:outline-none focus-visible:ring-2 whitespace-nowrap disabled:opacity-50"
308
+ style={{
309
+ border: '1px solid var(--color-surface-700)',
310
+ color: 'var(--color-text-primary)',
311
+ }}
312
+ onMouseEnter={e => {
313
+ if (!e.currentTarget.disabled) {
314
+ e.currentTarget.style.backgroundColor = 'var(--color-surface-800)';
315
+ }
316
+ }}
317
+ onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
318
+ >
319
+ View on explorer
320
+ </button>
321
+ <button
322
+ type="button"
323
+ onClick={handleCopyTransactionId}
324
+ disabled={copiedTxId}
325
+ className="flex-1 py-[7px] px-3 bg-transparent rounded-full text-sm font-medium leading-[18px] tracking-[0.14px] transition-colors focus:outline-none focus-visible:ring-2 whitespace-nowrap disabled:opacity-100 flex items-center justify-center gap-1.5"
326
+ style={{
327
+ border: '1px solid var(--color-surface-700)',
328
+ color: 'var(--color-text-primary)',
329
+ }}
330
+ onMouseEnter={e => {
331
+ if (!copiedTxId) {
332
+ e.currentTarget.style.backgroundColor = 'var(--color-surface-800)';
333
+ }
334
+ }}
335
+ onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
336
+ >
337
+ {copiedTxId && <CheckIcon className="w-3.5 h-3.5" />}
338
+ {copiedTxId ? 'Copied!' : 'Copy transaction ID'}
339
+ </button>
340
+ </div>
341
+ </div>
342
+ </div>
343
+ </div>
344
+ </div>
345
+ );
346
+ }
@@ -0,0 +1,212 @@
1
+ import { useState } from 'react';
2
+ import { useStore } from '../store';
3
+ import { ChevronLeftIcon } from '../components/icons/ChevronLeftIcon';
4
+ import { CheckIcon } from '../components/icons/CheckIcon';
5
+ import LockIcon from '../assets/lock-icon-yellow.svg';
6
+ import { exportKeyfile, downloadKeyfile } from '../../shared/keyfile';
7
+ import { Alert } from '../components/Alert';
8
+
9
+ /**
10
+ * ViewSecretPhraseScreen - Display user's 24-word secret phrase
11
+ * Shows mnemonic secret phrase with security warnings and reveal functionality
12
+ */
13
+ export function ViewSecretPhraseScreen() {
14
+ const { navigate, onboardingMnemonic, setOnboardingMnemonic } = useStore();
15
+ const [isRevealed, setIsRevealed] = useState(false);
16
+ const [error, setError] = useState('');
17
+ const [isExporting, setIsExporting] = useState(false);
18
+ const [copiedSeed, setCopiedSeed] = useState(false);
19
+
20
+ // Get secret phrase from temporary store (set by KeySettingsPasswordScreen)
21
+ const secretPhrase = onboardingMnemonic ? onboardingMnemonic.split(' ') : [];
22
+
23
+ function handleBack() {
24
+ // Clear mnemonic from memory when leaving screen
25
+ setOnboardingMnemonic(null);
26
+ navigate('settings');
27
+ }
28
+
29
+ function handleReveal() {
30
+ setIsRevealed(true);
31
+ }
32
+
33
+ async function handleCopySecretPhrase() {
34
+ if (onboardingMnemonic) {
35
+ try {
36
+ await navigator.clipboard.writeText(onboardingMnemonic);
37
+ setCopiedSeed(true);
38
+ setTimeout(() => setCopiedSeed(false), 2000);
39
+ } catch (err) {
40
+ console.error('Failed to copy secret phrase:', err);
41
+ }
42
+ }
43
+ }
44
+
45
+ function handleDownloadKeyfile() {
46
+ if (!onboardingMnemonic) {
47
+ setError('No mnemonic available');
48
+ return;
49
+ }
50
+
51
+ setIsExporting(true);
52
+ setError('');
53
+
54
+ try {
55
+ const keyfile = exportKeyfile(onboardingMnemonic);
56
+ const timestamp = new Date().toISOString().split('T')[0];
57
+ downloadKeyfile(keyfile, `nockchain-keyfile-${timestamp}.json`);
58
+ } catch (err) {
59
+ setError('Failed to export keyfile');
60
+ console.error(err);
61
+ } finally {
62
+ setIsExporting(false);
63
+ }
64
+ }
65
+
66
+ return (
67
+ <div
68
+ className="w-[357px] h-[600px] flex flex-col"
69
+ style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
70
+ >
71
+ {/* Header */}
72
+ <header
73
+ className="flex items-center justify-between px-4 py-3 min-h-[64px]"
74
+ style={{ backgroundColor: 'var(--color-bg)' }}
75
+ >
76
+ <button
77
+ className="w-8 h-8 flex items-center justify-center p-2 transition-opacity hover:opacity-70"
78
+ onClick={handleBack}
79
+ >
80
+ <ChevronLeftIcon className="w-5 h-5" />
81
+ </button>
82
+ <h1 className="font-sans font-medium text-base tracking-[0.16px] leading-[22px]">
83
+ View secret phrase
84
+ </h1>
85
+ <div className="w-8 h-8" />
86
+ </header>
87
+
88
+ {/* Content - Scrollable */}
89
+ <div className="flex-1 overflow-y-auto pt-2">
90
+ <div className="px-4 pb-4 flex flex-col gap-6">
91
+ {/* Title Section */}
92
+ <div className="flex flex-col items-center gap-3 w-full">
93
+ <div className="w-10 h-10 flex items-center justify-center">
94
+ <img src={LockIcon} alt="Lock" className="w-full h-full" />
95
+ </div>
96
+ <div className="flex flex-col gap-2 text-center w-full">
97
+ <h2 className="font-display font-medium text-2xl tracking-[-0.48px] leading-7">
98
+ View secret phrase
99
+ </h2>
100
+ <p
101
+ className="font-sans font-normal text-[13px] tracking-[0.26px] leading-[18px]"
102
+ style={{ color: 'var(--color-text-muted)' }}
103
+ >
104
+ Make sure no one is looking at your screen
105
+ </p>
106
+ </div>
107
+ </div>
108
+
109
+ {/* Download Keyfile Link */}
110
+ <button
111
+ onClick={handleDownloadKeyfile}
112
+ disabled={isExporting}
113
+ className="font-sans font-medium text-sm tracking-[0.14px] leading-[18px] text-center underline hover:opacity-70 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed"
114
+ >
115
+ {isExporting ? 'Downloading...' : 'Download keyfile'}
116
+ </button>
117
+
118
+ {/* Secret Phrase Grid */}
119
+ <div className="relative flex flex-col gap-2">
120
+ {/* 12 rows of 2 words each */}
121
+ {[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22].map(startIndex => (
122
+ <div key={startIndex} className="flex gap-2">
123
+ {/* Left word */}
124
+ <div
125
+ className="flex-1 rounded-lg overflow-hidden"
126
+ style={{
127
+ backgroundColor: 'var(--color-bg)',
128
+ border: '1px solid var(--color-surface-800)',
129
+ }}
130
+ >
131
+ <div className="flex items-center gap-2.5 p-2">
132
+ <div
133
+ className="w-7 h-7 rounded flex items-center justify-center flex-shrink-0"
134
+ style={{ backgroundColor: 'var(--color-surface-800)' }}
135
+ >
136
+ <span className="font-sans font-medium text-sm tracking-[0.14px] leading-[18px]">
137
+ {startIndex + 1}
138
+ </span>
139
+ </div>
140
+ <span className="font-sans font-medium text-sm tracking-[0.14px] leading-[18px] flex-1">
141
+ {secretPhrase[startIndex]}
142
+ </span>
143
+ </div>
144
+ </div>
145
+
146
+ {/* Right word */}
147
+ <div
148
+ className="flex-1 rounded-lg overflow-hidden"
149
+ style={{
150
+ backgroundColor: 'var(--color-bg)',
151
+ border: '1px solid var(--color-surface-800)',
152
+ }}
153
+ >
154
+ <div className="flex items-center gap-2.5 p-2">
155
+ <div
156
+ className="w-7 h-7 rounded flex items-center justify-center flex-shrink-0"
157
+ style={{ backgroundColor: 'var(--color-surface-800)' }}
158
+ >
159
+ <span className="font-sans font-medium text-sm tracking-[0.14px] leading-[18px]">
160
+ {startIndex + 2}
161
+ </span>
162
+ </div>
163
+ <span className="font-sans font-medium text-sm tracking-[0.14px] leading-[18px] flex-1">
164
+ {secretPhrase[startIndex + 1]}
165
+ </span>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ ))}
170
+
171
+ {/* Blur Overlay */}
172
+ {!isRevealed && (
173
+ <div
174
+ className="absolute inset-0 backdrop-blur-[6px] rounded-lg"
175
+ style={{
176
+ backgroundColor: 'var(--color-popover)',
177
+ border: '1px solid var(--color-surface-900)',
178
+ }}
179
+ />
180
+ )}
181
+ </div>
182
+ </div>
183
+ </div>
184
+
185
+ {/* Bottom Button - Pinned to bottom */}
186
+ <div
187
+ className="px-4 py-3 shrink-0"
188
+ style={{ borderTop: '1px solid var(--color-divider)', backgroundColor: 'var(--color-bg)' }}
189
+ >
190
+ <button
191
+ onClick={isRevealed ? handleCopySecretPhrase : handleReveal}
192
+ disabled={copiedSeed}
193
+ className="w-full h-12 rounded-lg font-sans font-medium text-sm tracking-[0.14px] leading-[18px] transition-opacity hover:opacity-90 flex items-center justify-center gap-2"
194
+ style={{
195
+ backgroundColor: 'var(--color-primary)',
196
+ color: '#000',
197
+ }}
198
+ >
199
+ {copiedSeed && <CheckIcon className="w-5 h-5" />}
200
+ {!isRevealed ? 'Show secret phrase' : copiedSeed ? 'Copied!' : 'Copy Secret Phrase'}
201
+ </button>
202
+ </div>
203
+
204
+ {/* Error message */}
205
+ {error && (
206
+ <div className="px-4 pb-3">
207
+ <Alert type="error">{error}</Alert>
208
+ </div>
209
+ )}
210
+ </div>
211
+ );
212
+ }
@@ -0,0 +1,123 @@
1
+ import { useStore } from '../store';
2
+ import { useState, useEffect } from 'react';
3
+ import { STORAGE_KEYS, INTERNAL_METHODS } from '../../shared/constants';
4
+ import { send } from '../utils/messaging';
5
+ import RoseLogo from '../assets/iris-logo.svg';
6
+ import { CloseIcon } from '../components/icons/CloseIcon';
7
+
8
+ export function WalletPermissionsScreen() {
9
+ const { navigate } = useStore();
10
+ const [approvedOrigins, setApprovedOrigins] = useState<string[]>([]);
11
+
12
+ useEffect(() => {
13
+ loadApprovedOrigins();
14
+ }, []);
15
+
16
+ async function loadApprovedOrigins() {
17
+ const stored = (await chrome.storage.local.get([STORAGE_KEYS.APPROVED_ORIGINS])) as Record<
18
+ string,
19
+ unknown
20
+ >;
21
+ const raw = stored[STORAGE_KEYS.APPROVED_ORIGINS];
22
+ const origins = Array.isArray(raw) ? raw.filter((x): x is string => typeof x === 'string') : [];
23
+ setApprovedOrigins(origins);
24
+ }
25
+
26
+ function handleClose() {
27
+ navigate('home');
28
+ }
29
+
30
+ async function handleRevoke(origin: string) {
31
+ try {
32
+ await send(INTERNAL_METHODS.REVOKE_ORIGIN, [{ origin }]);
33
+ await loadApprovedOrigins();
34
+ } catch (error) {
35
+ console.error('Failed to revoke origin:', error);
36
+ }
37
+ }
38
+
39
+ return (
40
+ <div
41
+ className="w-[357px] h-[600px] flex flex-col overflow-y-auto"
42
+ style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
43
+ >
44
+ {/* Header */}
45
+ <header
46
+ className="flex items-center justify-between px-4 py-3 min-h-[64px]"
47
+ style={{ backgroundColor: 'var(--color-bg)' }}
48
+ >
49
+ <div className="w-8 h-8 flex items-center justify-center shrink-0">
50
+ <img src={RoseLogo} alt="Rose" className="w-6 h-6" />
51
+ </div>
52
+ <h1 className="m-0 text-base font-medium leading-[22px] tracking-[0.16px]">
53
+ Wallet permissions
54
+ </h1>
55
+ <button
56
+ type="button"
57
+ onClick={handleClose}
58
+ className="w-8 h-8 rounded-lg flex items-center justify-center transition-colors focus:outline-none focus-visible:ring-2 shrink-0"
59
+ style={{ color: 'var(--color-text-primary)' }}
60
+ onMouseEnter={e => (e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')}
61
+ onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
62
+ aria-label="Close"
63
+ >
64
+ <CloseIcon />
65
+ </button>
66
+ </header>
67
+
68
+ {/* Content */}
69
+ <div className="flex flex-col gap-2 h-[536px] overflow-y-auto">
70
+ <div className="flex flex-col gap-2 px-4 py-2">
71
+ {approvedOrigins && approvedOrigins.length > 0 ? (
72
+ approvedOrigins.map(origin => (
73
+ <div
74
+ key={origin}
75
+ className="flex items-center justify-between p-2 rounded-lg transition-colors"
76
+ onMouseEnter={e =>
77
+ (e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')
78
+ }
79
+ onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
80
+ >
81
+ <div className="flex items-center gap-2.5 flex-1 min-w-0">
82
+ <div className="w-8 h-8 rounded-lg flex items-center justify-center overflow-hidden shrink-0">
83
+ {/* Site icon placeholder (first letter) */}
84
+ <div
85
+ className="w-5 h-5 flex items-center justify-center text-[12px] font-semibold"
86
+ style={{ color: 'var(--color-text-muted)' }}
87
+ >
88
+ {origin.charAt(0).toUpperCase()}
89
+ </div>
90
+ </div>
91
+ <span className="text-sm font-medium leading-[18px] tracking-[0.14px] truncate">
92
+ {origin}
93
+ </span>
94
+ </div>
95
+
96
+ <button
97
+ type="button"
98
+ title="Revoke permissions"
99
+ onClick={() => handleRevoke(origin)}
100
+ className="w-8 h-8 rounded-lg flex items-center justify-center p-1.5 transition-colors focus:outline-none focus-visible:ring-2 shrink-0"
101
+ style={{ backgroundColor: 'var(--color-red-light)' }}
102
+ onMouseEnter={e => (e.currentTarget.style.opacity = '0.8')}
103
+ onMouseLeave={e => (e.currentTarget.style.opacity = '1')}
104
+ >
105
+ <CloseIcon className="w-4 h-4 [filter:brightness(0)_saturate(100%)_invert(29%)_sepia(96%)_saturate(2447%)_hue-rotate(347deg)_brightness(92%)_contrast(93%)]" />
106
+ </button>
107
+ </div>
108
+ ))
109
+ ) : (
110
+ <div className="flex items-center justify-center text-center px-4 py-10">
111
+ <p
112
+ className="m-0 text-sm font-normal leading-[18px] tracking-[0.14px]"
113
+ style={{ color: 'var(--color-text-muted)' }}
114
+ >
115
+ No connected sites
116
+ </p>
117
+ </div>
118
+ )}
119
+ </div>
120
+ </div>
121
+ </div>
122
+ );
123
+ }