@deserialize/multi-vm-wallet 1.4.12 → 1.5.0

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 (191) hide show
  1. package/.claude/settings.local.json +7 -1
  2. package/BUILD_OPTIMIZATION_PLAN.md +640 -0
  3. package/BUILD_RESULTS.md +282 -0
  4. package/BUN_MIGRATION.md +415 -0
  5. package/CHANGELOG_SECURITY.md +573 -0
  6. package/IMPLEMENTATION_SUMMARY.md +494 -0
  7. package/SECURITY_AUDIT.md +1124 -0
  8. package/bun.lock +553 -0
  9. package/dist/IChainWallet.js +0 -5
  10. package/dist/bip32Old.js +0 -885
  11. package/dist/bip32Small.js +0 -79
  12. package/dist/bipTest.js +0 -362
  13. package/dist/constant.js +0 -17
  14. package/dist/english.js +0 -1
  15. package/dist/evm/aa-service/index.d.ts +0 -5
  16. package/dist/evm/aa-service/index.js +0 -14
  17. package/dist/evm/aa-service/lib/account-adapter.d.ts +0 -22
  18. package/dist/evm/aa-service/lib/account-adapter.js +0 -24
  19. package/dist/evm/aa-service/lib/kernel-account.d.ts +0 -30
  20. package/dist/evm/aa-service/lib/kernel-account.js +2 -67
  21. package/dist/evm/aa-service/lib/kernel-modules.d.ts +0 -177
  22. package/dist/evm/aa-service/lib/kernel-modules.js +4 -202
  23. package/dist/evm/aa-service/lib/session-keys.d.ts +0 -118
  24. package/dist/evm/aa-service/lib/session-keys.js +7 -151
  25. package/dist/evm/aa-service/lib/type.d.ts +0 -55
  26. package/dist/evm/aa-service/lib/type.js +0 -10
  27. package/dist/evm/aa-service/services/account-abstraction.d.ts +0 -426
  28. package/dist/evm/aa-service/services/account-abstraction.js +0 -461
  29. package/dist/evm/aa-service/services/bundler.d.ts +0 -6
  30. package/dist/evm/aa-service/services/bundler.js +0 -54
  31. package/dist/evm/evm.d.ts +10 -67
  32. package/dist/evm/evm.js +340 -102
  33. package/dist/evm/index.js +0 -3
  34. package/dist/evm/script.js +3 -17
  35. package/dist/evm/smartWallet.d.ts +0 -173
  36. package/dist/evm/smartWallet.js +0 -206
  37. package/dist/evm/smartWallet.types.d.ts +0 -6
  38. package/dist/evm/smartWallet.types.js +0 -8
  39. package/dist/evm/transaction.utils.d.ts +0 -242
  40. package/dist/evm/transaction.utils.js +4 -320
  41. package/dist/evm/transactionParsing.d.ts +0 -11
  42. package/dist/evm/transactionParsing.js +28 -147
  43. package/dist/evm/utils.d.ts +0 -46
  44. package/dist/evm/utils.js +1 -57
  45. package/dist/helpers/index.d.ts +0 -4
  46. package/dist/helpers/index.js +8 -44
  47. package/dist/helpers/routeScan.js +0 -1
  48. package/dist/index.js +0 -1
  49. package/dist/old.js +0 -884
  50. package/dist/price.js +0 -1
  51. package/dist/price.types.js +0 -2
  52. package/dist/rate-limiter.d.ts +28 -0
  53. package/dist/rate-limiter.js +95 -0
  54. package/dist/retry-logic.d.ts +14 -0
  55. package/dist/retry-logic.js +120 -0
  56. package/dist/savings/index.d.ts +1 -0
  57. package/dist/savings/index.js +16 -2
  58. package/dist/savings/saving-manager.d.ts +46 -0
  59. package/dist/savings/saving-manager.js +176 -0
  60. package/dist/savings/savings-operations.d.ts +39 -0
  61. package/dist/savings/savings-operations.js +141 -0
  62. package/dist/savings/smart-savings.d.ts +0 -63
  63. package/dist/savings/smart-savings.js +0 -78
  64. package/dist/savings/types.d.ts +0 -69
  65. package/dist/savings/types.js +0 -7
  66. package/dist/savings/validation.d.ts +9 -0
  67. package/dist/savings/validation.js +85 -0
  68. package/dist/svm/constant.js +0 -1
  69. package/dist/svm/index.js +0 -1
  70. package/dist/svm/svm.d.ts +7 -13
  71. package/dist/svm/svm.js +263 -46
  72. package/dist/svm/transactionParsing.d.ts +0 -7
  73. package/dist/svm/transactionParsing.js +3 -41
  74. package/dist/svm/transactionSender.js +0 -9
  75. package/dist/svm/utils.d.ts +0 -12
  76. package/dist/svm/utils.js +9 -60
  77. package/dist/test.d.ts +0 -4
  78. package/dist/test.js +15 -95
  79. package/dist/transaction-utils.d.ts +38 -0
  80. package/dist/transaction-utils.js +168 -0
  81. package/dist/types.d.ts +36 -0
  82. package/dist/types.js +0 -1
  83. package/dist/utils.js +0 -1
  84. package/dist/vm-validation.d.ts +11 -0
  85. package/dist/vm-validation.js +151 -0
  86. package/dist/vm.d.ts +14 -16
  87. package/dist/vm.js +64 -53
  88. package/dist/walletBip32.d.ts +2 -0
  89. package/dist/walletBip32.js +33 -66
  90. package/package.json +9 -4
  91. package/test-discovery.ts +235 -0
  92. package/test-pocket-discovery.ts +84 -0
  93. package/tsconfig.json +18 -11
  94. package/tsconfig.prod.json +10 -0
  95. package/utils/IChainWallet.ts +2 -0
  96. package/utils/evm/evm.ts +560 -39
  97. package/utils/rate-limiter.ts +179 -0
  98. package/utils/retry-logic.ts +271 -0
  99. package/utils/savings/EXAMPLES.md +883 -0
  100. package/utils/savings/SECURITY.md +731 -0
  101. package/utils/savings/index.ts +1 -1
  102. package/utils/savings/saving-manager.ts +656 -0
  103. package/utils/savings/savings-operations.ts +509 -0
  104. package/utils/savings/validation.ts +187 -0
  105. package/utils/svm/svm.ts +467 -20
  106. package/utils/test.ts +26 -3
  107. package/utils/transaction-utils.ts +394 -0
  108. package/utils/types.ts +100 -0
  109. package/utils/vm-validation.ts +280 -0
  110. package/utils/vm.ts +202 -24
  111. package/utils/walletBip32.ts +63 -3
  112. package/dist/IChainWallet.js.map +0 -1
  113. package/dist/bip32.d.ts +0 -9
  114. package/dist/bip32.js +0 -172
  115. package/dist/bip32.js.map +0 -1
  116. package/dist/bip32Old.js.map +0 -1
  117. package/dist/bip32Small.js.map +0 -1
  118. package/dist/bipTest.js.map +0 -1
  119. package/dist/constant.js.map +0 -1
  120. package/dist/english.js.map +0 -1
  121. package/dist/evm/SMART_WALLET_EXAMPLES.d.ts +0 -20
  122. package/dist/evm/SMART_WALLET_EXAMPLES.js +0 -451
  123. package/dist/evm/SMART_WALLET_EXAMPLES.js.map +0 -1
  124. package/dist/evm/aa-service/index.js.map +0 -1
  125. package/dist/evm/aa-service/lib/account-adapter.js.map +0 -1
  126. package/dist/evm/aa-service/lib/kernel-account.js.map +0 -1
  127. package/dist/evm/aa-service/lib/kernel-modules.js.map +0 -1
  128. package/dist/evm/aa-service/lib/session-keys.js.map +0 -1
  129. package/dist/evm/aa-service/lib/type.js.map +0 -1
  130. package/dist/evm/aa-service/services/account-abstraction.js.map +0 -1
  131. package/dist/evm/aa-service/services/bundler.js.map +0 -1
  132. package/dist/evm/evm.js.map +0 -1
  133. package/dist/evm/index.js.map +0 -1
  134. package/dist/evm/script.js.map +0 -1
  135. package/dist/evm/smartWallet.js.map +0 -1
  136. package/dist/evm/smartWallet.types.js.map +0 -1
  137. package/dist/evm/transaction.utils.js.map +0 -1
  138. package/dist/evm/transactionParsing.js.map +0 -1
  139. package/dist/evm/utils.js.map +0 -1
  140. package/dist/helpers/index.js.map +0 -1
  141. package/dist/helpers/routeScan.js.map +0 -1
  142. package/dist/index.js.map +0 -1
  143. package/dist/old.js.map +0 -1
  144. package/dist/price.js.map +0 -1
  145. package/dist/price.types.js.map +0 -1
  146. package/dist/privacy/artifact-manager.d.ts +0 -117
  147. package/dist/privacy/artifact-manager.js +0 -251
  148. package/dist/privacy/artifact-manager.js.map +0 -1
  149. package/dist/privacy/broadcaster-client.d.ts +0 -166
  150. package/dist/privacy/broadcaster-client.js +0 -261
  151. package/dist/privacy/broadcaster-client.js.map +0 -1
  152. package/dist/privacy/index.d.ts +0 -34
  153. package/dist/privacy/index.js +0 -56
  154. package/dist/privacy/index.js.map +0 -1
  155. package/dist/privacy/network-config.d.ts +0 -57
  156. package/dist/privacy/network-config.js +0 -118
  157. package/dist/privacy/network-config.js.map +0 -1
  158. package/dist/privacy/poi-helper.d.ts +0 -161
  159. package/dist/privacy/poi-helper.js +0 -249
  160. package/dist/privacy/poi-helper.js.map +0 -1
  161. package/dist/privacy/railgun-engine.d.ts +0 -135
  162. package/dist/privacy/railgun-engine.js +0 -205
  163. package/dist/privacy/railgun-engine.js.map +0 -1
  164. package/dist/privacy/railgun-privacy-wallet.d.ts +0 -288
  165. package/dist/privacy/railgun-privacy-wallet.js +0 -539
  166. package/dist/privacy/railgun-privacy-wallet.js.map +0 -1
  167. package/dist/privacy/types.d.ts +0 -229
  168. package/dist/privacy/types.js +0 -26
  169. package/dist/privacy/types.js.map +0 -1
  170. package/dist/savings/index.js.map +0 -1
  171. package/dist/savings/saving-actions.d.ts +0 -0
  172. package/dist/savings/saving-actions.js +0 -78
  173. package/dist/savings/saving-actions.js.map +0 -1
  174. package/dist/savings/savings-manager.d.ts +0 -126
  175. package/dist/savings/savings-manager.js +0 -234
  176. package/dist/savings/savings-manager.js.map +0 -1
  177. package/dist/savings/smart-savings.js.map +0 -1
  178. package/dist/savings/types.js.map +0 -1
  179. package/dist/svm/constant.js.map +0 -1
  180. package/dist/svm/index.js.map +0 -1
  181. package/dist/svm/svm.js.map +0 -1
  182. package/dist/svm/transactionParsing.js.map +0 -1
  183. package/dist/svm/transactionSender.js.map +0 -1
  184. package/dist/svm/utils.js.map +0 -1
  185. package/dist/test.js.map +0 -1
  186. package/dist/types.js.map +0 -1
  187. package/dist/utils.js.map +0 -1
  188. package/dist/vm.js.map +0 -1
  189. package/dist/walletBip32.js.map +0 -1
  190. package/utils/savings/saving-actions.ts +0 -92
  191. package/utils/savings/savings-manager.ts +0 -271
