@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,450 @@
1
+ /**
2
+ * Shared TypeScript types and interfaces
3
+ */
4
+
5
+ import { PROVIDER_METHODS, INTERNAL_METHODS, RPC_METHODS, ERROR_CODES } from './constants';
6
+
7
+ /**
8
+ * Account information for multi-account wallet
9
+ * Each account is derived from the same mnemonic using BIP-44 derivation paths
10
+ */
11
+ export interface Account {
12
+ /** User-defined account name (editable) */
13
+ name: string;
14
+ /** Nockchain V1 PKH address (40 bytes base58-encoded, ~54-55 chars) */
15
+ address: string;
16
+ /** BIP-44 derivation index (0, 1, 2, ...) */
17
+ index: number;
18
+ /** Icon style ID (1-15, defaults to index % 3 + 1 for variety) */
19
+ iconStyleId?: number;
20
+ /** Icon color (hex string, defaults to #5968fb) */
21
+ iconColor?: string;
22
+ /** Whether this account is hidden from the UI */
23
+ hidden?: boolean;
24
+ /** Timestamp when the account was created (milliseconds since epoch) */
25
+ createdAt?: number;
26
+ /** Derivation path: 'master' (master key) or 'derived' (child key at index) */
27
+ derivation?: 'master' | 'slip10';
28
+ }
29
+
30
+ /**
31
+ * Type for all valid RPC method names
32
+ */
33
+ export type RPCMethod = (typeof RPC_METHODS)[keyof typeof RPC_METHODS];
34
+
35
+ /**
36
+ * Type for provider method names only
37
+ */
38
+ export type ProviderMethod = (typeof PROVIDER_METHODS)[keyof typeof PROVIDER_METHODS];
39
+
40
+ /**
41
+ * Type for internal method names only
42
+ */
43
+ export type InternalMethod = (typeof INTERNAL_METHODS)[keyof typeof INTERNAL_METHODS];
44
+
45
+ /**
46
+ * Type for all valid error codes
47
+ */
48
+ export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
49
+
50
+ /**
51
+ * Transaction information
52
+ * Represents a blockchain transaction (sent or received)
53
+ */
54
+ export interface Transaction {
55
+ /** Unique transaction ID */
56
+ id: string;
57
+ /** Transaction type */
58
+ type: 'sent' | 'received';
59
+ /** Amount in NOCK */
60
+ amount: number;
61
+ /** Counterparty address (sender for received, recipient for sent) */
62
+ address: string;
63
+ /** Transaction timestamp */
64
+ timestamp: Date;
65
+ /** Transaction status */
66
+ status: 'confirmed' | 'pending' | 'failed';
67
+ }
68
+
69
+ /**
70
+ * Transaction details for confirmation screen
71
+ * Used to display transaction details immediately after sending
72
+ */
73
+ export interface TransactionDetails {
74
+ /** Transaction ID */
75
+ txid: string;
76
+ /** Amount in NOCK */
77
+ amount: number;
78
+ /** Transaction fee in NOCK */
79
+ fee: number;
80
+ /** Recipient address */
81
+ to?: string;
82
+ /** Sender address */
83
+ from?: string;
84
+ /** Protobuf transaction object for debugging/export (dev feature) */
85
+ protobufTx?: any;
86
+ /** Whether this is a sweep transaction (all UTXOs sent to recipient) */
87
+ sendMax?: boolean;
88
+ }
89
+
90
+ /**
91
+ * Structured balance information for an account
92
+ * Separates confirmed (on-chain) balance from pending transaction effects
93
+ */
94
+ export interface AccountBalance {
95
+ /** Balance from confirmed UTXOs on-chain */
96
+ confirmed: number;
97
+ /** Sum of (amount + fee) for pending outbound transactions */
98
+ pendingOut: number;
99
+ /** Sum of amounts for pending inbound transactions */
100
+ pendingIn: number;
101
+ /** Spendable balance: confirmed - pendingOut */
102
+ available: number;
103
+ }
104
+
105
+ /**
106
+ * Pending connection request from a dApp
107
+ */
108
+ export interface ConnectRequest {
109
+ /** Unique request ID */
110
+ id: string;
111
+ /** Origin of the requesting site (e.g., "https://app.example.com") */
112
+ origin: string;
113
+ /** Request timestamp */
114
+ timestamp: number;
115
+
116
+ /** Optional signing context */
117
+ signWith?: 'v1' | 'v0';
118
+
119
+ /** v0 derivation label when signWith='v0' */
120
+ v0Derivation?: 'master' | 'child0' | 'hard0';
121
+ }
122
+
123
+ /**
124
+ * Pending sign message request from a dApp
125
+ */
126
+ export interface SignRequest {
127
+ /** Unique request ID */
128
+ id: string;
129
+ /** Origin of the requesting site (e.g., "https://app.example.com") */
130
+ origin: string;
131
+ /** Message to be signed */
132
+ message: string;
133
+ /** Request timestamp */
134
+ timestamp: number;
135
+
136
+ /** Optional signing context */
137
+ signWith?: 'v1' | 'v0';
138
+
139
+ /** v0 derivation label when signWith='v0' */
140
+ v0Derivation?: 'master' | 'child0' | 'hard0';
141
+ }
142
+
143
+ /**
144
+ * Pending transaction approval request from a dApp
145
+ */
146
+ export interface TransactionRequest {
147
+ /** Unique request ID */
148
+ id: string;
149
+ /** Origin of the requesting site (e.g., "https://app.example.com") */
150
+ origin: string;
151
+ /** Recipient address */
152
+ to: string;
153
+ /** Amount in nicks */
154
+ amount: number;
155
+ /** Transaction fee in nicks */
156
+ fee: number;
157
+ /** Request timestamp */
158
+ timestamp: number;
159
+
160
+ /** Optional signing context */
161
+ signWith?: 'v1' | 'v0';
162
+
163
+ /** v0 derivation label when signWith='v0' */
164
+ v0Derivation?: 'master' | 'child0' | 'hard0';
165
+ }
166
+
167
+ /**
168
+ * Pending raw transaction signing request from a dApp
169
+ */
170
+ export interface SignRawTxRequest {
171
+ /** Unique request ID */
172
+ id: string;
173
+ /** Origin of the requesting site */
174
+ origin: string;
175
+ /** Raw transaction protobuf */
176
+ rawTx: any;
177
+ /** Input notes (protobuf) */
178
+ notes: any[];
179
+ /** Spend conditions (protobuf) */
180
+ spendConditions: any[];
181
+ /** Output notes (protobuf). Ignored from dApp side */
182
+ outputs?: any[];
183
+ /** Request timestamp */
184
+ timestamp: number;
185
+
186
+ /** Optional signing context */
187
+ signWith?: 'v1' | 'v0';
188
+
189
+ /** v0 derivation label when signWith='v0' */
190
+ v0Derivation?: 'master' | 'child0' | 'hard0';
191
+ }
192
+
193
+ /**
194
+ * Nockchain note (UTXO) structure
195
+ * Represents an unspent transaction output on the blockchain
196
+ */
197
+ export interface Note {
198
+ /** Note version (0 for legacy, 1 for v1) */
199
+ version: number;
200
+ /** Origin page (block height where note was created) */
201
+ originPage: bigint;
202
+ /** Minimum timelock (optional, for coinbase/timelocked notes) */
203
+ timelockMin?: bigint;
204
+ /** Maximum timelock (optional, for coinbase/timelocked notes) */
205
+ timelockMax?: bigint;
206
+ /** First 40 bytes of note name (80-byte identifier) */
207
+ nameFirst: Uint8Array;
208
+ /** Last 40 bytes of note name (80-byte identifier) */
209
+ nameLast: Uint8Array;
210
+ /** First name as base58 string (optional, from WASM gRPC client) */
211
+ nameFirstBase58?: string;
212
+ /** Last name as base58 string (optional, from WASM gRPC client) */
213
+ nameLastBase58?: string;
214
+ /** Note data hash as base58 string (optional, from WASM gRPC client) */
215
+ noteDataHashBase58?: string;
216
+ /** Public keys for lock (legacy format) */
217
+ lockPubkeys: Uint8Array[];
218
+ /** Number of keys required to unlock (legacy format) */
219
+ lockKeysRequired: bigint;
220
+ /** Source transaction hash */
221
+ sourceHash: Uint8Array;
222
+ /** Whether this note is from a coinbase (mining reward) transaction */
223
+ sourceIsCoinbase: boolean;
224
+ /** Amount in nicks (1 NOCK = 65,536 nicks) */
225
+ assets: number;
226
+ /** Raw protobuf note object from RPC (for Note.fromProtobuf) */
227
+ protoNote?: any;
228
+ }
229
+
230
+ // ============================================================================
231
+ // UTXO Store Types - For tracking note lifecycle and enabling successive sends
232
+ // ============================================================================
233
+
234
+ /**
235
+ * Note state in the UTXO store
236
+ * - available: Can be used in new transactions
237
+ * - in_flight: Reserved by a pending transaction (locked until confirmed or expired)
238
+ * - spent: Transaction confirmed, note is consumed
239
+ */
240
+ export type NoteState = 'available' | 'in_flight' | 'spent';
241
+
242
+ /**
243
+ * Stored note (UTXO) with state tracking
244
+ * This is the core type for the local UTXO store
245
+ */
246
+ export interface StoredNote {
247
+ /** Unique identifier: nameFirst:nameLast in base58 */
248
+ noteId: string;
249
+
250
+ /** Account that owns this note */
251
+ accountAddress: string;
252
+
253
+ // Chain identity
254
+ /** Source transaction hash (base58) that created this note */
255
+ sourceHash: string;
256
+ /** Block height where this note was created */
257
+ originPage: number;
258
+
259
+ // Value
260
+ /** Amount in nicks (1 NOCK = 65,536 nicks) */
261
+ assets: number;
262
+
263
+ // For WASM transaction building
264
+ /** First 40 bytes of note name as base58 */
265
+ nameFirst: string;
266
+ /** Last 40 bytes of note name as base58 */
267
+ nameLast: string;
268
+ /** Note data hash as base58 (for WASM) */
269
+ noteDataHashBase58: string;
270
+ /** Raw protobuf note for WasmNote.fromProtobuf() */
271
+ protoNote: any;
272
+
273
+ // State tracking
274
+ /** Current state of this note */
275
+ state: NoteState;
276
+ /** If in_flight, which wallet transaction is using this note */
277
+ pendingTxId?: string;
278
+ /** Was this note change from one of our own transactions? */
279
+ isChange?: boolean;
280
+
281
+ // Metadata
282
+ /** Timestamp when this note was first discovered (ms since epoch) */
283
+ discoveredAt: number;
284
+ }
285
+
286
+ /**
287
+ * UTXO store structure - per account
288
+ */
289
+ export interface UTXOStore {
290
+ [accountAddress: string]: {
291
+ notes: StoredNote[];
292
+ /** Version for optimistic locking / conflict detection */
293
+ version: number;
294
+ };
295
+ }
296
+
297
+ // ============================================================================
298
+ // Wallet Transaction Types - Separate from UTXO lifecycle
299
+ // ============================================================================
300
+
301
+ /**
302
+ * Wallet transaction status
303
+ * - created: Transaction object created but not yet broadcasted
304
+ * - broadcast_pending: Attempting to broadcast
305
+ * - broadcasted_unconfirmed: In mempool, waiting for confirmation
306
+ * - confirmed: Included in a block
307
+ * - failed: Broadcast or confirmation failed
308
+ * - expired: Timed out waiting for confirmation
309
+ */
310
+ export type WalletTxStatus =
311
+ | 'created'
312
+ | 'broadcast_pending'
313
+ | 'broadcasted_unconfirmed'
314
+ | 'confirmed'
315
+ | 'failed'
316
+ | 'expired';
317
+
318
+ /**
319
+ * Wallet transaction record
320
+ * Links to StoredNotes via noteIds
321
+ */
322
+ export interface WalletTransaction {
323
+ /** Internal unique ID (UUID) */
324
+ id: string;
325
+
326
+ /** On-chain transaction hash (known after broadcast) */
327
+ txHash?: string;
328
+
329
+ /** Account that initiated/received this transaction */
330
+ accountAddress: string;
331
+
332
+ /** Transaction direction relative to this account */
333
+ direction: 'outgoing' | 'incoming' | 'self';
334
+
335
+ // Timestamps
336
+ /** When the transaction was created (ms since epoch) */
337
+ createdAt: number;
338
+ /** When the transaction was last updated (ms since epoch) */
339
+ updatedAt: number;
340
+
341
+ /** USD price per NOCK at the time of transaction */
342
+ priceUsdAtTime?: number;
343
+
344
+ /** Current status */
345
+ status: WalletTxStatus;
346
+
347
+ // For outgoing transactions
348
+ /** Note IDs used as inputs (spent) */
349
+ inputNoteIds?: string[];
350
+ /** Expected change note IDs (predicted before confirmation) */
351
+ expectedChangeNoteIds?: string[];
352
+ /** Expected change amount in nicks (sum of inputs - amount - fee) */
353
+ expectedChange?: number;
354
+ /** Recipient address */
355
+ recipient?: string;
356
+ /** Amount sent in nicks */
357
+ amount?: number;
358
+ /** Fee paid in nicks */
359
+ fee?: number;
360
+
361
+ // For incoming transactions
362
+ /** Note IDs received */
363
+ receivedNoteIds?: string[];
364
+ /** Sender address (if known) */
365
+ sender?: string;
366
+
367
+ // Confirmation tracking
368
+ /** Block height when confirmed */
369
+ confirmedAtBlock?: number;
370
+ /** Number of confirmations */
371
+ confirmations?: number;
372
+ }
373
+
374
+ /**
375
+ * Wallet transaction store - per account
376
+ */
377
+ export interface WalletTxStore {
378
+ [accountAddress: string]: WalletTransaction[];
379
+ }
380
+
381
+ /**
382
+ * Per-account sync state for incremental UTXO syncing
383
+ */
384
+ export interface AccountSyncState {
385
+ accountAddress: string;
386
+ /** Last block height that was fully synced (inclusive) */
387
+ lastSyncedHeight: number;
388
+ /** Timestamp of last successful sync */
389
+ lastSyncedAt: number;
390
+ }
391
+
392
+ /**
393
+ * Sync state store - per account
394
+ */
395
+ export interface SyncStateStore {
396
+ [accountAddress: string]: AccountSyncState;
397
+ }
398
+
399
+ /**
400
+ * Expected change output - tracked before broadcast to identify change vs incoming
401
+ */
402
+ export interface ExpectedChange {
403
+ /** Wallet transaction ID that will produce this change */
404
+ walletTxId: string;
405
+ /** Predicted note ID (if deterministic) */
406
+ expectedNoteId?: string;
407
+ /** Expected transaction hash (if known before broadcast) */
408
+ expectedTxHash?: string;
409
+ /** Account address */
410
+ accountAddress: string;
411
+ }
412
+
413
+ // ============================================================================
414
+ // UTXO Diff Types - For pure diff computation in utxo-sync
415
+ // ============================================================================
416
+
417
+ /**
418
+ * Fetched UTXO from chain (before being stored)
419
+ */
420
+ export interface FetchedUTXO {
421
+ noteId: string;
422
+ sourceHash: string;
423
+ originPage: number;
424
+ assets: number;
425
+ nameFirst: string;
426
+ nameLast: string;
427
+ noteDataHashBase58: string;
428
+ protoNote: any;
429
+ }
430
+
431
+ /**
432
+ * Result of diffing local store against chain state
433
+ */
434
+ export interface UTXODiff {
435
+ /** UTXOs on chain that are not in our local store (new) */
436
+ newUTXOs: FetchedUTXO[];
437
+ /** UTXOs that exist both locally and on chain (unchanged) */
438
+ stillUnspent: StoredNote[];
439
+ /** UTXOs in our local store that are no longer on chain (spent) */
440
+ nowSpent: StoredNote[];
441
+ /** Map of noteId -> walletTxId for notes that are change from our own txs */
442
+ isChangeMap: Map<string, string>;
443
+ }
444
+
445
+ /**
446
+ * Last sync timestamps per account address
447
+ */
448
+ export interface LastSyncTimestamps {
449
+ [accountAddress: string]: number;
450
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * UTXO Diff - Pure functions for computing UTXO state changes
3
+ *
4
+ * This module contains NO storage operations or chrome APIs.
5
+ * It's purely functional, making it easy to test.
6
+ */
7
+
8
+ import type { StoredNote, FetchedUTXO, UTXODiff, WalletTransaction } from './types';
9
+
10
+ /**
11
+ * Compute the diff between local UTXO store and fetched chain state
12
+ *
13
+ * @param localNotes - Notes currently in our local store
14
+ * @param fetchedUTXOs - UTXOs fetched from the chain
15
+ * @param pendingOutgoingTxs - Our pending outgoing transactions (for confirmation detection)
16
+ * @param allOutgoingTxs - All outgoing transactions including confirmed (for change detection)
17
+ * @returns Diff result with new, unchanged, and spent notes
18
+ */
19
+ export function computeUTXODiff(
20
+ localNotes: StoredNote[],
21
+ fetchedUTXOs: FetchedUTXO[],
22
+ pendingOutgoingTxs: WalletTransaction[],
23
+ allOutgoingTxs?: WalletTransaction[]
24
+ ): UTXODiff {
25
+ // Build lookup maps
26
+ const localMap = new Map<string, StoredNote>();
27
+ for (const note of localNotes) {
28
+ localMap.set(note.noteId, note);
29
+ }
30
+
31
+ const fetchedMap = new Map<string, FetchedUTXO>();
32
+ for (const utxo of fetchedUTXOs) {
33
+ fetchedMap.set(utxo.noteId, utxo);
34
+ }
35
+
36
+ // Build a map of expectedChange amounts to tx IDs for change detection
37
+ // We match new UTXOs by their amount against expected change from our outgoing transactions
38
+ const txsForChangeDetection = allOutgoingTxs || pendingOutgoingTxs;
39
+ const expectedChangeToTxId = new Map<number, string>();
40
+ for (const tx of txsForChangeDetection) {
41
+ if (tx.expectedChange && tx.expectedChange > 0) {
42
+ expectedChangeToTxId.set(tx.expectedChange, tx.id);
43
+ }
44
+ }
45
+
46
+ // Compute diff
47
+ const newUTXOs: FetchedUTXO[] = [];
48
+ const stillUnspent: StoredNote[] = [];
49
+ const nowSpent: StoredNote[] = [];
50
+ const isChangeMap = new Map<string, string>();
51
+
52
+ // Find new UTXOs (on chain but not in local store)
53
+ for (const [noteId, fetched] of fetchedMap) {
54
+ const local = localMap.get(noteId);
55
+
56
+ if (!local) {
57
+ // This is a new UTXO we haven't seen before
58
+ newUTXOs.push(fetched);
59
+
60
+ // Check if this is change from one of our outgoing transactions
61
+ // Match by expectedChange amount (RPC doesn't provide usable sourceHash)
62
+ const matchedTxId = expectedChangeToTxId.get(fetched.assets);
63
+ if (matchedTxId) {
64
+ isChangeMap.set(noteId, matchedTxId);
65
+ // Remove from map to prevent double-matching
66
+ expectedChangeToTxId.delete(fetched.assets);
67
+ }
68
+ } else if (local.state !== 'spent') {
69
+ // Still exists on chain and in our store
70
+ stillUnspent.push(local);
71
+ }
72
+ }
73
+
74
+ // Find spent notes (in local store but not on chain)
75
+ for (const [noteId, local] of localMap) {
76
+ // Skip already-spent notes (we're tracking state, not presence)
77
+ if (local.state === 'spent') {
78
+ continue;
79
+ }
80
+
81
+ if (!fetchedMap.has(noteId)) {
82
+ // Note was in our store but is no longer on chain = spent
83
+ nowSpent.push(local);
84
+ }
85
+ }
86
+
87
+ return {
88
+ newUTXOs,
89
+ stillUnspent,
90
+ nowSpent,
91
+ isChangeMap,
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Classify a new UTXO as incoming payment vs change
97
+ *
98
+ * @param newUTXO - The new UTXO to classify
99
+ * @param isChangeMap - Map from computeUTXODiff identifying change UTXOs
100
+ * @returns { isChange: boolean, walletTxId?: string }
101
+ */
102
+ export function classifyNewUTXO(
103
+ newUTXO: FetchedUTXO,
104
+ isChangeMap: Map<string, string>
105
+ ): { isChange: boolean; walletTxId?: string } {
106
+ const walletTxId = isChangeMap.get(newUTXO.noteId);
107
+ if (walletTxId) {
108
+ return { isChange: true, walletTxId };
109
+ }
110
+ return { isChange: false };
111
+ }
112
+
113
+ /**
114
+ * Filter notes by state
115
+ */
116
+ export function filterNotesByState(notes: StoredNote[], state: StoredNote['state']): StoredNote[] {
117
+ return notes.filter(n => n.state === state);
118
+ }
119
+
120
+ /**
121
+ * Get notes that are locked by a specific transaction
122
+ */
123
+ export function getNotesForTransaction(notes: StoredNote[], walletTxId: string): StoredNote[] {
124
+ return notes.filter(n => n.pendingTxId === walletTxId);
125
+ }
126
+
127
+ /**
128
+ * Check if any notes are expired (in_flight for too long)
129
+ *
130
+ * @param notes - Notes to check
131
+ * @param maxAgeMs - Maximum age in milliseconds before considered expired
132
+ * @returns Notes that have been pending too long
133
+ */
134
+ export function findExpiredNotes(notes: StoredNote[], maxAgeMs: number): StoredNote[] {
135
+ const now = Date.now();
136
+ return notes.filter(note => {
137
+ if (note.state !== 'in_flight') {
138
+ return false;
139
+ }
140
+ // Use discoveredAt as proxy for when the note was locked
141
+ // In practice, we'd want a separate lockedAt timestamp
142
+ return now - note.discoveredAt > maxAgeMs;
143
+ });
144
+ }
145
+
146
+ /**
147
+ * Find transactions that should be marked as expired
148
+ *
149
+ * @param transactions - Wallet transactions to check
150
+ * @param maxAgeMs - Maximum age before expiry
151
+ * @returns Transactions that have exceeded the timeout
152
+ */
153
+ export function findExpiredTransactions(
154
+ transactions: WalletTransaction[],
155
+ maxAgeMs: number
156
+ ): WalletTransaction[] {
157
+ const now = Date.now();
158
+ return transactions.filter(tx => {
159
+ // Only check pending states
160
+ if (
161
+ tx.status !== 'created' &&
162
+ tx.status !== 'broadcast_pending' &&
163
+ tx.status !== 'broadcasted_unconfirmed'
164
+ ) {
165
+ return false;
166
+ }
167
+ return now - tx.createdAt > maxAgeMs;
168
+ });
169
+ }
170
+
171
+ /**
172
+ * Check if a transaction's inputs have been spent
173
+ * Used to detect confirmation when we see input notes disappear from chain
174
+ *
175
+ * @param tx - The transaction to check
176
+ * @param nowSpent - Notes that are no longer on chain
177
+ * @returns true if all inputs are spent
178
+ */
179
+ export function areTransactionInputsSpent(tx: WalletTransaction, nowSpent: StoredNote[]): boolean {
180
+ if (!tx.inputNoteIds || tx.inputNoteIds.length === 0) {
181
+ return false;
182
+ }
183
+
184
+ const spentNoteIds = new Set(nowSpent.map(n => n.noteId));
185
+ return tx.inputNoteIds.every(noteId => spentNoteIds.has(noteId));
186
+ }
187
+
188
+ /**
189
+ * Match expected change to actual new UTXOs
190
+ * This is called after a transaction confirms to link change outputs
191
+ *
192
+ * @param tx - The confirmed transaction
193
+ * @param newUTXOs - New UTXOs discovered in this sync
194
+ * @param isChangeMap - Map identifying which new UTXOs are change
195
+ * @returns Note IDs that are confirmed change for this transaction
196
+ */
197
+ export function matchChangeOutputs(
198
+ tx: WalletTransaction,
199
+ newUTXOs: FetchedUTXO[],
200
+ isChangeMap: Map<string, string>
201
+ ): string[] {
202
+ const changeNoteIds: string[] = [];
203
+
204
+ for (const utxo of newUTXOs) {
205
+ const matchedTxId = isChangeMap.get(utxo.noteId);
206
+ if (matchedTxId === tx.id) {
207
+ changeNoteIds.push(utxo.noteId);
208
+ }
209
+ }
210
+
211
+ return changeNoteIds;
212
+ }