@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.
- package/.claude/settings.local.json +7 -1
- package/BUILD_OPTIMIZATION_PLAN.md +640 -0
- package/BUILD_RESULTS.md +282 -0
- package/BUN_MIGRATION.md +415 -0
- package/CHANGELOG_SECURITY.md +573 -0
- package/IMPLEMENTATION_SUMMARY.md +494 -0
- package/SECURITY_AUDIT.md +1124 -0
- package/bun.lock +553 -0
- package/dist/IChainWallet.js +0 -5
- package/dist/bip32Old.js +0 -885
- package/dist/bip32Small.js +0 -79
- package/dist/bipTest.js +0 -362
- package/dist/constant.js +0 -17
- package/dist/english.js +0 -1
- package/dist/evm/aa-service/index.d.ts +0 -5
- package/dist/evm/aa-service/index.js +0 -14
- package/dist/evm/aa-service/lib/account-adapter.d.ts +0 -22
- package/dist/evm/aa-service/lib/account-adapter.js +0 -24
- package/dist/evm/aa-service/lib/kernel-account.d.ts +0 -30
- package/dist/evm/aa-service/lib/kernel-account.js +2 -67
- package/dist/evm/aa-service/lib/kernel-modules.d.ts +0 -177
- package/dist/evm/aa-service/lib/kernel-modules.js +4 -202
- package/dist/evm/aa-service/lib/session-keys.d.ts +0 -118
- package/dist/evm/aa-service/lib/session-keys.js +7 -151
- package/dist/evm/aa-service/lib/type.d.ts +0 -55
- package/dist/evm/aa-service/lib/type.js +0 -10
- package/dist/evm/aa-service/services/account-abstraction.d.ts +0 -426
- package/dist/evm/aa-service/services/account-abstraction.js +0 -461
- package/dist/evm/aa-service/services/bundler.d.ts +0 -6
- package/dist/evm/aa-service/services/bundler.js +0 -54
- package/dist/evm/evm.d.ts +10 -67
- package/dist/evm/evm.js +340 -102
- package/dist/evm/index.js +0 -3
- package/dist/evm/script.js +3 -17
- package/dist/evm/smartWallet.d.ts +0 -173
- package/dist/evm/smartWallet.js +0 -206
- package/dist/evm/smartWallet.types.d.ts +0 -6
- package/dist/evm/smartWallet.types.js +0 -8
- package/dist/evm/transaction.utils.d.ts +0 -242
- package/dist/evm/transaction.utils.js +4 -320
- package/dist/evm/transactionParsing.d.ts +0 -11
- package/dist/evm/transactionParsing.js +28 -147
- package/dist/evm/utils.d.ts +0 -46
- package/dist/evm/utils.js +1 -57
- package/dist/helpers/index.d.ts +0 -4
- package/dist/helpers/index.js +8 -44
- package/dist/helpers/routeScan.js +0 -1
- package/dist/index.js +0 -1
- package/dist/old.js +0 -884
- package/dist/price.js +0 -1
- package/dist/price.types.js +0 -2
- package/dist/rate-limiter.d.ts +28 -0
- package/dist/rate-limiter.js +95 -0
- package/dist/retry-logic.d.ts +14 -0
- package/dist/retry-logic.js +120 -0
- package/dist/savings/index.d.ts +1 -0
- package/dist/savings/index.js +16 -2
- package/dist/savings/saving-manager.d.ts +46 -0
- package/dist/savings/saving-manager.js +176 -0
- package/dist/savings/savings-operations.d.ts +39 -0
- package/dist/savings/savings-operations.js +141 -0
- package/dist/savings/smart-savings.d.ts +0 -63
- package/dist/savings/smart-savings.js +0 -78
- package/dist/savings/types.d.ts +0 -69
- package/dist/savings/types.js +0 -7
- package/dist/savings/validation.d.ts +9 -0
- package/dist/savings/validation.js +85 -0
- package/dist/svm/constant.js +0 -1
- package/dist/svm/index.js +0 -1
- package/dist/svm/svm.d.ts +7 -13
- package/dist/svm/svm.js +263 -46
- package/dist/svm/transactionParsing.d.ts +0 -7
- package/dist/svm/transactionParsing.js +3 -41
- package/dist/svm/transactionSender.js +0 -9
- package/dist/svm/utils.d.ts +0 -12
- package/dist/svm/utils.js +9 -60
- package/dist/test.d.ts +0 -4
- package/dist/test.js +15 -95
- package/dist/transaction-utils.d.ts +38 -0
- package/dist/transaction-utils.js +168 -0
- package/dist/types.d.ts +36 -0
- package/dist/types.js +0 -1
- package/dist/utils.js +0 -1
- package/dist/vm-validation.d.ts +11 -0
- package/dist/vm-validation.js +151 -0
- package/dist/vm.d.ts +14 -16
- package/dist/vm.js +64 -53
- package/dist/walletBip32.d.ts +2 -0
- package/dist/walletBip32.js +33 -66
- package/package.json +9 -4
- package/test-discovery.ts +235 -0
- package/test-pocket-discovery.ts +84 -0
- package/tsconfig.json +18 -11
- package/tsconfig.prod.json +10 -0
- package/utils/IChainWallet.ts +2 -0
- package/utils/evm/evm.ts +560 -39
- package/utils/rate-limiter.ts +179 -0
- package/utils/retry-logic.ts +271 -0
- package/utils/savings/EXAMPLES.md +883 -0
- package/utils/savings/SECURITY.md +731 -0
- package/utils/savings/index.ts +1 -1
- package/utils/savings/saving-manager.ts +656 -0
- package/utils/savings/savings-operations.ts +509 -0
- package/utils/savings/validation.ts +187 -0
- package/utils/svm/svm.ts +467 -20
- package/utils/test.ts +26 -3
- package/utils/transaction-utils.ts +394 -0
- package/utils/types.ts +100 -0
- package/utils/vm-validation.ts +280 -0
- package/utils/vm.ts +202 -24
- package/utils/walletBip32.ts +63 -3
- package/dist/IChainWallet.js.map +0 -1
- package/dist/bip32.d.ts +0 -9
- package/dist/bip32.js +0 -172
- package/dist/bip32.js.map +0 -1
- package/dist/bip32Old.js.map +0 -1
- package/dist/bip32Small.js.map +0 -1
- package/dist/bipTest.js.map +0 -1
- package/dist/constant.js.map +0 -1
- package/dist/english.js.map +0 -1
- package/dist/evm/SMART_WALLET_EXAMPLES.d.ts +0 -20
- package/dist/evm/SMART_WALLET_EXAMPLES.js +0 -451
- package/dist/evm/SMART_WALLET_EXAMPLES.js.map +0 -1
- package/dist/evm/aa-service/index.js.map +0 -1
- package/dist/evm/aa-service/lib/account-adapter.js.map +0 -1
- package/dist/evm/aa-service/lib/kernel-account.js.map +0 -1
- package/dist/evm/aa-service/lib/kernel-modules.js.map +0 -1
- package/dist/evm/aa-service/lib/session-keys.js.map +0 -1
- package/dist/evm/aa-service/lib/type.js.map +0 -1
- package/dist/evm/aa-service/services/account-abstraction.js.map +0 -1
- package/dist/evm/aa-service/services/bundler.js.map +0 -1
- package/dist/evm/evm.js.map +0 -1
- package/dist/evm/index.js.map +0 -1
- package/dist/evm/script.js.map +0 -1
- package/dist/evm/smartWallet.js.map +0 -1
- package/dist/evm/smartWallet.types.js.map +0 -1
- package/dist/evm/transaction.utils.js.map +0 -1
- package/dist/evm/transactionParsing.js.map +0 -1
- package/dist/evm/utils.js.map +0 -1
- package/dist/helpers/index.js.map +0 -1
- package/dist/helpers/routeScan.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/old.js.map +0 -1
- package/dist/price.js.map +0 -1
- package/dist/price.types.js.map +0 -1
- package/dist/privacy/artifact-manager.d.ts +0 -117
- package/dist/privacy/artifact-manager.js +0 -251
- package/dist/privacy/artifact-manager.js.map +0 -1
- package/dist/privacy/broadcaster-client.d.ts +0 -166
- package/dist/privacy/broadcaster-client.js +0 -261
- package/dist/privacy/broadcaster-client.js.map +0 -1
- package/dist/privacy/index.d.ts +0 -34
- package/dist/privacy/index.js +0 -56
- package/dist/privacy/index.js.map +0 -1
- package/dist/privacy/network-config.d.ts +0 -57
- package/dist/privacy/network-config.js +0 -118
- package/dist/privacy/network-config.js.map +0 -1
- package/dist/privacy/poi-helper.d.ts +0 -161
- package/dist/privacy/poi-helper.js +0 -249
- package/dist/privacy/poi-helper.js.map +0 -1
- package/dist/privacy/railgun-engine.d.ts +0 -135
- package/dist/privacy/railgun-engine.js +0 -205
- package/dist/privacy/railgun-engine.js.map +0 -1
- package/dist/privacy/railgun-privacy-wallet.d.ts +0 -288
- package/dist/privacy/railgun-privacy-wallet.js +0 -539
- package/dist/privacy/railgun-privacy-wallet.js.map +0 -1
- package/dist/privacy/types.d.ts +0 -229
- package/dist/privacy/types.js +0 -26
- package/dist/privacy/types.js.map +0 -1
- package/dist/savings/index.js.map +0 -1
- package/dist/savings/saving-actions.d.ts +0 -0
- package/dist/savings/saving-actions.js +0 -78
- package/dist/savings/saving-actions.js.map +0 -1
- package/dist/savings/savings-manager.d.ts +0 -126
- package/dist/savings/savings-manager.js +0 -234
- package/dist/savings/savings-manager.js.map +0 -1
- package/dist/savings/smart-savings.js.map +0 -1
- package/dist/savings/types.js.map +0 -1
- package/dist/svm/constant.js.map +0 -1
- package/dist/svm/index.js.map +0 -1
- package/dist/svm/svm.js.map +0 -1
- package/dist/svm/transactionParsing.js.map +0 -1
- package/dist/svm/transactionSender.js.map +0 -1
- package/dist/svm/utils.js.map +0 -1
- package/dist/test.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/dist/vm.js.map +0 -1
- package/dist/walletBip32.js.map +0 -1
- package/utils/savings/saving-actions.ts +0 -92
- package/utils/savings/savings-manager.ts +0 -271
package/utils/savings/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
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
|
+
}
|