@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
package/dist/test.js
CHANGED
|
@@ -119,12 +119,119 @@ const testPrice = async () => {
|
|
|
119
119
|
walletA.getPrices(['0x98d0baa52b2D063E780DE12F615f963Fe8537553']).then(console.log);
|
|
120
120
|
};
|
|
121
121
|
const testSavingsPocket = async () => {
|
|
122
|
+
console.log('\n========== EVM Savings Test ==========');
|
|
122
123
|
const mnemonic = evm_1.EVMVM.generateMnemonicFromPrivateKey(evmPrivateKeyExposed);
|
|
123
|
-
console.log('
|
|
124
|
-
const savingsManager = new savings_1.
|
|
124
|
+
console.log('Mnemonic:', mnemonic);
|
|
125
|
+
const savingsManager = new savings_1.EVMSavingsManager(mnemonic, evmChainConfig, wallet.index);
|
|
125
126
|
const pocket0 = savingsManager.getPocket(0);
|
|
126
|
-
|
|
127
|
-
console.log('
|
|
127
|
+
console.log('\nPocket 0 Info:');
|
|
128
|
+
console.log(' Address:', pocket0.address);
|
|
129
|
+
console.log(' Derivation path:', pocket0.derivationPath);
|
|
130
|
+
console.log(' Index:', pocket0.index);
|
|
131
|
+
const balance = await savingsManager.getPocketBalance(0, []);
|
|
132
|
+
console.log('\nPocket 0 Balance:');
|
|
133
|
+
console.log(' Native balance:', balance[0].balance.formatted, 'ETH');
|
|
134
|
+
const pocket1 = savingsManager.getPocket(1);
|
|
135
|
+
const pocket2 = savingsManager.getPocket(2);
|
|
136
|
+
console.log('\nAdditional Pockets:');
|
|
137
|
+
console.log(' Pocket 1:', pocket1.address);
|
|
138
|
+
console.log(' Pocket 2:', pocket2.address);
|
|
139
|
+
savingsManager.dispose();
|
|
140
|
+
console.log('\nā
EVM Savings Test Complete\n');
|
|
141
|
+
};
|
|
142
|
+
const testSavingsPocketSVM = async () => {
|
|
143
|
+
console.log('\n========== SVM (Solana) Savings Test ==========');
|
|
144
|
+
const mnemonic = evm_1.EVMVM.generateMnemonicFromPrivateKey(evmPrivateKeyExposed);
|
|
145
|
+
console.log('Mnemonic:', mnemonic);
|
|
146
|
+
const savingsManager = new savings_1.SVMSavingsManager(mnemonic, chainConfig.rpcUrl, 0);
|
|
147
|
+
const pocket0 = savingsManager.getPocket(0);
|
|
148
|
+
console.log('\nPocket 0 Info:');
|
|
149
|
+
console.log(' Address:', pocket0.address.toBase58());
|
|
150
|
+
console.log(' Derivation path:', pocket0.derivationPath);
|
|
151
|
+
console.log(' Index:', pocket0.index);
|
|
152
|
+
const balance = await savingsManager.getPocketBalance(0, []);
|
|
153
|
+
console.log('\nPocket 0 Balance:');
|
|
154
|
+
console.log(' Native balance:', balance[0].balance.formatted, 'SOL');
|
|
155
|
+
const pocket1 = savingsManager.getPocket(1);
|
|
156
|
+
const pocket2 = savingsManager.getPocket(2);
|
|
157
|
+
console.log('\nAdditional Pockets:');
|
|
158
|
+
console.log(' Pocket 1:', pocket1.address.toBase58());
|
|
159
|
+
console.log(' Pocket 2:', pocket2.address.toBase58());
|
|
160
|
+
console.log('\nš” Note: Solana uses coin type 501, so addresses differ from EVM');
|
|
161
|
+
savingsManager.dispose();
|
|
162
|
+
console.log('\nā
SVM Savings Test Complete\n');
|
|
163
|
+
};
|
|
164
|
+
const testSavingsPocketMultiChain = async () => {
|
|
165
|
+
console.log('\n========== Multi-Chain Savings Test ==========');
|
|
166
|
+
const mnemonic = evm_1.EVMVM.generateMnemonicFromPrivateKey(evmPrivateKeyExposed);
|
|
167
|
+
console.log('Mnemonic:', mnemonic);
|
|
168
|
+
const multiChainManager = new savings_1.MultiChainSavingsManager(mnemonic, [
|
|
169
|
+
{
|
|
170
|
+
id: 'base',
|
|
171
|
+
type: 'EVM',
|
|
172
|
+
config: evmChainConfig
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'ethereum',
|
|
176
|
+
type: 'EVM',
|
|
177
|
+
config: {
|
|
178
|
+
chainId: 1,
|
|
179
|
+
name: 'Ethereum',
|
|
180
|
+
rpcUrl: 'https://eth-mainnet.g.alchemy.com/v2/demo',
|
|
181
|
+
explorerUrl: 'https://etherscan.io',
|
|
182
|
+
nativeToken: { name: 'Ethereum', symbol: 'ETH', decimals: 18 },
|
|
183
|
+
confirmationNo: 1,
|
|
184
|
+
vmType: 'EVM'
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 'solana',
|
|
189
|
+
type: 'SVM',
|
|
190
|
+
config: {
|
|
191
|
+
rpcUrl: chainConfig.rpcUrl
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
], 0);
|
|
195
|
+
console.log('\nAvailable chains:', multiChainManager.getChains());
|
|
196
|
+
console.log('EVM chains:', multiChainManager.getEVMChains());
|
|
197
|
+
console.log('SVM chains:', multiChainManager.getSVMChains());
|
|
198
|
+
console.log('\nPocket 0 Addresses Across Chains:');
|
|
199
|
+
const baseAddress = multiChainManager.getPocketAddress('base', 0);
|
|
200
|
+
const ethAddress = multiChainManager.getPocketAddress('ethereum', 0);
|
|
201
|
+
const solAddress = multiChainManager.getPocketAddress('solana', 0);
|
|
202
|
+
console.log(' Base:', baseAddress);
|
|
203
|
+
console.log(' Ethereum:', ethAddress);
|
|
204
|
+
console.log(' Solana:', solAddress);
|
|
205
|
+
console.log('\nš EVM Address Check:');
|
|
206
|
+
console.log(' Base === Ethereum?', baseAddress === ethAddress, 'ā
');
|
|
207
|
+
console.log(' Base === Solana?', baseAddress === solAddress, 'ā (different chain type)');
|
|
208
|
+
console.log('\nš Getting balances across all chains...');
|
|
209
|
+
try {
|
|
210
|
+
const balances = await multiChainManager.getPocketBalanceAcrossChains(0, new Map([
|
|
211
|
+
['base', []],
|
|
212
|
+
['ethereum', []],
|
|
213
|
+
['solana', []]
|
|
214
|
+
]));
|
|
215
|
+
console.log('\nBalances for Pocket 0:');
|
|
216
|
+
balances.forEach(chainBalance => {
|
|
217
|
+
console.log(`\n ${chainBalance.chainId.toUpperCase()} (${chainBalance.chainType}):`);
|
|
218
|
+
console.log(` Address: ${chainBalance.address}`);
|
|
219
|
+
chainBalance.balances.forEach(bal => {
|
|
220
|
+
const tokenName = bal.token === 'native' ? 'Native' : bal.token;
|
|
221
|
+
console.log(` ${tokenName}: ${bal.balance.formatted}`);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
console.log(' ā ļø Balance fetch error (expected for demo RPCs):', error.message);
|
|
227
|
+
}
|
|
228
|
+
console.log('\nš§ Advanced: Direct chain manager access');
|
|
229
|
+
const baseManager = multiChainManager.getEVMManager('base');
|
|
230
|
+
const solanaManager = multiChainManager.getSVMManager('solana');
|
|
231
|
+
console.log(' Base manager type:', baseManager.constructor.name);
|
|
232
|
+
console.log(' Solana manager type:', solanaManager.constructor.name);
|
|
233
|
+
multiChainManager.dispose();
|
|
234
|
+
console.log('\nā
Multi-Chain Savings Test Complete\n');
|
|
128
235
|
};
|
|
129
236
|
const RPC_URL = chainConfig.rpcUrl;
|
|
130
237
|
const connection = new web3_js_1.Connection(RPC_URL);
|
package/package.json
CHANGED
package/utils/evm/evm.ts
CHANGED
package/utils/index.ts
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
export * from "./walletBip32"
|
|
2
2
|
export * from "./types"
|
|
3
3
|
export * from "./vm"
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
// Export main classes and types from EVM and SVM
|
|
6
|
+
// Note: Utility functions (discoverTokens, getTokenBalance, etc.) have name conflicts
|
|
7
|
+
// Import them directly from './evm' or './svm' submodules when needed
|
|
8
|
+
export { EVMVM, EVMChainWallet, EVMSmartWallet } from "./evm"
|
|
9
|
+
export { SVMVM, SVMChainWallet, } from "./svm"
|
|
10
|
+
|
|
11
|
+
// Re-export all EVM exports as namespace to avoid conflicts
|
|
12
|
+
export * as evm from "./evm"
|
|
13
|
+
// Re-export all SVM exports as namespace to avoid conflicts
|
|
14
|
+
export * as svm from "./svm"
|
|
15
|
+
|
|
6
16
|
export * from "./constant"
|
|
7
17
|
export * from "bs58"
|
|
8
18
|
export * from "@solana/web3.js"
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EVM Savings Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages savings pockets across EVM-compatible chains (Ethereum, Polygon, BSC, Arbitrum, etc.)
|
|
5
|
+
* All EVM chains use the same addresses (coin type 60 in BIP-44)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { SavingsManager, Pocket } from "./savings-manager";
|
|
9
|
+
import { Hex, PublicClient, WalletClient, createPublicClient, createWalletClient, http } from "viem";
|
|
10
|
+
import { EVMDeriveChildPrivateKey, mnemonicToSeed } from "../walletBip32";
|
|
11
|
+
import { ChainWalletConfig, Balance, TransactionResult } from "../types";
|
|
12
|
+
import { ethers } from "ethers";
|
|
13
|
+
import {
|
|
14
|
+
fromChainToViemChain,
|
|
15
|
+
getNativeBalance,
|
|
16
|
+
getTokenBalance,
|
|
17
|
+
sendNativeToken,
|
|
18
|
+
sendERC20Token
|
|
19
|
+
} from "../evm";
|
|
20
|
+
import { SavingsValidation } from "./validation";
|
|
21
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* EVM Savings Manager
|
|
25
|
+
*
|
|
26
|
+
* Provides savings pocket functionality for EVM-compatible chains.
|
|
27
|
+
* Uses BIP-44 derivation path: m/44'/60'/{pocketIndex}'/0/{walletIndex}
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const manager = new EVMSavingsManager(
|
|
32
|
+
* mnemonic,
|
|
33
|
+
* { chainId: 1, name: 'ethereum', rpcUrl: 'https://...' },
|
|
34
|
+
* 0 // wallet index
|
|
35
|
+
* );
|
|
36
|
+
*
|
|
37
|
+
* // Get pocket
|
|
38
|
+
* const pocket = manager.getPocket(0);
|
|
39
|
+
* console.log(pocket.address); // 0x...
|
|
40
|
+
*
|
|
41
|
+
* // Get balances
|
|
42
|
+
* const balances = await manager.getPocketBalance(0, [usdcAddress]);
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export class EVMSavingsManager extends SavingsManager<Hex, PublicClient, WalletClient> {
|
|
46
|
+
coinType = 60;
|
|
47
|
+
derivationPathBase = "m/44'/60'/";
|
|
48
|
+
|
|
49
|
+
private chain: ChainWalletConfig;
|
|
50
|
+
private _client?: PublicClient;
|
|
51
|
+
private masterAddress?: Hex;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Create a new EVMSavingsManager
|
|
55
|
+
*
|
|
56
|
+
* @param mnemonic - BIP-39 mnemonic phrase
|
|
57
|
+
* @param chain - Chain configuration with RPC URL and chain details
|
|
58
|
+
* @param walletIndex - Wallet index in derivation path (default: 0)
|
|
59
|
+
* @param masterAddress - Optional master wallet address
|
|
60
|
+
*/
|
|
61
|
+
constructor(
|
|
62
|
+
mnemonic: string,
|
|
63
|
+
chain: ChainWalletConfig,
|
|
64
|
+
walletIndex: number = 0,
|
|
65
|
+
masterAddress?: Hex
|
|
66
|
+
) {
|
|
67
|
+
super(mnemonic, walletIndex);
|
|
68
|
+
|
|
69
|
+
SavingsValidation.validateChainId(chain.chainId);
|
|
70
|
+
if (masterAddress) {
|
|
71
|
+
SavingsValidation.validateAddress(masterAddress, 'Master address');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.chain = chain;
|
|
75
|
+
this.masterAddress = masterAddress;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get or create the RPC client on-demand
|
|
80
|
+
*
|
|
81
|
+
* Lazy initialization allows the client to be garbage collected between operations.
|
|
82
|
+
*/
|
|
83
|
+
get client(): PublicClient {
|
|
84
|
+
if (!this._client) {
|
|
85
|
+
this._client = this.createClient(this.chain.rpcUrl);
|
|
86
|
+
}
|
|
87
|
+
return this._client;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create an RPC client for this chain
|
|
92
|
+
*
|
|
93
|
+
* @param rpcUrl - RPC endpoint URL
|
|
94
|
+
* @returns PublicClient instance
|
|
95
|
+
*/
|
|
96
|
+
createClient(rpcUrl: string): PublicClient {
|
|
97
|
+
return createPublicClient({
|
|
98
|
+
chain: fromChainToViemChain(this.chain),
|
|
99
|
+
transport: http(rpcUrl)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Clear the cached RPC client
|
|
105
|
+
*/
|
|
106
|
+
clearClient(): void {
|
|
107
|
+
this._client = undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Derive a savings pocket at the specified account index
|
|
112
|
+
*
|
|
113
|
+
* @param accountIndex - Account index (0-based)
|
|
114
|
+
* @returns Pocket object with privateKey, address, derivationPath, and index
|
|
115
|
+
*/
|
|
116
|
+
derivePocket(accountIndex: number): Pocket<Hex> {
|
|
117
|
+
this.checkNotDisposed();
|
|
118
|
+
SavingsValidation.validateAccountIndex(accountIndex);
|
|
119
|
+
|
|
120
|
+
// Add 1 to preserve index 0 for main wallet
|
|
121
|
+
const pocketIndex = accountIndex + 1;
|
|
122
|
+
const derivationPath = `${this.derivationPathBase}${pocketIndex}'/0/${this.walletIndex}`;
|
|
123
|
+
const seed = mnemonicToSeed(this.mnemonic);
|
|
124
|
+
const { privateKey } = EVMDeriveChildPrivateKey(seed, this.walletIndex, derivationPath);
|
|
125
|
+
const wallet = new ethers.Wallet(privateKey);
|
|
126
|
+
|
|
127
|
+
const pocket: Pocket<Hex> = {
|
|
128
|
+
privateKey,
|
|
129
|
+
address: wallet.address as Hex,
|
|
130
|
+
derivationPath,
|
|
131
|
+
index: pocketIndex
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
this.pockets.set(accountIndex, pocket);
|
|
135
|
+
return pocket;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get the main wallet credentials
|
|
140
|
+
*
|
|
141
|
+
* @returns Main wallet object with privateKey, address, and derivationPath
|
|
142
|
+
*/
|
|
143
|
+
getMainWallet() {
|
|
144
|
+
this.checkNotDisposed();
|
|
145
|
+
const derivationPath = `${this.derivationPathBase}0'/0/${this.walletIndex}`;
|
|
146
|
+
const seed = mnemonicToSeed(this.mnemonic);
|
|
147
|
+
const { privateKey } = EVMDeriveChildPrivateKey(seed, this.walletIndex, derivationPath);
|
|
148
|
+
const wallet = new ethers.Wallet(privateKey);
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
privateKey,
|
|
152
|
+
address: wallet.address as Hex,
|
|
153
|
+
derivationPath
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get the main wallet address
|
|
159
|
+
*
|
|
160
|
+
* @returns Main wallet address as Hex
|
|
161
|
+
*/
|
|
162
|
+
getMainWalletAddress(): Hex {
|
|
163
|
+
if (this.masterAddress) return this.masterAddress;
|
|
164
|
+
return this.getMainWallet().address;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get token balances for a specific pocket
|
|
169
|
+
*
|
|
170
|
+
* @param pocketIndex - Pocket index to check
|
|
171
|
+
* @param tokens - Array of ERC-20 token addresses to query
|
|
172
|
+
* @returns Array of balance objects
|
|
173
|
+
*/
|
|
174
|
+
async getPocketBalance(pocketIndex: number, tokens: string[]): Promise<{
|
|
175
|
+
address: Hex | 'native';
|
|
176
|
+
balance: Balance;
|
|
177
|
+
}[]> {
|
|
178
|
+
SavingsValidation.validateAccountIndex(pocketIndex);
|
|
179
|
+
|
|
180
|
+
if (!Array.isArray(tokens)) {
|
|
181
|
+
throw new Error('Tokens must be an array');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const pocket = this.getPocket(pocketIndex);
|
|
185
|
+
const balances: { address: Hex | 'native'; balance: Balance }[] = [];
|
|
186
|
+
|
|
187
|
+
// Get native balance
|
|
188
|
+
const nativeBalance = await getNativeBalance(pocket.address, this.client);
|
|
189
|
+
balances.push({ address: 'native', balance: nativeBalance });
|
|
190
|
+
|
|
191
|
+
// Get token balances
|
|
192
|
+
await Promise.all(tokens.map(async (token) => {
|
|
193
|
+
SavingsValidation.validateAddress(token, 'Token address');
|
|
194
|
+
const tokenBalance = await getTokenBalance(token as Hex, pocket.address, this.client);
|
|
195
|
+
balances.push({ address: token as Hex, balance: tokenBalance });
|
|
196
|
+
}));
|
|
197
|
+
|
|
198
|
+
return balances;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get balances for multiple pockets
|
|
203
|
+
*
|
|
204
|
+
* @param pocketIndices - Array of pocket indices
|
|
205
|
+
* @param tokens - Array of token addresses to query
|
|
206
|
+
* @returns Array of balance arrays per pocket
|
|
207
|
+
*/
|
|
208
|
+
async getTotalTokenBalanceOfAllPockets(
|
|
209
|
+
tokens: string[],
|
|
210
|
+
pockets: number[]
|
|
211
|
+
): Promise<Array<{ address: Hex | 'native'; balance: Balance; }[]>> {
|
|
212
|
+
if (!Array.isArray(tokens) || tokens.length === 0) {
|
|
213
|
+
throw new Error('Tokens array must be non-empty');
|
|
214
|
+
}
|
|
215
|
+
if (!Array.isArray(pockets) || pockets.length === 0) {
|
|
216
|
+
throw new Error('Pockets array must be non-empty');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Validate all inputs
|
|
220
|
+
tokens.forEach((token, index) => {
|
|
221
|
+
SavingsValidation.validateAddress(token, `Token at index ${index}`);
|
|
222
|
+
});
|
|
223
|
+
pockets.forEach((pocket) => {
|
|
224
|
+
SavingsValidation.validateAccountIndex(pocket);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Fetch balances for all pockets in parallel
|
|
228
|
+
const allBalances = await Promise.all(
|
|
229
|
+
pockets.map((p: number) => this.getPocketBalance(p, tokens))
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
return allBalances;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Transfer native tokens from main wallet to a pocket
|
|
237
|
+
*
|
|
238
|
+
* @param mainWallet - WalletClient for the main wallet
|
|
239
|
+
* @param pocketIndex - Destination pocket index
|
|
240
|
+
* @param amount - Amount to transfer as string (in ether units)
|
|
241
|
+
* @returns Transaction result
|
|
242
|
+
*/
|
|
243
|
+
async transferToPocket(
|
|
244
|
+
mainWallet: WalletClient,
|
|
245
|
+
pocketIndex: number,
|
|
246
|
+
amount: string
|
|
247
|
+
): Promise<TransactionResult> {
|
|
248
|
+
SavingsValidation.validateAccountIndex(pocketIndex);
|
|
249
|
+
SavingsValidation.validateAmountString(amount, 'Transfer amount');
|
|
250
|
+
|
|
251
|
+
const pocket = this.getPocket(pocketIndex);
|
|
252
|
+
return await sendNativeToken(mainWallet, this.client, pocket.address, amount, 5);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Transfer ERC-20 tokens from main wallet to a pocket
|
|
257
|
+
*
|
|
258
|
+
* @param mainWallet - WalletClient for the main wallet
|
|
259
|
+
* @param tokenAddress - ERC-20 token contract address
|
|
260
|
+
* @param pocketIndex - Destination pocket index
|
|
261
|
+
* @param amount - Amount to transfer (in token base units)
|
|
262
|
+
* @returns Transaction result
|
|
263
|
+
*/
|
|
264
|
+
async transferTokenToPocket(
|
|
265
|
+
mainWallet: WalletClient,
|
|
266
|
+
tokenAddress: string,
|
|
267
|
+
pocketIndex: number,
|
|
268
|
+
amount: bigint
|
|
269
|
+
): Promise<TransactionResult> {
|
|
270
|
+
SavingsValidation.validateAddress(tokenAddress, 'Token address');
|
|
271
|
+
SavingsValidation.validateAccountIndex(pocketIndex);
|
|
272
|
+
SavingsValidation.validateAmount(amount, 'Transfer amount');
|
|
273
|
+
|
|
274
|
+
const pocket = this.getPocket(pocketIndex);
|
|
275
|
+
return await sendERC20Token(mainWallet, this.client, tokenAddress as Hex, pocket.address, amount, 5);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Create a Viem Account from a pocket's private key
|
|
280
|
+
*
|
|
281
|
+
* @param pocketIndex - Pocket index
|
|
282
|
+
* @returns Viem Account instance
|
|
283
|
+
*/
|
|
284
|
+
accountFromPocketId(pocketIndex: number) {
|
|
285
|
+
const pocket = this.getPocket(pocketIndex);
|
|
286
|
+
return privateKeyToAccount(`0x${pocket.privateKey}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Verify that a stored pocket address matches the derived address
|
|
291
|
+
*
|
|
292
|
+
* @param accountIndex - Pocket index
|
|
293
|
+
* @param storedAddress - Address to verify
|
|
294
|
+
* @returns true if addresses match
|
|
295
|
+
*/
|
|
296
|
+
verifyPocketAddress(accountIndex: number, storedAddress: string): boolean {
|
|
297
|
+
SavingsValidation.validateAccountIndex(accountIndex);
|
|
298
|
+
SavingsValidation.validateAddress(storedAddress, 'Stored address');
|
|
299
|
+
|
|
300
|
+
const pocket = this.getPocket(accountIndex);
|
|
301
|
+
return pocket.address.toLowerCase() === storedAddress.toLowerCase();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Send tokens from a pocket back to the main wallet
|
|
306
|
+
*
|
|
307
|
+
* @param pocketIndex - Source pocket index
|
|
308
|
+
* @param amount - Amount to send (in base units)
|
|
309
|
+
* @param token - Token address or "native"
|
|
310
|
+
* @returns Transaction result
|
|
311
|
+
*/
|
|
312
|
+
async sendToMainWallet(
|
|
313
|
+
pocketIndex: number,
|
|
314
|
+
amount: bigint,
|
|
315
|
+
token: Hex | "native"
|
|
316
|
+
): Promise<TransactionResult> {
|
|
317
|
+
SavingsValidation.validateAccountIndex(pocketIndex);
|
|
318
|
+
|
|
319
|
+
if (typeof amount !== 'bigint' || amount <= 0n) {
|
|
320
|
+
throw new Error(`Amount must be a positive bigint, got: ${amount}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (token !== 'native') {
|
|
324
|
+
SavingsValidation.validateAddress(token, 'Token address');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const pocket = this.getPocket(pocketIndex);
|
|
328
|
+
const account = this.accountFromPocketId(pocketIndex);
|
|
329
|
+
const mainWalletAddress = this.getMainWalletAddress();
|
|
330
|
+
|
|
331
|
+
const walletClient = createWalletClient({
|
|
332
|
+
account,
|
|
333
|
+
transport: http(this.chain.rpcUrl),
|
|
334
|
+
chain: fromChainToViemChain(this.chain)
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
if (token === "native") {
|
|
338
|
+
return await sendNativeToken(walletClient, this.client, mainWalletAddress, amount);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return await sendERC20Token(walletClient, this.client, token, mainWalletAddress, amount);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Dispose and clear all resources
|
|
346
|
+
*/
|
|
347
|
+
dispose(): void {
|
|
348
|
+
super.dispose();
|
|
349
|
+
this.clearClient();
|
|
350
|
+
if (this.masterAddress) {
|
|
351
|
+
(this as any).masterAddress = undefined;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
package/utils/savings/index.ts
CHANGED
|
@@ -1 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
// Base abstract class and types
|
|
2
|
+
export * from "./savings-manager";
|
|
3
|
+
|
|
4
|
+
// Chain-specific implementations
|
|
5
|
+
export * from "./evm-savings";
|
|
6
|
+
export * from "./svm-savings";
|
|
7
|
+
|
|
8
|
+
// Multi-chain orchestrator
|
|
9
|
+
export * from "./multi-chain-savings";
|
|
10
|
+
|
|
11
|
+
// Types and validation
|
|
12
|
+
export * from "./types";
|
|
13
|
+
export * from "./validation";
|
|
14
|
+
|
|
15
|
+
// Legacy exports (old single-chain manager - rename to avoid conflict)
|
|
16
|
+
export { BaseSavingsManager as LegacyBaseSavingsManager, SavingsManager as LegacySavingsManager } from "./saving-manager";
|
|
17
|
+
export * from "./smart-savings";
|
|
18
|
+
export * from "./savings-operations";
|