@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,482 @@
1
+ /**
2
+ * Zustand store for popup UI state and navigation
3
+ */
4
+
5
+ import { create } from 'zustand';
6
+ import { INTERNAL_METHODS, APPROVAL_CONSTANTS, NOCK_TO_NICKS } from '../shared/constants';
7
+ import { hasIncompleteOnboarding } from '../shared/onboarding';
8
+ import {
9
+ Account,
10
+ AccountBalance,
11
+ TransactionDetails,
12
+ SignRequest,
13
+ SignRawTxRequest,
14
+ TransactionRequest,
15
+ ConnectRequest,
16
+ WalletTransaction,
17
+ } from '../shared/types';
18
+ import { send } from './utils/messaging';
19
+
20
+ /**
21
+ * All available screens in the wallet
22
+ */
23
+ export type Screen =
24
+ // Onboarding flow
25
+ | 'onboarding-start'
26
+ | 'onboarding-create'
27
+ | 'onboarding-backup'
28
+ | 'onboarding-verify'
29
+ | 'onboarding-success'
30
+ | 'onboarding-import'
31
+ | 'onboarding-import-v0'
32
+ | 'onboarding-import-success'
33
+ | 'onboarding-resume-backup'
34
+
35
+ // Main app screens
36
+ | 'home'
37
+ | 'settings'
38
+ | 'theme-settings'
39
+ | 'lock-time'
40
+ | 'key-settings'
41
+ | 'view-secret-phrase'
42
+ | 'wallet-permissions'
43
+ | 'wallet-settings'
44
+ | 'wallet-styling'
45
+ | 'about'
46
+ | 'recovery-phrase'
47
+
48
+ // Transaction screens
49
+ | 'send'
50
+ | 'send-review'
51
+ | 'send-submitted'
52
+ | 'sent'
53
+ | 'receive'
54
+ | 'tx-details'
55
+
56
+ // Approval screens
57
+ | 'connect-approval'
58
+ | 'sign-message'
59
+ | 'approve-transaction'
60
+ | 'approve-sign-raw-tx'
61
+
62
+ // System
63
+ | 'locked';
64
+
65
+ /**
66
+ * Wallet state synced from background service worker
67
+ */
68
+ interface WalletState {
69
+ locked: boolean;
70
+ address: string | null;
71
+ accounts: Account[];
72
+ currentAccount: Account | null;
73
+ balance: number;
74
+ availableBalance: number;
75
+ spendableBalance: number; // Sum of UTXOs that are available (not in_flight) - can be spent NOW
76
+ accountBalances: Record<string, number>; // Map of address -> confirmed balance
77
+ accountSpendableBalances: Record<string, number>; // Map of address -> spendable balance (available UTXOs only)
78
+ accountBalanceDetails: Record<string, AccountBalance>; // Map of address -> detailed balance
79
+ }
80
+
81
+ /**
82
+ * Main app store
83
+ */
84
+ interface AppStore {
85
+ // Navigation
86
+ currentScreen: Screen;
87
+ navigate: (screen: Screen) => void;
88
+
89
+ // Navigation history for back button
90
+ history: Screen[];
91
+ goBack: () => void;
92
+
93
+ // Wallet state (synced from service worker)
94
+ wallet: WalletState;
95
+ syncWallet: (state: WalletState) => void;
96
+
97
+ // Temporary onboarding state (cleared after completion)
98
+ onboardingMnemonic: string | null;
99
+ setOnboardingMnemonic: (mnemonic: string | null) => void;
100
+ onboardingMnemonicV0: string | null;
101
+ setOnboardingMnemonicV0: (mnemonicV0: string | null) => void;
102
+
103
+ // Last transaction details (for showing confirmation screen)
104
+ lastTransaction: TransactionDetails | null;
105
+ setLastTransaction: (transaction: TransactionDetails | null) => void;
106
+
107
+ // Pending connect request (for showing approval screen)
108
+ pendingConnectRequest: ConnectRequest | null;
109
+ setPendingConnectRequest: (request: ConnectRequest | null) => void;
110
+
111
+ // Pending sign request (for showing approval screen)
112
+ pendingSignRequest: SignRequest | null;
113
+ setPendingSignRequest: (request: SignRequest | null) => void;
114
+
115
+ // Pending sign raw transaction request (for showing approval screen)
116
+ pendingSignRawTxRequest: SignRawTxRequest | null;
117
+ setPendingSignRawTxRequest: (request: SignRawTxRequest | null) => void;
118
+
119
+ // Pending transaction request (for showing approval screen)
120
+ pendingTransactionRequest: TransactionRequest | null;
121
+ setPendingTransactionRequest: (request: TransactionRequest | null) => void;
122
+
123
+ // Wallet transactions for current account (from UTXO store)
124
+ walletTransactions: WalletTransaction[];
125
+ setWalletTransactions: (transactions: WalletTransaction[]) => void;
126
+
127
+ // Selected transaction for viewing details
128
+ selectedTransaction: WalletTransaction | null;
129
+ setSelectedTransaction: (transaction: WalletTransaction | null) => void;
130
+
131
+ // Balance fetching state
132
+ isBalanceFetching: boolean;
133
+
134
+ // Initialization state - true once cached balances have been loaded
135
+ isInitialized: boolean;
136
+
137
+ // Price data
138
+ priceUsd: number;
139
+ priceChange24h: number;
140
+ isPriceFetching: boolean;
141
+
142
+ // Initialize app - checks vault status and navigates appropriately
143
+ initialize: () => Promise<void>;
144
+
145
+ // Fetch balance from blockchain
146
+ fetchBalance: () => Promise<void>;
147
+
148
+ // Fetch price from CoinGecko
149
+ fetchPrice: () => Promise<void>;
150
+
151
+ // Fetch wallet transactions from UTXO store
152
+ fetchWalletTransactions: () => Promise<void>;
153
+ }
154
+
155
+ /**
156
+ * Create the store
157
+ */
158
+ export const useStore = create<AppStore>((set, get) => ({
159
+ // Initial state
160
+ currentScreen: 'locked',
161
+ history: [],
162
+
163
+ wallet: {
164
+ locked: true,
165
+ address: null,
166
+ accounts: [],
167
+ currentAccount: null,
168
+ balance: 0,
169
+ availableBalance: 0,
170
+ spendableBalance: 0,
171
+ accountBalances: {},
172
+ accountSpendableBalances: {},
173
+ accountBalanceDetails: {},
174
+ },
175
+
176
+ onboardingMnemonic: null,
177
+ onboardingMnemonicV0: null,
178
+ onboardingImportVersion: 1,
179
+ lastTransaction: null,
180
+ pendingConnectRequest: null,
181
+ pendingSignRequest: null,
182
+ pendingSignRawTxRequest: null,
183
+ pendingTransactionRequest: null,
184
+ walletTransactions: [],
185
+ selectedTransaction: null,
186
+ isBalanceFetching: false,
187
+ isInitialized: false,
188
+ priceUsd: 0,
189
+ priceChange24h: 0,
190
+ isPriceFetching: false,
191
+
192
+ // Navigate to a new screen
193
+ navigate: (screen: Screen) => {
194
+ const current = get().currentScreen;
195
+ set({
196
+ currentScreen: screen,
197
+ history: [...get().history, current],
198
+ });
199
+ },
200
+
201
+ // Go back to previous screen
202
+ goBack: () => {
203
+ const history = get().history;
204
+ if (history.length === 0) return;
205
+
206
+ const previous = history[history.length - 1];
207
+ set({
208
+ currentScreen: previous,
209
+ history: history.slice(0, -1),
210
+ });
211
+ },
212
+
213
+ // Sync wallet state from background
214
+ syncWallet: (state: WalletState) => {
215
+ set({ wallet: state });
216
+ },
217
+
218
+ // Set temporary mnemonic during onboarding
219
+ setOnboardingMnemonic: (mnemonic: string | null) => {
220
+ set({ onboardingMnemonic: mnemonic });
221
+ },
222
+ setOnboardingMnemonicV0: (mnemonicV0: string | null) => {
223
+ set({ onboardingMnemonicV0: mnemonicV0 });
224
+ },
225
+
226
+ // Set last transaction details
227
+ setLastTransaction: (transaction: TransactionDetails | null) => {
228
+ set({ lastTransaction: transaction });
229
+ },
230
+
231
+ // Set pending connect request
232
+ setPendingConnectRequest: (request: ConnectRequest | null) => {
233
+ set({ pendingConnectRequest: request });
234
+ },
235
+
236
+ // Set pending sign request
237
+ setPendingSignRequest: (request: SignRequest | null) => {
238
+ set({ pendingSignRequest: request });
239
+ },
240
+
241
+ // Set pending sign raw transaction request
242
+ setPendingSignRawTxRequest: (request: SignRawTxRequest | null) => {
243
+ set({ pendingSignRawTxRequest: request });
244
+ },
245
+
246
+ // Set pending transaction request
247
+ setPendingTransactionRequest: (request: TransactionRequest | null) => {
248
+ set({ pendingTransactionRequest: request });
249
+ },
250
+
251
+ // Set wallet transactions
252
+ setWalletTransactions: (transactions: WalletTransaction[]) => {
253
+ set({ walletTransactions: transactions });
254
+ },
255
+
256
+ // Set selected transaction for viewing details
257
+ setSelectedTransaction: (transaction: WalletTransaction | null) => {
258
+ set({ selectedTransaction: transaction });
259
+ },
260
+
261
+ // Initialize app on load
262
+ initialize: async () => {
263
+ try {
264
+ // Check if we're opening for an approval request
265
+ const hash = window.location.hash.slice(1); // Remove '#'
266
+ const isApprovalRequest =
267
+ hash.startsWith(APPROVAL_CONSTANTS.CONNECT_HASH_PREFIX) ||
268
+ hash.startsWith(APPROVAL_CONSTANTS.TRANSACTION_HASH_PREFIX) ||
269
+ hash.startsWith(APPROVAL_CONSTANTS.SIGN_MESSAGE_HASH_PREFIX);
270
+
271
+ // Get current vault state from service worker
272
+ const state = await send<{
273
+ locked: boolean;
274
+ hasVault: boolean;
275
+ address: string;
276
+ accounts: Account[];
277
+ currentAccount: Account | null;
278
+ }>(INTERNAL_METHODS.GET_STATE);
279
+
280
+ // Load cached balances from storage (for offline access)
281
+ const { STORAGE_KEYS } = await import('../shared/constants');
282
+ const stored = await chrome.storage.local.get([STORAGE_KEYS.CACHED_BALANCES]);
283
+ const cachedBalances = (stored[STORAGE_KEYS.CACHED_BALANCES] || {}) as Record<string, number>;
284
+
285
+ // Initial wallet state with confirmed balances (available balance computed after TX fetch)
286
+ const confirmedBalance = state.currentAccount
287
+ ? cachedBalances[state.currentAccount.address] || 0
288
+ : 0;
289
+ const walletState: WalletState = {
290
+ locked: state.locked,
291
+ address: state.address || null,
292
+ accounts: state.accounts || [],
293
+ currentAccount: state.currentAccount || null,
294
+ balance: confirmedBalance,
295
+ availableBalance: confirmedBalance, // Will be recalculated after fetching transactions
296
+ spendableBalance: confirmedBalance, // Will be recalculated after fetching transactions
297
+ accountBalances: cachedBalances, // Load all cached balances
298
+ accountSpendableBalances: cachedBalances, // Will be recalculated after fetching transactions
299
+ accountBalanceDetails: {},
300
+ };
301
+
302
+ // Determine initial screen
303
+ let initialScreen: Screen;
304
+
305
+ if (isApprovalRequest) {
306
+ // For approval requests, don't override the screen
307
+ // Let the approval useEffect handle navigation
308
+ initialScreen = walletState.locked ? 'locked' : 'home';
309
+ } else if (!state.hasVault) {
310
+ // No vault exists - start onboarding
311
+ initialScreen = 'onboarding-start';
312
+ } else {
313
+ // Check if user has incomplete onboarding (created wallet but didn't complete backup)
314
+ const incompleteOnboarding = await hasIncompleteOnboarding();
315
+
316
+ if (incompleteOnboarding) {
317
+ // User needs to complete their backup - show resume screen
318
+ initialScreen = 'onboarding-resume-backup';
319
+ } else if (walletState.locked) {
320
+ // Vault exists but locked
321
+ initialScreen = 'locked';
322
+ } else {
323
+ // Vault unlocked - go to home
324
+ initialScreen = 'home';
325
+ }
326
+ }
327
+
328
+ set({
329
+ wallet: walletState,
330
+ currentScreen: initialScreen,
331
+ isInitialized: true,
332
+ });
333
+
334
+ // Fetch balance if wallet is unlocked
335
+ if (!walletState.locked && walletState.address) {
336
+ get().fetchBalance();
337
+ get().fetchWalletTransactions();
338
+ }
339
+ } catch (error) {
340
+ console.error('Failed to initialize app:', error);
341
+ // Default to locked screen on error
342
+ set({ currentScreen: 'locked' });
343
+ }
344
+ },
345
+
346
+ // Fetch balance from UTXO store for all accounts
347
+ // Also syncs UTXOs from chain (runs in popup context where WASM works)
348
+ fetchBalance: async () => {
349
+ try {
350
+ set({ isBalanceFetching: true });
351
+
352
+ const accounts = get().wallet.accounts;
353
+ const currentAccount = get().wallet.currentAccount;
354
+
355
+ if (!currentAccount || accounts.length === 0) {
356
+ set({ isBalanceFetching: false });
357
+ return;
358
+ }
359
+
360
+ // Sync UTXOs from chain for all accounts (runs in popup context where WASM works)
361
+ try {
362
+ const { syncAccountUTXOs } = await import('../shared/utxo-sync');
363
+ const { createBrowserClient } = await import('../shared/rpc-client-browser');
364
+ const rpcClient = createBrowserClient();
365
+
366
+ for (const account of accounts) {
367
+ try {
368
+ await syncAccountUTXOs(account.address, rpcClient);
369
+ } catch (syncErr) {
370
+ console.warn(`[Store] UTXO sync failed for ${account.name}:`, syncErr);
371
+ }
372
+ }
373
+ } catch (importErr) {
374
+ console.warn('[Store] Could not import sync modules:', importErr);
375
+ }
376
+
377
+ // Fetch UTXO store balance for ALL accounts
378
+ const accountBalances: Record<string, number> = {};
379
+ const accountSpendableBalances: Record<string, number> = {};
380
+
381
+ for (const account of accounts) {
382
+ try {
383
+ const storeBalance = await send<{
384
+ available: number;
385
+ spendableNow: number;
386
+ pendingOut: number;
387
+ pendingChange: number;
388
+ total: number;
389
+ utxoCount: number;
390
+ availableUtxoCount: number;
391
+ }>(INTERNAL_METHODS.GET_BALANCE_FROM_STORE, [account.address]);
392
+
393
+ // Convert from nicks to NOCK for display
394
+ const availableNock = storeBalance.available / NOCK_TO_NICKS;
395
+ const spendableNock = storeBalance.spendableNow / NOCK_TO_NICKS;
396
+ accountBalances[account.address] = availableNock;
397
+ accountSpendableBalances[account.address] = spendableNock;
398
+ } catch (err) {
399
+ console.warn(`[Store] Could not get balance for ${account.name}:`, err);
400
+ // Keep previous balance if fetch fails
401
+ accountBalances[account.address] = get().wallet.accountBalances[account.address] ?? 0;
402
+ accountSpendableBalances[account.address] =
403
+ get().wallet.accountSpendableBalances[account.address] ?? 0;
404
+ }
405
+ }
406
+
407
+ // Get current account's detailed balance
408
+ const currentBalance = accountBalances[currentAccount.address] ?? 0;
409
+ const currentSpendable = accountSpendableBalances[currentAccount.address] ?? 0;
410
+
411
+ // Persist balances to chrome.storage.local for offline access
412
+ try {
413
+ const { STORAGE_KEYS } = await import('../shared/constants');
414
+ await chrome.storage.local.set({
415
+ [STORAGE_KEYS.CACHED_BALANCES]: accountBalances,
416
+ });
417
+ } catch (cacheErr) {
418
+ console.warn('[Store] Failed to cache balances:', cacheErr);
419
+ }
420
+
421
+ set({
422
+ wallet: {
423
+ ...get().wallet,
424
+ balance: currentBalance,
425
+ availableBalance: currentBalance,
426
+ spendableBalance: currentSpendable,
427
+ accountBalances,
428
+ accountSpendableBalances,
429
+ },
430
+ isBalanceFetching: false,
431
+ });
432
+ } catch (error) {
433
+ console.error('[Store] Failed to fetch balance:', error);
434
+ set({ isBalanceFetching: false });
435
+ }
436
+ },
437
+
438
+ // Fetch price from CoinGecko
439
+ fetchPrice: async () => {
440
+ try {
441
+ set({ isPriceFetching: true });
442
+
443
+ const { fetchNockPrice } = await import('../shared/price-api');
444
+ const priceData = await fetchNockPrice();
445
+
446
+ set({
447
+ priceUsd: priceData.usd,
448
+ priceChange24h: priceData.usd_24h_change,
449
+ isPriceFetching: false,
450
+ });
451
+ } catch (error) {
452
+ console.error('[Store] Failed to fetch price:', error);
453
+ set({ isPriceFetching: false });
454
+ }
455
+ },
456
+
457
+ // Fetch wallet transactions from UTXO store
458
+ // Called directly from popup context (not via background) to avoid service worker limitations
459
+ fetchWalletTransactions: async () => {
460
+ try {
461
+ const currentAccount = get().wallet.currentAccount;
462
+ if (!currentAccount) return;
463
+
464
+ // Capture the address we're fetching for to detect account switches
465
+ const fetchingForAddress = currentAccount.address;
466
+
467
+ // Import and call directly from popup context (avoids service worker document issue)
468
+ const { getWalletTransactions } = await import('../shared/utxo-store');
469
+ const transactions = await getWalletTransactions(fetchingForAddress);
470
+
471
+ // Check if user switched accounts while we were fetching
472
+ const accountAfterFetch = get().wallet.currentAccount;
473
+ if (accountAfterFetch?.address !== fetchingForAddress) {
474
+ return;
475
+ }
476
+
477
+ set({ walletTransactions: transactions });
478
+ } catch (error) {
479
+ console.error('Failed to fetch wallet transactions:', error);
480
+ }
481
+ },
482
+ }));