@deserialize/multi-vm-wallet 1.5.35 → 1.6.1

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/dist/utils.js CHANGED
@@ -1,9 +1,17 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getPrivateKeyFromAnother = void 0;
6
+ exports.getAddressPortfolioValue = exports.detectVmTypeFromAddress = exports.getPrivateKeyFromAnother = void 0;
4
7
  const evm_1 = require("./evm");
5
8
  const svm_1 = require("./svm");
6
9
  const sha2_1 = require("@noble/hashes/sha2");
10
+ const web3_js_1 = require("@solana/web3.js");
11
+ const utils_1 = require("./evm/utils");
12
+ const utils_2 = require("./svm/utils");
13
+ const bn_js_1 = __importDefault(require("bn.js"));
14
+ const price_1 = require("./price");
7
15
  const getPrivateKeyFromAnother = (privateKey, fromVm, toVm) => {
8
16
  if (fromVm === "EVM") {
9
17
  if (toVm === "SVM") {
@@ -20,3 +28,193 @@ const getPrivateKeyFromAnother = (privateKey, fromVm, toVm) => {
20
28
  }
21
29
  };
22
30
  exports.getPrivateKeyFromAnother = getPrivateKeyFromAnother;
31
+ const EVM_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/;
32
+ const detectVmTypeFromAddress = (address) => {
33
+ if (EVM_ADDRESS_REGEX.test(address)) {
34
+ return "EVM";
35
+ }
36
+ try {
37
+ new web3_js_1.PublicKey(address);
38
+ return "SVM";
39
+ }
40
+ catch {
41
+ throw new Error(`Could not infer VM type from address: ${address}`);
42
+ }
43
+ };
44
+ exports.detectVmTypeFromAddress = detectVmTypeFromAddress;
45
+ const normalizePriceMap = (prices) => {
46
+ const priceMap = new Map();
47
+ for (const item of prices) {
48
+ if (!item?.tokenAddress || typeof item.price !== "number")
49
+ continue;
50
+ priceMap.set(item.tokenAddress.toLowerCase(), item.price);
51
+ }
52
+ return priceMap;
53
+ };
54
+ const enrichWithUsdValues = (items, priceMap) => {
55
+ return items.map((item) => {
56
+ const key = String(item.tokenAddress).toLowerCase();
57
+ const priceUsd = priceMap.get(key) ?? null;
58
+ const valueUsd = priceUsd === null ? null : item.balanceFormatted * priceUsd;
59
+ return {
60
+ ...item,
61
+ priceUsd,
62
+ valueUsd,
63
+ };
64
+ });
65
+ };
66
+ const buildTotals = (items) => {
67
+ let valueUsd = 0;
68
+ let pricedItems = 0;
69
+ let unpricedItems = 0;
70
+ for (const item of items) {
71
+ if (item.valueUsd === null) {
72
+ unpricedItems += 1;
73
+ continue;
74
+ }
75
+ pricedItems += 1;
76
+ valueUsd += item.valueUsd;
77
+ }
78
+ return { valueUsd, pricedItems, unpricedItems };
79
+ };
80
+ const getAddressPortfolioValue = async (params) => {
81
+ const { chain, address, includeNative = true, tokenAddresses } = params;
82
+ const vmType = params.vmType ?? (0, exports.detectVmTypeFromAddress)(address);
83
+ if (vmType === "EVM" && !EVM_ADDRESS_REGEX.test(address)) {
84
+ throw new Error(`Invalid EVM address: ${address}`);
85
+ }
86
+ if (vmType === "SVM") {
87
+ new web3_js_1.PublicKey(address);
88
+ }
89
+ if (vmType === "EVM") {
90
+ const client = (0, utils_1.createPublicClientFromChainConfig)(chain);
91
+ const items = [];
92
+ const priceTargets = new Set();
93
+ if (includeNative) {
94
+ const nativeBalance = await (0, utils_1.getNativeBalance)(address, client);
95
+ const nativeRaw = nativeBalance.balance.toString();
96
+ items.push({
97
+ tokenAddress: "native",
98
+ symbol: chain.nativeToken.symbol,
99
+ decimals: chain.nativeToken.decimals,
100
+ balanceRaw: nativeRaw,
101
+ balanceFormatted: nativeBalance.formatted,
102
+ });
103
+ priceTargets.add("native");
104
+ }
105
+ if (tokenAddresses && tokenAddresses.length > 0) {
106
+ const tokenResults = await Promise.all(tokenAddresses.map(async (tokenAddress) => {
107
+ const balance = await (0, utils_1.getTokenBalance)(tokenAddress, address, client);
108
+ const raw = balance.balance.toString();
109
+ return {
110
+ tokenAddress,
111
+ symbol: tokenAddress,
112
+ decimals: balance.decimal,
113
+ balanceRaw: raw,
114
+ balanceFormatted: balance.formatted,
115
+ };
116
+ }));
117
+ for (const token of tokenResults) {
118
+ items.push(token);
119
+ priceTargets.add(token.tokenAddress);
120
+ }
121
+ }
122
+ else {
123
+ const discovered = await (0, utils_1.discoverTokens)(address, chain);
124
+ for (const token of discovered) {
125
+ const raw = String(token.balance);
126
+ const decimals = token.decimals ?? 0;
127
+ const rawBn = new bn_js_1.default(raw);
128
+ const divisor = Math.pow(10, decimals);
129
+ const formatted = divisor === 0 ? 0 : Number(rawBn.toString()) / divisor;
130
+ items.push({
131
+ tokenAddress: token.address,
132
+ symbol: token.symbol,
133
+ decimals,
134
+ balanceRaw: raw,
135
+ balanceFormatted: formatted,
136
+ });
137
+ priceTargets.add(token.address);
138
+ }
139
+ }
140
+ const priceResult = await (0, price_1.fetchPrices)({
141
+ vm: "EVM",
142
+ chainId: chain.chainId,
143
+ tokenAddresses: Array.from(priceTargets),
144
+ });
145
+ const priceMap = normalizePriceMap(priceResult.data?.prices ?? []);
146
+ const enrichedItems = enrichWithUsdValues(items, priceMap);
147
+ return {
148
+ address,
149
+ vmType,
150
+ chainId: chain.chainId,
151
+ items: enrichedItems,
152
+ totals: buildTotals(enrichedItems),
153
+ };
154
+ }
155
+ const svmAddress = new web3_js_1.PublicKey(address);
156
+ const connection = new web3_js_1.Connection(chain.rpcUrl);
157
+ const items = [];
158
+ const priceTargets = new Set();
159
+ if (includeNative) {
160
+ const nativeBalance = await (0, utils_2.getSvmNativeBalance)(svmAddress, connection);
161
+ items.push({
162
+ tokenAddress: "native",
163
+ symbol: chain.nativeToken.symbol,
164
+ decimals: chain.nativeToken.decimals,
165
+ balanceRaw: nativeBalance.balance.toString(),
166
+ balanceFormatted: nativeBalance.formatted,
167
+ });
168
+ priceTargets.add("native");
169
+ }
170
+ if (tokenAddresses && tokenAddresses.length > 0) {
171
+ const tokenResults = await Promise.all(tokenAddresses.map(async (tokenAddress) => {
172
+ const pubkey = new web3_js_1.PublicKey(tokenAddress);
173
+ const tokenBalance = await svm_1.SVMVM.getTokenBalance(svmAddress, pubkey, connection);
174
+ return {
175
+ tokenAddress,
176
+ symbol: tokenAddress,
177
+ decimals: tokenBalance.decimal,
178
+ balanceRaw: tokenBalance.balance.toString(),
179
+ balanceFormatted: tokenBalance.formatted,
180
+ };
181
+ }));
182
+ for (const token of tokenResults) {
183
+ items.push(token);
184
+ priceTargets.add(token.tokenAddress);
185
+ }
186
+ }
187
+ else {
188
+ const discovered = await (0, utils_2.discoverTokens)(svmAddress, connection);
189
+ for (const token of discovered) {
190
+ const raw = String(token.balance);
191
+ const decimals = token.decimals ?? 0;
192
+ const rawBn = new bn_js_1.default(raw);
193
+ const divisor = Math.pow(10, decimals);
194
+ const formatted = divisor === 0 ? 0 : Number(rawBn.toString()) / divisor;
195
+ items.push({
196
+ tokenAddress: token.address,
197
+ symbol: token.symbol,
198
+ decimals,
199
+ balanceRaw: raw,
200
+ balanceFormatted: formatted,
201
+ });
202
+ priceTargets.add(token.address);
203
+ }
204
+ }
205
+ const priceResult = await (0, price_1.fetchPrices)({
206
+ vm: "SVM",
207
+ chainId: chain.chainId,
208
+ tokenAddresses: Array.from(priceTargets),
209
+ });
210
+ const priceMap = normalizePriceMap(priceResult.data?.prices ?? []);
211
+ const enrichedItems = enrichWithUsdValues(items, priceMap);
212
+ return {
213
+ address,
214
+ vmType,
215
+ chainId: chain.chainId,
216
+ items: enrichedItems,
217
+ totals: buildTotals(enrichedItems),
218
+ };
219
+ };
220
+ exports.getAddressPortfolioValue = getAddressPortfolioValue;
@@ -87,7 +87,7 @@ function GenerateSeed(_mnemonic) {
87
87
  function EVMDeriveChildPrivateKey(seed, index, derivationPath) {
88
88
  vm_validation_1.VMValidation.validateSeed(seed);
89
89
  vm_validation_1.VMValidation.validateIndex(index, 'Wallet index');
90
- const path = `${derivationPath}${index}'`;
90
+ const path = derivationPath.endsWith("/") ? `${derivationPath}${index}` : derivationPath;
91
91
  const scureNode = bip32_1.HDKey.fromMasterSeed(buffer_1.Buffer.from(seed, "hex"));
92
92
  const child = scureNode.derive(path);
93
93
  if (!child.privateKey) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deserialize/multi-vm-wallet",
3
- "version": "1.5.35",
3
+ "version": "1.6.1",
4
4
  "devDependencies": {
5
5
  "@types/bn.js": "^5.2.0",
6
6
  "@types/crypto-js": "^4.2.2",
package/utils/constant.ts CHANGED
@@ -15,7 +15,21 @@ export const DefaultChains: ChainWalletConfig[] = [{
15
15
  logoUrl: "https://solana.com/src/img/branding/solanaLogoMark.svg",
16
16
  vmType: "SVM",
17
17
  savings: {
18
- tokens: []
18
+ supported: true,
19
+ tokens: [
20
+ {
21
+ name: "USDC",
22
+ symbol: "USDC",
23
+ decimals: 6,
24
+ address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
25
+ },
26
+ {
27
+ name: "USDT",
28
+ symbol: "USDT",
29
+ decimals: 6,
30
+ address: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
31
+ }
32
+ ]
19
33
  }
20
34
  }
21
35
 
@@ -34,6 +48,7 @@ export const DefaultChains: ChainWalletConfig[] = [{
34
48
  logoUrl: "https://push.org/assets/website/favicons/favicon.svg",
35
49
  vmType: "EVM",
36
50
  savings: {
51
+ supported: false,
37
52
  tokens: []
38
53
  }
39
54
  }
@@ -53,7 +68,24 @@ export const DefaultChains: ChainWalletConfig[] = [{
53
68
  },
54
69
  testnet: false,
55
70
  logoUrl: "https://etherscan.io/images/svg/brands/ethereum-original-light.svg",
56
- vmType: "EVM"
71
+ vmType: "EVM",
72
+ savings: {
73
+ supported: true,
74
+ tokens: [{
75
+ name: "USDC",
76
+ symbol: "USDC",
77
+ decimals: 6,
78
+ address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
79
+ },
80
+ {
81
+ name: "USDT",
82
+ symbol: "USDT",
83
+ decimals: 6,
84
+ address: "0xdac17f958d2ee523a2206206994597c13d831ec7"
85
+ }
86
+
87
+ ]
88
+ }
57
89
  }, {
58
90
  chainId: 56,
59
91
  name: "BSC",
@@ -68,7 +100,18 @@ export const DefaultChains: ChainWalletConfig[] = [{
68
100
  logoUrl: "https://bscscan.com/assets/bsc/images/svg/logos/token-light.svg?v=25.10.5.0",
69
101
  vmType: "EVM",
70
102
  savings: {
71
- tokens: []
103
+ supported: true,
104
+ tokens: [
105
+
106
+ {
107
+ name: "BUSD",
108
+ symbol: "BUSD",
109
+ decimals: 6,
110
+ address: "0x55d398326f99059fF775485246999027B3197955"
111
+ }
112
+
113
+
114
+ ]
72
115
  }
73
116
  },
74
117
 
@@ -101,7 +144,15 @@ export const DefaultChains: ChainWalletConfig[] = [{
101
144
  logoUrl: "https://avatars.githubusercontent.com/u/108554348?s=200&v=4",
102
145
  vmType: "EVM",
103
146
  savings: {
104
- tokens: []
147
+ supported: true,
148
+ tokens: [
149
+ {
150
+ name: "USDC",
151
+ symbol: "USDC",
152
+ decimals: 6,
153
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
154
+ }
155
+ ]
105
156
  }
106
157
  },
107
158
 
@@ -119,6 +170,7 @@ export const DefaultChains: ChainWalletConfig[] = [{
119
170
  logoUrl: "https://avatars.githubusercontent.com/u/119917794?s=280&v=4",
120
171
  vmType: "EVM",
121
172
  savings: {
173
+ supported: false,
122
174
  tokens: []
123
175
  }
124
176
  },
@@ -136,6 +188,7 @@ export const DefaultChains: ChainWalletConfig[] = [{
136
188
  logoUrl: "https://avatars.githubusercontent.com/u/58791460?s=280&v=4",
137
189
  vmType: "EVM",
138
190
  savings: {
191
+ supported: false,
139
192
  tokens: []
140
193
  }
141
194
  },
@@ -155,6 +208,7 @@ export const DefaultChains: ChainWalletConfig[] = [{
155
208
  logoUrl: "https://polygonscan.com/images/svg/brands/polygon-light.svg?v=0.0.36",
156
209
  vmType: "EVM",
157
210
  savings: {
211
+ supported: false,
158
212
  tokens: []
159
213
  }
160
214
  }
package/utils/evm/evm.ts CHANGED
@@ -806,7 +806,7 @@ export class EVMVM extends VM<string, string, PublicClient> {
806
806
  try {
807
807
  // Pocket derivation: m/44'/60'/{accountIndex+1}'/0/{walletIndex}
808
808
  const pocketIndex = accountIndex + 1;
809
- const derivationPath = `m/44'/60'/${pocketIndex}'/0/${walletIndex}'`;
809
+ const derivationPath = `m/44'/60'/${pocketIndex}'/0/${walletIndex}`;
810
810
 
811
811
  // Derive pocket
812
812
  const { privateKey } = EVMDeriveChildPrivateKey(this.seed, walletIndex, `m/44'/60'/${pocketIndex}'/0/`);
@@ -119,9 +119,10 @@ export class EVMSavingsManager extends SavingsManager<Hex, PublicClient, WalletC
119
119
 
120
120
  // Add 1 to preserve index 0 for main wallet
121
121
  const pocketIndex = accountIndex + 1;
122
- const derivationPath = `${this.derivationPathBase}${pocketIndex}'/0/${this.walletIndex}`;
122
+ const derivationPathBase = `${this.derivationPathBase}${pocketIndex}'/0/`;
123
+ const derivationPath = `${derivationPathBase}${this.walletIndex}'`;
123
124
  const seed = mnemonicToSeed(this.mnemonic);
124
- const { privateKey } = EVMDeriveChildPrivateKey(seed, this.walletIndex, derivationPath);
125
+ const { privateKey } = EVMDeriveChildPrivateKey(seed, this.walletIndex, derivationPathBase);
125
126
  const wallet = new ethers.Wallet(privateKey);
126
127
 
127
128
  const pocket: Pocket<Hex> = {
@@ -142,9 +143,10 @@ export class EVMSavingsManager extends SavingsManager<Hex, PublicClient, WalletC
142
143
  */
143
144
  getMainWallet() {
144
145
  this.checkNotDisposed();
145
- const derivationPath = `${this.derivationPathBase}0'/0/${this.walletIndex}`;
146
+ const derivationPathBase = `${this.derivationPathBase}0'/0/`;
147
+ const derivationPath = `${derivationPathBase}${this.walletIndex}'`;
146
148
  const seed = mnemonicToSeed(this.mnemonic);
147
- const { privateKey } = EVMDeriveChildPrivateKey(seed, this.walletIndex, derivationPath);
149
+ const { privateKey } = EVMDeriveChildPrivateKey(seed, this.walletIndex, derivationPathBase);
148
150
  const wallet = new ethers.Wallet(privateKey);
149
151
 
150
152
  return {
@@ -10,6 +10,7 @@ import { SVMSavingsManager } from "./svm-savings";
10
10
  import { ChainWalletConfig, Balance } from "../types";
11
11
  import { Hex } from "viem";
12
12
  import { PublicKey } from "@solana/web3.js";
13
+ import { fetchPrices } from "../price";
13
14
 
14
15
  /**
15
16
  * Chain type identifier
@@ -46,6 +47,8 @@ export interface PocketBalance {
46
47
  token: string;
47
48
  /** Balance information */
48
49
  balance: Balance;
50
+ /** Balance in USD (if unavailable, defaults to 0) */
51
+ balanceInUsd: number;
49
52
  }[];
50
53
  }
51
54
 
@@ -90,6 +93,44 @@ export class MultiChainSavingsManager {
90
93
  // Track chain configs
91
94
  private chainConfigs: Map<string, ChainConfig> = new Map();
92
95
 
96
+ private getTokenKey(tokenAddress: string, chainType: ChainType): string {
97
+ if (chainType === 'EVM') {
98
+ return tokenAddress.toLowerCase();
99
+ }
100
+ return tokenAddress;
101
+ }
102
+
103
+ private normalizePriceMap(
104
+ prices: Array<{ tokenAddress: string; price: number }>,
105
+ chainType: ChainType
106
+ ): Map<string, number> {
107
+ const priceMap = new Map<string, number>();
108
+ for (const item of prices) {
109
+ if (!item?.tokenAddress || typeof item.price !== 'number') continue;
110
+ priceMap.set(this.getTokenKey(item.tokenAddress, chainType), item.price);
111
+ }
112
+ return priceMap;
113
+ }
114
+
115
+ private async getPriceMap(
116
+ chain: ChainConfig,
117
+ tokenAddresses: string[]
118
+ ): Promise<Map<string, number>> {
119
+ const chainWithId = chain.config as Partial<ChainWalletConfig>;
120
+ if (typeof chainWithId.chainId !== 'number') {
121
+ return new Map();
122
+ }
123
+
124
+ const uniqueTokenAddresses = Array.from(new Set(tokenAddresses));
125
+ const priceResult = await fetchPrices({
126
+ vm: chain.type,
127
+ chainId: chainWithId.chainId,
128
+ tokenAddresses: uniqueTokenAddresses
129
+ });
130
+
131
+ return this.normalizePriceMap(priceResult.data?.prices ?? [], chain.type);
132
+ }
133
+
93
134
  /**
94
135
  * Create a new MultiChainSavingsManager
95
136
  *
@@ -132,14 +173,13 @@ export class MultiChainSavingsManager {
132
173
  throw new Error(`Chain with id '${chain.id}' already exists`);
133
174
  }
134
175
 
135
- this.chainConfigs.set(chain.id, chain);
136
-
137
176
  if (chain.type === 'EVM') {
138
177
  const manager = new EVMSavingsManager(
139
178
  this.mnemonic,
140
179
  chain.config as ChainWalletConfig,
141
180
  this.walletIndex
142
181
  );
182
+ this.chainConfigs.set(chain.id, chain);
143
183
  this.evmManagers.set(chain.id, manager);
144
184
  } else if (chain.type === 'SVM') {
145
185
  const config = chain.config as { rpcUrl: string };
@@ -151,6 +191,7 @@ export class MultiChainSavingsManager {
151
191
  config.rpcUrl,
152
192
  this.walletIndex
153
193
  );
194
+ this.chainConfigs.set(chain.id, chain);
154
195
  this.svmManagers.set(chain.id, manager);
155
196
  } else {
156
197
  throw new Error(`Unknown chain type: ${chain.type}`);
@@ -253,30 +294,52 @@ export class MultiChainSavingsManager {
253
294
  const manager = this.evmManagers.get(chainId)!;
254
295
  const balances = await manager.getPocketBalance(pocketIndex, tokens);
255
296
  const pocket = manager.getPocket(pocketIndex);
297
+ const balanceRows = balances.map(b => ({
298
+ token: b.address === 'native' ? 'native' : b.address,
299
+ balance: b.balance
300
+ }));
301
+ const priceMap = await this.getPriceMap(
302
+ chain,
303
+ balanceRows.map(row => row.token)
304
+ );
256
305
 
257
306
  return {
258
307
  chainId,
259
308
  chainType: 'EVM',
260
309
  pocketIndex,
261
310
  address: pocket.address,
262
- balances: balances.map(b => ({
263
- token: b.address === 'native' ? 'native' : b.address,
264
- balance: b.balance
311
+ balances: balanceRows.map(row => ({
312
+ token: row.token,
313
+ balance: row.balance,
314
+ balanceInUsd:
315
+ row.balance.formatted *
316
+ (priceMap.get(this.getTokenKey(row.token, 'EVM')) ?? 0)
265
317
  }))
266
318
  };
267
319
  } else {
268
320
  const manager = this.svmManagers.get(chainId)!;
269
321
  const balances = await manager.getPocketBalance(pocketIndex, tokens);
270
322
  const pocket = manager.getPocket(pocketIndex);
323
+ const balanceRows = balances.map(b => ({
324
+ token: b.address === 'native' ? 'native' : b.address.toBase58(),
325
+ balance: b.balance
326
+ }));
327
+ const priceMap = await this.getPriceMap(
328
+ chain,
329
+ balanceRows.map(row => row.token)
330
+ );
271
331
 
272
332
  return {
273
333
  chainId,
274
334
  chainType: 'SVM',
275
335
  pocketIndex,
276
336
  address: pocket.address.toBase58(),
277
- balances: balances.map(b => ({
278
- token: b.address === 'native' ? 'native' : b.address.toBase58(),
279
- balance: b.balance
337
+ balances: balanceRows.map(row => ({
338
+ token: row.token,
339
+ balance: row.balance,
340
+ balanceInUsd:
341
+ row.balance.formatted *
342
+ (priceMap.get(this.getTokenKey(row.token, 'SVM')) ?? 0)
280
343
  }))
281
344
  };
282
345
  }
@@ -1,4 +1,4 @@
1
- import { EVMDeriveChildPrivateKey, GenerateSeed, mnemonicToSeed } from "../walletBip32";
1
+ import { EVMDeriveChildPrivateKey, mnemonicToSeed } from "../walletBip32";
2
2
  import { ethers } from "ethers";
3
3
  import { WalletClient, PublicClient, Hex, Chain, createWalletClient, createPublicClient, http } from "viem";
4
4
  import { } from "../utils";
@@ -141,11 +141,12 @@ export class BaseSavingsManager {
141
141
 
142
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
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);
144
+ const derivationPathBase = `m/44'/60'/${pocketIndex}'/0/`;
145
+ const derivationPath = `${derivationPathBase}${this.walletIndex}'`;
146
+ const { privateKey } = EVMDeriveChildPrivateKey(mnemonicToSeed(this.mnemonic), this.walletIndex, derivationPathBase);
146
147
  const wallet = new ethers.Wallet(privateKey);
147
148
  const pocket = { privateKey, address: wallet.address, derivationPath, index: pocketIndex };
148
- this.pockets.set(pocketIndex, pocket);
149
+ this.pockets.set(accountIndex, pocket);
149
150
  return pocket;
150
151
  }
151
152
 
@@ -164,8 +165,13 @@ export class BaseSavingsManager {
164
165
  * ```
165
166
  */
166
167
  getMainWallet() {
167
- const mainWalletDerivationPath = `m/44'/60'/0'/0/${this.walletIndex}`;
168
- const { privateKey } = EVMDeriveChildPrivateKey(this.mnemonic, this.walletIndex, mainWalletDerivationPath);
168
+ const mainWalletDerivationPathBase = `m/44'/60'/0'/0/`;
169
+ const mainWalletDerivationPath = `${mainWalletDerivationPathBase}${this.walletIndex}'`;
170
+ const { privateKey } = EVMDeriveChildPrivateKey(
171
+ mnemonicToSeed(this.mnemonic),
172
+ this.walletIndex,
173
+ mainWalletDerivationPathBase
174
+ );
169
175
  const wallet = new ethers.Wallet(privateKey);
170
176
  return { privateKey, address: wallet.address, derivationPath: mainWalletDerivationPath };
171
177
  }
@@ -185,8 +191,7 @@ export class BaseSavingsManager {
185
191
  */
186
192
  getMainWalletAddress(): Hex {
187
193
  if (this.masterAddress) return this.masterAddress;
188
- const { privateKey } = this.getMainWallet()
189
- return new ethers.Wallet(privateKey).address as Hex
194
+ return this.getMainWallet().address as Hex
190
195
  }
191
196
 
192
197
  /**
@@ -342,8 +347,8 @@ export class BaseSavingsManager {
342
347
  * ```
343
348
  */
344
349
  accountFromPocketId(p: number): Account {
345
- // Validation is done in derivePocket
346
- return privateKeyToAccount(`0x${this.derivePocket(p).privateKey}`)
350
+ // Validation is done in getPocket
351
+ return privateKeyToAccount(`0x${this.getPocket(p).privateKey}`)
347
352
  }
348
353
 
349
354
  /**
@@ -599,16 +604,13 @@ export class SavingsManager extends BaseSavingsManager {
599
604
  * Withdraws either native tokens or ERC-20 tokens from a pocket to the main wallet.
600
605
  *
601
606
  * @param pocketIndex - Index of the pocket to withdraw from
602
- * @param amountInEther - Amount to send (interpreted as bigint, not actual ether units)
607
+ * @param amount - Amount to send in base units
603
608
  * @param token - Token address or "native" for native blockchain token
604
609
  * @returns Transaction result containing hash and success status
605
610
  *
606
611
  * @throws Error if validation fails
607
612
  *
608
613
  * @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
614
  * @example
613
615
  * ```typescript
614
616
  * // Send native token from pocket to main wallet
@@ -626,13 +628,11 @@ export class SavingsManager extends BaseSavingsManager {
626
628
  * );
627
629
  * ```
628
630
  */
629
- async sendToMainWallet(pocketIndex: number, amountInEther: number, token: Hex | "native"): Promise<TransactionResult> {
631
+ async sendToMainWallet(pocketIndex: number, amount: bigint, token: Hex | "native"): Promise<TransactionResult> {
630
632
  // Validate inputs
631
633
  SavingsValidation.validateAccountIndex(pocketIndex);
632
634
 
633
- if (typeof amountInEther !== 'number' || amountInEther <= 0) {
634
- throw new Error(`Amount must be a positive number, got: ${amountInEther}`);
635
- }
635
+ SavingsValidation.validateAmount(amount, 'Transfer amount');
636
636
 
637
637
  if (token !== 'native') {
638
638
  SavingsValidation.validateAddress(token, 'Token address');
@@ -648,9 +648,9 @@ export class SavingsManager extends BaseSavingsManager {
648
648
  }
649
649
  )
650
650
  if (token === "native") {
651
- return await sendNativeToken(walletClient, this.client, mainWalletAddress, BigInt(amountInEther))
651
+ return await sendNativeToken(walletClient, this.client, mainWalletAddress, amount)
652
652
  }
653
- const res = await sendERC20Token(walletClient, this.client, token, mainWalletAddress as Hex, BigInt(amountInEther))
653
+ const res = await sendERC20Token(walletClient, this.client, token, mainWalletAddress as Hex, amount)
654
654
  return res
655
655
  }
656
656
  }