@@ -0,0 +1,883 @@
1
+ # Savings Feature Usage Examples
2
+
3
+ This document provides practical examples for implementing the savings feature in different types of wallet applications.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Browser Extension Example](#browser-extension-example)
8
+ 2. [React Native Mobile App Example](#react-native-mobile-app-example)
9
+ 3. [Common Operations](#common-operations)
10
+ 4. [Error Handling](#error-handling)
11
+ 5. [Testing](#testing)
12
+
13
+ ## Browser Extension Example
14
+
15
+ Complete implementation for a Chrome extension using stateless operations.
16
+
17
+ ### Background Script (background.js)
18
+
19
+ ```typescript
20
+ import { SavingsOperations } from '@wallet/utils/savings/savings-operations';
21
+ import { JsonRpcProvider } from 'ethers';
22
+
23
+ // Stateless operations - no mnemonic stored
24
+ const operations = new SavingsOperations();
25
+
26
+ // Session management
27
+ class ExtensionWalletSession {
28
+ private password: string | null = null;
29
+ private lockTimeout?: NodeJS.Timeout;
30
+ private readonly AUTO_LOCK_DELAY = 15 * 60 * 1000; // 15 minutes
31
+
32
+ async unlock(password: string): Promise<boolean> {
33
+ try {
34
+ // Verify password by attempting to decrypt
35
+ await this.decryptMnemonic(password);
36
+
37
+ // Store password for session
38
+ this.password = password;
39
+
40
+ // Set auto-lock timer
41
+ this.resetLockTimer();
42
+
43
+ return true;
44
+ } catch (error) {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ lock(): void {
50
+ this.password = null;
51
+ if (this.lockTimeout) {
52
+ clearTimeout(this.lockTimeout);
53
+ }
54
+ }
55
+
56
+ isUnlocked(): boolean {
57
+ return this.password !== null;
58
+ }
59
+
60
+ private resetLockTimer(): void {
61
+ if (this.lockTimeout) {
62
+ clearTimeout(this.lockTimeout);
63
+ }
64
+
65
+ this.lockTimeout = setTimeout(() => {
66
+ this.lock();
67
+ // Notify popup that wallet is locked
68
+ chrome.runtime.sendMessage({ type: 'WALLET_LOCKED' });
69
+ }, this.AUTO_LOCK_DELAY);
70
+ }
71
+
72
+ private async decryptMnemonic(password: string): Promise<string> {
73
+ const { encryptedMnemonic } = await chrome.storage.local.get('encryptedMnemonic');
74
+
75
+ if (!encryptedMnemonic) {
76
+ throw new Error('No wallet found');
77
+ }
78
+
79
+ // Decrypt using Web Crypto API
80
+ const key = await this.deriveKey(password);
81
+ const decrypted = await this.decrypt(encryptedMnemonic, key);
82
+
83
+ return decrypted;
84
+ }
85
+
86
+ private async deriveKey(password: string): Promise<CryptoKey> {
87
+ const encoder = new TextEncoder();
88
+ const passwordBuffer = encoder.encode(password);
89
+
90
+ // Import password as key material
91
+ const keyMaterial = await crypto.subtle.importKey(
92
+ 'raw',
93
+ passwordBuffer,
94
+ 'PBKDF2',
95
+ false,
96
+ ['deriveKey']
97
+ );
98
+
99
+ // Get salt from storage (generated during wallet creation)
100
+ const { salt } = await chrome.storage.local.get('salt');
101
+
102
+ // Derive encryption key using PBKDF2
103
+ return await crypto.subtle.deriveKey(
104
+ {
105
+ name: 'PBKDF2',
106
+ salt: new Uint8Array(salt),
107
+ iterations: 100000,
108
+ hash: 'SHA-256'
109
+ },
110
+ keyMaterial,
111
+ { name: 'AES-GCM', length: 256 },
112
+ false,
113
+ ['decrypt']
114
+ );
115
+ }
116
+
117
+ private async decrypt(encryptedData: ArrayBuffer, key: CryptoKey): Promise<string> {
118
+ const decoder = new TextDecoder();
119
+ const decrypted = await crypto.subtle.decrypt(
120
+ { name: 'AES-GCM', iv: new Uint8Array(encryptedData.slice(0, 12)) },
121
+ key,
122
+ encryptedData.slice(12)
123
+ );
124
+
125
+ return decoder.decode(decrypted);
126
+ }
127
+
128
+ async executeOperation<T>(
129
+ operation: (mnemonic: string) => Promise<T>
130
+ ): Promise<T> {
131
+ if (!this.password) {
132
+ throw new Error('Wallet locked');
133
+ }
134
+
135
+ // Reset timeout on each operation
136
+ this.resetLockTimer();
137
+
138
+ // Get mnemonic for this operation only
139
+ const mnemonic = await this.decryptMnemonic(this.password);
140
+
141
+ try {
142
+ return await operation(mnemonic);
143
+ } finally {
144
+ // Mnemonic goes out of scope and will be garbage collected
145
+ }
146
+ }
147
+ }
148
+
149
+ const session = new ExtensionWalletSession();
150
+
151
+ // Message handlers
152
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
153
+ (async () => {
154
+ try {
155
+ switch (message.type) {
156
+ case 'UNLOCK_WALLET':
157
+ const unlocked = await session.unlock(message.password);
158
+ sendResponse({ success: unlocked });
159
+ break;
160
+
161
+ case 'LOCK_WALLET':
162
+ session.lock();
163
+ sendResponse({ success: true });
164
+ break;
165
+
166
+ case 'GET_POCKET_BALANCE':
167
+ const balance = await session.executeOperation(async (mnemonic) => {
168
+ const provider = new JsonRpcProvider(message.rpcUrl);
169
+ return await operations.getPocketBalance(
170
+ mnemonic,
171
+ {
172
+ accountIndex: message.pocketIndex,
173
+ walletIndex: 0
174
+ },
175
+ provider
176
+ );
177
+ });
178
+ sendResponse({ success: true, balance });
179
+ break;
180
+
181
+ case 'TRANSFER_FROM_POCKET':
182
+ const result = await session.executeOperation(async (mnemonic) => {
183
+ const provider = new JsonRpcProvider(message.rpcUrl);
184
+ return await operations.transferFromPocket(
185
+ mnemonic,
186
+ {
187
+ accountIndex: message.pocketIndex,
188
+ walletIndex: 0,
189
+ to: message.to,
190
+ amount: BigInt(message.amount)
191
+ },
192
+ provider,
193
+ message.chain
194
+ );
195
+ });
196
+ sendResponse({ success: true, result });
197
+ break;
198
+
199
+ case 'GET_POCKET_ADDRESS':
200
+ const address = await session.executeOperation(async (mnemonic) => {
201
+ return operations.getPocketAddress(mnemonic, message.pocketIndex, 0);
202
+ });
203
+ sendResponse({ success: true, address });
204
+ break;
205
+
206
+ default:
207
+ sendResponse({ success: false, error: 'Unknown message type' });
208
+ }
209
+ } catch (error) {
210
+ sendResponse({ success: false, error: error.message });
211
+ }
212
+ })();
213
+
214
+ return true; // Keep channel open for async response
215
+ });
216
+ ```
217
+
218
+ ### Popup Script (popup.tsx)
219
+
220
+ ```typescript
221
+ import React, { useState, useEffect } from 'react';
222
+ import { formatUnits } from 'ethers';
223
+
224
+ function PocketView() {
225
+ const [pockets, setPockets] = useState<Array<{ index: number; balance: string }>>([]);
226
+ const [loading, setLoading] = useState(false);
227
+
228
+ useEffect(() => {
229
+ loadPockets();
230
+ }, []);
231
+
232
+ async function loadPockets() {
233
+ setLoading(true);
234
+ try {
235
+ // Load balances for pockets 0-4
236
+ const promises = [0, 1, 2, 3, 4].map(async (index) => {
237
+ const response = await chrome.runtime.sendMessage({
238
+ type: 'GET_POCKET_BALANCE',
239
+ pocketIndex: index,
240
+ rpcUrl: 'https://eth.llamarpc.com'
241
+ });
242
+
243
+ if (response.success) {
244
+ return {
245
+ index,
246
+ balance: formatUnits(response.balance, 18)
247
+ };
248
+ }
249
+ return null;
250
+ });
251
+
252
+ const results = await Promise.all(promises);
253
+ setPockets(results.filter(p => p !== null && parseFloat(p.balance) > 0));
254
+ } catch (error) {
255
+ console.error('Failed to load pockets:', error);
256
+ } finally {
257
+ setLoading(false);
258
+ }
259
+ }
260
+
261
+ async function transferFromPocket(pocketIndex: number, to: string, amount: string) {
262
+ setLoading(true);
263
+ try {
264
+ const response = await chrome.runtime.sendMessage({
265
+ type: 'TRANSFER_FROM_POCKET',
266
+ pocketIndex,
267
+ to,
268
+ amount: parseUnits(amount, 18).toString(),
269
+ rpcUrl: 'https://eth.llamarpc.com',
270
+ chain: {
271
+ chainId: 1,
272
+ name: 'Ethereum',
273
+ rpcUrl: 'https://eth.llamarpc.com'
274
+ }
275
+ });
276
+
277
+ if (response.success) {
278
+ alert(`Transaction sent: ${response.result.hash}`);
279
+ loadPockets(); // Reload balances
280
+ } else {
281
+ alert(`Error: ${response.error}`);
282
+ }
283
+ } catch (error) {
284
+ alert(`Error: ${error.message}`);
285
+ } finally {
286
+ setLoading(false);
287
+ }
288
+ }
289
+
290
+ return (
291
+ <div className="pocket-view">
292
+ <h2>Savings Pockets</h2>
293
+
294
+ {loading && <p>Loading...</p>}
295
+
296
+ <div className="pockets-list">
297
+ {pockets.map(pocket => (
298
+ <div key={pocket.index} className="pocket-item">
299
+ <h3>Pocket {pocket.index}</h3>
300
+ <p>Balance: {pocket.balance} ETH</p>
301
+ <button onClick={() => {
302
+ const to = prompt('Recipient address:');
303
+ const amount = prompt('Amount (ETH):');
304
+ if (to && amount) {
305
+ transferFromPocket(pocket.index, to, amount);
306
+ }
307
+ }}>
308
+ Withdraw
309
+ </button>
310
+ </div>
311
+ ))}
312
+ </div>
313
+ </div>
314
+ );
315
+ }
316
+
317
+ export default PocketView;
318
+ ```
319
+
320
+ ## React Native Mobile App Example
321
+
322
+ Complete implementation for a mobile app using stateful manager.
323
+
324
+ ### Wallet Service
325
+
326
+ ```typescript
327
+ import { SavingsManager } from '@wallet/utils/savings/saving-manager';
328
+ import { createWalletClient, createPublicClient, http } from 'viem';
329
+ import { mainnet } from 'viem/chains';
330
+ import * as Keychain from 'react-native-keychain';
331
+ import TouchID from 'react-native-touch-id';
332
+ import { AppState, AppStateStatus } from 'react-native';
333
+
334
+ class WalletService {
335
+ private manager: SavingsManager | null = null;
336
+ private lockTimer?: NodeJS.Timeout;
337
+ private readonly AUTO_LOCK_DELAY = 5 * 60 * 1000; // 5 minutes
338
+
339
+ constructor() {
340
+ // Listen for app state changes
341
+ AppState.addEventListener('change', this.handleAppStateChange);
342
+ }
343
+
344
+ /**
345
+ * Initialize wallet with biometric authentication
346
+ */
347
+ async initialize(): Promise<boolean> {
348
+ try {
349
+ // Require biometric authentication
350
+ await TouchID.authenticate('Unlock wallet', {
351
+ title: 'Authentication Required',
352
+ fallbackLabel: 'Use Passcode'
353
+ });
354
+
355
+ // Get mnemonic from secure storage
356
+ const credentials = await Keychain.getGenericPassword({
357
+ service: 'com.myapp.wallet'
358
+ });
359
+
360
+ if (!credentials) {
361
+ throw new Error('Wallet not found');
362
+ }
363
+
364
+ // Create manager
365
+ this.manager = new SavingsManager(
366
+ credentials.password,
367
+ {
368
+ chainId: 1,
369
+ name: 'Ethereum',
370
+ rpcUrl: 'https://eth.llamarpc.com'
371
+ },
372
+ 0 // wallet index
373
+ );
374
+
375
+ // Set auto-lock timer
376
+ this.resetLockTimer();
377
+
378
+ return true;
379
+ } catch (error) {
380
+ console.error('Failed to initialize wallet:', error);
381
+ return false;
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Lock wallet and clear sensitive data
387
+ */
388
+ lock(): void {
389
+ if (this.manager) {
390
+ this.manager.dispose();
391
+ this.manager = null;
392
+ }
393
+
394
+ if (this.lockTimer) {
395
+ clearTimeout(this.lockTimer);
396
+ }
397
+ }
398
+
399
+ isUnlocked(): boolean {
400
+ return this.manager !== null;
401
+ }
402
+
403
+ private resetLockTimer(): void {
404
+ if (this.lockTimer) {
405
+ clearTimeout(this.lockTimer);
406
+ }
407
+
408
+ this.lockTimer = setTimeout(() => {
409
+ this.lock();
410
+ }, this.AUTO_LOCK_DELAY);
411
+ }
412
+
413
+ private handleAppStateChange = (nextAppState: AppStateStatus) => {
414
+ if (nextAppState === 'background' || nextAppState === 'inactive') {
415
+ // Lock immediately when app backgrounds
416
+ this.lock();
417
+ }
418
+ };
419
+
420
+ /**
421
+ * Get manager instance (throws if locked)
422
+ */
423
+ getManager(): SavingsManager {
424
+ if (!this.manager) {
425
+ throw new Error('Wallet locked - please authenticate');
426
+ }
427
+
428
+ // Reset auto-lock timer on each access
429
+ this.resetLockTimer();
430
+
431
+ return this.manager;
432
+ }
433
+
434
+ /**
435
+ * Create a new savings pocket
436
+ */
437
+ async createPocket(pocketIndex: number): Promise<string> {
438
+ const manager = this.getManager();
439
+ const pocket = manager.getPocket(pocketIndex);
440
+ return pocket.address;
441
+ }
442
+
443
+ /**
444
+ * Get pocket balance
445
+ */
446
+ async getPocketBalance(pocketIndex: number, tokens: string[]): Promise<any> {
447
+ const manager = this.getManager();
448
+ return await manager.getPocketTokenBalance(tokens, pocketIndex);
449
+ }
450
+
451
+ /**
452
+ * Transfer to pocket
453
+ */
454
+ async transferToPocket(
455
+ walletClient: any,
456
+ pocketIndex: number,
457
+ amount: string
458
+ ): Promise<any> {
459
+ const manager = this.getManager();
460
+ return await manager.transferToPocket(walletClient, pocketIndex, amount);
461
+ }
462
+
463
+ /**
464
+ * Transfer from pocket back to main wallet
465
+ */
466
+ async transferFromPocket(
467
+ pocketIndex: number,
468
+ amount: number,
469
+ token: string = 'native'
470
+ ): Promise<any> {
471
+ const manager = this.getManager();
472
+ return await manager.sendToMainWallet(pocketIndex, amount, token as any);
473
+ }
474
+ }
475
+
476
+ export const walletService = new WalletService();
477
+ ```
478
+
479
+ ### React Component
480
+
481
+ ```typescript
482
+ import React, { useState, useEffect } from 'react';
483
+ import { View, Text, Button, FlatList, Alert } from 'react-native';
484
+ import { walletService } from './WalletService';
485
+
486
+ function PocketsScreen() {
487
+ const [pockets, setPockets] = useState<Array<{ index: number; address: string; balance: any }>>([]);
488
+ const [loading, setLoading] = useState(false);
489
+
490
+ useEffect(() => {
491
+ loadPockets();
492
+ }, []);
493
+
494
+ async function loadPockets() {
495
+ setLoading(true);
496
+ try {
497
+ // Check if wallet is unlocked
498
+ if (!walletService.isUnlocked()) {
499
+ const unlocked = await walletService.initialize();
500
+ if (!unlocked) {
501
+ Alert.alert('Error', 'Failed to unlock wallet');
502
+ return;
503
+ }
504
+ }
505
+
506
+ // Load pockets 0-4
507
+ const pocketData = await Promise.all(
508
+ [0, 1, 2, 3, 4].map(async (index) => {
509
+ try {
510
+ const address = await walletService.createPocket(index);
511
+ const balance = await walletService.getPocketBalance(index, []);
512
+
513
+ return { index, address, balance };
514
+ } catch (error) {
515
+ console.error(`Failed to load pocket ${index}:`, error);
516
+ return null;
517
+ }
518
+ })
519
+ );
520
+
521
+ setPockets(pocketData.filter(p => p !== null));
522
+ } catch (error) {
523
+ Alert.alert('Error', error.message);
524
+ } finally {
525
+ setLoading(false);
526
+ }
527
+ }
528
+
529
+ async function handleWithdraw(pocketIndex: number) {
530
+ Alert.prompt(
531
+ 'Withdraw from Pocket',
532
+ 'Enter amount (ETH):',
533
+ async (amount) => {
534
+ try {
535
+ setLoading(true);
536
+
537
+ const result = await walletService.transferFromPocket(
538
+ pocketIndex,
539
+ parseFloat(amount) * 1e18, // Convert to wei
540
+ 'native'
541
+ );
542
+
543
+ Alert.alert('Success', `Transaction: ${result.hash}`);
544
+ loadPockets(); // Reload
545
+ } catch (error) {
546
+ Alert.alert('Error', error.message);
547
+ } finally {
548
+ setLoading(false);
549
+ }
550
+ }
551
+ );
552
+ }
553
+
554
+ return (
555
+ <View style={{ padding: 20 }}>
556
+ <Text style={{ fontSize: 24, fontWeight: 'bold' }}>Savings Pockets</Text>
557
+
558
+ {loading && <Text>Loading...</Text>}
559
+
560
+ <FlatList
561
+ data={pockets}
562
+ keyExtractor={(item) => item.index.toString()}
563
+ renderItem={({ item }) => (
564
+ <View style={{ padding: 15, borderWidth: 1, marginVertical: 5 }}>
565
+ <Text style={{ fontWeight: 'bold' }}>Pocket {item.index}</Text>
566
+ <Text>Address: {item.address.slice(0, 10)}...</Text>
567
+ <Text>
568
+ Balance: {item.balance[0]?.balance.formatted || '0'} ETH
569
+ </Text>
570
+ <Button title="Withdraw" onPress={() => handleWithdraw(item.index)} />
571
+ </View>
572
+ )}
573
+ />
574
+
575
+ <Button title="Refresh" onPress={loadPockets} />
576
+ </View>
577
+ );
578
+ }
579
+
580
+ export default PocketsScreen;
581
+ ```
582
+
583
+ ## Common Operations
584
+
585
+ ### Creating a Wallet
586
+
587
+ ```typescript
588
+ import { SavingsManager, SavingsOperations } from '@wallet/utils/savings';
589
+ import { generateMnemonic } from 'bip39';
590
+
591
+ // Generate new mnemonic
592
+ const mnemonic = generateMnemonic();
593
+
594
+ // Encrypt and store securely (implementation depends on platform)
595
+ await secureStorage.storeMnemonic(mnemonic, password);
596
+
597
+ // Option 1: Stateful manager
598
+ const manager = new SavingsManager(mnemonic, chainConfig);
599
+
600
+ // Option 2: Stateless operations
601
+ const operations = new SavingsOperations();
602
+ ```
603
+
604
+ ### Getting Pocket Address
605
+
606
+ ```typescript
607
+ // Stateless
608
+ const address = operations.getPocketAddress(mnemonic, 1, 0);
609
+ console.log(`Pocket 1 address: ${address}`);
610
+
611
+ // Stateful
612
+ const pocket = manager.getPocket(1);
613
+ console.log(`Pocket 1 address: ${pocket.address}`);
614
+ ```
615
+
616
+ ### Checking Pocket Balance
617
+
618
+ ```typescript
619
+ import { formatUnits } from 'ethers';
620
+
621
+ // Stateless
622
+ const balance = await operations.getPocketBalance(
623
+ mnemonic,
624
+ { accountIndex: 1, walletIndex: 0 },
625
+ provider
626
+ );
627
+ console.log(`Balance: ${formatUnits(balance, 18)} ETH`);
628
+
629
+ // Stateful
630
+ const balances = await manager.getPocketTokenBalance(
631
+ ['0xTokenAddress'], // Token addresses to check
632
+ 1 // Pocket index
633
+ );
634
+ console.log(`Native: ${balances[0].balance.formatted}`);
635
+ ```
636
+
637
+ ### Transferring to Pocket
638
+
639
+ ```typescript
640
+ import { parseUnits } from 'ethers';
641
+ import { createWalletClient, http } from 'viem';
642
+ import { mainnet } from 'viem/chains';
643
+
644
+ // Create wallet client for main wallet
645
+ const walletClient = createWalletClient({
646
+ account: mainAccount,
647
+ chain: mainnet,
648
+ transport: http()
649
+ });
650
+
651
+ // Transfer to pocket (stateful only)
652
+ const result = await manager.transferToPocket(
653
+ walletClient,
654
+ 1, // Pocket index
655
+ '0.1' // Amount as string
656
+ );
657
+
658
+ console.log(`Transaction: ${result.hash}`);
659
+ ```
660
+
661
+ ### Transferring from Pocket
662
+
663
+ ```typescript
664
+ // Stateless
665
+ const result = await operations.transferFromPocket(
666
+ mnemonic,
667
+ {
668
+ accountIndex: 1,
669
+ walletIndex: 0,
670
+ to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
671
+ amount: parseUnits('0.1', 18)
672
+ },
673
+ provider,
674
+ chain
675
+ );
676
+
677
+ // Stateful
678
+ const result = await manager.sendToMainWallet(
679
+ 1, // Pocket index
680
+ 100000000000000000, // Amount in wei
681
+ 'native' // or token address
682
+ );
683
+ ```
684
+
685
+ ### Transferring Tokens from Pocket
686
+
687
+ ```typescript
688
+ // Stateless
689
+ const result = await operations.transferTokenFromPocket(
690
+ mnemonic,
691
+ {
692
+ accountIndex: 1,
693
+ walletIndex: 0,
694
+ tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
695
+ to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
696
+ amount: parseUnits('100', 6) // 100 USDC (6 decimals)
697
+ },
698
+ provider,
699
+ chain
700
+ );
701
+
702
+ // Stateful
703
+ const result = await manager.sendToMainWallet(
704
+ 1, // Pocket index
705
+ 100000000, // 100 USDC in base units
706
+ '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' // Token address
707
+ );
708
+ ```
709
+
710
+ ## Error Handling
711
+
712
+ ### Input Validation Errors
713
+
714
+ ```typescript
715
+ import { SavingsValidation } from '@wallet/utils/savings/validation';
716
+
717
+ try {
718
+ // This will throw if invalid
719
+ SavingsValidation.validateAddress('invalid', 'Recipient');
720
+ SavingsValidation.validateAmount(0n, 'Transfer amount');
721
+ } catch (error) {
722
+ // Handle validation error
723
+ console.error('Validation failed:', error.message);
724
+ // Show user-friendly message
725
+ alert(`Invalid input: ${error.message}`);
726
+ }
727
+ ```
728
+
729
+ ### Transaction Errors
730
+
731
+ ```typescript
732
+ try {
733
+ const result = await operations.transferFromPocket(
734
+ mnemonic,
735
+ options,
736
+ provider,
737
+ chain
738
+ );
739
+
740
+ if (!result.status) {
741
+ throw new Error('Transaction failed');
742
+ }
743
+
744
+ console.log('Success:', result.hash);
745
+ } catch (error) {
746
+ if (error.message.includes('insufficient funds')) {
747
+ alert('Insufficient balance in pocket');
748
+ } else if (error.message.includes('user rejected')) {
749
+ alert('Transaction cancelled');
750
+ } else {
751
+ alert(`Transaction failed: ${error.message}`);
752
+ }
753
+ }
754
+ ```
755
+
756
+ ### Authentication Errors
757
+
758
+ ```typescript
759
+ try {
760
+ await walletService.initialize();
761
+ } catch (error) {
762
+ if (error.message.includes('authentication failed')) {
763
+ alert('Biometric authentication failed. Please try again.');
764
+ } else if (error.message.includes('not found')) {
765
+ // No wallet exists - show onboarding
766
+ navigation.navigate('CreateWallet');
767
+ } else {
768
+ alert(`Error: ${error.message}`);
769
+ }
770
+ }
771
+ ```
772
+
773
+ ## Testing
774
+
775
+ ### Unit Tests
776
+
777
+ ```typescript
778
+ import { SavingsOperations } from '@wallet/utils/savings/savings-operations';
779
+ import { SavingsValidation } from '@wallet/utils/savings/validation';
780
+
781
+ describe('Savings Operations', () => {
782
+ const mnemonic = 'test mnemonic phrase...';
783
+ const operations = new SavingsOperations();
784
+
785
+ it('should derive consistent addresses', () => {
786
+ const address1 = operations.getPocketAddress(mnemonic, 0, 0);
787
+ const address2 = operations.getPocketAddress(mnemonic, 0, 0);
788
+
789
+ expect(address1).toBe(address2);
790
+ expect(address1).toMatch(/^0x[a-fA-F0-9]{40}$/);
791
+ });
792
+
793
+ it('should validate addresses', () => {
794
+ expect(() => {
795
+ SavingsValidation.validateAddress('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb');
796
+ }).not.toThrow();
797
+
798
+ expect(() => {
799
+ SavingsValidation.validateAddress('invalid');
800
+ }).toThrow();
801
+ });
802
+
803
+ it('should validate amounts', () => {
804
+ expect(() => {
805
+ SavingsValidation.validateAmount(1000n);
806
+ }).not.toThrow();
807
+
808
+ expect(() => {
809
+ SavingsValidation.validateAmount(0n);
810
+ }).toThrow('must be positive');
811
+ });
812
+ });
813
+ ```
814
+
815
+ ### Integration Tests
816
+
817
+ ```typescript
818
+ import { SavingsManager } from '@wallet/utils/savings/saving-manager';
819
+ import { JsonRpcProvider } from 'ethers';
820
+
821
+ describe('Savings Manager Integration', () => {
822
+ let manager: SavingsManager;
823
+ const testMnemonic = 'test test test test test test test test test test test junk';
824
+
825
+ beforeEach(() => {
826
+ manager = new SavingsManager(
827
+ testMnemonic,
828
+ {
829
+ chainId: 1,
830
+ name: 'Ethereum',
831
+ rpcUrl: 'https://eth.llamarpc.com'
832
+ },
833
+ 0
834
+ );
835
+ });
836
+
837
+ afterEach(() => {
838
+ manager.dispose();
839
+ });
840
+
841
+ it('should create pockets with unique addresses', () => {
842
+ const pocket0 = manager.getPocket(0);
843
+ const pocket1 = manager.getPocket(1);
844
+
845
+ expect(pocket0.address).not.toBe(pocket1.address);
846
+ expect(pocket0.derivationPath).not.toBe(pocket1.derivationPath);
847
+ });
848
+
849
+ it('should verify pocket addresses', () => {
850
+ const pocket = manager.getPocket(0);
851
+ const isValid = manager.verifyPocketAddress(0, pocket.address);
852
+
853
+ expect(isValid).toBe(true);
854
+ });
855
+
856
+ it('should clear sensitive data on dispose', () => {
857
+ manager.dispose();
858
+
859
+ // Should throw after disposal
860
+ expect(() => manager.getPocket(0)).toThrow();
861
+ });
862
+ });
863
+ ```
864
+
865
+ ## Best Practices Summary
866
+
867
+ 1. **Use Stateless for Extensions**: Browser extensions benefit from stateless operations due to background script lifecycle.
868
+
869
+ 2. **Use Stateful for Mobile**: Mobile apps can safely use stateful managers with proper lifecycle management.
870
+
871
+ 3. **Always Authenticate**: Require biometric/password authentication before sensitive operations.
872
+
873
+ 4. **Handle App Lifecycle**: Clear sensitive data when app backgrounds or closes.
874
+
875
+ 5. **Validate Inputs**: Use provided validation utilities before operations.
876
+
877
+ 6. **Handle Errors Gracefully**: Provide user-friendly error messages.
878
+
879
+ 7. **Test Thoroughly**: Write unit and integration tests for security features.
880
+
881
+ 8. **Educate Users**: Provide clear instructions and warnings about security.
882
+
883
+ For more information, see [SECURITY.md](./SECURITY.md) for complete security guidance.