@deserialize/multi-vm-wallet 1.1.4 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constant.js +16 -2
- package/dist/constant.js.map +1 -1
- package/dist/evm/evm.d.ts +3 -1
- package/dist/evm/evm.js +154 -9
- package/dist/evm/evm.js.map +1 -1
- package/dist/evm/script.d.ts +24 -0
- package/dist/evm/script.js +111 -0
- package/dist/evm/script.js.map +1 -0
- package/dist/evm/utils.d.ts +13 -0
- package/dist/evm/utils.js +36 -7
- package/dist/evm/utils.js.map +1 -1
- package/dist/test.js +37 -11
- package/dist/test.js.map +1 -1
- package/package.json +6 -4
- package/utils/constant.ts +16 -2
- package/utils/evm/evm.ts +242 -9
- package/utils/evm/script.ts +160 -0
- package/utils/evm/utils.ts +46 -15
- package/utils/test.ts +45 -10
- package/utils/todo.txt +7 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import { ethers } from 'ethers';
|
|
4
|
+
|
|
5
|
+
// Simplified ABI for Uniswap V3 Router
|
|
6
|
+
const ROUTER_ABI = [
|
|
7
|
+
'function exactInputSingle((address tokenIn, address tokenIn, uint24 fee, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) external payable returns (uint256 amountOut)',
|
|
8
|
+
'function exactOutputSingle((address tokenIn, address tokenIn, uint24 fee, address recipient, uint256 deadline, uint256 amountOut, uint256 amountInMaximum, uint160 sqrtPriceLimitX96)) external payable returns (uint256 amountIn)'
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
// ERC20 ABI (simplified)
|
|
12
|
+
const ERC20_ABI = [
|
|
13
|
+
'function approve(address spender, uint256 amount) external returns (bool)',
|
|
14
|
+
'function allowance(address owner, address spender) external view returns (uint256)',
|
|
15
|
+
'function balanceOf(address account) external view returns (uint256)'
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export interface SwapParams {
|
|
19
|
+
privateKey: string;
|
|
20
|
+
tokenIn: string;
|
|
21
|
+
tokenOut: string;
|
|
22
|
+
tokenInDecimal:number;
|
|
23
|
+
amountIn: string;
|
|
24
|
+
adminAddress: string;
|
|
25
|
+
routerAddress: string;
|
|
26
|
+
minAmountOut:string;
|
|
27
|
+
rpcUrl: string;
|
|
28
|
+
feeTier?: number; // 500 = 0.05%, 3000 = 0.3%, 10000 = 1%
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const quote = async (quoterAddress:string,rpc:string,tokenIn:string, tokenOut:string, amountIn:string, fee:number,sqrtPriceLimitX96="0") => {
|
|
32
|
+
const provider = new ethers.JsonRpcProvider(rpc);
|
|
33
|
+
|
|
34
|
+
const viewABI = [
|
|
35
|
+
{
|
|
36
|
+
inputs: [
|
|
37
|
+
{ internalType: "address", name: "tokenIn", type: "address" },
|
|
38
|
+
{ internalType: "address", name: "tokenOut", type: "address" },
|
|
39
|
+
{ internalType: "uint24", name: "fee", type: "uint24" },
|
|
40
|
+
{ internalType: "uint256", name: "amountIn", type: "uint256" },
|
|
41
|
+
{ internalType: "uint160", name: "sqrtPriceLimitX96", type: "uint160" },
|
|
42
|
+
],
|
|
43
|
+
name: "quoteExactInputSingle",
|
|
44
|
+
outputs: [{ internalType: "uint256", name: "amountOut", type: "uint256" }],
|
|
45
|
+
stateMutability: "view",
|
|
46
|
+
type: "function",
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const quoter = new ethers.Contract(quoterAddress, viewABI, provider);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const amountOut = await quoter.quoteExactInputSingle(
|
|
54
|
+
tokenIn,
|
|
55
|
+
tokenOut,
|
|
56
|
+
fee,
|
|
57
|
+
amountIn,
|
|
58
|
+
sqrtPriceLimitX96
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
console.log("Amount Out:", amountOut.toString());
|
|
62
|
+
return amountOut.toString();
|
|
63
|
+
} catch (err:any) {
|
|
64
|
+
console.error("Quote failed:", err);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
export const quickSwapSetup = async (params: SwapParams) => {
|
|
70
|
+
const {
|
|
71
|
+
privateKey,
|
|
72
|
+
tokenIn,
|
|
73
|
+
tokenOut,
|
|
74
|
+
amountIn,
|
|
75
|
+
tokenInDecimal,
|
|
76
|
+
adminAddress,
|
|
77
|
+
routerAddress,
|
|
78
|
+
rpcUrl,
|
|
79
|
+
minAmountOut,
|
|
80
|
+
|
|
81
|
+
feeTier = 100 // 0.01% fee tier
|
|
82
|
+
} = params;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// Setup provider and wallet
|
|
86
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
87
|
+
const wallet = new ethers.Wallet(privateKey, provider);
|
|
88
|
+
|
|
89
|
+
// Create contract instances
|
|
90
|
+
const router = new ethers.Contract(routerAddress, ROUTER_ABI, wallet);
|
|
91
|
+
const tokenInContract = new ethers.Contract(tokenIn, ERC20_ABI, wallet);
|
|
92
|
+
|
|
93
|
+
// Convert amount to wei
|
|
94
|
+
const amountInWei = ethers.parseUnits(amountIn, tokenInDecimal); // Assuming 18 decimals
|
|
95
|
+
|
|
96
|
+
// Check and approve token allowance
|
|
97
|
+
const allowance = await tokenInContract.allowance(wallet.address, routerAddress);
|
|
98
|
+
|
|
99
|
+
if (allowance < amountInWei) {
|
|
100
|
+
console.log('Approving tokens...');
|
|
101
|
+
const approveTx = await tokenInContract.approve(routerAddress, amountInWei);
|
|
102
|
+
await approveTx.wait();
|
|
103
|
+
console.log('Approval confirmed');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Calculate amount with fee (0.01% to admin)
|
|
107
|
+
const feeAmount = amountInWei * BigInt(1) / BigInt(10000); // 0.01%
|
|
108
|
+
const amountAfterFee = amountInWei - feeAmount;
|
|
109
|
+
|
|
110
|
+
// Transfer fee to admin
|
|
111
|
+
if (feeAmount > 0) {
|
|
112
|
+
console.log(`Transferring ${ethers.formatUnits(feeAmount, tokenIn)} fee to admin`);
|
|
113
|
+
const feeTransferTx = await tokenInContract.transfer(adminAddress, feeAmount);
|
|
114
|
+
await feeTransferTx.wait();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Set deadline (10 minutes from now)
|
|
118
|
+
const deadline = Math.floor(Date.now() / 1000) + 600;
|
|
119
|
+
|
|
120
|
+
// Prepare swap parameters
|
|
121
|
+
const swapParams = {
|
|
122
|
+
tokenIn: tokenIn,
|
|
123
|
+
tokenOut: tokenOut,
|
|
124
|
+
fee: feeTier,
|
|
125
|
+
recipient: wallet.address,
|
|
126
|
+
deadline: deadline,
|
|
127
|
+
amountIn: amountAfterFee,
|
|
128
|
+
amountOutMinimum: minAmountOut,
|
|
129
|
+
sqrtPriceLimitX96: 0
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
console.log('Executing swap...');
|
|
133
|
+
|
|
134
|
+
// Execute swap
|
|
135
|
+
const swapTx = await router.exactInputSingle(swapParams);
|
|
136
|
+
const receipt = await swapTx.wait();
|
|
137
|
+
|
|
138
|
+
console.log('Swap successful!');
|
|
139
|
+
console.log('Transaction hash:', receipt.hash);
|
|
140
|
+
|
|
141
|
+
// Check final balances
|
|
142
|
+
const tokenOutContract = new ethers.Contract(tokenOut, ERC20_ABI, wallet);
|
|
143
|
+
const finalBalance = await tokenOutContract.balanceOf(wallet.address);
|
|
144
|
+
|
|
145
|
+
console.log(`Final ${tokenOut} balance:`, ethers.formatUnits(finalBalance, 18));
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
success: true,
|
|
149
|
+
txHash: receipt.hash,
|
|
150
|
+
amountOut: finalBalance.toString()
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error('Swap failed:', error);
|
|
155
|
+
return {
|
|
156
|
+
success: false,
|
|
157
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
};
|
package/utils/evm/utils.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import { Balance, TokenInfo } from '../types'
|
|
3
2
|
import { JsonRpcProvider, Contract, Wallet, TransactionRequest, TransactionResponse, TransactionReceipt } from 'ethers'
|
|
4
3
|
import BN from 'bn.js'
|
|
@@ -24,6 +23,7 @@ interface TransactionResult {
|
|
|
24
23
|
effectiveGasPrice?: bigint
|
|
25
24
|
blockNumber?: number
|
|
26
25
|
confirmations: number
|
|
26
|
+
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface SwapParams {
|
|
@@ -114,6 +114,10 @@ const KYBER_SUPPORTED_CHAINS: { [key: string]: string } = {
|
|
|
114
114
|
'59144': 'linea'
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
+
export const DESERIALIZED_SUPPORTED_CHAINS: { [key: string]: string } = {
|
|
118
|
+
'16661': '0gMainnet',
|
|
119
|
+
};
|
|
120
|
+
|
|
117
121
|
interface KyberSwapParams {
|
|
118
122
|
chainId: string;
|
|
119
123
|
tokenIn: string;
|
|
@@ -143,13 +147,18 @@ const ERC20_ABI = [
|
|
|
143
147
|
]
|
|
144
148
|
|
|
145
149
|
export const getNativeBalance = async (address: string, provider: JsonRpcProvider): Promise<Balance> => {
|
|
146
|
-
const balance = await provider.getBalance(address)
|
|
150
|
+
const balance = await provider.getBalance(address); // returns BigInt in ethers v6
|
|
151
|
+
|
|
152
|
+
const dem = 10n ** 18n;
|
|
153
|
+
|
|
154
|
+
// ⚠️ BigInt division truncates, so avoid dividing BigInt directly
|
|
155
|
+
const formatted = Number(balance) / Number(dem);
|
|
147
156
|
|
|
148
157
|
return {
|
|
149
|
-
balance: new BN(balance),
|
|
150
|
-
formatted
|
|
158
|
+
balance: new BN(balance.toString()), // raw wei as BN
|
|
159
|
+
formatted, // e.g. 0.1
|
|
151
160
|
decimal: 18
|
|
152
|
-
}
|
|
161
|
+
};
|
|
153
162
|
}
|
|
154
163
|
|
|
155
164
|
export const getTokenInfo = async (
|
|
@@ -755,6 +764,9 @@ export async function performSwap(params: {
|
|
|
755
764
|
chargeFeeBy?: 'currency_in' | 'currency_out';
|
|
756
765
|
clientId?: string;
|
|
757
766
|
}): Promise<TransactionParams> {
|
|
767
|
+
if (!KYBER_SUPPORTED_CHAINS[params.chainId]) {
|
|
768
|
+
throw new Error(`KyberSwap does not support chain ID: ${params.chainId}`);
|
|
769
|
+
}
|
|
758
770
|
try {
|
|
759
771
|
console.log('Starting KyberSwap aggregation...', {
|
|
760
772
|
tokenIn: params.tokenIn,
|
|
@@ -762,9 +774,9 @@ export async function performSwap(params: {
|
|
|
762
774
|
amountIn: params.amountIn,
|
|
763
775
|
chainId: params.chainId
|
|
764
776
|
});
|
|
765
|
-
|
|
777
|
+
|
|
766
778
|
console.log('Fetching best swap route across all DEXs...');
|
|
767
|
-
|
|
779
|
+
|
|
768
780
|
const routeResponse = await getKyberSwapRoute({
|
|
769
781
|
chainId: params.chainId,
|
|
770
782
|
tokenIn: params.tokenIn,
|
|
@@ -785,7 +797,7 @@ export async function performSwap(params: {
|
|
|
785
797
|
}
|
|
786
798
|
|
|
787
799
|
const { routeSummary, routerAddress } = routeResponse.data;
|
|
788
|
-
|
|
800
|
+
|
|
789
801
|
// Debug: Log what we actually received
|
|
790
802
|
console.log('routeSummary keys:', Object.keys(routeSummary));
|
|
791
803
|
console.log('routeSummary.swaps exists:', !!routeSummary.swaps);
|
|
@@ -801,13 +813,13 @@ export async function performSwap(params: {
|
|
|
801
813
|
// Only try to access swaps if it exists
|
|
802
814
|
swapsCount: routeSummary.swaps ? routeSummary.swaps.length : 0,
|
|
803
815
|
// Only extract exchange names if swaps exists and has the expected structure
|
|
804
|
-
dexSources: routeSummary.swaps && Array.isArray(routeSummary.swaps)
|
|
816
|
+
dexSources: routeSummary.swaps && Array.isArray(routeSummary.swaps)
|
|
805
817
|
? routeSummary.swaps.map(swap => swap?.exchange || 'unknown').filter(Boolean)
|
|
806
818
|
: ['unknown']
|
|
807
819
|
});
|
|
808
820
|
|
|
809
821
|
console.log('Building executable transaction...');
|
|
810
|
-
|
|
822
|
+
|
|
811
823
|
const buildResponse = await buildKyberSwapTransaction(
|
|
812
824
|
params.chainId,
|
|
813
825
|
routeSummary,
|
|
@@ -845,7 +857,7 @@ export async function performSwap(params: {
|
|
|
845
857
|
|
|
846
858
|
} catch (error) {
|
|
847
859
|
console.error('❌ KyberSwap aggregation failed:', error);
|
|
848
|
-
|
|
860
|
+
|
|
849
861
|
// More detailed error logging
|
|
850
862
|
if (error instanceof Error) {
|
|
851
863
|
console.error('Error details:', {
|
|
@@ -854,7 +866,7 @@ export async function performSwap(params: {
|
|
|
854
866
|
name: error.name
|
|
855
867
|
});
|
|
856
868
|
}
|
|
857
|
-
|
|
869
|
+
|
|
858
870
|
throw new Error(`Swap preparation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
859
871
|
}
|
|
860
872
|
}
|
|
@@ -867,6 +879,10 @@ export function isChainSupportedByKyber(chainId: string): boolean {
|
|
|
867
879
|
return chainId in KYBER_SUPPORTED_CHAINS;
|
|
868
880
|
}
|
|
869
881
|
|
|
882
|
+
export function isChainSupportedByDebonk(chainId: string): boolean {
|
|
883
|
+
return chainId in DESERIALIZED_SUPPORTED_CHAINS;
|
|
884
|
+
}
|
|
885
|
+
|
|
870
886
|
export function getNativeTokenAddress(): string {
|
|
871
887
|
return '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
|
|
872
888
|
}
|
|
@@ -909,7 +925,22 @@ export function prepareSwapParams(
|
|
|
909
925
|
};
|
|
910
926
|
}
|
|
911
927
|
|
|
928
|
+
/**
|
|
929
|
+
* Normalize token address for Debonk
|
|
930
|
+
* Converts 'native' or variations to the standard 0xEeeee... address
|
|
931
|
+
*/
|
|
932
|
+
// export function normalizeTokenAddressForDebonk(tokenAddress: string): string {
|
|
933
|
+
// if (tokenAddress === 'native' ||
|
|
934
|
+
// tokenAddress.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee') {
|
|
935
|
+
// return getNativeTokenAddress();
|
|
936
|
+
// }
|
|
937
|
+
// return tokenAddress;
|
|
938
|
+
// }
|
|
912
939
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
940
|
+
/**
|
|
941
|
+
* Convert slippage from basis points to percentage for Debonk
|
|
942
|
+
* Input: 50 (0.5% in bps) -> Output: 0.5 (percentage)
|
|
943
|
+
*/
|
|
944
|
+
export function convertSlippageForDebonk(slippageBps: number): number {
|
|
945
|
+
return slippageBps / 100;
|
|
946
|
+
}
|
package/utils/test.ts
CHANGED
|
@@ -3,29 +3,31 @@ import { generateKey } from "crypto";
|
|
|
3
3
|
|
|
4
4
|
import base58 from "bs58";
|
|
5
5
|
import { NATIVE_MINT } from "@solana/spl-token";
|
|
6
|
-
import { GenerateNewMnemonic } from "./bip32";
|
|
6
|
+
// import { GenerateNewMnemonic } from "./bip32";
|
|
7
7
|
import { SVMChainWallet, SVMVM } from "./svm";
|
|
8
8
|
import { VM } from "./vm";
|
|
9
9
|
import { ChainWalletConfig } from "./types";
|
|
10
|
-
import { Keypair } from ".";
|
|
11
|
-
|
|
10
|
+
import { EVMChainWallet, EVMVM, Keypair } from ".";
|
|
11
|
+
import { JsonRpcProvider } from "ethers";
|
|
12
|
+
import BN from "bn.js";
|
|
13
|
+
// const mnemonic = GenerateNewMnemonic()
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
console.log('mnemonic: ', mnemonic);
|
|
16
|
+
// console.log('mnemonic: ', mnemonic);
|
|
15
17
|
|
|
16
|
-
const seed = VM.mnemonicToSeed(mnemonic)
|
|
18
|
+
// const seed = VM.mnemonicToSeed(mnemonic)
|
|
17
19
|
const pKey = "4QxETeX9pndiF1XNghUiDTnZnHq3cfjmuPLBJysrgocsLq1yb8w96aPWALa8ZnRZWmDU4wM8Tg8d1ZRVVByj7uXE"
|
|
18
20
|
|
|
19
21
|
export const testUserKeyPair = Keypair.fromSecretKey(base58.decode(pKey));
|
|
20
22
|
const x = testUserKeyPair instanceof Keypair;
|
|
21
|
-
const vm = new SVMVM(seed)
|
|
23
|
+
// const vm = new SVMVM(seed)
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
// const vmFromMnemonic = SVMVM.fromMnemonic(mnemonic)
|
|
25
27
|
// const keyFromMnemonic = vmFromMnemonic.generatePrivateKey(0)
|
|
26
28
|
// console.log('keyFromMnemonic: ', keyFromMnemonic.privateKey.publicKey);
|
|
27
|
-
const key = vm.generatePrivateKey(0)
|
|
28
|
-
console.log('key: ', key.privateKey.publicKey);
|
|
29
|
+
// const key = vm.generatePrivateKey(0)
|
|
30
|
+
// console.log('key: ', key.privateKey.publicKey);
|
|
29
31
|
const chainConfig: ChainWalletConfig = {
|
|
30
32
|
chainId: "solana-mainnet",
|
|
31
33
|
name: "Solana",
|
|
@@ -36,9 +38,42 @@ const chainConfig: ChainWalletConfig = {
|
|
|
36
38
|
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
const evmChainConfig: ChainWalletConfig = {
|
|
42
|
+
chainId: "evm-mainnet",
|
|
43
|
+
name: "Ethereum",
|
|
44
|
+
rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/vB5mKztdJeFdz9RkW99Qf",
|
|
45
|
+
explorerUrl: "https://explorer.ethereum.com",
|
|
46
|
+
nativeToken: { name: "Ethereum", symbol: "ETH", decimals: 18 },
|
|
47
|
+
confirmationNo: 1,
|
|
48
|
+
}
|
|
49
|
+
const gChainConfig: ChainWalletConfig = {
|
|
50
|
+
chainId: "16661",
|
|
51
|
+
name: "Ethereum",
|
|
52
|
+
rpcUrl: "https://evmrpc.0g.ai",
|
|
53
|
+
explorerUrl: "https://chainscan.0g.ai",
|
|
54
|
+
nativeToken: {
|
|
55
|
+
name: "OG Mainnet",
|
|
56
|
+
symbol: "0G",
|
|
57
|
+
decimals: 18,
|
|
58
|
+
},
|
|
59
|
+
confirmationNo: 1,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// const wallet = new SVMChainWallet(chainConfig, testUserKeyPair, 0)
|
|
63
|
+
// const wallet = new EVMChainWallet(chainConfig, testUserKeyPair, 0)
|
|
64
|
+
const address = "0x34906823118C27B83243a4d3B19dd2984DdfB645"
|
|
65
|
+
EVMVM.getNativeBalance(address, new JsonRpcProvider(gChainConfig.rpcUrl)).then(e => {
|
|
66
|
+
const bal = (e.balance as BN).toString()
|
|
67
|
+
console.log('bal: ', bal);
|
|
68
|
+
console.log('native balance: ', e)
|
|
69
|
+
|
|
70
|
+
})
|
|
71
|
+
// console.log('wallet: ', wallet);
|
|
72
|
+
|
|
73
|
+
// wallet.getNativeBalance().then(e => console.log('native balance: ', e))
|
|
74
|
+
|
|
75
|
+
|
|
39
76
|
|
|
40
|
-
const wallet = new SVMChainWallet(chainConfig, testUserKeyPair, key.index)
|
|
41
|
-
console.log('wallet: ', wallet);
|
|
42
77
|
// const toBuy = new PublicKey("9BB6NFEcjBCtnNLFko2FqVQBq8HHM13kCyYcdQbgpump")
|
|
43
78
|
// wallet.swap({
|
|
44
79
|
// name: NATIVE_MINT.toBase58(),
|
package/utils/todo.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Discover tokens for users both for evm and svm
|
|
2
|
+
// Discover wallet address for users based on private staticAccountKeys
|
|
3
|
+
// wallet activities from the blockchain per wallet address
|
|
4
|
+
// transaction insight both evm and svm / transaction preview / transaction quote / simulate transactions and show result
|
|
5
|
+
//
|
|
6
|
+
// provide good images for the blockchains
|
|
7
|
+
// connect wallet
|