@deserialize/multi-vm-wallet 1.5.11 ā 1.5.21
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/MULTI_CHAIN_SAVINGS_PLAN.md +1028 -0
- package/MULTI_CHAIN_SAVINGS_USAGE.md +428 -0
- package/SAVINGS_TEST_EXAMPLES.md +411 -0
- package/dist/evm/evm.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +32 -2
- package/dist/savings/evm-savings.d.ts +52 -0
- package/dist/savings/evm-savings.js +159 -0
- package/dist/savings/index.d.ts +9 -1
- package/dist/savings/index.js +12 -1
- package/dist/savings/multi-chain-savings.d.ts +48 -0
- package/dist/savings/multi-chain-savings.js +203 -0
- package/dist/savings/savings-manager.d.ts +28 -0
- package/dist/savings/savings-manager.js +55 -0
- package/dist/savings/svm-savings.d.ts +40 -0
- package/dist/savings/svm-savings.js +176 -0
- package/dist/svm/index.d.ts +1 -0
- package/dist/svm/index.js +1 -0
- package/dist/test.js +111 -4
- package/package.json +1 -1
- package/utils/evm/evm.ts +1 -1
- package/utils/index.ts +12 -2
- package/utils/savings/evm-savings.ts +354 -0
- package/utils/savings/index.ts +18 -1
- package/utils/savings/multi-chain-savings.ts +458 -0
- package/utils/savings/savings-manager.ts +189 -0
- package/utils/savings/svm-savings.ts +394 -0
- package/utils/svm/index.ts +2 -1
- package/utils/test.ts +167 -8
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVM (Solana) Savings Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages savings pockets on Solana blockchain
|
|
5
|
+
* Uses BIP-44 coin type 501 (different addresses from EVM)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { SavingsManager, Pocket } from "./savings-manager";
|
|
9
|
+
import { Connection, PublicKey, Keypair, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
|
|
10
|
+
import { SVMDeriveChildPrivateKey, mnemonicToSeed } from "../walletBip32";
|
|
11
|
+
import { Balance, TransactionResult } from "../types";
|
|
12
|
+
import {
|
|
13
|
+
getSvmNativeBalance,
|
|
14
|
+
getTokenBalance as getSvmTokenBalance,
|
|
15
|
+
signAndSendTransaction,
|
|
16
|
+
getTransferNativeTransaction,
|
|
17
|
+
getTransferTokenTransaction
|
|
18
|
+
} from "../svm";
|
|
19
|
+
import { SavingsValidation } from "./validation";
|
|
20
|
+
import BN from "bn.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Solana Savings Manager
|
|
24
|
+
*
|
|
25
|
+
* Provides savings pocket functionality for Solana blockchain.
|
|
26
|
+
* Uses BIP-44 derivation path: m/44'/501'/{pocketIndex}'/0/{walletIndex}
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const manager = new SVMSavingsManager(
|
|
31
|
+
* mnemonic,
|
|
32
|
+
* 'https://api.mainnet-beta.solana.com',
|
|
33
|
+
* 0 // wallet index
|
|
34
|
+
* );
|
|
35
|
+
*
|
|
36
|
+
* // Get pocket
|
|
37
|
+
* const pocket = manager.getPocket(0);
|
|
38
|
+
* console.log(pocket.address.toBase58()); // Solana address
|
|
39
|
+
*
|
|
40
|
+
* // Get balances
|
|
41
|
+
* const balances = await manager.getPocketBalance(0, [usdcMint]);
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export class SVMSavingsManager extends SavingsManager<PublicKey, Connection, Keypair> {
|
|
45
|
+
coinType = 501;
|
|
46
|
+
derivationPathBase = "m/44'/501'/";
|
|
47
|
+
|
|
48
|
+
private rpcUrl: string;
|
|
49
|
+
private _client?: Connection;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a new SVMSavingsManager
|
|
53
|
+
*
|
|
54
|
+
* @param mnemonic - BIP-39 mnemonic phrase
|
|
55
|
+
* @param rpcUrl - Solana RPC endpoint URL
|
|
56
|
+
* @param walletIndex - Wallet index in derivation path (default: 0)
|
|
57
|
+
*/
|
|
58
|
+
constructor(
|
|
59
|
+
mnemonic: string,
|
|
60
|
+
rpcUrl: string,
|
|
61
|
+
walletIndex: number = 0
|
|
62
|
+
) {
|
|
63
|
+
super(mnemonic, walletIndex);
|
|
64
|
+
|
|
65
|
+
if (!rpcUrl || typeof rpcUrl !== 'string') {
|
|
66
|
+
throw new Error('RPC URL must be a non-empty string');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.rpcUrl = rpcUrl;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get or create the RPC client on-demand
|
|
74
|
+
*
|
|
75
|
+
* Lazy initialization allows the client to be garbage collected between operations.
|
|
76
|
+
*/
|
|
77
|
+
get client(): Connection {
|
|
78
|
+
if (!this._client) {
|
|
79
|
+
this._client = this.createClient(this.rpcUrl);
|
|
80
|
+
}
|
|
81
|
+
return this._client;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Create a Connection to Solana
|
|
86
|
+
*
|
|
87
|
+
* @param rpcUrl - RPC endpoint URL
|
|
88
|
+
* @returns Connection instance
|
|
89
|
+
*/
|
|
90
|
+
createClient(rpcUrl: string): Connection {
|
|
91
|
+
return new Connection(rpcUrl, 'confirmed');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Clear the cached RPC client
|
|
96
|
+
*/
|
|
97
|
+
clearClient(): void {
|
|
98
|
+
this._client = undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Derive a savings pocket at the specified account index
|
|
103
|
+
*
|
|
104
|
+
* @param accountIndex - Account index (0-based)
|
|
105
|
+
* @returns Pocket object with privateKey (Keypair), address (PublicKey), derivationPath, and index
|
|
106
|
+
*/
|
|
107
|
+
derivePocket(accountIndex: number): Pocket<PublicKey> {
|
|
108
|
+
this.checkNotDisposed();
|
|
109
|
+
SavingsValidation.validateAccountIndex(accountIndex);
|
|
110
|
+
|
|
111
|
+
// Add 1 to preserve index 0 for main wallet
|
|
112
|
+
const pocketIndex = accountIndex + 1;
|
|
113
|
+
const derivationPath = `${this.derivationPathBase}${pocketIndex}'/0/${this.walletIndex}`;
|
|
114
|
+
const seed = mnemonicToSeed(this.mnemonic);
|
|
115
|
+
const keypair = SVMDeriveChildPrivateKey(seed, this.walletIndex, derivationPath);
|
|
116
|
+
|
|
117
|
+
const pocket: Pocket<PublicKey> = {
|
|
118
|
+
privateKey: keypair,
|
|
119
|
+
address: keypair.publicKey,
|
|
120
|
+
derivationPath,
|
|
121
|
+
index: pocketIndex
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
this.pockets.set(accountIndex, pocket);
|
|
125
|
+
return pocket;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get the main wallet credentials
|
|
130
|
+
*
|
|
131
|
+
* @returns Main wallet object with privateKey (Keypair), address (PublicKey), and derivationPath
|
|
132
|
+
*/
|
|
133
|
+
getMainWallet() {
|
|
134
|
+
this.checkNotDisposed();
|
|
135
|
+
const derivationPath = `${this.derivationPathBase}0'/0/${this.walletIndex}`;
|
|
136
|
+
const seed = mnemonicToSeed(this.mnemonic);
|
|
137
|
+
const keypair = SVMDeriveChildPrivateKey(seed, this.walletIndex, derivationPath);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
privateKey: keypair,
|
|
141
|
+
address: keypair.publicKey,
|
|
142
|
+
derivationPath
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get the main wallet address
|
|
148
|
+
*
|
|
149
|
+
* @returns Main wallet PublicKey
|
|
150
|
+
*/
|
|
151
|
+
getMainWalletAddress(): PublicKey {
|
|
152
|
+
return this.getMainWallet().address;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get token balances for a specific pocket
|
|
157
|
+
*
|
|
158
|
+
* @param pocketIndex - Pocket index to check
|
|
159
|
+
* @param tokens - Array of SPL token mint addresses
|
|
160
|
+
* @returns Array of balance objects
|
|
161
|
+
*/
|
|
162
|
+
async getPocketBalance(pocketIndex: number, tokens: string[]): Promise<{
|
|
163
|
+
address: PublicKey | 'native';
|
|
164
|
+
balance: Balance;
|
|
165
|
+
}[]> {
|
|
166
|
+
SavingsValidation.validateAccountIndex(pocketIndex);
|
|
167
|
+
|
|
168
|
+
if (!Array.isArray(tokens)) {
|
|
169
|
+
throw new Error('Tokens must be an array');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const pocket = this.getPocket(pocketIndex);
|
|
173
|
+
const balances: { address: PublicKey | 'native'; balance: Balance }[] = [];
|
|
174
|
+
|
|
175
|
+
// Get native SOL balance
|
|
176
|
+
const nativeBalance = await getSvmNativeBalance(pocket.address, this.client);
|
|
177
|
+
balances.push({ address: 'native', balance: nativeBalance });
|
|
178
|
+
|
|
179
|
+
// Get SPL token balances
|
|
180
|
+
await Promise.all(tokens.map(async (token) => {
|
|
181
|
+
try {
|
|
182
|
+
const tokenPubkey = new PublicKey(token);
|
|
183
|
+
const tokenBalanceData = await getSvmTokenBalance(pocket.address, tokenPubkey, this.client);
|
|
184
|
+
|
|
185
|
+
// Handle the case where getTokenBalance returns 0 or TokenAmount
|
|
186
|
+
if (tokenBalanceData === 0) {
|
|
187
|
+
balances.push({
|
|
188
|
+
address: tokenPubkey,
|
|
189
|
+
balance: { balance: new BN(0), formatted: 0, decimal: 0 }
|
|
190
|
+
});
|
|
191
|
+
} else {
|
|
192
|
+
const balance: Balance = {
|
|
193
|
+
balance: new BN(tokenBalanceData.amount),
|
|
194
|
+
formatted: tokenBalanceData.uiAmount || 0,
|
|
195
|
+
decimal: tokenBalanceData.decimals
|
|
196
|
+
};
|
|
197
|
+
balances.push({ address: tokenPubkey, balance });
|
|
198
|
+
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
// Token account might not exist, push zero balance
|
|
201
|
+
balances.push({
|
|
202
|
+
address: new PublicKey(token),
|
|
203
|
+
balance: { balance: new BN(0), formatted: 0, decimal: 0 }
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}));
|
|
207
|
+
|
|
208
|
+
return balances;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get balances for multiple pockets
|
|
213
|
+
*
|
|
214
|
+
* @param tokens - Array of token mint addresses
|
|
215
|
+
* @param pockets - Array of pocket indices
|
|
216
|
+
* @returns Array of balance arrays per pocket
|
|
217
|
+
*/
|
|
218
|
+
async getTotalTokenBalanceOfAllPockets(
|
|
219
|
+
tokens: string[],
|
|
220
|
+
pockets: number[]
|
|
221
|
+
): Promise<Array<{ address: PublicKey | 'native'; balance: Balance; }[]>> {
|
|
222
|
+
if (!Array.isArray(tokens) || tokens.length === 0) {
|
|
223
|
+
throw new Error('Tokens array must be non-empty');
|
|
224
|
+
}
|
|
225
|
+
if (!Array.isArray(pockets) || pockets.length === 0) {
|
|
226
|
+
throw new Error('Pockets array must be non-empty');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Validate all pocket indices
|
|
230
|
+
pockets.forEach((pocket) => {
|
|
231
|
+
SavingsValidation.validateAccountIndex(pocket);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Fetch balances for all pockets in parallel
|
|
235
|
+
const allBalances = await Promise.all(
|
|
236
|
+
pockets.map((p: number) => this.getPocketBalance(p, tokens))
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
return allBalances;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Transfer SOL from main wallet to a pocket
|
|
244
|
+
*
|
|
245
|
+
* @param mainWallet - Keypair for the main wallet
|
|
246
|
+
* @param pocketIndex - Destination pocket index
|
|
247
|
+
* @param amount - Amount to transfer in lamports
|
|
248
|
+
* @returns Transaction result
|
|
249
|
+
*/
|
|
250
|
+
async transferToPocket(
|
|
251
|
+
mainWallet: Keypair,
|
|
252
|
+
pocketIndex: number,
|
|
253
|
+
amount: bigint
|
|
254
|
+
): Promise<TransactionResult> {
|
|
255
|
+
SavingsValidation.validateAccountIndex(pocketIndex);
|
|
256
|
+
|
|
257
|
+
if (typeof amount !== 'bigint' || amount <= 0n) {
|
|
258
|
+
throw new Error(`Amount must be a positive bigint, got: ${amount}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const pocket = this.getPocket(pocketIndex);
|
|
262
|
+
const tx = await getTransferNativeTransaction(
|
|
263
|
+
mainWallet,
|
|
264
|
+
pocket.address,
|
|
265
|
+
Number(amount),
|
|
266
|
+
this.client
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const hash = await signAndSendTransaction(tx, this.client, mainWallet);
|
|
270
|
+
return { success: true, hash };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Transfer SPL tokens from main wallet to a pocket
|
|
275
|
+
*
|
|
276
|
+
* @param mainWallet - Keypair for the main wallet
|
|
277
|
+
* @param tokenInfo - SPL token information (address, decimals, etc.)
|
|
278
|
+
* @param pocketIndex - Destination pocket index
|
|
279
|
+
* @param amount - Amount to transfer (in token base units)
|
|
280
|
+
* @returns Transaction result
|
|
281
|
+
*/
|
|
282
|
+
async transferTokenToPocket(
|
|
283
|
+
mainWallet: Keypair,
|
|
284
|
+
tokenInfo: { address: string; decimals: number },
|
|
285
|
+
pocketIndex: number,
|
|
286
|
+
amount: bigint
|
|
287
|
+
): Promise<TransactionResult> {
|
|
288
|
+
SavingsValidation.validateAccountIndex(pocketIndex);
|
|
289
|
+
|
|
290
|
+
if (typeof amount !== 'bigint' || amount <= 0n) {
|
|
291
|
+
throw new Error(`Amount must be a positive bigint, got: ${amount}`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const pocket = this.getPocket(pocketIndex);
|
|
295
|
+
|
|
296
|
+
const tx = await getTransferTokenTransaction(
|
|
297
|
+
mainWallet,
|
|
298
|
+
pocket.address,
|
|
299
|
+
tokenInfo as any, // TokenInfo type
|
|
300
|
+
Number(amount),
|
|
301
|
+
this.client
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const hash = await signAndSendTransaction(tx, this.client, mainWallet);
|
|
305
|
+
return { success: true, hash };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get Keypair from pocket (for signing transactions)
|
|
310
|
+
*
|
|
311
|
+
* @param pocketIndex - Pocket index
|
|
312
|
+
* @returns Keypair instance
|
|
313
|
+
*/
|
|
314
|
+
accountFromPocketId(pocketIndex: number): Keypair {
|
|
315
|
+
const pocket = this.getPocket(pocketIndex);
|
|
316
|
+
return pocket.privateKey;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Verify that a stored pocket address matches the derived address
|
|
321
|
+
*
|
|
322
|
+
* @param accountIndex - Pocket index
|
|
323
|
+
* @param storedAddress - Address to verify (base58 string)
|
|
324
|
+
* @returns true if addresses match
|
|
325
|
+
*/
|
|
326
|
+
verifyPocketAddress(accountIndex: number, storedAddress: string): boolean {
|
|
327
|
+
SavingsValidation.validateAccountIndex(accountIndex);
|
|
328
|
+
|
|
329
|
+
if (!storedAddress || typeof storedAddress !== 'string') {
|
|
330
|
+
throw new Error('Stored address must be a non-empty string');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
const pocket = this.getPocket(accountIndex);
|
|
335
|
+
const storedPubkey = new PublicKey(storedAddress);
|
|
336
|
+
return pocket.address.equals(storedPubkey);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Send tokens from a pocket back to the main wallet
|
|
344
|
+
*
|
|
345
|
+
* @param pocketIndex - Source pocket index
|
|
346
|
+
* @param amount - Amount to send (in base units)
|
|
347
|
+
* @param token - Token info object or "native" for SOL
|
|
348
|
+
* @returns Transaction result
|
|
349
|
+
*/
|
|
350
|
+
async sendToMainWallet(
|
|
351
|
+
pocketIndex: number,
|
|
352
|
+
amount: bigint,
|
|
353
|
+
token: { address: string; decimals: number } | "native"
|
|
354
|
+
): Promise<TransactionResult> {
|
|
355
|
+
SavingsValidation.validateAccountIndex(pocketIndex);
|
|
356
|
+
|
|
357
|
+
if (typeof amount !== 'bigint' || amount <= 0n) {
|
|
358
|
+
throw new Error(`Amount must be a positive bigint, got: ${amount}`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const pocket = this.getPocket(pocketIndex);
|
|
362
|
+
const mainWalletAddress = this.getMainWalletAddress();
|
|
363
|
+
|
|
364
|
+
if (token === "native") {
|
|
365
|
+
const tx = await getTransferNativeTransaction(
|
|
366
|
+
pocket.privateKey,
|
|
367
|
+
mainWalletAddress,
|
|
368
|
+
Number(amount),
|
|
369
|
+
this.client
|
|
370
|
+
);
|
|
371
|
+
const hash = await signAndSendTransaction(tx, this.client, pocket.privateKey);
|
|
372
|
+
return { success: true, hash };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const tx = await getTransferTokenTransaction(
|
|
376
|
+
pocket.privateKey,
|
|
377
|
+
mainWalletAddress,
|
|
378
|
+
token as any, // TokenInfo type
|
|
379
|
+
Number(amount),
|
|
380
|
+
this.client
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const hash = await signAndSendTransaction(tx, this.client, pocket.privateKey);
|
|
384
|
+
return { success: true, hash };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Dispose and clear all resources
|
|
389
|
+
*/
|
|
390
|
+
dispose(): void {
|
|
391
|
+
super.dispose();
|
|
392
|
+
this.clearClient();
|
|
393
|
+
}
|
|
394
|
+
}
|
package/utils/svm/index.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from "./svm"
|
|
1
|
+
export * from "./svm"
|
|
2
|
+
export * from "./utils"
|
package/utils/test.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { entryPoint07Address } from "viem/account-abstraction";
|
|
|
22
22
|
import { KERNEL_V3_3, KernelVersionToAddressesMap } from '@zerodev/sdk/constants';
|
|
23
23
|
import { deserializeSessionKey } from "./evm/aa-service";
|
|
24
24
|
import { toSpendingLimitHook } from "@zerodev/hooks"
|
|
25
|
-
import {
|
|
25
|
+
import { EVMSavingsManager, SVMSavingsManager, MultiChainSavingsManager } from "./savings";
|
|
26
26
|
// const mnemonic = GenerateNewMnemonic()
|
|
27
27
|
|
|
28
28
|
|
|
@@ -212,27 +212,186 @@ const testPrice = async () => {
|
|
|
212
212
|
walletA.getPrices(['0x98d0baa52b2D063E780DE12F615f963Fe8537553']).then(console.log)
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
// ============================================
|
|
216
|
+
// EVM Savings Pocket Test
|
|
217
|
+
// ============================================
|
|
215
218
|
const testSavingsPocket = async () => {
|
|
219
|
+
console.log('\n========== EVM Savings Test ==========');
|
|
220
|
+
const mnemonic = EVMVM.generateMnemonicFromPrivateKey(evmPrivateKeyExposed)
|
|
221
|
+
console.log('Mnemonic:', mnemonic);
|
|
222
|
+
|
|
223
|
+
// Using new EVMSavingsManager (multi-chain compatible)
|
|
224
|
+
const savingsManager = new EVMSavingsManager(mnemonic, evmChainConfig, wallet.index)
|
|
216
225
|
|
|
226
|
+
const pocket0 = savingsManager.getPocket(0)
|
|
227
|
+
console.log('\nPocket 0 Info:');
|
|
228
|
+
console.log(' Address:', pocket0.address);
|
|
229
|
+
console.log(' Derivation path:', pocket0.derivationPath);
|
|
230
|
+
console.log(' Index:', pocket0.index);
|
|
231
|
+
|
|
232
|
+
// Uncomment to test transfer
|
|
233
|
+
// const deposited = await savingsManager.transferToPocket(walletA.wallet, 0, "0.00001")
|
|
234
|
+
// console.log('deposited: ', deposited);
|
|
217
235
|
|
|
236
|
+
// Get balance (updated API: getPocketBalance(pocketIndex, tokens[]))
|
|
237
|
+
const balance = await savingsManager.getPocketBalance(0, [])
|
|
238
|
+
console.log('\nPocket 0 Balance:');
|
|
239
|
+
console.log(' Native balance:', balance[0].balance.formatted, 'ETH');
|
|
240
|
+
|
|
241
|
+
// Get multiple pockets
|
|
242
|
+
const pocket1 = savingsManager.getPocket(1)
|
|
243
|
+
const pocket2 = savingsManager.getPocket(2)
|
|
244
|
+
console.log('\nAdditional Pockets:');
|
|
245
|
+
console.log(' Pocket 1:', pocket1.address);
|
|
246
|
+
console.log(' Pocket 2:', pocket2.address);
|
|
247
|
+
|
|
248
|
+
// Cleanup
|
|
249
|
+
savingsManager.dispose();
|
|
250
|
+
console.log('\nā
EVM Savings Test Complete\n');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ============================================
|
|
254
|
+
// SVM (Solana) Savings Pocket Test
|
|
255
|
+
// ============================================
|
|
256
|
+
const testSavingsPocketSVM = async () => {
|
|
257
|
+
console.log('\n========== SVM (Solana) Savings Test ==========');
|
|
218
258
|
const mnemonic = EVMVM.generateMnemonicFromPrivateKey(evmPrivateKeyExposed)
|
|
219
|
-
console.log('
|
|
259
|
+
console.log('Mnemonic:', mnemonic);
|
|
220
260
|
|
|
221
|
-
|
|
261
|
+
// Using SVMSavingsManager for Solana
|
|
262
|
+
const savingsManager = new SVMSavingsManager(
|
|
263
|
+
mnemonic,
|
|
264
|
+
chainConfig.rpcUrl,
|
|
265
|
+
0 // wallet index
|
|
266
|
+
)
|
|
222
267
|
|
|
223
268
|
const pocket0 = savingsManager.getPocket(0)
|
|
269
|
+
console.log('\nPocket 0 Info:');
|
|
270
|
+
console.log(' Address:', pocket0.address.toBase58());
|
|
271
|
+
console.log(' Derivation path:', pocket0.derivationPath);
|
|
272
|
+
console.log(' Index:', pocket0.index);
|
|
273
|
+
|
|
274
|
+
// Get balance
|
|
275
|
+
const balance = await savingsManager.getPocketBalance(0, [])
|
|
276
|
+
console.log('\nPocket 0 Balance:');
|
|
277
|
+
console.log(' Native balance:', balance[0].balance.formatted, 'SOL');
|
|
278
|
+
|
|
279
|
+
// Get multiple pockets
|
|
280
|
+
const pocket1 = savingsManager.getPocket(1)
|
|
281
|
+
const pocket2 = savingsManager.getPocket(2)
|
|
282
|
+
console.log('\nAdditional Pockets:');
|
|
283
|
+
console.log(' Pocket 1:', pocket1.address.toBase58());
|
|
284
|
+
console.log(' Pocket 2:', pocket2.address.toBase58());
|
|
285
|
+
|
|
286
|
+
// Note: Solana addresses are DIFFERENT from EVM addresses!
|
|
287
|
+
console.log('\nš” Note: Solana uses coin type 501, so addresses differ from EVM');
|
|
288
|
+
|
|
289
|
+
// Cleanup
|
|
290
|
+
savingsManager.dispose();
|
|
291
|
+
console.log('\nā
SVM Savings Test Complete\n');
|
|
292
|
+
}
|
|
224
293
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
294
|
+
// ============================================
|
|
295
|
+
// Multi-Chain Savings Test
|
|
296
|
+
// ============================================
|
|
297
|
+
const testSavingsPocketMultiChain = async () => {
|
|
298
|
+
console.log('\n========== Multi-Chain Savings Test ==========');
|
|
299
|
+
const mnemonic = EVMVM.generateMnemonicFromPrivateKey(evmPrivateKeyExposed)
|
|
300
|
+
console.log('Mnemonic:', mnemonic);
|
|
229
301
|
|
|
230
|
-
//
|
|
302
|
+
// Create multi-chain manager with EVM and Solana
|
|
303
|
+
const multiChainManager = new MultiChainSavingsManager(
|
|
304
|
+
mnemonic,
|
|
305
|
+
[
|
|
306
|
+
{
|
|
307
|
+
id: 'base',
|
|
308
|
+
type: 'EVM',
|
|
309
|
+
config: evmChainConfig
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: 'ethereum',
|
|
313
|
+
type: 'EVM',
|
|
314
|
+
config: {
|
|
315
|
+
chainId: 1,
|
|
316
|
+
name: 'Ethereum',
|
|
317
|
+
rpcUrl: 'https://eth-mainnet.g.alchemy.com/v2/demo',
|
|
318
|
+
explorerUrl: 'https://etherscan.io',
|
|
319
|
+
nativeToken: { name: 'Ethereum', symbol: 'ETH', decimals: 18 },
|
|
320
|
+
confirmationNo: 1,
|
|
321
|
+
vmType: 'EVM'
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
id: 'solana',
|
|
326
|
+
type: 'SVM',
|
|
327
|
+
config: {
|
|
328
|
+
rpcUrl: chainConfig.rpcUrl
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
],
|
|
332
|
+
0 // wallet index
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
console.log('\nAvailable chains:', multiChainManager.getChains());
|
|
336
|
+
console.log('EVM chains:', multiChainManager.getEVMChains());
|
|
337
|
+
console.log('SVM chains:', multiChainManager.getSVMChains());
|
|
338
|
+
|
|
339
|
+
// Get pocket 0 address on different chains
|
|
340
|
+
console.log('\nPocket 0 Addresses Across Chains:');
|
|
341
|
+
const baseAddress = multiChainManager.getPocketAddress('base', 0);
|
|
342
|
+
const ethAddress = multiChainManager.getPocketAddress('ethereum', 0);
|
|
343
|
+
const solAddress = multiChainManager.getPocketAddress('solana', 0);
|
|
344
|
+
|
|
345
|
+
console.log(' Base:', baseAddress);
|
|
346
|
+
console.log(' Ethereum:', ethAddress);
|
|
347
|
+
console.log(' Solana:', solAddress);
|
|
348
|
+
|
|
349
|
+
// Verify EVM chains share addresses
|
|
350
|
+
console.log('\nš EVM Address Check:');
|
|
351
|
+
console.log(' Base === Ethereum?', baseAddress === ethAddress, 'ā
');
|
|
352
|
+
console.log(' Base === Solana?', baseAddress === solAddress, 'ā (different chain type)');
|
|
353
|
+
|
|
354
|
+
// Get balances across chains
|
|
355
|
+
console.log('\nš Getting balances across all chains...');
|
|
356
|
+
try {
|
|
357
|
+
const balances = await multiChainManager.getPocketBalanceAcrossChains(
|
|
358
|
+
0, // pocket index
|
|
359
|
+
new Map([
|
|
360
|
+
['base', []], // No tokens, just native
|
|
361
|
+
['ethereum', []],
|
|
362
|
+
['solana', []]
|
|
363
|
+
])
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
console.log('\nBalances for Pocket 0:');
|
|
367
|
+
balances.forEach(chainBalance => {
|
|
368
|
+
console.log(`\n ${chainBalance.chainId.toUpperCase()} (${chainBalance.chainType}):`);
|
|
369
|
+
console.log(` Address: ${chainBalance.address}`);
|
|
370
|
+
chainBalance.balances.forEach(bal => {
|
|
371
|
+
const tokenName = bal.token === 'native' ? 'Native' : bal.token;
|
|
372
|
+
console.log(` ${tokenName}: ${bal.balance.formatted}`);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.log(' ā ļø Balance fetch error (expected for demo RPCs):', (error as Error).message);
|
|
377
|
+
}
|
|
231
378
|
|
|
379
|
+
// Get specific chain managers for advanced operations
|
|
380
|
+
console.log('\nš§ Advanced: Direct chain manager access');
|
|
381
|
+
const baseManager = multiChainManager.getEVMManager('base');
|
|
382
|
+
const solanaManager = multiChainManager.getSVMManager('solana');
|
|
383
|
+
console.log(' Base manager type:', baseManager.constructor.name);
|
|
384
|
+
console.log(' Solana manager type:', solanaManager.constructor.name);
|
|
232
385
|
|
|
386
|
+
// Cleanup
|
|
387
|
+
multiChainManager.dispose();
|
|
388
|
+
console.log('\nā
Multi-Chain Savings Test Complete\n');
|
|
233
389
|
}
|
|
234
390
|
|
|
391
|
+
// Uncomment to run tests
|
|
235
392
|
// testSavingsPocket()
|
|
393
|
+
// testSavingsPocketSVM()
|
|
394
|
+
// testSavingsPocketMultiChain()
|
|
236
395
|
|
|
237
396
|
// testPrice()
|
|
238
397
|
|