@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
@@ -1 +1 @@
1
- // export * from "./saving-actions"
1
+ export * from "./saving-manager"
@@ -0,0 +1,656 @@
1
+ import { EVMDeriveChildPrivateKey, GenerateSeed, mnemonicToSeed } from "../walletBip32";
2
+ import { ethers } from "ethers";
3
+ import { WalletClient, PublicClient, Hex, Chain, createWalletClient, createPublicClient, http } from "viem";
4
+ import { } from "../utils";
5
+ import { Balance, ChainWalletConfig, TransactionResult } from "../types";
6
+ import { fromChainToViemChain, getNativeBalance, getTokenBalance, sendERC20Token, sendNativeToken } from "../evm";
7
+ import { fetchPrices } from "../price";
8
+ import { Account, privateKeyToAccount } from "viem/accounts";
9
+ import { SavingsValidation } from "./validation";
10
+
11
+ /**
12
+ * Base class for managing multi-pocket savings accounts for EVM wallets
13
+ *
14
+ * Provides core functionality for creating and managing savings pockets derived from a master mnemonic.
15
+ * Each pocket is a separate wallet address derived using BIP-44 paths, allowing for isolated savings accounts.
16
+ *
17
+ * @remarks
18
+ * - Pockets are derived using BIP-44 path: `m/44'/60'/{pocketIndex}'/0/{walletIndex}`
19
+ * - Pocket index 0 is reserved for the main wallet
20
+ * - All pocket addresses are deterministically derived from the mnemonic
21
+ * - Supports optional master address for scenarios where the savings manager is created from a derived key
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const manager = new BaseSavingsManager(
26
+ * 'your twelve word mnemonic phrase here...',
27
+ * 0, // wallet index
28
+ * chainConfig,
29
+ * '0x...' // optional master address
30
+ * );
31
+ *
32
+ * // Get a pocket
33
+ * const pocket = manager.getPocket(1);
34
+ * console.log(pocket.address); // Derived pocket address
35
+ * ```
36
+ */
37
+ export class BaseSavingsManager {
38
+ private mnemonic: string;
39
+ private walletIndex: number;
40
+ chain: ChainWalletConfig;
41
+ private _client?: PublicClient;
42
+ private pockets: Map<number, { privateKey: string; address: string; derivationPath: string, index: number }> = new Map();
43
+ masterAddress: Hex | undefined
44
+
45
+ /**
46
+ * Creates a new BaseSavingsManager instance
47
+ *
48
+ * @param mnemonic - BIP-39 mnemonic phrase used to derive all wallet addresses
49
+ * @param walletIndex - Wallet index in the derivation path (default: 0)
50
+ * @param chain - Chain configuration containing RPC URL and chain details
51
+ * @param masterAddress - Optional master wallet address (used when manager is created from derived key)
52
+ * @throws Error if validation fails
53
+ *
54
+ * @remarks
55
+ * RPC client is created on-demand to support browser extension scenarios
56
+ * where background scripts need to sleep
57
+ */
58
+ constructor(mnemonic: string, walletIndex: number = 0, chain: ChainWalletConfig, masterAddress?: Hex) {
59
+ // Validate inputs
60
+ SavingsValidation.validateMnemonic(mnemonic);
61
+ SavingsValidation.validateWalletIndex(walletIndex);
62
+ SavingsValidation.validateChainId(chain.chainId);
63
+
64
+ if (masterAddress) {
65
+ SavingsValidation.validateAddress(masterAddress, 'Master address');
66
+ }
67
+
68
+ this.mnemonic = mnemonic;
69
+ this.chain = chain
70
+ this.walletIndex = walletIndex;
71
+ if (masterAddress) {
72
+ this.masterAddress = masterAddress
73
+ }
74
+ // Client is created on-demand via getter
75
+ }
76
+
77
+ /**
78
+ * Gets or creates the RPC client on-demand
79
+ *
80
+ * Creates a new PublicClient if one doesn't exist. This allows the client
81
+ * to be garbage collected between operations, which is important for
82
+ * browser extensions where background scripts need to sleep.
83
+ *
84
+ * @returns PublicClient instance for making RPC calls
85
+ *
86
+ * @remarks
87
+ * The client is lazily initialized and cached until disposed.
88
+ * Call `dispose()` or `clearClient()` to release the client.
89
+ */
90
+ get client(): PublicClient {
91
+ if (!this._client) {
92
+ this._client = createPublicClient({
93
+ chain: fromChainToViemChain(this.chain),
94
+ transport: http(this.chain.rpcUrl)
95
+ });
96
+ }
97
+ return this._client;
98
+ }
99
+
100
+ /**
101
+ * Clears the cached RPC client
102
+ *
103
+ * Releases the RPC client so it can be garbage collected.
104
+ * Useful for browser extensions where background scripts need to sleep.
105
+ *
106
+ * @remarks
107
+ * The client will be recreated on next access if needed.
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * // After completing operations
112
+ * manager.clearClient();
113
+ * ```
114
+ */
115
+ clearClient(): void {
116
+ this._client = undefined;
117
+ }
118
+
119
+
120
+
121
+ /**
122
+ * Derives a savings pocket at the specified account index
123
+ *
124
+ * Creates a deterministic wallet address for a savings pocket using BIP-44 derivation.
125
+ * The actual derivation uses `accountIndex + 1` to preserve index 0 for the main wallet.
126
+ *
127
+ * @param accountIndex - The account index for the pocket (0-based, gets incremented internally)
128
+ * @returns Pocket object containing privateKey, address, derivationPath, and index
129
+ *
130
+ * @remarks
131
+ * - Uses derivation path: `m/44'/60'/{accountIndex + 1}'/0/{walletIndex}`
132
+ * - Cached in memory after first derivation
133
+ * - Private method, use `getPocket()` for public access
134
+ *
135
+ * @throws Error if validation fails
136
+ * @private
137
+ */
138
+ private derivePocket(accountIndex: number) {
139
+ // Validate account index
140
+ SavingsValidation.validateAccountIndex(accountIndex);
141
+
142
+ //? for the sake of derivation we will add one to the index of the pocket that was passed so as to preserve the index 0 as the main wallet index
143
+ const pocketIndex = accountIndex + 1
144
+ const derivationPath = `m/44'/60'/${pocketIndex}'/0/${this.walletIndex}`;
145
+ const { privateKey } = EVMDeriveChildPrivateKey(mnemonicToSeed(this.mnemonic), this.walletIndex, derivationPath);
146
+ const wallet = new ethers.Wallet(privateKey);
147
+ const pocket = { privateKey, address: wallet.address, derivationPath, index: pocketIndex };
148
+ this.pockets.set(pocketIndex, pocket);
149
+ return pocket;
150
+ }
151
+
152
+ /**
153
+ * Gets the main wallet credentials
154
+ *
155
+ * Derives the main wallet using BIP-44 path at account index 0.
156
+ *
157
+ * @returns Object containing privateKey, address, and derivationPath of the main wallet
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * const mainWallet = manager.getMainWallet();
162
+ * console.log(mainWallet.address); // Main wallet address
163
+ * console.log(mainWallet.derivationPath); // m/44'/60'/0'/0/0
164
+ * ```
165
+ */
166
+ getMainWallet() {
167
+ const mainWalletDerivationPath = `m/44'/60'/0'/0/${this.walletIndex}`;
168
+ const { privateKey } = EVMDeriveChildPrivateKey(this.mnemonic, this.walletIndex, mainWalletDerivationPath);
169
+ const wallet = new ethers.Wallet(privateKey);
170
+ return { privateKey, address: wallet.address, derivationPath: mainWalletDerivationPath };
171
+ }
172
+
173
+ /**
174
+ * Gets the main wallet address
175
+ *
176
+ * Returns the master address if provided during construction, otherwise derives it from the mnemonic.
177
+ *
178
+ * @returns The main wallet address as a Hex string
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * const address = manager.getMainWalletAddress();
183
+ * console.log(address); // 0x...
184
+ * ```
185
+ */
186
+ getMainWalletAddress(): Hex {
187
+ if (this.masterAddress) return this.masterAddress;
188
+ const { privateKey } = this.getMainWallet()
189
+ return new ethers.Wallet(privateKey).address as Hex
190
+ }
191
+
192
+ /**
193
+ * Gets or creates a savings pocket at the specified index
194
+ *
195
+ * Retrieves a cached pocket if it exists, otherwise derives a new one.
196
+ * This is the primary method for accessing savings pockets.
197
+ *
198
+ * @param accountIndex - The pocket index (0-based)
199
+ * @returns Pocket object containing privateKey, address, derivationPath, and index
200
+ *
201
+ * @throws Error if validation fails
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * // Get first savings pocket
206
+ * const pocket1 = manager.getPocket(0);
207
+ * console.log(pocket1.address); // Pocket address
208
+ *
209
+ * // Get second savings pocket
210
+ * const pocket2 = manager.getPocket(1);
211
+ * console.log(pocket2.derivationPath); // m/44'/60'/2'/0/0
212
+ * ```
213
+ */
214
+ getPocket(accountIndex: number) {
215
+ // Validation is done in derivePocket
216
+ if (!this.pockets.has(accountIndex)) {
217
+ return this.derivePocket(accountIndex);
218
+ }
219
+ return this.pockets.get(accountIndex)!;
220
+ }
221
+ /**
222
+ * Transfers native tokens from the main wallet to a savings pocket
223
+ *
224
+ * Sends the native blockchain token (e.g., ETH, MATIC, BNB) to the specified pocket address.
225
+ *
226
+ * @param mainWallet - Viem WalletClient instance for the main wallet
227
+ * @param pocketIndex - Index of the destination pocket
228
+ * @param amount - Amount to transfer as a string
229
+ * @returns Transaction result containing hash and success status
230
+ *
231
+ * @throws Error if validation fails
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * const result = await manager.transferToPocket(
236
+ * walletClient,
237
+ * 0, // First savings pocket
238
+ * '0.1' // 0.1 ETH
239
+ * );
240
+ * console.log(result.hash); // Transaction hash
241
+ * ```
242
+ */
243
+ async transferToPocket(
244
+ mainWallet: WalletClient,
245
+ pocketIndex: number,
246
+ amount: string
247
+ ): Promise<TransactionResult> {
248
+ // Validate inputs
249
+ SavingsValidation.validateAccountIndex(pocketIndex);
250
+ SavingsValidation.validateAmountString(amount, 'Transfer amount');
251
+
252
+ const pocket = this.getPocket(pocketIndex);
253
+ return await sendNativeToken(mainWallet, this.client, pocket.address as Hex, amount, 5);
254
+ }
255
+
256
+ /**
257
+ * Transfers ERC-20 tokens from the main wallet to a savings pocket
258
+ *
259
+ * Sends ERC-20 tokens to the specified pocket address.
260
+ *
261
+ * @param mainWallet - Viem WalletClient instance for the main wallet
262
+ * @param tokenAddress - Contract address of the ERC-20 token
263
+ * @param pocketIndex - Index of the destination pocket
264
+ * @param amount - Amount to transfer in token's base units (e.g., wei for 18 decimal tokens)
265
+ * @returns Transaction result containing hash and success status
266
+ *
267
+ * @throws Error if validation fails
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * const result = await manager.transferTokenToPocket(
272
+ * walletClient,
273
+ * '0x...', // USDC contract address
274
+ * 0, // First savings pocket
275
+ * 1000000n // 1 USDC (6 decimals)
276
+ * );
277
+ * console.log(result.hash);
278
+ * ```
279
+ */
280
+ async transferTokenToPocket(
281
+ mainWallet: WalletClient,
282
+ tokenAddress: string,
283
+ pocketIndex: number,
284
+ amount: bigint
285
+ ): Promise<TransactionResult> {
286
+ // Validate inputs
287
+ SavingsValidation.validateAddress(tokenAddress, 'Token address');
288
+ SavingsValidation.validateAccountIndex(pocketIndex);
289
+ SavingsValidation.validateAmount(amount, 'Transfer amount');
290
+
291
+ const pocket = this.getPocket(pocketIndex);
292
+ return await sendERC20Token(mainWallet, this.client, tokenAddress as Hex, pocket.address as Hex, amount, 5);
293
+ }
294
+
295
+ /**
296
+ * Verifies that a stored pocket address matches the derived address
297
+ *
298
+ * Security check to ensure the stored address hasn't been tampered with.
299
+ * Always derives the address fresh from the mnemonic for comparison.
300
+ *
301
+ * @param accountIndex - Index of the pocket to verify
302
+ * @param storedAddress - The address to verify against
303
+ * @returns true if addresses match (case-insensitive), false otherwise
304
+ *
305
+ * @throws Error if validation fails
306
+ *
307
+ * @example
308
+ * ```typescript
309
+ * const isValid = manager.verifyPocketAddress(0, '0x...');
310
+ * if (!isValid) {
311
+ * console.error('Address mismatch! Possible tampering detected.');
312
+ * }
313
+ * ```
314
+ */
315
+ verifyPocketAddress(accountIndex: number, storedAddress: string) {
316
+ // Validate inputs
317
+ SavingsValidation.validateAccountIndex(accountIndex);
318
+ SavingsValidation.validateAddress(storedAddress, 'Stored address');
319
+
320
+ const pocket = this.getPocket(accountIndex);
321
+ return pocket.address.toLowerCase() === storedAddress.toLowerCase();
322
+ }
323
+
324
+ /**
325
+ * Creates a Viem Account instance from a pocket's private key
326
+ *
327
+ * Converts the pocket's private key into a Viem Account object for use with Viem WalletClients.
328
+ *
329
+ * @param p - Pocket index
330
+ * @returns Viem Account instance for the pocket
331
+ *
332
+ * @throws Error if validation fails
333
+ *
334
+ * @example
335
+ * ```typescript
336
+ * const pocketAccount = manager.accountFromPocketId(0);
337
+ * const walletClient = createWalletClient({
338
+ * account: pocketAccount,
339
+ * chain: mainnet,
340
+ * transport: http()
341
+ * });
342
+ * ```
343
+ */
344
+ accountFromPocketId(p: number): Account {
345
+ // Validation is done in derivePocket
346
+ return privateKeyToAccount(`0x${this.derivePocket(p).privateKey}`)
347
+ }
348
+
349
+ /**
350
+ * Clears a specific pocket's cached private key from memory
351
+ *
352
+ * Zeros out the private key string for security. The pocket can be re-derived if needed later.
353
+ *
354
+ * @param accountIndex - Index of the pocket to clear
355
+ *
356
+ * @remarks
357
+ * This provides defense-in-depth by clearing sensitive data when no longer needed.
358
+ * However, JavaScript strings are immutable, so we can only clear our reference.
359
+ * The actual memory may persist until garbage collection.
360
+ *
361
+ * @example
362
+ * ```typescript
363
+ * // Use a pocket
364
+ * const pocket = manager.getPocket(0);
365
+ * // ... use the pocket ...
366
+ *
367
+ * // Clear it when done
368
+ * manager.clearPocket(0);
369
+ * ```
370
+ */
371
+ clearPocket(accountIndex: number): void {
372
+ SavingsValidation.validateAccountIndex(accountIndex);
373
+
374
+ if (this.pockets.has(accountIndex)) {
375
+ const pocket = this.pockets.get(accountIndex)!;
376
+
377
+ // Attempt to clear the private key string
378
+ // Note: JavaScript strings are immutable, so this only clears our reference
379
+ // The actual memory will be cleared by garbage collection
380
+ (pocket as any).privateKey = '';
381
+
382
+ // Remove from cache
383
+ this.pockets.delete(accountIndex);
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Clears all cached pocket private keys from memory
389
+ *
390
+ * Removes all cached pockets and attempts to zero out their private keys.
391
+ * Pockets can be re-derived if needed later.
392
+ *
393
+ * @remarks
394
+ * Call this method when:
395
+ * - User locks the wallet
396
+ * - Application goes to background (mobile)
397
+ * - Extension popup closes (browser extension)
398
+ * - Session ends
399
+ *
400
+ * @example
401
+ * ```typescript
402
+ * // Clear all pockets when user locks wallet
403
+ * manager.clearAllPockets();
404
+ * ```
405
+ */
406
+ clearAllPockets(): void {
407
+ for (const [index, pocket] of this.pockets.entries()) {
408
+ // Attempt to clear the private key string
409
+ (pocket as any).privateKey = '';
410
+ }
411
+
412
+ // Clear the map
413
+ this.pockets.clear();
414
+ }
415
+
416
+ /**
417
+ * Clears all sensitive data from memory
418
+ *
419
+ * Attempts to clear mnemonic and all cached private keys from memory.
420
+ * Also releases the RPC client.
421
+ * After calling this method, the manager instance should not be used.
422
+ *
423
+ * @remarks
424
+ * IMPORTANT: JavaScript strings are immutable, so this method can only clear
425
+ * references. The actual memory will be cleared by garbage collection.
426
+ *
427
+ * For maximum security:
428
+ * 1. Call this method when done with the manager
429
+ * 2. Remove all references to the manager instance
430
+ * 3. Allow garbage collection to occur
431
+ *
432
+ * Call this method when:
433
+ * - User logs out
434
+ * - Session ends permanently
435
+ * - Application closes
436
+ *
437
+ * @example
438
+ * ```typescript
439
+ * // When user logs out
440
+ * manager.dispose();
441
+ * manager = null; // Remove reference
442
+ * ```
443
+ */
444
+ dispose(): void {
445
+ // Clear all cached pockets
446
+ this.clearAllPockets();
447
+
448
+ // Clear RPC client
449
+ this.clearClient();
450
+
451
+ // Attempt to clear mnemonic
452
+ // Note: JavaScript strings are immutable, so this only clears our reference
453
+ (this as any).mnemonic = '';
454
+
455
+ // Clear master address if present
456
+ if (this.masterAddress) {
457
+ (this as any).masterAddress = undefined;
458
+ }
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Extended savings manager with balance querying capabilities
464
+ *
465
+ * Extends BaseSavingsManager with additional methods for querying balances across
466
+ * multiple pockets and transferring funds back to the main wallet.
467
+ *
468
+ * @example
469
+ * ```typescript
470
+ * const savingsManager = new SavingsManager(
471
+ * 'your twelve word mnemonic phrase here...',
472
+ * chainConfig,
473
+ * 0 // wallet index
474
+ * );
475
+ *
476
+ * // Get balances for a pocket
477
+ * const balances = await savingsManager.getPocketTokenBalance(
478
+ * ['0xUSDC...', '0xDAI...'],
479
+ * 0 // pocket index
480
+ * );
481
+ * ```
482
+ */
483
+ export class SavingsManager extends BaseSavingsManager {
484
+
485
+ /**
486
+ * Creates a new SavingsManager instance
487
+ *
488
+ * @param mnemonic - BIP-39 mnemonic phrase used to derive all wallet addresses
489
+ * @param chain - Chain configuration containing RPC URL and chain details
490
+ * @param walletIndex - Wallet index in the derivation path (default: 0)
491
+ */
492
+ constructor(mnemonic: string, chain: ChainWalletConfig, walletIndex: number = 0,) {
493
+ super(mnemonic, walletIndex, chain)
494
+ }
495
+
496
+ /**
497
+ * Gets total token balances across all specified pockets
498
+ *
499
+ * @param tokens - Array of token contract addresses to query
500
+ * @param pockets - Array of pocket indices to check
501
+ * @returns Promise resolving to array of balance objects
502
+ *
503
+ * @throws Error if validation fails
504
+ *
505
+ * @example
506
+ * ```typescript
507
+ * const balances = await manager.getTotalTokenBalanceOfAllPockets(
508
+ * ['0xUSDC...', '0xDAI...'],
509
+ * [0, 1, 2] // Check first three pockets
510
+ * );
511
+ * ```
512
+ */
513
+ async getTotalTokenBalanceOfAllPockets(
514
+ tokens: string[],
515
+ pockets: number[]
516
+ ): Promise<Array<{ address: Hex | 'native'; balance: Balance; }[]>> {
517
+ // Validate inputs
518
+ if (!Array.isArray(tokens) || tokens.length === 0) {
519
+ throw new Error('Tokens array must be non-empty');
520
+ }
521
+ if (!Array.isArray(pockets) || pockets.length === 0) {
522
+ throw new Error('Pockets array must be non-empty');
523
+ }
524
+
525
+ // Validate all token addresses
526
+ tokens.forEach((token, index) => {
527
+ SavingsValidation.validateAddress(token, `Token at index ${index}`);
528
+ });
529
+
530
+ // Validate all pocket indices
531
+ pockets.forEach((pocket, index) => {
532
+ SavingsValidation.validateAccountIndex(pocket);
533
+ });
534
+
535
+ // Fetch balances for all pockets
536
+ const allBalances = await Promise.all(
537
+ pockets.map((p: number) => this.getPocketTokenBalance(tokens, p))
538
+ );
539
+
540
+ return allBalances;
541
+ }
542
+
543
+ /**
544
+ * Gets token balances for a specific savings pocket
545
+ *
546
+ * Queries the native token balance and all specified ERC-20 token balances for a pocket.
547
+ *
548
+ * @param tokens - Array of ERC-20 token contract addresses to query
549
+ * @param pocket - Pocket index to check balances for
550
+ * @returns Promise resolving to array of balance objects containing address and balance info
551
+ *
552
+ * @throws Error if validation fails
553
+ *
554
+ * @example
555
+ * ```typescript
556
+ * const balances = await manager.getPocketTokenBalance(
557
+ * ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'], // USDC
558
+ * 0 // First pocket
559
+ * );
560
+ *
561
+ * console.log(balances);
562
+ * // [
563
+ * // { address: 'native', balance: { balance: BN, formatted: 1.5, decimal: 18 } },
564
+ * // { address: '0xA0b8...', balance: { balance: BN, formatted: 100, decimal: 6 } }
565
+ * // ]
566
+ * ```
567
+ */
568
+ async getPocketTokenBalance(tokens: string[], pocket: number): Promise<{
569
+ address: Hex | 'native';
570
+ balance: Balance;
571
+ }[]> {
572
+ // Validate inputs
573
+ if (!Array.isArray(tokens)) {
574
+ throw new Error('Tokens must be an array');
575
+ }
576
+ SavingsValidation.validateAccountIndex(pocket);
577
+
578
+ // Validate all token addresses
579
+ tokens.forEach((token, index) => {
580
+ SavingsValidation.validateAddress(token, `Token at index ${index}`);
581
+ });
582
+
583
+ const account = this.accountFromPocketId(pocket)
584
+ const nativeBalance = await getNativeBalance(account.address, this.client)
585
+ const balancesList: {
586
+ address: Hex | 'native';
587
+ balance: Balance;
588
+ }[] = [{ address: 'native', balance: nativeBalance }]
589
+ await Promise.all(tokens.map(async (t: string) => {
590
+ const ercBalance = await getTokenBalance(t as Hex, account.address, this.client)
591
+ balancesList.push({ balance: ercBalance, address: t as Hex })
592
+ }))
593
+ return balancesList
594
+ }
595
+
596
+ /**
597
+ * Sends tokens from a savings pocket back to the main wallet
598
+ *
599
+ * Withdraws either native tokens or ERC-20 tokens from a pocket to the main wallet.
600
+ *
601
+ * @param pocketIndex - Index of the pocket to withdraw from
602
+ * @param amountInEther - Amount to send (interpreted as bigint, not actual ether units)
603
+ * @param token - Token address or "native" for native blockchain token
604
+ * @returns Transaction result containing hash and success status
605
+ *
606
+ * @throws Error if validation fails
607
+ *
608
+ * @remarks
609
+ * Despite the parameter name, amountInEther is actually interpreted as a raw bigint value,
610
+ * not converted from ether units. Consider renaming to `amount` for clarity.
611
+ *
612
+ * @example
613
+ * ```typescript
614
+ * // Send native token from pocket to main wallet
615
+ * const result = await manager.sendToMainWallet(
616
+ * 0, // From pocket 0
617
+ * 1000000000000000000n, // 1 ETH in wei
618
+ * 'native'
619
+ * );
620
+ *
621
+ * // Send ERC-20 token
622
+ * const result2 = await manager.sendToMainWallet(
623
+ * 0,
624
+ * 1000000n, // 1 USDC (6 decimals)
625
+ * '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
626
+ * );
627
+ * ```
628
+ */
629
+ async sendToMainWallet(pocketIndex: number, amountInEther: number, token: Hex | "native"): Promise<TransactionResult> {
630
+ // Validate inputs
631
+ SavingsValidation.validateAccountIndex(pocketIndex);
632
+
633
+ if (typeof amountInEther !== 'number' || amountInEther <= 0) {
634
+ throw new Error(`Amount must be a positive number, got: ${amountInEther}`);
635
+ }
636
+
637
+ if (token !== 'native') {
638
+ SavingsValidation.validateAddress(token, 'Token address');
639
+ }
640
+
641
+ const account = this.accountFromPocketId(pocketIndex)
642
+ const mainWalletAddress = this.getMainWalletAddress()
643
+ const walletClient = createWalletClient(
644
+ {
645
+ account,
646
+ transport: http(this.client.chain?.rpcUrls.default.http[0]),
647
+ chain: this.client.chain
648
+ }
649
+ )
650
+ if (token === "native") {
651
+ return await sendNativeToken(walletClient, this.client, mainWalletAddress, BigInt(amountInEther))
652
+ }
653
+ const res = await sendERC20Token(walletClient, this.client, token, mainWalletAddress as Hex, BigInt(amountInEther))
654
+ return res
655
+ }
656
+ }