@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/constant.js +54 -4
- package/dist/evm/evm.js +1 -1
- package/dist/savings/evm-savings.js +6 -4
- package/dist/savings/multi-chain-savings.d.ts +4 -0
- package/dist/savings/multi-chain-savings.js +51 -7
- package/dist/savings/saving-manager.d.ts +1 -1
- package/dist/savings/saving-manager.js +13 -14
- package/dist/savings/savings-operations.js +4 -2
- package/dist/savings/svm-savings.d.ts +1 -0
- package/dist/savings/svm-savings.js +16 -8
- package/dist/test.js +2 -0
- package/dist/types.d.ts +2 -1
- package/dist/utils.d.ts +30 -1
- package/dist/utils.js +199 -1
- package/dist/walletBip32.js +1 -1
- package/package.json +1 -1
- package/utils/constant.ts +58 -4
- package/utils/evm/evm.ts +1 -1
- package/utils/savings/evm-savings.ts +6 -4
- package/utils/savings/multi-chain-savings.ts +71 -8
- package/utils/savings/saving-manager.ts +20 -20
- package/utils/savings/savings-operations.ts +11 -9
- package/utils/savings/svm-savings.ts +17 -8
- package/utils/test.ts +1 -1
- package/utils/types.ts +2 -1
- package/utils/utils.ts +250 -4
- package/utils/walletBip32.ts +5 -2
|
@@ -44,7 +44,7 @@ import { EVMDeriveChildPrivateKey, mnemonicToSeed } from "../walletBip32";
|
|
|
44
44
|
export interface TransferFromPocketOptions {
|
|
45
45
|
/** Wallet index in derivation path (default: 0) */
|
|
46
46
|
walletIndex?: number;
|
|
47
|
-
/**
|
|
47
|
+
/** Pocket index to transfer from (0-based) */
|
|
48
48
|
accountIndex: number;
|
|
49
49
|
/** Destination address */
|
|
50
50
|
to: string;
|
|
@@ -76,7 +76,7 @@ export interface TransferTokenFromPocketOptions extends Omit<TransferFromPocketO
|
|
|
76
76
|
export interface GetPocketBalanceOptions {
|
|
77
77
|
/** Wallet index in derivation path (default: 0) */
|
|
78
78
|
walletIndex?: number;
|
|
79
|
-
/**
|
|
79
|
+
/** Pocket index (0-based) */
|
|
80
80
|
accountIndex: number;
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -129,7 +129,7 @@ export class SavingsOperations {
|
|
|
129
129
|
* Derives a savings pocket wallet from mnemonic
|
|
130
130
|
*
|
|
131
131
|
* @param mnemonic - BIP-39 mnemonic phrase
|
|
132
|
-
* @param accountIndex -
|
|
132
|
+
* @param accountIndex - Pocket index (0-based)
|
|
133
133
|
* @param walletIndex - Wallet index (default: 0)
|
|
134
134
|
* @param provider - RPC provider to connect wallet to
|
|
135
135
|
* @returns Derived wallet, address, and cleanup function
|
|
@@ -137,7 +137,7 @@ export class SavingsOperations {
|
|
|
137
137
|
* @remarks
|
|
138
138
|
* IMPORTANT: Always call cleanup() when done with the wallet to zero out private key
|
|
139
139
|
*
|
|
140
|
-
* Derivation path: m/44'/60'/{accountIndex}'/0/{walletIndex}'
|
|
140
|
+
* Derivation path: m/44'/60'/{accountIndex + 1}'/0/{walletIndex}'
|
|
141
141
|
*
|
|
142
142
|
* @throws Error if validation fails
|
|
143
143
|
*
|
|
@@ -154,9 +154,10 @@ export class SavingsOperations {
|
|
|
154
154
|
SavingsValidation.validateAccountIndex(accountIndex);
|
|
155
155
|
SavingsValidation.validateWalletIndex(walletIndex);
|
|
156
156
|
|
|
157
|
-
// Derive pocket private key
|
|
157
|
+
// Derive pocket private key (account 0 is main wallet, so pockets start at +1)
|
|
158
|
+
const pocketIndex = accountIndex + 1;
|
|
158
159
|
const seed = mnemonicToSeed(mnemonic);
|
|
159
|
-
const { privateKey } = EVMDeriveChildPrivateKey(seed, walletIndex, `m/44'/60'/${
|
|
160
|
+
const { privateKey } = EVMDeriveChildPrivateKey(seed, walletIndex, `m/44'/60'/${pocketIndex}'/0/`);
|
|
160
161
|
|
|
161
162
|
// Create wallet
|
|
162
163
|
const wallet = new Wallet(privateKey, provider);
|
|
@@ -473,7 +474,7 @@ export class SavingsOperations {
|
|
|
473
474
|
* Get the address of a savings pocket without storing credentials
|
|
474
475
|
*
|
|
475
476
|
* @param mnemonic - BIP-39 mnemonic phrase
|
|
476
|
-
* @param accountIndex -
|
|
477
|
+
* @param accountIndex - Pocket index (0-based)
|
|
477
478
|
* @param walletIndex - Wallet index (default: 0)
|
|
478
479
|
* @returns Pocket address
|
|
479
480
|
*
|
|
@@ -495,9 +496,10 @@ export class SavingsOperations {
|
|
|
495
496
|
SavingsValidation.validateAccountIndex(accountIndex);
|
|
496
497
|
SavingsValidation.validateWalletIndex(walletIndex);
|
|
497
498
|
|
|
498
|
-
// Derive address
|
|
499
|
+
// Derive address (account 0 is main wallet, so pockets start at +1)
|
|
500
|
+
const pocketIndex = accountIndex + 1;
|
|
499
501
|
const seed = mnemonicToSeed(mnemonic);
|
|
500
|
-
const { privateKey } = EVMDeriveChildPrivateKey(seed, walletIndex, `m/44'/60'/${
|
|
502
|
+
const { privateKey } = EVMDeriveChildPrivateKey(seed, walletIndex, `m/44'/60'/${pocketIndex}'/0/`);
|
|
501
503
|
const wallet = new Wallet(privateKey);
|
|
502
504
|
const address = wallet.address;
|
|
503
505
|
|
|
@@ -98,6 +98,13 @@ export class SVMSavingsManager extends SavingsManager<PublicKey, Connection, Key
|
|
|
98
98
|
this._client = undefined;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
private toSafeNumberAmount(amount: bigint, label: string): number {
|
|
102
|
+
if (amount > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
103
|
+
throw new Error(`${label} exceeds Number.MAX_SAFE_INTEGER and cannot be represented safely: ${amount}`);
|
|
104
|
+
}
|
|
105
|
+
return Number(amount);
|
|
106
|
+
}
|
|
107
|
+
|
|
101
108
|
/**
|
|
102
109
|
* Derive a savings pocket at the specified account index
|
|
103
110
|
*
|
|
@@ -110,9 +117,10 @@ export class SVMSavingsManager extends SavingsManager<PublicKey, Connection, Key
|
|
|
110
117
|
|
|
111
118
|
// Add 1 to preserve index 0 for main wallet
|
|
112
119
|
const pocketIndex = accountIndex + 1;
|
|
113
|
-
const
|
|
120
|
+
const derivationPathBase = `${this.derivationPathBase}${pocketIndex}'/0/`;
|
|
121
|
+
const derivationPath = `${derivationPathBase}${this.walletIndex}'`;
|
|
114
122
|
const seed = mnemonicToSeed(this.mnemonic);
|
|
115
|
-
const keypair = SVMDeriveChildPrivateKey(seed, this.walletIndex,
|
|
123
|
+
const keypair = SVMDeriveChildPrivateKey(seed, this.walletIndex, derivationPathBase);
|
|
116
124
|
|
|
117
125
|
const pocket: Pocket<PublicKey> = {
|
|
118
126
|
privateKey: keypair,
|
|
@@ -132,9 +140,10 @@ export class SVMSavingsManager extends SavingsManager<PublicKey, Connection, Key
|
|
|
132
140
|
*/
|
|
133
141
|
getMainWallet() {
|
|
134
142
|
this.checkNotDisposed();
|
|
135
|
-
const
|
|
143
|
+
const derivationPathBase = `${this.derivationPathBase}0'/0/`;
|
|
144
|
+
const derivationPath = `${derivationPathBase}${this.walletIndex}'`;
|
|
136
145
|
const seed = mnemonicToSeed(this.mnemonic);
|
|
137
|
-
const keypair = SVMDeriveChildPrivateKey(seed, this.walletIndex,
|
|
146
|
+
const keypair = SVMDeriveChildPrivateKey(seed, this.walletIndex, derivationPathBase);
|
|
138
147
|
|
|
139
148
|
return {
|
|
140
149
|
privateKey: keypair,
|
|
@@ -262,7 +271,7 @@ export class SVMSavingsManager extends SavingsManager<PublicKey, Connection, Key
|
|
|
262
271
|
const tx = await getTransferNativeTransaction(
|
|
263
272
|
mainWallet,
|
|
264
273
|
pocket.address,
|
|
265
|
-
|
|
274
|
+
this.toSafeNumberAmount(amount, 'Native transfer amount'),
|
|
266
275
|
this.client
|
|
267
276
|
);
|
|
268
277
|
|
|
@@ -297,7 +306,7 @@ export class SVMSavingsManager extends SavingsManager<PublicKey, Connection, Key
|
|
|
297
306
|
mainWallet,
|
|
298
307
|
pocket.address,
|
|
299
308
|
tokenInfo as any, // TokenInfo type
|
|
300
|
-
|
|
309
|
+
this.toSafeNumberAmount(amount, 'Token transfer amount'),
|
|
301
310
|
this.client
|
|
302
311
|
);
|
|
303
312
|
|
|
@@ -365,7 +374,7 @@ export class SVMSavingsManager extends SavingsManager<PublicKey, Connection, Key
|
|
|
365
374
|
const tx = await getTransferNativeTransaction(
|
|
366
375
|
pocket.privateKey,
|
|
367
376
|
mainWalletAddress,
|
|
368
|
-
|
|
377
|
+
this.toSafeNumberAmount(amount, 'Native withdrawal amount'),
|
|
369
378
|
this.client
|
|
370
379
|
);
|
|
371
380
|
const hash = await signAndSendTransaction(tx, this.client, pocket.privateKey);
|
|
@@ -376,7 +385,7 @@ export class SVMSavingsManager extends SavingsManager<PublicKey, Connection, Key
|
|
|
376
385
|
pocket.privateKey,
|
|
377
386
|
mainWalletAddress,
|
|
378
387
|
token as any, // TokenInfo type
|
|
379
|
-
|
|
388
|
+
this.toSafeNumberAmount(amount, 'Token withdrawal amount'),
|
|
380
389
|
this.client
|
|
381
390
|
);
|
|
382
391
|
|
package/utils/test.ts
CHANGED
|
@@ -399,7 +399,7 @@ const testAddressClass = async () => {
|
|
|
399
399
|
const evmAddressClass = new EVMChainAddress(evmChainConfig, "0xC9C1D854b82BA9b4FB6f6D58E9EF3d1fAEd601AA", 0)
|
|
400
400
|
const res = await evmAddressClass.getNativeBalance()
|
|
401
401
|
console.log('res: ', res);
|
|
402
|
-
|
|
402
|
+
SVMVM.generateMnemonicFromPrivateKey
|
|
403
403
|
// const svmAddressClass = new SVMChainAddress( )
|
|
404
404
|
}
|
|
405
405
|
testAddressClass()
|
package/utils/types.ts
CHANGED
|
@@ -25,7 +25,8 @@ export interface ChainWalletConfig {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface ChainSavingConfig {
|
|
28
|
-
|
|
28
|
+
supported: boolean; // Whether savings tracking is supported on this chain
|
|
29
|
+
tokens: TokenInfo[]; // List of token addresses to track for savings
|
|
29
30
|
|
|
30
31
|
}
|
|
31
32
|
|
package/utils/utils.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import { EVMVM
|
|
1
|
+
import { EVMVM } from "./evm";
|
|
2
2
|
import { SVMVM } from "./svm";
|
|
3
3
|
import { ChainWalletConfig, vmTypes } from "./types";
|
|
4
4
|
import { sha256 } from "@noble/hashes/sha2";
|
|
5
|
+
import { Connection, PublicKey } from "@solana/web3.js";
|
|
6
|
+
import { createPublicClientFromChainConfig, discoverTokens as discoverEvmTokens, getNativeBalance as getEvmNativeBalance, getTokenBalance as getEvmTokenBalance } from "./evm/utils";
|
|
7
|
+
import { discoverTokens as discoverSvmTokens, getSvmNativeBalance } from "./svm/utils";
|
|
8
|
+
import { Hex } from "viem";
|
|
9
|
+
import BN from "bn.js";
|
|
10
|
+
import { fetchPrices } from "./price";
|
|
5
11
|
|
|
6
12
|
|
|
7
13
|
export const getPrivateKeyFromAnother = (privateKey: any, fromVm: vmTypes, toVm: vmTypes) => {
|
|
@@ -20,8 +26,248 @@ export const getPrivateKeyFromAnother = (privateKey: any, fromVm: vmTypes, toVm:
|
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
export interface AddressPortfolioItem {
|
|
30
|
+
tokenAddress: string | "native";
|
|
31
|
+
symbol: string;
|
|
32
|
+
decimals: number;
|
|
33
|
+
balanceRaw: string;
|
|
34
|
+
balanceFormatted: number;
|
|
35
|
+
priceUsd: number | null;
|
|
36
|
+
valueUsd: number | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AddressPortfolioResult {
|
|
40
|
+
address: string;
|
|
41
|
+
vmType: vmTypes;
|
|
42
|
+
chainId: number;
|
|
43
|
+
items: AddressPortfolioItem[];
|
|
44
|
+
totals: {
|
|
45
|
+
valueUsd: number;
|
|
46
|
+
pricedItems: number;
|
|
47
|
+
unpricedItems: number;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface AddressPortfolioParams {
|
|
52
|
+
chain: ChainWalletConfig;
|
|
53
|
+
address: string;
|
|
54
|
+
vmType?: vmTypes;
|
|
55
|
+
includeNative?: boolean;
|
|
56
|
+
tokenAddresses?: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const EVM_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/;
|
|
60
|
+
|
|
61
|
+
export const detectVmTypeFromAddress = (address: string): vmTypes => {
|
|
62
|
+
if (EVM_ADDRESS_REGEX.test(address)) {
|
|
63
|
+
return "EVM";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
new PublicKey(address);
|
|
68
|
+
return "SVM";
|
|
69
|
+
} catch {
|
|
70
|
+
throw new Error(`Could not infer VM type from address: ${address}`);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const normalizePriceMap = (prices: Array<{ tokenAddress: string; price: number }>) => {
|
|
75
|
+
const priceMap = new Map<string, number>();
|
|
76
|
+
for (const item of prices) {
|
|
77
|
+
if (!item?.tokenAddress || typeof item.price !== "number") continue;
|
|
78
|
+
priceMap.set(item.tokenAddress.toLowerCase(), item.price);
|
|
79
|
+
}
|
|
80
|
+
return priceMap;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const enrichWithUsdValues = (
|
|
84
|
+
items: Omit<AddressPortfolioItem, "priceUsd" | "valueUsd">[],
|
|
85
|
+
priceMap: Map<string, number>
|
|
86
|
+
): AddressPortfolioResult["items"] => {
|
|
87
|
+
return items.map((item) => {
|
|
88
|
+
const key = String(item.tokenAddress).toLowerCase();
|
|
89
|
+
const priceUsd = priceMap.get(key) ?? null;
|
|
90
|
+
const valueUsd = priceUsd === null ? null : item.balanceFormatted * priceUsd;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
...item,
|
|
94
|
+
priceUsd,
|
|
95
|
+
valueUsd,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const buildTotals = (items: AddressPortfolioItem[]) => {
|
|
101
|
+
let valueUsd = 0;
|
|
102
|
+
let pricedItems = 0;
|
|
103
|
+
let unpricedItems = 0;
|
|
104
|
+
|
|
105
|
+
for (const item of items) {
|
|
106
|
+
if (item.valueUsd === null) {
|
|
107
|
+
unpricedItems += 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
pricedItems += 1;
|
|
111
|
+
valueUsd += item.valueUsd;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { valueUsd, pricedItems, unpricedItems };
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const getAddressPortfolioValue = async (params: AddressPortfolioParams): Promise<AddressPortfolioResult> => {
|
|
118
|
+
const { chain, address, includeNative = true, tokenAddresses } = params;
|
|
119
|
+
const vmType = params.vmType ?? detectVmTypeFromAddress(address);
|
|
120
|
+
|
|
121
|
+
if (vmType === "EVM" && !EVM_ADDRESS_REGEX.test(address)) {
|
|
122
|
+
throw new Error(`Invalid EVM address: ${address}`);
|
|
123
|
+
}
|
|
124
|
+
if (vmType === "SVM") {
|
|
125
|
+
// Throws if invalid
|
|
126
|
+
new PublicKey(address);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (vmType === "EVM") {
|
|
130
|
+
const client = createPublicClientFromChainConfig(chain);
|
|
131
|
+
|
|
132
|
+
const items: Omit<AddressPortfolioItem, "priceUsd" | "valueUsd">[] = [];
|
|
133
|
+
const priceTargets = new Set<string>();
|
|
134
|
+
|
|
135
|
+
if (includeNative) {
|
|
136
|
+
const nativeBalance = await getEvmNativeBalance(address as Hex, client);
|
|
137
|
+
const nativeRaw = nativeBalance.balance.toString();
|
|
138
|
+
items.push({
|
|
139
|
+
tokenAddress: "native",
|
|
140
|
+
symbol: chain.nativeToken.symbol,
|
|
141
|
+
decimals: chain.nativeToken.decimals,
|
|
142
|
+
balanceRaw: nativeRaw,
|
|
143
|
+
balanceFormatted: nativeBalance.formatted,
|
|
144
|
+
});
|
|
145
|
+
priceTargets.add("native");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (tokenAddresses && tokenAddresses.length > 0) {
|
|
149
|
+
const tokenResults = await Promise.all(
|
|
150
|
+
tokenAddresses.map(async (tokenAddress) => {
|
|
151
|
+
const balance = await getEvmTokenBalance(tokenAddress as Hex, address as Hex, client);
|
|
152
|
+
const raw = balance.balance.toString();
|
|
153
|
+
return {
|
|
154
|
+
tokenAddress,
|
|
155
|
+
symbol: tokenAddress,
|
|
156
|
+
decimals: balance.decimal,
|
|
157
|
+
balanceRaw: raw,
|
|
158
|
+
balanceFormatted: balance.formatted,
|
|
159
|
+
};
|
|
160
|
+
})
|
|
161
|
+
);
|
|
162
|
+
for (const token of tokenResults) {
|
|
163
|
+
items.push(token);
|
|
164
|
+
priceTargets.add(token.tokenAddress);
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
const discovered = await discoverEvmTokens(address, chain);
|
|
168
|
+
for (const token of discovered) {
|
|
169
|
+
const raw = String(token.balance);
|
|
170
|
+
const decimals = token.decimals ?? 0;
|
|
171
|
+
const rawBn = new BN(raw);
|
|
172
|
+
const divisor = Math.pow(10, decimals);
|
|
173
|
+
const formatted = divisor === 0 ? 0 : Number(rawBn.toString()) / divisor;
|
|
174
|
+
|
|
175
|
+
items.push({
|
|
176
|
+
tokenAddress: token.address,
|
|
177
|
+
symbol: token.symbol,
|
|
178
|
+
decimals,
|
|
179
|
+
balanceRaw: raw,
|
|
180
|
+
balanceFormatted: formatted,
|
|
181
|
+
});
|
|
182
|
+
priceTargets.add(token.address);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const priceResult = await fetchPrices({
|
|
187
|
+
vm: "EVM",
|
|
188
|
+
chainId: chain.chainId,
|
|
189
|
+
tokenAddresses: Array.from(priceTargets),
|
|
190
|
+
});
|
|
191
|
+
const priceMap = normalizePriceMap(priceResult.data?.prices ?? []);
|
|
192
|
+
const enrichedItems = enrichWithUsdValues(items, priceMap);
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
address,
|
|
196
|
+
vmType,
|
|
197
|
+
chainId: chain.chainId,
|
|
198
|
+
items: enrichedItems,
|
|
199
|
+
totals: buildTotals(enrichedItems),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const svmAddress = new PublicKey(address);
|
|
204
|
+
const connection = new Connection(chain.rpcUrl);
|
|
205
|
+
const items: Omit<AddressPortfolioItem, "priceUsd" | "valueUsd">[] = [];
|
|
206
|
+
const priceTargets = new Set<string>();
|
|
207
|
+
|
|
208
|
+
if (includeNative) {
|
|
209
|
+
const nativeBalance = await getSvmNativeBalance(svmAddress, connection);
|
|
210
|
+
items.push({
|
|
211
|
+
tokenAddress: "native",
|
|
212
|
+
symbol: chain.nativeToken.symbol,
|
|
213
|
+
decimals: chain.nativeToken.decimals,
|
|
214
|
+
balanceRaw: nativeBalance.balance.toString(),
|
|
215
|
+
balanceFormatted: nativeBalance.formatted,
|
|
216
|
+
});
|
|
217
|
+
priceTargets.add("native");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (tokenAddresses && tokenAddresses.length > 0) {
|
|
221
|
+
const tokenResults = await Promise.all(
|
|
222
|
+
tokenAddresses.map(async (tokenAddress) => {
|
|
223
|
+
const pubkey = new PublicKey(tokenAddress);
|
|
224
|
+
const tokenBalance = await SVMVM.getTokenBalance(svmAddress, pubkey, connection);
|
|
225
|
+
return {
|
|
226
|
+
tokenAddress,
|
|
227
|
+
symbol: tokenAddress,
|
|
228
|
+
decimals: tokenBalance.decimal,
|
|
229
|
+
balanceRaw: tokenBalance.balance.toString(),
|
|
230
|
+
balanceFormatted: tokenBalance.formatted,
|
|
231
|
+
};
|
|
232
|
+
})
|
|
233
|
+
);
|
|
234
|
+
for (const token of tokenResults) {
|
|
235
|
+
items.push(token);
|
|
236
|
+
priceTargets.add(token.tokenAddress);
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
const discovered = await discoverSvmTokens(svmAddress, connection);
|
|
240
|
+
for (const token of discovered) {
|
|
241
|
+
const raw = String(token.balance);
|
|
242
|
+
const decimals = token.decimals ?? 0;
|
|
243
|
+
const rawBn = new BN(raw);
|
|
244
|
+
const divisor = Math.pow(10, decimals);
|
|
245
|
+
const formatted = divisor === 0 ? 0 : Number(rawBn.toString()) / divisor;
|
|
246
|
+
|
|
247
|
+
items.push({
|
|
248
|
+
tokenAddress: token.address,
|
|
249
|
+
symbol: token.symbol,
|
|
250
|
+
decimals,
|
|
251
|
+
balanceRaw: raw,
|
|
252
|
+
balanceFormatted: formatted,
|
|
253
|
+
});
|
|
254
|
+
priceTargets.add(token.address);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
24
257
|
|
|
25
|
-
|
|
26
|
-
|
|
258
|
+
const priceResult = await fetchPrices({
|
|
259
|
+
vm: "SVM",
|
|
260
|
+
chainId: chain.chainId,
|
|
261
|
+
tokenAddresses: Array.from(priceTargets),
|
|
262
|
+
});
|
|
263
|
+
const priceMap = normalizePriceMap(priceResult.data?.prices ?? []);
|
|
264
|
+
const enrichedItems = enrichWithUsdValues(items, priceMap);
|
|
27
265
|
|
|
266
|
+
return {
|
|
267
|
+
address,
|
|
268
|
+
vmType,
|
|
269
|
+
chainId: chain.chainId,
|
|
270
|
+
items: enrichedItems,
|
|
271
|
+
totals: buildTotals(enrichedItems),
|
|
272
|
+
};
|
|
273
|
+
};
|
package/utils/walletBip32.ts
CHANGED
|
@@ -137,7 +137,10 @@ export function EVMDeriveChildPrivateKey(seed: string, index: number, derivation
|
|
|
137
137
|
VMValidation.validateIndex(index, 'Wallet index');
|
|
138
138
|
// VMValidation.validateDerivationPath(derivationPath + index + "'", 'EVM');
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
// MetaMask-compatible EVM path uses unhardened address index:
|
|
141
|
+
// m/44'/60'/account'/change/address_index
|
|
142
|
+
// If a full path is passed (no trailing slash), use it as-is.
|
|
143
|
+
const path = derivationPath.endsWith("/") ? `${derivationPath}${index}` : derivationPath;
|
|
141
144
|
const scureNode = HDKey.fromMasterSeed(Buffer.from(seed, "hex"));
|
|
142
145
|
const child = scureNode.derive(path);
|
|
143
146
|
|
|
@@ -218,4 +221,4 @@ function hardenedDerivation(
|
|
|
218
221
|
key: hmacResult.slice(0, 32), // Left 32 bytes
|
|
219
222
|
chainCode: hmacResult.slice(32, 64) // Right 32 bytes
|
|
220
223
|
};
|
|
221
|
-
}
|
|
224
|
+
}
|