@deserialize/multi-vm-wallet 1.3.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/IChainWallet.d.ts +2 -0
- package/dist/IChainWallet.js.map +1 -1
- package/dist/evm/evm.d.ts +14 -172
- package/dist/evm/evm.js +38 -504
- package/dist/evm/evm.js.map +1 -1
- package/dist/evm/transaction.utils.d.ts +3 -3
- package/dist/evm/utils.d.ts +115 -80
- package/dist/evm/utils.js +272 -497
- package/dist/evm/utils.js.map +1 -1
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +5 -0
- package/dist/helpers/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/price.d.ts +2 -0
- package/dist/price.js +33 -0
- package/dist/price.js.map +1 -0
- package/dist/price.types.d.ts +38 -0
- package/dist/price.types.js +4 -0
- package/dist/price.types.js.map +1 -0
- package/dist/savings/index.d.ts +0 -0
- package/dist/savings/index.js +25 -0
- package/dist/savings/index.js.map +1 -0
- package/dist/savings/saving-actions.d.ts +29 -0
- package/dist/savings/saving-actions.js +56 -0
- package/dist/savings/saving-actions.js.map +1 -0
- package/dist/savings/savings-manager.d.ts +1 -1
- package/dist/savings/savings-manager.js +3 -3
- package/dist/savings/savings-manager.js.map +1 -1
- package/dist/svm/svm.d.ts +2 -0
- package/dist/svm/svm.js +12 -0
- package/dist/svm/svm.js.map +1 -1
- package/dist/test.js +7 -7
- package/dist/test.js.map +1 -1
- package/dist/vm.js.map +1 -1
- package/package.json +1 -1
- package/utils/IChainWallet.ts +2 -0
- package/utils/evm/evm.ts +326 -681
- package/utils/evm/utils.ts +438 -662
- package/utils/helpers/index.ts +6 -0
- package/utils/index.ts +1 -0
- package/utils/price.ts +37 -0
- package/utils/price.types.ts +45 -0
- package/utils/savings/index.ts +28 -0
- package/utils/savings/saving-actions.ts +77 -0
- package/utils/savings/savings-manager.ts +1 -1
- package/utils/svm/svm.ts +16 -2
- package/utils/test.ts +13 -4
- package/utils/vm.ts +2 -1
package/utils/evm/evm.ts
CHANGED
|
@@ -8,36 +8,32 @@ import { EVMDeriveChildPrivateKey } from "../walletBip32";
|
|
|
8
8
|
import { ChainWallet } from "../IChainWallet";
|
|
9
9
|
import { Balance, ChainWalletConfig, NFTInfo, UserTokenBalance, TokenInfo, TransactionResult, NFT } from "../types";
|
|
10
10
|
import { VM } from "../vm";
|
|
11
|
-
import { ethers,
|
|
11
|
+
import { ethers, formatUnits, Interface } from "ethers";
|
|
12
12
|
import BN from "bn.js";
|
|
13
13
|
import {
|
|
14
14
|
getNativeBalance,
|
|
15
15
|
getTokenBalance,
|
|
16
16
|
sendERC20Token,
|
|
17
17
|
sendNativeToken,
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
checkAndApprove,
|
|
20
20
|
signSendAndConfirm,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
formatAmountToWei,
|
|
24
|
-
isChainSupportedByKyber,
|
|
25
|
-
isChainSupportedByDebonk,
|
|
26
|
-
// normalizeTokenAddressForDebonk,
|
|
27
|
-
convertSlippageForDebonk,
|
|
21
|
+
|
|
22
|
+
|
|
28
23
|
TransactionParams,
|
|
29
24
|
approveToken,
|
|
30
|
-
|
|
25
|
+
|
|
31
26
|
getTokenInfo,
|
|
32
27
|
DESERIALIZED_SUPPORTED_CHAINS,
|
|
33
28
|
discoverTokens,
|
|
34
|
-
discoverNFTs
|
|
29
|
+
discoverNFTs,
|
|
30
|
+
fromChainToViemChain
|
|
35
31
|
} from "./utils";
|
|
36
32
|
import { EVMTransactionHistoryItem, getEVMTransactionHistory } from "./transactionParsing";
|
|
37
|
-
import { createPublicClient, Hex, http,
|
|
33
|
+
import { createPublicClient, Hex, http, parseEther, parseUnits, Call, walletActions, WalletClient, createWalletClient, Chain, ClientConfig, EIP1193RequestFn, TransportConfig, PublicClient, ChainConfig } from "viem";
|
|
38
34
|
import { EVMSmartWallet } from "./smartWallet";
|
|
39
35
|
import { SmartWalletOptions } from "./aa-service";
|
|
40
|
-
|
|
36
|
+
|
|
41
37
|
import { SmartSavingsManager } from "../savings/smart-savings";
|
|
42
38
|
import {
|
|
43
39
|
SavingsAccount,
|
|
@@ -47,6 +43,11 @@ import {
|
|
|
47
43
|
TransferToSavingsOptions,
|
|
48
44
|
WithdrawFromSavingsOptions
|
|
49
45
|
} from "../savings/types";
|
|
46
|
+
import { fetchPrices } from "../price";
|
|
47
|
+
import { PriceResponse } from "../price.types";
|
|
48
|
+
import { Account, privateKeyToAccount } from "viem/accounts";
|
|
49
|
+
// import { extendWalletClientWithSavings } from "../savings";
|
|
50
|
+
import { SavingsManager } from "../savings/saving-actions";
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
interface DebonkQuoteResponse {
|
|
@@ -85,7 +86,8 @@ interface DebonkSwapResult {
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
|
|
88
|
-
export class EVMVM extends VM<string, string,
|
|
89
|
+
export class EVMVM extends VM<string, string, PublicClient> {
|
|
90
|
+
|
|
89
91
|
derivationPath = "m/44'/60'/0'/0/"; // Default EVM derivation path
|
|
90
92
|
|
|
91
93
|
constructor(seed: string) {
|
|
@@ -139,7 +141,7 @@ export class EVMVM extends VM<string, string, JsonRpcProvider> {
|
|
|
139
141
|
};
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
static fromMnemonic(mnemonic: string): VM<string, string,
|
|
144
|
+
static fromMnemonic(mnemonic: string): VM<string, string, PublicClient> {
|
|
143
145
|
const seed = VM.mnemonicToSeed(mnemonic)
|
|
144
146
|
return new EVMVM(seed)
|
|
145
147
|
}
|
|
@@ -148,63 +150,40 @@ export class EVMVM extends VM<string, string, JsonRpcProvider> {
|
|
|
148
150
|
return ethers.isAddress(address);
|
|
149
151
|
}
|
|
150
152
|
|
|
151
|
-
static async getNativeBalance(address: string, connection:
|
|
153
|
+
static async getNativeBalance(address: string, connection: PublicClient): Promise<Balance> {
|
|
152
154
|
// Implement native balance retrieval logic here
|
|
153
|
-
return await getNativeBalance(address, connection)
|
|
155
|
+
return await getNativeBalance(address as Hex, connection)
|
|
154
156
|
}
|
|
155
157
|
|
|
156
|
-
static async getTokenBalance(address: string, tokenAddress: string, connection:
|
|
158
|
+
static async getTokenBalance(address: string, tokenAddress: string, connection: PublicClient): Promise<Balance> {
|
|
157
159
|
// Implement token balance retrieval logic here
|
|
158
|
-
return await getTokenBalance(tokenAddress, address, connection)
|
|
160
|
+
return await getTokenBalance(tokenAddress as Hex, address as Hex, connection)
|
|
159
161
|
}
|
|
160
162
|
}
|
|
161
163
|
|
|
162
|
-
export class EVMChainWallet extends ChainWallet<string, string,
|
|
163
|
-
|
|
164
|
-
wallet: Wallet
|
|
164
|
+
export class EVMChainWallet extends ChainWallet<string, string, PublicClient> {
|
|
165
|
+
wallet: WalletClient
|
|
165
166
|
private smartWallet?: EVMSmartWallet
|
|
166
167
|
private savingsManager?: SavingsManager
|
|
167
168
|
private smartSavingsManager?: SmartSavingsManager
|
|
168
169
|
|
|
169
170
|
constructor(config: ChainWalletConfig, privateKey: string, index: number) {
|
|
170
171
|
super(config, privateKey, index);
|
|
171
|
-
this.connection =
|
|
172
|
-
const wallet = new Wallet(privateKey, this.connection);
|
|
173
|
-
this.wallet = wallet
|
|
174
|
-
this.address = wallet.address;
|
|
175
|
-
this.privateKey = privateKey;
|
|
176
|
-
|
|
177
|
-
//client for viem
|
|
178
|
-
this.client = createPublicClient(
|
|
172
|
+
this.connection = createPublicClient(
|
|
179
173
|
{
|
|
180
|
-
chain:
|
|
181
|
-
rpcUrls: {
|
|
182
|
-
default: {
|
|
183
|
-
http: [config.rpcUrl]
|
|
184
|
-
}
|
|
185
|
-
},
|
|
186
|
-
id: config.chainId,
|
|
187
|
-
name: config.name,
|
|
188
|
-
nativeCurrency: {
|
|
189
|
-
name: config.nativeToken.name,
|
|
190
|
-
symbol: config.nativeToken.symbol,
|
|
191
|
-
decimals: config.nativeToken.decimals
|
|
192
|
-
},
|
|
193
|
-
blockExplorers: {
|
|
194
|
-
default: {
|
|
195
|
-
name: config.name + " Explorer",
|
|
196
|
-
url: config.explorerUrl,
|
|
197
|
-
apiUrl: config.explorerUrl
|
|
198
|
-
},
|
|
199
|
-
|
|
200
|
-
},
|
|
201
|
-
testnet: config.testnet || false
|
|
202
|
-
|
|
203
|
-
},
|
|
174
|
+
chain: fromChainToViemChain(config),
|
|
204
175
|
transport: http(config.rpcUrl)
|
|
205
176
|
},
|
|
206
|
-
|
|
207
177
|
)
|
|
178
|
+
const account = privateKeyToAccount(privateKey as Hex)
|
|
179
|
+
this.wallet = createWalletClient({
|
|
180
|
+
account,
|
|
181
|
+
chain: fromChainToViemChain(config),
|
|
182
|
+
transport: http(config.rpcUrl)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
this.address = account.address
|
|
186
|
+
this.privateKey = privateKey;
|
|
208
187
|
}
|
|
209
188
|
|
|
210
189
|
// ============================================
|
|
@@ -227,6 +206,10 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
227
206
|
return !!this.smartWallet;
|
|
228
207
|
}
|
|
229
208
|
|
|
209
|
+
createSavingsManager(mnemonic: string, index: number = 0, chain: ChainConfig) {
|
|
210
|
+
return new SavingsManager(mnemonic, index, chain)
|
|
211
|
+
}
|
|
212
|
+
|
|
230
213
|
/**
|
|
231
214
|
* Validate that AA is available for sponsored transactions
|
|
232
215
|
* @throws Error with helpful message if AA is not available
|
|
@@ -364,8 +347,8 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
364
347
|
// Existing Wallet Methods
|
|
365
348
|
// ============================================
|
|
366
349
|
|
|
367
|
-
getWallet():
|
|
368
|
-
return
|
|
350
|
+
getWallet(): WalletClient {
|
|
351
|
+
return this.wallet
|
|
369
352
|
}
|
|
370
353
|
|
|
371
354
|
generateAddress(): string {
|
|
@@ -383,7 +366,7 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
383
366
|
}
|
|
384
367
|
|
|
385
368
|
async getTokenInfo(tokenAddress: string) {
|
|
386
|
-
return await EVMVM.getTokenInfo(tokenAddress, this.connection!)
|
|
369
|
+
return await EVMVM.getTokenInfo(tokenAddress as Hex, this.connection!)
|
|
387
370
|
}
|
|
388
371
|
|
|
389
372
|
async discoverToken(): Promise<UserTokenBalance<string>[]> {
|
|
@@ -398,24 +381,38 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
398
381
|
|
|
399
382
|
async transferNative(to: string, amount: number): Promise<TransactionResult> {
|
|
400
383
|
const wallet = this.getWallet();
|
|
401
|
-
return await sendNativeToken(wallet, to, amount.toString(),
|
|
384
|
+
return await sendNativeToken(wallet, this.connection!, to as Hex, amount.toString(), this.config.confirmationNo || 5);
|
|
402
385
|
}
|
|
403
386
|
|
|
404
387
|
async transferToken(tokenAddress: TokenInfo, to: string, amount: number): Promise<TransactionResult> {
|
|
405
388
|
const wallet = this.getWallet();
|
|
406
|
-
return await sendERC20Token(wallet, tokenAddress.address, to, amount.toString(),
|
|
389
|
+
return await sendERC20Token(wallet, this.connection!, tokenAddress.address as Hex, to as Hex, BigInt(amount.toString()), this.config.confirmationNo || 5);
|
|
407
390
|
}
|
|
408
391
|
|
|
409
392
|
async getTransactionHistory(): Promise<EVMTransactionHistoryItem[]> {
|
|
410
393
|
const wallet = this.getWallet();
|
|
411
394
|
let res: EVMTransactionHistoryItem
|
|
412
395
|
try {
|
|
413
|
-
return await getEVMTransactionHistory(this.
|
|
396
|
+
return await getEVMTransactionHistory(this.connection!, this.address as Hex);
|
|
414
397
|
} catch (error) {
|
|
415
398
|
return []
|
|
416
399
|
}
|
|
417
400
|
}
|
|
418
401
|
|
|
402
|
+
async getPrices(tokenAddresses: string[]): Promise<PriceResponse> {
|
|
403
|
+
const result = await fetchPrices({
|
|
404
|
+
vm: 'EVM',
|
|
405
|
+
chainId: this.config.chainId,
|
|
406
|
+
tokenAddresses,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
if (result.error) {
|
|
410
|
+
throw new Error(result.error.message);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return result.data as PriceResponse;
|
|
414
|
+
}
|
|
415
|
+
|
|
419
416
|
// Updated swap method signature to match base class so created another method to use it inside swap
|
|
420
417
|
async swap(
|
|
421
418
|
tokenAddress: TokenInfo,
|
|
@@ -423,187 +420,12 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
423
420
|
amount: number,
|
|
424
421
|
slippage: number = 50
|
|
425
422
|
): Promise<TransactionResult> {
|
|
426
|
-
|
|
427
|
-
return {
|
|
428
|
-
success: false,
|
|
429
|
-
hash: "",
|
|
430
|
-
error: "Amount must be greater than 0"
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
const tokenOut: TokenInfo = {
|
|
434
|
-
address: to,
|
|
435
|
-
name: '',
|
|
436
|
-
symbol: '',
|
|
437
|
-
decimals: 18
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
return await this.performCompleteSwap(tokenAddress, tokenOut, amount, slippage);
|
|
423
|
+
throw new Error("Not Implemented")
|
|
441
424
|
}
|
|
442
425
|
|
|
443
|
-
async performCompleteSwap(
|
|
444
|
-
tokenIn: TokenInfo,
|
|
445
|
-
tokenOut: TokenInfo,
|
|
446
|
-
amount: number,
|
|
447
|
-
slippage: number = 50,
|
|
448
|
-
recipient?: string,
|
|
449
|
-
deadline?: number
|
|
450
|
-
): Promise<TransactionResult> {
|
|
451
|
-
try {
|
|
452
|
-
const wallet = this.getWallet();
|
|
453
|
-
const chainId = (await this.connection!.getNetwork()).chainId.toString();
|
|
454
|
-
|
|
455
|
-
console.log(` Starting swap on chain ${chainId}:`, {
|
|
456
|
-
from: tokenIn.symbol || tokenIn.address,
|
|
457
|
-
to: tokenOut.symbol || tokenOut.address,
|
|
458
|
-
amount: amount,
|
|
459
|
-
slippage: `${slippage / 100}%`
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// Check if this is a 0G chain that should use Debonk
|
|
463
|
-
if (isChainSupportedByDebonk(chainId)) {
|
|
464
|
-
console.log('Using Debonk API for 0G chain swap');
|
|
465
|
-
return await this.performDebonkSwap(tokenIn, tokenOut, amount, slippage, recipient, deadline);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Otherwise use Kyber (existing flow)
|
|
469
|
-
console.log('Using KyberSwap for non-0G chain swap');
|
|
470
|
-
return await this.performKyberSwap(tokenIn, tokenOut, amount, slippage, recipient, deadline);
|
|
471
|
-
|
|
472
|
-
} catch (error) {
|
|
473
|
-
console.error('Swap failed:', error);
|
|
474
|
-
throw new Error(`Swap failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
private async performDebonkSwap(
|
|
479
|
-
tokenIn: TokenInfo,
|
|
480
|
-
tokenOut: TokenInfo,
|
|
481
|
-
amount: number,
|
|
482
|
-
slippage: number = 50, // bps (e.g., 50 = 0.5%)
|
|
483
|
-
recipient?: string,
|
|
484
|
-
deadline?: number
|
|
485
|
-
): Promise<DebonkSwapResult> {
|
|
486
|
-
try {
|
|
487
|
-
const BASE_URL = 'https://evm-api.deserialize.xyz';
|
|
488
|
-
const tokenInAddress = tokenIn.address;
|
|
489
|
-
const tokenOutAddress = tokenOut.address;
|
|
490
|
-
|
|
491
|
-
// Convert amount to wei (multiply by 10^18 for 18 decimal tokens)
|
|
492
|
-
const amountInWei = (amount * Math.pow(10, tokenIn.decimals || 18)).toString();
|
|
493
|
-
|
|
494
|
-
// Convert slippage from bps to percentage (e.g., 50 bps -> 0.5%)
|
|
495
|
-
const slippagePercentage = slippage / 100;
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
// Step 1: Get quote from API
|
|
499
|
-
|
|
500
|
-
const quotePayload = {
|
|
501
|
-
tokenA: tokenInAddress,
|
|
502
|
-
tokenB: tokenOutAddress,
|
|
503
|
-
amountIn: amountInWei, // Use wei amount
|
|
504
|
-
dexId: "ZERO_G"
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
const quoteResponse = await fetch(`${BASE_URL}/quote`, {
|
|
509
|
-
method: 'POST',
|
|
510
|
-
headers: {
|
|
511
|
-
'Content-Type': 'application/json',
|
|
512
|
-
},
|
|
513
|
-
body: JSON.stringify(quotePayload)
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
if (!quoteResponse.ok) {
|
|
517
|
-
const errorText = await quoteResponse.text();
|
|
518
|
-
console.error("Quote API error response:", errorText);
|
|
519
|
-
return this.fail(`Quote API failed: ${quoteResponse.status} ${errorText}`);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
const quote: DebonkQuoteResponse = await quoteResponse.json();
|
|
523
|
-
|
|
524
|
-
// Step 2: Fix the quote dexId for swap API (it expects "ALL")
|
|
525
|
-
const modifiedQuote = {
|
|
526
|
-
...quote,
|
|
527
|
-
dexId: "ALL" // Change from "ZERO_G" to "ALL" as required by swap API
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
// Step 3: Get wallet address
|
|
531
|
-
const walletAddress = await this.getWallet().getAddress();
|
|
532
|
-
|
|
533
|
-
const swapPayload = {
|
|
534
|
-
publicKey: walletAddress,
|
|
535
|
-
slippage: slippagePercentage,
|
|
536
|
-
quote: modifiedQuote
|
|
537
|
-
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const swapResponse = await fetch(`${BASE_URL}/swap`, {
|
|
542
|
-
method: 'POST',
|
|
543
|
-
headers: {
|
|
544
|
-
'Content-Type': 'application/json',
|
|
545
|
-
},
|
|
546
|
-
body: JSON.stringify(swapPayload)
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
if (!swapResponse.ok) {
|
|
550
|
-
const errorText = await swapResponse.text();
|
|
551
|
-
console.error("Swap API error response:", errorText);
|
|
552
|
-
return this.fail(`Swap API failed: ${swapResponse.status} ${errorText}`);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
const swapData: DebonkSwapResponse = await swapResponse.json();
|
|
556
|
-
|
|
557
|
-
const wallet = this.getWallet();
|
|
558
|
-
let lastTxHash = '';
|
|
559
426
|
|
|
560
|
-
// Step 5: Execute each transaction sequentially
|
|
561
427
|
|
|
562
|
-
for (let i = 0; i < swapData.transactions.length; i++) {
|
|
563
|
-
const transaction = swapData.transactions[i];
|
|
564
428
|
|
|
565
|
-
// Prepare transaction object
|
|
566
|
-
const txRequest = {
|
|
567
|
-
to: transaction.to,
|
|
568
|
-
data: transaction.data,
|
|
569
|
-
value: transaction.value,
|
|
570
|
-
// gasPrice: 70000000000,
|
|
571
|
-
// gasLimit: 70000, // Increase significantly
|
|
572
|
-
};
|
|
573
|
-
|
|
574
|
-
try {
|
|
575
|
-
const txResponse = await wallet.sendTransaction(txRequest);
|
|
576
|
-
console.log(`Transaction ${i + 1} sent:`, txResponse.hash);
|
|
577
|
-
|
|
578
|
-
// Wait for confirmation
|
|
579
|
-
const receipt = await txResponse.wait();
|
|
580
|
-
|
|
581
|
-
if (!receipt) {
|
|
582
|
-
return this.fail(`Transaction ${i + 1} failed - no receipt received`);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
const txHash = receipt.hash || txResponse.hash;
|
|
586
|
-
lastTxHash = txHash;
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
} catch (txError: any) {
|
|
590
|
-
console.error(`Transaction ${i + 1} failed:`, txError);
|
|
591
|
-
return this.fail(`Transaction ${i + 1} failed: ${txError?.message ?? String(txError)}`);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
return {
|
|
598
|
-
success: true,
|
|
599
|
-
hash: lastTxHash // Return the hash of the last transaction
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
} catch (error: any) {
|
|
603
|
-
console.error("Debonk API swap error:", error);
|
|
604
|
-
return this.fail(`Debonk API swap failed: ${error?.message ?? String(error)}`);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
429
|
|
|
608
430
|
// Helper method for EVMChainWallet class
|
|
609
431
|
private fail(message: string): DebonkSwapResult {
|
|
@@ -615,170 +437,9 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
615
437
|
}
|
|
616
438
|
|
|
617
439
|
|
|
618
|
-
private async performKyberSwap(
|
|
619
|
-
tokenIn: TokenInfo,
|
|
620
|
-
tokenOut: TokenInfo,
|
|
621
|
-
amount: number,
|
|
622
|
-
slippage: number = 50,
|
|
623
|
-
recipient?: string,
|
|
624
|
-
deadline?: number
|
|
625
|
-
): Promise<TransactionResult> {
|
|
626
|
-
try {
|
|
627
|
-
const wallet = this.getWallet();
|
|
628
|
-
const chainId = (await this.connection!.getNetwork()).chainId.toString();
|
|
629
|
-
|
|
630
|
-
if (!isChainSupportedByKyber(chainId)) {
|
|
631
|
-
throw new Error(`Chain ${chainId} is not supported by KyberSwap`);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const isNativeIn = tokenIn.address === 'native' ||
|
|
635
|
-
tokenIn.address.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
|
636
|
-
const isNativeOut = tokenOut.address === 'native' ||
|
|
637
|
-
tokenOut.address.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
|
638
|
-
|
|
639
|
-
let tokenInDecimals = 18;
|
|
640
|
-
if (!isNativeIn && tokenIn.decimals) {
|
|
641
|
-
tokenInDecimals = tokenIn.decimals;
|
|
642
|
-
} else if (!isNativeIn) {
|
|
643
|
-
const tokenBalance = await this.getTokenBalance(tokenIn.address);
|
|
644
|
-
tokenInDecimals = tokenBalance.decimal;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
const { tokenInAddress, tokenOutAddress, formattedAmountIn } = prepareSwapParams(
|
|
648
|
-
tokenIn.address,
|
|
649
|
-
tokenOut.address,
|
|
650
|
-
amount.toString(),
|
|
651
|
-
tokenInDecimals,
|
|
652
|
-
isNativeIn,
|
|
653
|
-
isNativeOut
|
|
654
|
-
);
|
|
655
440
|
|
|
656
441
|
|
|
657
442
|
|
|
658
|
-
if (isNativeIn) {
|
|
659
|
-
const nativeBalance = await this.getNativeBalance();
|
|
660
|
-
const requiredAmount = new BN(formattedAmountIn);
|
|
661
|
-
if (nativeBalance.balance.lt(requiredAmount)) {
|
|
662
|
-
throw new Error(`Insufficient native balance. Required: ${amount}, Available: ${nativeBalance.formatted}`);
|
|
663
|
-
}
|
|
664
|
-
} else {
|
|
665
|
-
const tokenBalance = await this.getTokenBalance(tokenIn.address);
|
|
666
|
-
const requiredAmount = new BN(formattedAmountIn);
|
|
667
|
-
if (tokenBalance.balance.lt(requiredAmount)) {
|
|
668
|
-
throw new Error(`Insufficient token balance. Required: ${amount}, Available: ${tokenBalance.formatted}`);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
const swapTx = await performSwap({
|
|
673
|
-
chainId,
|
|
674
|
-
tokenIn: tokenInAddress,
|
|
675
|
-
tokenOut: tokenOutAddress,
|
|
676
|
-
amountIn: formattedAmountIn,
|
|
677
|
-
sender: this.address,
|
|
678
|
-
recipient: recipient || this.address,
|
|
679
|
-
slippageTolerance: slippage,
|
|
680
|
-
deadline: deadline ? Math.floor(Date.now() / 1000) + deadline : Math.floor(Date.now() / 1000) + 1200, // 20 minutes default
|
|
681
|
-
clientId: 'EVMChainWallet'
|
|
682
|
-
});
|
|
683
|
-
|
|
684
|
-
console.log('Kyber swap transaction prepared:', {
|
|
685
|
-
to: swapTx.to,
|
|
686
|
-
dataLength: swapTx.data?.length || 0,
|
|
687
|
-
gasLimit: swapTx.gasLimit?.toString(),
|
|
688
|
-
value: swapTx.value?.toString()
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
if (!isNativeIn) {
|
|
692
|
-
|
|
693
|
-
const approvalResult = await checkAndApprove(
|
|
694
|
-
wallet,
|
|
695
|
-
tokenIn.address,
|
|
696
|
-
swapTx.to,
|
|
697
|
-
formattedAmountIn,
|
|
698
|
-
undefined,
|
|
699
|
-
undefined,
|
|
700
|
-
this.config.confirmationNo || 1
|
|
701
|
-
);
|
|
702
|
-
|
|
703
|
-
if (approvalResult.approvalNeeded && approvalResult.approvalResult) {
|
|
704
|
-
if (!approvalResult.approvalResult.success) {
|
|
705
|
-
throw new Error('Token approval failed');
|
|
706
|
-
}
|
|
707
|
-
console.log('Token approval successful');
|
|
708
|
-
} else if (approvalResult.approvalNeeded) {
|
|
709
|
-
throw new Error('Token approval was needed but failed');
|
|
710
|
-
} else {
|
|
711
|
-
console.log('Token approval not needed - sufficient allowance');
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
const result = await signSendAndConfirm(
|
|
716
|
-
wallet,
|
|
717
|
-
{
|
|
718
|
-
to: swapTx.to,
|
|
719
|
-
data: swapTx.data,
|
|
720
|
-
value: swapTx.value || '0',
|
|
721
|
-
gasLimit: swapTx.gasLimit
|
|
722
|
-
},
|
|
723
|
-
this.config.confirmationNo || 1,
|
|
724
|
-
);
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
return result;
|
|
728
|
-
|
|
729
|
-
} catch (error) {
|
|
730
|
-
|
|
731
|
-
throw new Error(`Kyber swap failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
async getSwapQuote(
|
|
736
|
-
tokenIn: TokenInfo,
|
|
737
|
-
tokenOut: TokenInfo,
|
|
738
|
-
amount: number
|
|
739
|
-
): Promise<{
|
|
740
|
-
amountOut: string;
|
|
741
|
-
priceImpact: string;
|
|
742
|
-
gasEstimate: string;
|
|
743
|
-
route: string[];
|
|
744
|
-
}> {
|
|
745
|
-
|
|
746
|
-
try {
|
|
747
|
-
const chainId = (await this.connection!.getNetwork()).chainId.toString();
|
|
748
|
-
|
|
749
|
-
if (!isChainSupportedByKyber(chainId)) {
|
|
750
|
-
throw new Error(`Chain ${chainId} is not supported by KyberSwap`);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
const isNativeIn = tokenIn.address === 'native' ||
|
|
754
|
-
tokenIn.address.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
|
755
|
-
const isNativeOut = tokenOut.address === 'native' ||
|
|
756
|
-
tokenOut.address.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
|
757
|
-
|
|
758
|
-
let tokenInDecimals = 18;
|
|
759
|
-
if (!isNativeIn && tokenIn.decimals) {
|
|
760
|
-
tokenInDecimals = tokenIn.decimals;
|
|
761
|
-
} else if (!isNativeIn) {
|
|
762
|
-
const tokenBalance = await this.getTokenBalance(tokenIn.address);
|
|
763
|
-
tokenInDecimals = tokenBalance.decimal;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
const { tokenInAddress, tokenOutAddress, formattedAmountIn } = prepareSwapParams(
|
|
767
|
-
tokenIn.address,
|
|
768
|
-
tokenOut.address,
|
|
769
|
-
amount.toString(),
|
|
770
|
-
tokenInDecimals,
|
|
771
|
-
isNativeIn,
|
|
772
|
-
isNativeOut
|
|
773
|
-
);
|
|
774
|
-
|
|
775
|
-
throw new Error("Quote functionality requires direct API integration - use the swap method for full execution");
|
|
776
|
-
|
|
777
|
-
} catch (error) {
|
|
778
|
-
console.error('Error getting swap quote:', error);
|
|
779
|
-
throw error;
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
443
|
async approveToken(params: {
|
|
783
444
|
tokenAddress: string
|
|
784
445
|
spender: string
|
|
@@ -787,303 +448,287 @@ export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider>
|
|
|
787
448
|
gasLimit?: string | bigint
|
|
788
449
|
}): Promise<TransactionResult> {
|
|
789
450
|
const signer = this.getWallet()
|
|
451
|
+
|
|
790
452
|
const r = await approveToken(
|
|
791
453
|
signer,
|
|
792
|
-
|
|
793
|
-
params.
|
|
794
|
-
params.
|
|
795
|
-
params.
|
|
454
|
+
this.connection!,
|
|
455
|
+
params.tokenAddress as Hex,
|
|
456
|
+
params.spender as Hex,
|
|
457
|
+
BigInt(params.amountRaw),
|
|
796
458
|
params.confirmations ?? this.config.confirmationNo ?? 1,
|
|
797
459
|
)
|
|
798
|
-
return {
|
|
799
|
-
hash: r.hash,
|
|
800
|
-
success: r.success,
|
|
801
|
-
error: (r as any).error,
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
460
|
|
|
805
|
-
async executeContractMethod(params: {
|
|
806
|
-
contractAddress: string
|
|
807
|
-
abi: any[]
|
|
808
|
-
methodName: string
|
|
809
|
-
methodParams?: any[]
|
|
810
|
-
value?: string | bigint
|
|
811
|
-
gasLimit?: string | bigint
|
|
812
|
-
confirmations?: number
|
|
813
|
-
}): Promise<TransactionResult> {
|
|
814
|
-
const signer = this.getWallet()
|
|
815
|
-
const r = await executeContractMethod(
|
|
816
|
-
signer,
|
|
817
|
-
params.contractAddress,
|
|
818
|
-
params.abi,
|
|
819
|
-
params.methodName,
|
|
820
|
-
params.methodParams ?? [],
|
|
821
|
-
params.value,
|
|
822
|
-
)
|
|
823
461
|
return {
|
|
824
462
|
hash: r.hash,
|
|
825
463
|
success: r.success,
|
|
826
|
-
error: (r as any).error,
|
|
827
464
|
}
|
|
828
465
|
}
|
|
829
|
-
async signMessage(message: string): Promise<string> {
|
|
830
|
-
const signer = this.getWallet()
|
|
831
|
-
return signer.signMessage(message)
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// ============================================
|
|
835
|
-
// Savings Pocket Methods
|
|
836
|
-
// ============================================
|
|
837
|
-
|
|
838
|
-
/**
|
|
839
|
-
* Get or create the SavingsManager instance (lazy initialization)
|
|
840
|
-
* @private
|
|
841
|
-
*/
|
|
842
|
-
private getSavingsManager(): SavingsManager {
|
|
843
|
-
if (!this.savingsManager) {
|
|
844
|
-
// Create a VM instance from the current wallet's seed
|
|
845
|
-
// We need to get the seed from the privateKey
|
|
846
|
-
// For now, we'll create an EVMVM from the wallet
|
|
847
|
-
// Note: This requires access to the seed, which we'll need to handle
|
|
848
|
-
throw new Error(
|
|
849
|
-
"SavingsManager requires access to the seed phrase. " +
|
|
850
|
-
"Please initialize the wallet with a seed phrase or mnemonic to use savings features."
|
|
851
|
-
);
|
|
852
|
-
}
|
|
853
|
-
return this.savingsManager;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Initialize savings functionality with a seed phrase
|
|
858
|
-
*
|
|
859
|
-
* This must be called before using any savings methods.
|
|
860
|
-
* The seed is used to derive savings accounts using BIP-44.
|
|
861
|
-
*
|
|
862
|
-
* @param seed - The wallet seed (hex string)
|
|
863
|
-
*
|
|
864
|
-
* @example
|
|
865
|
-
* const seed = VM.mnemonicToSeed(mnemonic);
|
|
866
|
-
* wallet.initializeSavings(seed);
|
|
867
|
-
*/
|
|
868
|
-
initializeSavings(seed: string): void {
|
|
869
|
-
const vm = new EVMVM(seed);
|
|
870
|
-
this.savingsManager = new SavingsManager(vm);
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
/**
|
|
874
|
-
* Derive a new savings account from BIP-44 account index
|
|
875
|
-
*
|
|
876
|
-
* @param accountIndex - The BIP-44 account index (1+ for savings, 0 is main wallet)
|
|
877
|
-
* @returns SavingsAccount with derived address and private key
|
|
878
|
-
*
|
|
879
|
-
* @example
|
|
880
|
-
* const savingsAccount1 = wallet.deriveSavingsAccount(1); // m/44'/60'/1'/0/0
|
|
881
|
-
* const savingsAccount2 = wallet.deriveSavingsAccount(2); // m/44'/60'/2'/0/0
|
|
882
|
-
*/
|
|
883
|
-
deriveSavingsAccount(accountIndex: number): SavingsAccount {
|
|
884
|
-
return this.getSavingsManager().createSavingsAccount(accountIndex);
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
/**
|
|
888
|
-
* Transfer native tokens to a savings account
|
|
889
|
-
*
|
|
890
|
-
* Security: Always derives the destination address from accountIndex.
|
|
891
|
-
*
|
|
892
|
-
* @param accountIndex - Savings account index to deposit to
|
|
893
|
-
* @param amount - Amount in ether units (e.g., "1.5" for 1.5 ETH)
|
|
894
|
-
* @param options - Optional security and priority settings
|
|
895
|
-
* @returns Transaction result
|
|
896
|
-
*
|
|
897
|
-
* @example
|
|
898
|
-
* const result = await wallet.transferToSavings(1, "1.5"); // Send 1.5 ETH to savings account 1
|
|
899
|
-
*/
|
|
900
|
-
async transferToSavings(
|
|
901
|
-
accountIndex: number,
|
|
902
|
-
amount: string,
|
|
903
|
-
options?: TransferToSavingsOptions
|
|
904
|
-
): Promise<TransactionResult> {
|
|
905
|
-
const manager = this.getSavingsManager();
|
|
906
|
-
|
|
907
|
-
// Build transaction using derived address
|
|
908
|
-
const amountWei = parseEther(amount);
|
|
909
|
-
const txParams = manager.buildDepositTransaction(accountIndex, amountWei, options);
|
|
910
|
-
|
|
911
|
-
// Execute using existing transferNative method
|
|
912
|
-
return await this.transferNative(txParams.to, Number(amount));
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
/**
|
|
916
|
-
* Withdraw native tokens from a savings account
|
|
917
|
-
*
|
|
918
|
-
* Security: Uses the derived private key for signing.
|
|
919
|
-
*
|
|
920
|
-
* @param accountIndex - Savings account index to withdraw from
|
|
921
|
-
* @param to - Destination address
|
|
922
|
-
* @param amount - Amount in ether units (e.g., "0.5" for 0.5 ETH)
|
|
923
|
-
* @param options - Optional security and priority settings
|
|
924
|
-
* @returns Transaction result
|
|
925
|
-
*
|
|
926
|
-
* @example
|
|
927
|
-
* const result = await wallet.withdrawFromSavings(1, destinationAddress, "0.5");
|
|
928
|
-
*/
|
|
929
|
-
async withdrawFromSavings(
|
|
930
|
-
accountIndex: number,
|
|
931
|
-
to: string,
|
|
932
|
-
amount: string,
|
|
933
|
-
options?: WithdrawFromSavingsOptions
|
|
934
|
-
): Promise<TransactionResult> {
|
|
935
|
-
const manager = this.getSavingsManager();
|
|
936
|
-
|
|
937
|
-
// Build withdrawal transaction
|
|
938
|
-
const amountWei = parseEther(amount);
|
|
939
|
-
const withdrawalParams = manager.buildWithdrawalTransaction(
|
|
940
|
-
accountIndex,
|
|
941
|
-
to as Hex,
|
|
942
|
-
amountWei,
|
|
943
|
-
options
|
|
944
|
-
);
|
|
945
|
-
|
|
946
|
-
// Create a temporary wallet with the savings account private key
|
|
947
|
-
const savingsWallet = new Wallet(withdrawalParams.privateKey, this.connection);
|
|
948
|
-
|
|
949
|
-
// Send transaction using the savings wallet
|
|
950
|
-
const tx = await savingsWallet.sendTransaction({
|
|
951
|
-
to: withdrawalParams.to,
|
|
952
|
-
value: withdrawalParams.value
|
|
953
|
-
});
|
|
954
|
-
|
|
955
|
-
const receipt = await tx.wait(this.config.confirmationNo || 1);
|
|
956
|
-
|
|
957
|
-
return {
|
|
958
|
-
success: receipt?.status === 1,
|
|
959
|
-
hash: receipt?.hash || tx.hash
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
466
|
|
|
963
|
-
/**
|
|
964
|
-
* Verify a stored savings address matches the derived address
|
|
965
|
-
*
|
|
966
|
-
* Security: Prevents database tampering attacks.
|
|
967
|
-
*
|
|
968
|
-
* @param accountIndex - The account index to verify
|
|
969
|
-
* @param storedAddress - The address from storage/database
|
|
970
|
-
* @returns Verification result
|
|
971
|
-
*
|
|
972
|
-
* @example
|
|
973
|
-
* const result = wallet.verifySavingsAddress(1, storedAddress);
|
|
974
|
-
* if (!result.isValid) {
|
|
975
|
-
* console.error("Security alert: Address tampering detected!");
|
|
976
|
-
* }
|
|
977
|
-
*/
|
|
978
|
-
verifySavingsAddress(accountIndex: number, storedAddress: Hex): AddressVerificationResult {
|
|
979
|
-
return this.getSavingsManager().verifySavingsAddress(accountIndex, storedAddress);
|
|
980
|
-
}
|
|
981
467
|
|
|
982
|
-
/**
|
|
983
|
-
* Audit multiple savings addresses at once
|
|
984
|
-
*
|
|
985
|
-
* @param addresses - Map of accountIndex to stored address
|
|
986
|
-
* @returns Audit result with summary and details
|
|
987
|
-
*
|
|
988
|
-
* @example
|
|
989
|
-
* const addresses = new Map([[1, "0xabc..."], [2, "0xdef..."]]);
|
|
990
|
-
* const audit = wallet.auditSavingsAddresses(addresses);
|
|
991
|
-
* console.log(`Valid: ${audit.valid}/${audit.total}`);
|
|
992
|
-
*/
|
|
993
|
-
auditSavingsAddresses(addresses: Map<number, Hex>): SavingsAuditResult {
|
|
994
|
-
return this.getSavingsManager().auditSavingsAddresses(addresses);
|
|
995
|
-
}
|
|
996
468
|
|
|
997
|
-
|
|
998
|
-
* Get savings account information without exposing private key
|
|
999
|
-
*
|
|
1000
|
-
* @param accountIndex - The account index
|
|
1001
|
-
* @returns Public account information (address and derivation path)
|
|
1002
|
-
*
|
|
1003
|
-
* @example
|
|
1004
|
-
* const info = wallet.getSavingsAccountInfo(1);
|
|
1005
|
-
* console.log(`Address: ${info.address}`);
|
|
1006
|
-
* console.log(`Path: ${info.derivationPath}`); // m/44'/60'/1'/0/0
|
|
1007
|
-
*/
|
|
1008
|
-
getSavingsAccountInfo(accountIndex: number): Omit<SavingsAccount, 'privateKey'> {
|
|
1009
|
-
return this.getSavingsManager().getSavingsAccountInfo(accountIndex);
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
// ============================================
|
|
1013
|
-
// Smart Savings Methods (EIP-7702)
|
|
1014
|
-
// ============================================
|
|
1015
|
-
|
|
1016
|
-
/**
|
|
1017
|
-
* Get or create the SmartSavingsManager instance (lazy initialization)
|
|
1018
|
-
* @private
|
|
1019
|
-
*/
|
|
1020
|
-
private getSmartSavingsManager(): SmartSavingsManager {
|
|
1021
|
-
if (!this.smartSavingsManager) {
|
|
1022
|
-
this.smartSavingsManager = new SmartSavingsManager(this.config);
|
|
1023
|
-
}
|
|
1024
|
-
return this.smartSavingsManager;
|
|
1025
|
-
}
|
|
469
|
+
async signMessage(message: string): Promise<string> {
|
|
1026
470
|
|
|
1027
|
-
|
|
1028
|
-
* Upgrade a savings account to a smart account with EIP-7702 delegation
|
|
1029
|
-
*
|
|
1030
|
-
* This enables advanced features:
|
|
1031
|
-
* - Lock modules for time-locked savings
|
|
1032
|
-
* - Hooks for spend & save
|
|
1033
|
-
* - Session keys for periodic savings
|
|
1034
|
-
* - Sponsored transactions via paymaster
|
|
1035
|
-
*
|
|
1036
|
-
* @param accountIndex - The savings account index to upgrade
|
|
1037
|
-
* @param options - Optional smart wallet configuration
|
|
1038
|
-
* @param autoInitialize - Whether to initialize the smart wallet (default: false)
|
|
1039
|
-
* @returns SmartSavingsAccount with EVMSmartWallet instance
|
|
1040
|
-
*
|
|
1041
|
-
* @example
|
|
1042
|
-
* // Upgrade without initialization
|
|
1043
|
-
* const smartSavings = await wallet.upgradeSavingsToSmartAccount(1);
|
|
1044
|
-
* await smartSavings.smartWallet.initialize(); // Initialize separately
|
|
1045
|
-
*
|
|
1046
|
-
* @example
|
|
1047
|
-
* // Upgrade and initialize in one step
|
|
1048
|
-
* const smartSavings = await wallet.upgradeSavingsToSmartAccount(1, {}, true);
|
|
1049
|
-
* // Ready to use immediately
|
|
1050
|
-
*/
|
|
1051
|
-
async upgradeSavingsToSmartAccount(
|
|
1052
|
-
accountIndex: number,
|
|
1053
|
-
options?: SmartWalletOptions,
|
|
1054
|
-
autoInitialize: boolean = false
|
|
1055
|
-
): Promise<SmartSavingsAccount> {
|
|
1056
|
-
const manager = this.getSmartSavingsManager();
|
|
1057
|
-
|
|
1058
|
-
// First derive the basic savings account
|
|
1059
|
-
const basicSavings = this.deriveSavingsAccount(accountIndex);
|
|
1060
|
-
|
|
1061
|
-
// Then upgrade to smart account
|
|
1062
|
-
if (autoInitialize) {
|
|
1063
|
-
return await manager.upgradeSavingsAndInitialize(basicSavings, options);
|
|
1064
|
-
} else {
|
|
1065
|
-
return await manager.upgradeSavingsToSmartAccount(basicSavings, options);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
471
|
+
const signer = this.wallet
|
|
1068
472
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
*
|
|
1072
|
-
* @returns true if chain supports Account Abstraction
|
|
1073
|
-
*
|
|
1074
|
-
* @example
|
|
1075
|
-
* if (wallet.isSmartSavingsSupported()) {
|
|
1076
|
-
* const smartSavings = await wallet.upgradeSavingsToSmartAccount(1);
|
|
1077
|
-
* } else {
|
|
1078
|
-
* console.log("This chain doesn't support smart savings");
|
|
1079
|
-
* }
|
|
1080
|
-
*/
|
|
1081
|
-
isSmartSavingsSupported(): boolean {
|
|
1082
|
-
try {
|
|
1083
|
-
const manager = this.getSmartSavingsManager();
|
|
1084
|
-
return manager.canUpgradeToSmartAccount();
|
|
1085
|
-
} catch (error) {
|
|
1086
|
-
return false;
|
|
473
|
+
if (!signer.account) {
|
|
474
|
+
throw new Error("Account is required for signing, signer.account from this.wallet is undefined")
|
|
1087
475
|
}
|
|
1088
|
-
|
|
476
|
+
return signer.signMessage({ message, account: signer.account?.address })
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// // ============================================
|
|
480
|
+
// // Savings Pocket Methods
|
|
481
|
+
// // ============================================
|
|
482
|
+
|
|
483
|
+
// /**
|
|
484
|
+
// * Get or create the SavingsManager instance (lazy initialization)
|
|
485
|
+
// * @private
|
|
486
|
+
// */
|
|
487
|
+
// private getSavingsManager(): SavingsManager {
|
|
488
|
+
// if (!this.savingsManager) {
|
|
489
|
+
// // Create a VM instance from the current wallet's seed
|
|
490
|
+
// // We need to get the seed from the privateKey
|
|
491
|
+
// // For now, we'll create an EVMVM from the wallet
|
|
492
|
+
// // Note: This requires access to the seed, which we'll need to handle
|
|
493
|
+
// throw new Error(
|
|
494
|
+
// "SavingsManager requires access to the seed phrase. " +
|
|
495
|
+
// "Please initialize the wallet with a seed phrase or mnemonic to use savings features."
|
|
496
|
+
// );
|
|
497
|
+
// }
|
|
498
|
+
// return this.savingsManager;
|
|
499
|
+
// }
|
|
500
|
+
|
|
501
|
+
// /**
|
|
502
|
+
// * Initialize savings functionality with a seed phrase
|
|
503
|
+
// *
|
|
504
|
+
// * This must be called before using any savings methods.
|
|
505
|
+
// * The seed is used to derive savings accounts using BIP-44.
|
|
506
|
+
// *
|
|
507
|
+
// * @param seed - The wallet seed (hex string)
|
|
508
|
+
// *
|
|
509
|
+
// * @example
|
|
510
|
+
// * const seed = VM.mnemonicToSeed(mnemonic);
|
|
511
|
+
// * wallet.initializeSavings(seed);
|
|
512
|
+
// */
|
|
513
|
+
// initializeSavings(seed: string): void {
|
|
514
|
+
// const vm = new EVMVM(seed);
|
|
515
|
+
// this.savingsManager = new SavingsManager(vm);
|
|
516
|
+
// }
|
|
517
|
+
|
|
518
|
+
// /**
|
|
519
|
+
// * Derive a new savings account from BIP-44 account index
|
|
520
|
+
// *
|
|
521
|
+
// * @param accountIndex - The BIP-44 account index (1+ for savings, 0 is main wallet)
|
|
522
|
+
// * @returns SavingsAccount with derived address and private key
|
|
523
|
+
// *
|
|
524
|
+
// * @example
|
|
525
|
+
// * const savingsAccount1 = wallet.deriveSavingsAccount(1); // m/44'/60'/1'/0/0
|
|
526
|
+
// * const savingsAccount2 = wallet.deriveSavingsAccount(2); // m/44'/60'/2'/0/0
|
|
527
|
+
// */
|
|
528
|
+
// deriveSavingsAccount(accountIndex: number): SavingsAccount {
|
|
529
|
+
// return this.getSavingsManager().createSavingsAccount(accountIndex);
|
|
530
|
+
// }
|
|
531
|
+
|
|
532
|
+
// /**
|
|
533
|
+
// * Transfer native tokens to a savings account
|
|
534
|
+
// *
|
|
535
|
+
// * Security: Always derives the destination address from accountIndex.
|
|
536
|
+
// *
|
|
537
|
+
// * @param accountIndex - Savings account index to deposit to
|
|
538
|
+
// * @param amount - Amount in ether units (e.g., "1.5" for 1.5 ETH)
|
|
539
|
+
// * @param options - Optional security and priority settings
|
|
540
|
+
// * @returns Transaction result
|
|
541
|
+
// *
|
|
542
|
+
// * @example
|
|
543
|
+
// * const result = await wallet.transferToSavings(1, "1.5"); // Send 1.5 ETH to savings account 1
|
|
544
|
+
// */
|
|
545
|
+
// async transferToSavings(
|
|
546
|
+
// accountIndex: number,
|
|
547
|
+
// amount: string,
|
|
548
|
+
// options?: TransferToSavingsOptions
|
|
549
|
+
// ): Promise<TransactionResult> {
|
|
550
|
+
// const manager = this.getSavingsManager();
|
|
551
|
+
|
|
552
|
+
// // Build transaction using derived address
|
|
553
|
+
// const amountWei = parseEther(amount);
|
|
554
|
+
// const txParams = manager.buildDepositTransaction(accountIndex, amountWei, options);
|
|
555
|
+
|
|
556
|
+
// // Execute using existing transferNative method
|
|
557
|
+
// return await this.transferNative(txParams.to, Number(amount));
|
|
558
|
+
// }
|
|
559
|
+
|
|
560
|
+
// // /**
|
|
561
|
+
// // * Withdraw native tokens from a savings account
|
|
562
|
+
// // *
|
|
563
|
+
// // * Security: Uses the derived private key for signing.
|
|
564
|
+
// // *
|
|
565
|
+
// // * @param accountIndex - Savings account index to withdraw from
|
|
566
|
+
// // * @param to - Destination address
|
|
567
|
+
// // * @param amount - Amount in ether units (e.g., "0.5" for 0.5 ETH)
|
|
568
|
+
// // * @param options - Optional security and priority settings
|
|
569
|
+
// // * @returns Transaction result
|
|
570
|
+
// // *
|
|
571
|
+
// // * @example
|
|
572
|
+
// // * const result = await wallet.withdrawFromSavings(1, destinationAddress, "0.5");
|
|
573
|
+
// // */
|
|
574
|
+
// // async withdrawFromSavings(
|
|
575
|
+
// // accountIndex: number,
|
|
576
|
+
// // to: string,
|
|
577
|
+
// // amount: string,
|
|
578
|
+
// // options?: WithdrawFromSavingsOptions
|
|
579
|
+
// // ): Promise<TransactionResult> {
|
|
580
|
+
// // const manager = this.getSavingsManager();
|
|
581
|
+
|
|
582
|
+
// // // Build withdrawal transaction
|
|
583
|
+
// // const amountWei = parseEther(amount);
|
|
584
|
+
// // const withdrawalParams = manager.buildWithdrawalTransaction(
|
|
585
|
+
// // accountIndex,
|
|
586
|
+
// // to as Hex,
|
|
587
|
+
// // amountWei,
|
|
588
|
+
// // options
|
|
589
|
+
// // );
|
|
590
|
+
|
|
591
|
+
// // // Create a temporary wallet with the savings account private key
|
|
592
|
+
// // const savingsWallet = new Wallet(withdrawalParams.privateKey, this.connection);
|
|
593
|
+
|
|
594
|
+
// // // Send transaction using the savings wallet
|
|
595
|
+
// // const tx = await savingsWallet.sendTransaction({
|
|
596
|
+
// // to: withdrawalParams.to,
|
|
597
|
+
// // value: withdrawalParams.value
|
|
598
|
+
// // });
|
|
599
|
+
|
|
600
|
+
// // const receipt = await tx.wait(this.config.confirmationNo || 1);
|
|
601
|
+
|
|
602
|
+
// // return {
|
|
603
|
+
// // success: receipt?.status === 1,
|
|
604
|
+
// // hash: receipt?.hash || tx.hash
|
|
605
|
+
// // };
|
|
606
|
+
// // }
|
|
607
|
+
|
|
608
|
+
// /**
|
|
609
|
+
// * Verify a stored savings address matches the derived address
|
|
610
|
+
// *
|
|
611
|
+
// * Security: Prevents database tampering attacks.
|
|
612
|
+
// *
|
|
613
|
+
// * @param accountIndex - The account index to verify
|
|
614
|
+
// * @param storedAddress - The address from storage/database
|
|
615
|
+
// * @returns Verification result
|
|
616
|
+
// *
|
|
617
|
+
// * @example
|
|
618
|
+
// * const result = wallet.verifySavingsAddress(1, storedAddress);
|
|
619
|
+
// * if (!result.isValid) {
|
|
620
|
+
// * console.error("Security alert: Address tampering detected!");
|
|
621
|
+
// * }
|
|
622
|
+
// */
|
|
623
|
+
// verifySavingsAddress(accountIndex: number, storedAddress: Hex): AddressVerificationResult {
|
|
624
|
+
// return this.getSavingsManager().verifySavingsAddress(accountIndex, storedAddress);
|
|
625
|
+
// }
|
|
626
|
+
|
|
627
|
+
// /**
|
|
628
|
+
// * Audit multiple savings addresses at once
|
|
629
|
+
// *
|
|
630
|
+
// * @param addresses - Map of accountIndex to stored address
|
|
631
|
+
// * @returns Audit result with summary and details
|
|
632
|
+
// *
|
|
633
|
+
// * @example
|
|
634
|
+
// * const addresses = new Map([[1, "0xabc..."], [2, "0xdef..."]]);
|
|
635
|
+
// * const audit = wallet.auditSavingsAddresses(addresses);
|
|
636
|
+
// * console.log(`Valid: ${audit.valid}/${audit.total}`);
|
|
637
|
+
// */
|
|
638
|
+
// auditSavingsAddresses(addresses: Map<number, Hex>): SavingsAuditResult {
|
|
639
|
+
// return this.getSavingsManager().auditSavingsAddresses(addresses);
|
|
640
|
+
// }
|
|
641
|
+
|
|
642
|
+
// /**
|
|
643
|
+
// * Get savings account information without exposing private key
|
|
644
|
+
// *
|
|
645
|
+
// * @param accountIndex - The account index
|
|
646
|
+
// * @returns Public account information (address and derivation path)
|
|
647
|
+
// *
|
|
648
|
+
// * @example
|
|
649
|
+
// * const info = wallet.getSavingsAccountInfo(1);
|
|
650
|
+
// * console.log(`Address: ${info.address}`);
|
|
651
|
+
// * console.log(`Path: ${info.derivationPath}`); // m/44'/60'/1'/0/0
|
|
652
|
+
// */
|
|
653
|
+
// getSavingsAccountInfo(accountIndex: number): Omit<SavingsAccount, 'privateKey'> {
|
|
654
|
+
// return this.getSavingsManager().getSavingsAccountInfo(accountIndex);
|
|
655
|
+
// }
|
|
656
|
+
|
|
657
|
+
// // ============================================
|
|
658
|
+
// // Smart Savings Methods (EIP-7702)
|
|
659
|
+
// // ============================================
|
|
660
|
+
|
|
661
|
+
// /**
|
|
662
|
+
// * Get or create the SmartSavingsManager instance (lazy initialization)
|
|
663
|
+
// * @private
|
|
664
|
+
// */
|
|
665
|
+
// private getSmartSavingsManager(): SmartSavingsManager {
|
|
666
|
+
// if (!this.smartSavingsManager) {
|
|
667
|
+
// this.smartSavingsManager = new SmartSavingsManager(this.config);
|
|
668
|
+
// }
|
|
669
|
+
// return this.smartSavingsManager;
|
|
670
|
+
// }
|
|
671
|
+
|
|
672
|
+
// /**
|
|
673
|
+
// * Upgrade a savings account to a smart account with EIP-7702 delegation
|
|
674
|
+
// *
|
|
675
|
+
// * This enables advanced features:
|
|
676
|
+
// * - Lock modules for time-locked savings
|
|
677
|
+
// * - Hooks for spend & save
|
|
678
|
+
// * - Session keys for periodic savings
|
|
679
|
+
// * - Sponsored transactions via paymaster
|
|
680
|
+
// *
|
|
681
|
+
// * @param accountIndex - The savings account index to upgrade
|
|
682
|
+
// * @param options - Optional smart wallet configuration
|
|
683
|
+
// * @param autoInitialize - Whether to initialize the smart wallet (default: false)
|
|
684
|
+
// * @returns SmartSavingsAccount with EVMSmartWallet instance
|
|
685
|
+
// *
|
|
686
|
+
// * @example
|
|
687
|
+
// * // Upgrade without initialization
|
|
688
|
+
// * const smartSavings = await wallet.upgradeSavingsToSmartAccount(1);
|
|
689
|
+
// * await smartSavings.smartWallet.initialize(); // Initialize separately
|
|
690
|
+
// *
|
|
691
|
+
// * @example
|
|
692
|
+
// * // Upgrade and initialize in one step
|
|
693
|
+
// * const smartSavings = await wallet.upgradeSavingsToSmartAccount(1, {}, true);
|
|
694
|
+
// * // Ready to use immediately
|
|
695
|
+
// */
|
|
696
|
+
// async upgradeSavingsToSmartAccount(
|
|
697
|
+
// accountIndex: number,
|
|
698
|
+
// options?: SmartWalletOptions,
|
|
699
|
+
// autoInitialize: boolean = false
|
|
700
|
+
// ): Promise<SmartSavingsAccount> {
|
|
701
|
+
// const manager = this.getSmartSavingsManager();
|
|
702
|
+
|
|
703
|
+
// // First derive the basic savings account
|
|
704
|
+
// const basicSavings = this.deriveSavingsAccount(accountIndex);
|
|
705
|
+
|
|
706
|
+
// // Then upgrade to smart account
|
|
707
|
+
// if (autoInitialize) {
|
|
708
|
+
// return await manager.upgradeSavingsAndInitialize(basicSavings, options);
|
|
709
|
+
// } else {
|
|
710
|
+
// return await manager.upgradeSavingsToSmartAccount(basicSavings, options);
|
|
711
|
+
// }
|
|
712
|
+
// }
|
|
713
|
+
|
|
714
|
+
// /**
|
|
715
|
+
// * Check if smart savings is supported on this chain
|
|
716
|
+
// *
|
|
717
|
+
// * @returns true if chain supports Account Abstraction
|
|
718
|
+
// *
|
|
719
|
+
// * @example
|
|
720
|
+
// * if (wallet.isSmartSavingsSupported()) {
|
|
721
|
+
// * const smartSavings = await wallet.upgradeSavingsToSmartAccount(1);
|
|
722
|
+
// * } else {
|
|
723
|
+
// * console.log("This chain doesn't support smart savings");
|
|
724
|
+
// * }
|
|
725
|
+
// */
|
|
726
|
+
// isSmartSavingsSupported(): boolean {
|
|
727
|
+
// try {
|
|
728
|
+
// const manager = this.getSmartSavingsManager();
|
|
729
|
+
// return manager.canUpgradeToSmartAccount();
|
|
730
|
+
// } catch (error) {
|
|
731
|
+
// return false;
|
|
732
|
+
// }
|
|
733
|
+
// }
|
|
1089
734
|
}
|