@deserialize/multi-vm-wallet 1.0.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/tsconfig.tsbuildinfo +1 -0
- package/dist/utils/IChainWallet.d.ts +16 -0
- package/dist/utils/IChainWallet.js +22 -0
- package/dist/utils/bip32.d.ts +11 -0
- package/dist/utils/bip32.js +99 -0
- package/dist/utils/evm/evm.d.ts +30 -0
- package/dist/utils/evm/evm.js +72 -0
- package/dist/utils/evm/index.d.ts +2 -0
- package/dist/utils/evm/index.js +18 -0
- package/dist/utils/evm/utils.d.ts +92 -0
- package/dist/utils/evm/utils.js +346 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +22 -0
- package/dist/utils/svm/index.d.ts +1 -0
- package/dist/utils/svm/index.js +17 -0
- package/dist/utils/svm/svm.d.ts +24 -0
- package/dist/utils/svm/svm.js +71 -0
- package/dist/utils/svm/transactionSender.d.ts +8 -0
- package/dist/utils/svm/transactionSender.js +83 -0
- package/dist/utils/svm/utils.d.ts +26 -0
- package/dist/utils/svm/utils.js +161 -0
- package/dist/utils/types.d.ts +44 -0
- package/dist/utils/types.js +9 -0
- package/dist/utils/vm.d.ts +13 -0
- package/dist/utils/vm.js +49 -0
- package/package.json +42 -0
- package/tsconfig.json +115 -0
- package/utils/IChainWallet.ts +36 -0
- package/utils/bip32.ts +66 -0
- package/utils/evm/evm.ts +84 -0
- package/utils/evm/index.ts +2 -0
- package/utils/evm/utils.ts +504 -0
- package/utils/index.ts +6 -0
- package/utils/svm/index.js +17 -0
- package/utils/svm/index.ts +1 -0
- package/utils/svm/svm.js +71 -0
- package/utils/svm/svm.ts +75 -0
- package/utils/svm/transactionSender.js +83 -0
- package/utils/svm/transactionSender.ts +108 -0
- package/utils/svm/utils.js +161 -0
- package/utils/svm/utils.ts +203 -0
- package/utils/types.ts +53 -0
- package/utils/vm.ts +26 -0
package/utils/evm/evm.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param phrase this is the pass phrase for this vm
|
|
4
|
+
* this is a class that will be responsible for creating several evm wallets
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { EVMDeriveChildPrivateKey, getSeedNode } from "../bip32";
|
|
8
|
+
import { ChainWallet } from "../IChainWallet";
|
|
9
|
+
import { Balance, ChainWalletConfig, NFTInfo, TokenInfo, TransactionResult } from "../types";
|
|
10
|
+
import { VM } from "../vm";
|
|
11
|
+
import { ethers, JsonRpcProvider, Wallet } from "ethers";
|
|
12
|
+
import { getNativeBalance, getTokenBalance, sendERC20Token, sendNativeToken } from "./utils";
|
|
13
|
+
|
|
14
|
+
export const createEvmVmPrivateKey = (phrase: string) => { }
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export class EVMVM extends VM<string, string, JsonRpcProvider> {
|
|
19
|
+
derivationPath = "m/44'/60'/0'/0/"; // Default EVM derivation path
|
|
20
|
+
|
|
21
|
+
constructor(mnemonic: string) {
|
|
22
|
+
super(mnemonic, "EVM");
|
|
23
|
+
}
|
|
24
|
+
generatePrivateKey(index: number, seedPhrase = this.mnemonic, derivationPath = this.derivationPath) {
|
|
25
|
+
const seed = this.mnemonicToSeed(seedPhrase);
|
|
26
|
+
const privateKey = EVMDeriveChildPrivateKey(seed, index, derivationPath).privateKey;
|
|
27
|
+
return { privateKey, index };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
validateAddress(address: string): boolean {
|
|
31
|
+
return ethers.isAddress(address);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static async getNativeBalance(address: string, connection: JsonRpcProvider): Promise<Balance> {
|
|
35
|
+
// Implement native balance retrieval logic here
|
|
36
|
+
return await getNativeBalance(address, connection)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static async getTokenBalance(address: string, tokenAddress: string, connection: JsonRpcProvider): Promise<Balance> {
|
|
40
|
+
// Implement token balance retrieval logic here
|
|
41
|
+
return await getTokenBalance(tokenAddress, address, connection)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class EVMChainWallet extends ChainWallet<string, string, JsonRpcProvider> {
|
|
46
|
+
|
|
47
|
+
constructor(config: ChainWalletConfig, privateKey: string, index: number) {
|
|
48
|
+
super(config, privateKey, index);
|
|
49
|
+
const wallet = new Wallet(privateKey);
|
|
50
|
+
this.address = wallet.address;
|
|
51
|
+
this.privateKey = privateKey;
|
|
52
|
+
this.connection = new JsonRpcProvider(config.rpcUrl)
|
|
53
|
+
}
|
|
54
|
+
getWallet(): Wallet {
|
|
55
|
+
return new Wallet(this.privateKey);
|
|
56
|
+
}
|
|
57
|
+
generateAddress(): string {
|
|
58
|
+
return this.address;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async getNativeBalance(): Promise<Balance> {
|
|
62
|
+
// Implement native balance retrieval logic here
|
|
63
|
+
return await EVMVM.getNativeBalance(this.address, this.connection!)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getTokenBalance(tokenAddress: string): Promise<Balance> {
|
|
67
|
+
// Implement token balance retrieval logic here
|
|
68
|
+
return await EVMVM.getTokenBalance(this.address, tokenAddress, this.connection!)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async transferNative(to: string, amount: number): Promise<TransactionResult> {
|
|
72
|
+
// Implement native transfer logic here
|
|
73
|
+
const wallet = this.getWallet()
|
|
74
|
+
return await sendNativeToken(wallet, to, amount.toString(), undefined, this.config.confirmationNo || 5)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async transferToken(tokenAddress: TokenInfo, to: string, amount: number): Promise<TransactionResult> {
|
|
78
|
+
// Implement token transfer logic here
|
|
79
|
+
const wallet = this.getWallet()
|
|
80
|
+
return await sendERC20Token(wallet, tokenAddress.address, to, amount.toString(), undefined, this.config.confirmationNo || 5)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
|
|
2
|
+
import { Balance } from '../types'
|
|
3
|
+
import { JsonRpcProvider, Contract, Wallet, TransactionRequest, TransactionResponse, TransactionReceipt } from 'ethers'
|
|
4
|
+
import BN from 'bn.js'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
interface TransactionParams {
|
|
8
|
+
to: string
|
|
9
|
+
value?: string | bigint // For native token transfers
|
|
10
|
+
data?: string // For contract calls
|
|
11
|
+
gasLimit?: string | bigint
|
|
12
|
+
gasPrice?: string | bigint
|
|
13
|
+
maxFeePerGas?: string | bigint // For EIP-1559
|
|
14
|
+
maxPriorityFeePerGas?: string | bigint // For EIP-1559
|
|
15
|
+
nonce?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface TransactionResult {
|
|
19
|
+
hash: string
|
|
20
|
+
receipt: TransactionReceipt | null
|
|
21
|
+
success: boolean
|
|
22
|
+
gasUsed?: bigint
|
|
23
|
+
effectiveGasPrice?: bigint
|
|
24
|
+
blockNumber?: number
|
|
25
|
+
confirmations: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ERC-20 ABI
|
|
29
|
+
const ERC20_ABI = [
|
|
30
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
31
|
+
"function decimals() view returns (uint8)",
|
|
32
|
+
"function symbol() view returns (string)",
|
|
33
|
+
"function name() view returns (string)",
|
|
34
|
+
"function transfer(address to, uint256 amount) returns (bool)",
|
|
35
|
+
"function transferFrom(address from, address to, uint256 amount) returns (bool)",
|
|
36
|
+
"function approve(address spender, uint256 amount) returns (bool)",
|
|
37
|
+
"function allowance(address owner, address spender) view returns (uint256)"
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
export const getNativeBalance = async (address: string, provider: JsonRpcProvider): Promise<Balance> => {
|
|
41
|
+
const balance = await provider.getBalance(address)
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
balance: new BN(balance),
|
|
45
|
+
formatted: Number(balance / 10n ** 18n),
|
|
46
|
+
decimal: 18
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const getTokenBalance = async (
|
|
51
|
+
tokenAddress: string,
|
|
52
|
+
walletAddress: string,
|
|
53
|
+
provider: JsonRpcProvider
|
|
54
|
+
): Promise<Balance> => {
|
|
55
|
+
try {
|
|
56
|
+
// Create contract instance
|
|
57
|
+
const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider)
|
|
58
|
+
|
|
59
|
+
// Get balance (returns BigNumber)
|
|
60
|
+
const balance: bigint = await tokenContract.balanceOf(walletAddress)
|
|
61
|
+
|
|
62
|
+
// Get decimals to format the balance properly
|
|
63
|
+
const decimals = await tokenContract.decimals()
|
|
64
|
+
// Format balance by dividing by 10^decimals
|
|
65
|
+
const formattedBalance = balance / (10n ** BigInt(decimals))
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
balance: new BN(balance.toString()), // Raw balance as BigNumber
|
|
69
|
+
formatted: Number(formattedBalance.toString()),
|
|
70
|
+
decimal: decimals
|
|
71
|
+
}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('Error fetching token balance:', error)
|
|
74
|
+
throw error
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Sign, send, and confirm any EVM transaction
|
|
80
|
+
*/
|
|
81
|
+
export const signSendAndConfirm = async (
|
|
82
|
+
wallet: Wallet, // Connected wallet (private key + provider)
|
|
83
|
+
transactionParams: TransactionParams,
|
|
84
|
+
confirmations: number = 1, // Number of confirmations to wait for
|
|
85
|
+
timeout: number = 300000 // 5 minutes timeout
|
|
86
|
+
): Promise<TransactionResult> => {
|
|
87
|
+
try {
|
|
88
|
+
// Prepare transaction object
|
|
89
|
+
const tx: TransactionRequest = {
|
|
90
|
+
to: transactionParams.to,
|
|
91
|
+
value: transactionParams.value || 0,
|
|
92
|
+
data: transactionParams.data || '0x',
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Set gas parameters
|
|
96
|
+
if (transactionParams.gasLimit) {
|
|
97
|
+
tx.gasLimit = transactionParams.gasLimit
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handle gas pricing (EIP-1559 vs legacy)
|
|
101
|
+
if (transactionParams.maxFeePerGas && transactionParams.maxPriorityFeePerGas) {
|
|
102
|
+
// EIP-1559 transaction
|
|
103
|
+
tx.maxFeePerGas = transactionParams.maxFeePerGas
|
|
104
|
+
tx.maxPriorityFeePerGas = transactionParams.maxPriorityFeePerGas
|
|
105
|
+
} else if (transactionParams.gasPrice) {
|
|
106
|
+
// Legacy transaction
|
|
107
|
+
tx.gasPrice = transactionParams.gasPrice
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Set nonce if provided
|
|
111
|
+
if (transactionParams.nonce !== undefined) {
|
|
112
|
+
tx.nonce = transactionParams.nonce
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Estimate gas if not provided
|
|
116
|
+
if (!tx.gasLimit) {
|
|
117
|
+
tx.gasLimit = await wallet.estimateGas(tx)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log('Sending transaction:', tx)
|
|
121
|
+
|
|
122
|
+
// Sign and send transaction
|
|
123
|
+
const txResponse: TransactionResponse = await wallet.sendTransaction(tx)
|
|
124
|
+
|
|
125
|
+
console.log(`Transaction sent with hash: ${txResponse.hash}`)
|
|
126
|
+
|
|
127
|
+
// Wait for confirmation with timeout
|
|
128
|
+
const receipt = await Promise.race([
|
|
129
|
+
txResponse.wait(confirmations),
|
|
130
|
+
new Promise<null>((_, reject) =>
|
|
131
|
+
setTimeout(() => reject(new Error('Transaction confirmation timeout')), timeout)
|
|
132
|
+
)
|
|
133
|
+
])
|
|
134
|
+
|
|
135
|
+
const result: TransactionResult = {
|
|
136
|
+
hash: txResponse.hash,
|
|
137
|
+
receipt: receipt,
|
|
138
|
+
success: receipt?.status === 1,
|
|
139
|
+
gasUsed: receipt?.gasUsed,
|
|
140
|
+
effectiveGasPrice: receipt?.gasPrice,
|
|
141
|
+
blockNumber: receipt?.blockNumber,
|
|
142
|
+
confirmations: confirmations
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(`Transaction ${result.success ? 'successful' : 'failed'}:`, result)
|
|
146
|
+
|
|
147
|
+
return result
|
|
148
|
+
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error('Transaction failed:', error)
|
|
151
|
+
throw error
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Send native token (ETH, BNB, MATIC, etc.)
|
|
157
|
+
*/
|
|
158
|
+
export const sendNativeToken = async (
|
|
159
|
+
wallet: Wallet,
|
|
160
|
+
to: string,
|
|
161
|
+
amount: string | bigint, // Amount in wei
|
|
162
|
+
gasLimit?: string | bigint,
|
|
163
|
+
confirmations: number = 1
|
|
164
|
+
): Promise<TransactionResult> => {
|
|
165
|
+
return await signSendAndConfirm(
|
|
166
|
+
wallet,
|
|
167
|
+
{
|
|
168
|
+
to,
|
|
169
|
+
value: (Number(amount) * 10 ** 18).toString(),
|
|
170
|
+
gasLimit
|
|
171
|
+
},
|
|
172
|
+
confirmations
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Send ERC-20 token
|
|
178
|
+
*/
|
|
179
|
+
export const sendERC20Token = async (
|
|
180
|
+
wallet: Wallet,
|
|
181
|
+
tokenAddress: string,
|
|
182
|
+
to: string,
|
|
183
|
+
amount: string | bigint, // Amount in token's smallest unit
|
|
184
|
+
gasLimit?: string | bigint,
|
|
185
|
+
confirmations: number = 1
|
|
186
|
+
): Promise<TransactionResult> => {
|
|
187
|
+
try {
|
|
188
|
+
// Create contract instance
|
|
189
|
+
const tokenContract = new Contract(tokenAddress, ERC20_ABI, wallet)
|
|
190
|
+
|
|
191
|
+
// Encode transfer function call
|
|
192
|
+
const data = tokenContract.interface.encodeFunctionData('transfer', [to, amount])
|
|
193
|
+
|
|
194
|
+
return await signSendAndConfirm(
|
|
195
|
+
wallet,
|
|
196
|
+
{
|
|
197
|
+
to: tokenAddress,
|
|
198
|
+
data,
|
|
199
|
+
gasLimit
|
|
200
|
+
},
|
|
201
|
+
confirmations
|
|
202
|
+
)
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error('ERC-20 transfer failed:', error)
|
|
205
|
+
throw error
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Execute any contract method
|
|
211
|
+
*/
|
|
212
|
+
export const executeContractMethod = async (
|
|
213
|
+
wallet: Wallet,
|
|
214
|
+
contractAddress: string,
|
|
215
|
+
abi: any[],
|
|
216
|
+
methodName: string,
|
|
217
|
+
methodParams: any[] = [],
|
|
218
|
+
value?: string | bigint, // For payable methods
|
|
219
|
+
gasLimit?: string | bigint,
|
|
220
|
+
confirmations: number = 1
|
|
221
|
+
): Promise<TransactionResult> => {
|
|
222
|
+
try {
|
|
223
|
+
// Create contract instance
|
|
224
|
+
const contract = new Contract(contractAddress, abi, wallet)
|
|
225
|
+
|
|
226
|
+
// Encode method call
|
|
227
|
+
const data = contract.interface.encodeFunctionData(methodName, methodParams)
|
|
228
|
+
|
|
229
|
+
return await signSendAndConfirm(
|
|
230
|
+
wallet,
|
|
231
|
+
{
|
|
232
|
+
to: contractAddress,
|
|
233
|
+
data,
|
|
234
|
+
value,
|
|
235
|
+
gasLimit
|
|
236
|
+
},
|
|
237
|
+
confirmations
|
|
238
|
+
)
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error('Contract method execution failed:', error)
|
|
241
|
+
throw error
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get current gas prices (both legacy and EIP-1559)
|
|
247
|
+
*/
|
|
248
|
+
export const getGasPrices = async (provider: JsonRpcProvider) => {
|
|
249
|
+
try {
|
|
250
|
+
const feeData = await provider.getFeeData()
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
// Legacy
|
|
254
|
+
gasPrice: feeData.gasPrice,
|
|
255
|
+
// EIP-1559
|
|
256
|
+
maxFeePerGas: feeData.maxFeePerGas,
|
|
257
|
+
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas
|
|
258
|
+
}
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.error('Error fetching gas prices:', error)
|
|
261
|
+
throw error
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Estimate gas for a transaction
|
|
267
|
+
*/
|
|
268
|
+
export const estimateGas = async (
|
|
269
|
+
provider: JsonRpcProvider,
|
|
270
|
+
transactionParams: TransactionParams
|
|
271
|
+
): Promise<bigint> => {
|
|
272
|
+
try {
|
|
273
|
+
const tx: TransactionRequest = {
|
|
274
|
+
to: transactionParams.to,
|
|
275
|
+
value: transactionParams.value || 0,
|
|
276
|
+
data: transactionParams.data || '0x'
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return await provider.estimateGas(tx)
|
|
280
|
+
} catch (error) {
|
|
281
|
+
console.error('Gas estimation failed:', error)
|
|
282
|
+
throw error
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Check ERC-20 token allowance
|
|
289
|
+
*/
|
|
290
|
+
export const checkAllowance = async (
|
|
291
|
+
tokenAddress: string,
|
|
292
|
+
owner: string,
|
|
293
|
+
spender: string,
|
|
294
|
+
provider: JsonRpcProvider
|
|
295
|
+
): Promise<{
|
|
296
|
+
allowance: bigint,
|
|
297
|
+
formatted: string,
|
|
298
|
+
decimals: number
|
|
299
|
+
}> => {
|
|
300
|
+
try {
|
|
301
|
+
const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider)
|
|
302
|
+
|
|
303
|
+
// Get allowance and decimals
|
|
304
|
+
const [allowance, decimals] = await Promise.all([
|
|
305
|
+
tokenContract.allowance(owner, spender),
|
|
306
|
+
tokenContract.decimals()
|
|
307
|
+
])
|
|
308
|
+
|
|
309
|
+
// Format allowance for display
|
|
310
|
+
const formattedAllowance = allowance / (10n ** BigInt(decimals))
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
allowance,
|
|
314
|
+
formatted: formattedAllowance.toString(),
|
|
315
|
+
decimals
|
|
316
|
+
}
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.error('Error checking allowance:', error)
|
|
319
|
+
throw error
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Check if allowance is sufficient for a transaction
|
|
325
|
+
*/
|
|
326
|
+
export const isAllowanceSufficient = async (
|
|
327
|
+
tokenAddress: string,
|
|
328
|
+
owner: string,
|
|
329
|
+
spender: string,
|
|
330
|
+
requiredAmount: string | bigint,
|
|
331
|
+
provider: JsonRpcProvider
|
|
332
|
+
): Promise<boolean> => {
|
|
333
|
+
try {
|
|
334
|
+
const { allowance } = await checkAllowance(tokenAddress, owner, spender, provider)
|
|
335
|
+
const required = typeof requiredAmount === 'string' ? BigInt(requiredAmount) : requiredAmount
|
|
336
|
+
|
|
337
|
+
return allowance >= required
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.error('Error checking allowance sufficiency:', error)
|
|
340
|
+
throw error
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Approve ERC-20 token spending
|
|
346
|
+
*/
|
|
347
|
+
export const approveToken = async (
|
|
348
|
+
wallet: Wallet,
|
|
349
|
+
tokenAddress: string,
|
|
350
|
+
spender: string,
|
|
351
|
+
amount: string | bigint, // Amount to approve, use MaxUint256 for unlimited
|
|
352
|
+
gasLimit?: string | bigint,
|
|
353
|
+
confirmations: number = 1
|
|
354
|
+
): Promise<TransactionResult> => {
|
|
355
|
+
try {
|
|
356
|
+
const tokenContract = new Contract(tokenAddress, ERC20_ABI, wallet)
|
|
357
|
+
|
|
358
|
+
// Encode approve function call
|
|
359
|
+
const data = tokenContract.interface.encodeFunctionData('approve', [spender, amount])
|
|
360
|
+
|
|
361
|
+
return await signSendAndConfirm(
|
|
362
|
+
wallet,
|
|
363
|
+
{
|
|
364
|
+
to: tokenAddress,
|
|
365
|
+
data,
|
|
366
|
+
gasLimit
|
|
367
|
+
},
|
|
368
|
+
confirmations
|
|
369
|
+
)
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error('Token approval failed:', error)
|
|
372
|
+
throw error
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Approve unlimited token spending (MaxUint256)
|
|
378
|
+
*/
|
|
379
|
+
export const approveTokenUnlimited = async (
|
|
380
|
+
wallet: Wallet,
|
|
381
|
+
tokenAddress: string,
|
|
382
|
+
spender: string,
|
|
383
|
+
gasLimit?: string | bigint,
|
|
384
|
+
confirmations: number = 1
|
|
385
|
+
): Promise<TransactionResult> => {
|
|
386
|
+
// MaxUint256 = 2^256 - 1
|
|
387
|
+
const MAX_UINT256 = "115792089237316195423570985008687907853269984665640564039457584007913129639935"
|
|
388
|
+
|
|
389
|
+
return await approveToken(wallet, tokenAddress, spender, MAX_UINT256, gasLimit, confirmations)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Check allowance and approve if necessary
|
|
394
|
+
*/
|
|
395
|
+
export const checkAndApprove = async (
|
|
396
|
+
wallet: Wallet,
|
|
397
|
+
tokenAddress: string,
|
|
398
|
+
spender: string,
|
|
399
|
+
requiredAmount: string | bigint,
|
|
400
|
+
approvalAmount?: string | bigint, // If not provided, will approve exactly the required amount
|
|
401
|
+
gasLimit?: string | bigint,
|
|
402
|
+
confirmations: number = 1
|
|
403
|
+
): Promise<{
|
|
404
|
+
approvalNeeded: boolean,
|
|
405
|
+
currentAllowance: bigint,
|
|
406
|
+
approvalResult?: TransactionResult
|
|
407
|
+
}> => {
|
|
408
|
+
try {
|
|
409
|
+
const owner = await wallet.getAddress()
|
|
410
|
+
const provider = wallet.provider as JsonRpcProvider
|
|
411
|
+
|
|
412
|
+
// Check current allowance
|
|
413
|
+
const { allowance } = await checkAllowance(tokenAddress, owner, spender, provider)
|
|
414
|
+
const required = typeof requiredAmount === 'string' ? BigInt(requiredAmount) : requiredAmount
|
|
415
|
+
|
|
416
|
+
const approvalNeeded = allowance < required
|
|
417
|
+
|
|
418
|
+
if (!approvalNeeded) {
|
|
419
|
+
return {
|
|
420
|
+
approvalNeeded: false,
|
|
421
|
+
currentAllowance: allowance
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
console.log(`Approval needed. Current: ${allowance}, Required: ${required}`)
|
|
426
|
+
|
|
427
|
+
// Determine approval amount
|
|
428
|
+
const amountToApprove = approvalAmount || required
|
|
429
|
+
|
|
430
|
+
// Execute approval
|
|
431
|
+
const approvalResult = await approveToken(
|
|
432
|
+
wallet,
|
|
433
|
+
tokenAddress,
|
|
434
|
+
spender,
|
|
435
|
+
amountToApprove,
|
|
436
|
+
gasLimit,
|
|
437
|
+
confirmations
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
approvalNeeded: true,
|
|
442
|
+
currentAllowance: allowance,
|
|
443
|
+
approvalResult
|
|
444
|
+
}
|
|
445
|
+
} catch (error) {
|
|
446
|
+
console.error('Check and approve failed:', error)
|
|
447
|
+
throw error
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Reset token allowance to zero (security best practice before setting new allowance)
|
|
453
|
+
*/
|
|
454
|
+
export const resetAllowance = async (
|
|
455
|
+
wallet: Wallet,
|
|
456
|
+
tokenAddress: string,
|
|
457
|
+
spender: string,
|
|
458
|
+
gasLimit?: string | bigint,
|
|
459
|
+
confirmations: number = 1
|
|
460
|
+
): Promise<TransactionResult> => {
|
|
461
|
+
return await approveToken(wallet, tokenAddress, spender, "0", gasLimit, confirmations)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Safe approve: Reset to zero first, then approve the desired amount
|
|
466
|
+
* (Some tokens like USDT require this)
|
|
467
|
+
*/
|
|
468
|
+
export const safeApprove = async (
|
|
469
|
+
wallet: Wallet,
|
|
470
|
+
tokenAddress: string,
|
|
471
|
+
spender: string,
|
|
472
|
+
amount: string | bigint,
|
|
473
|
+
gasLimit?: string | bigint,
|
|
474
|
+
confirmations: number = 1
|
|
475
|
+
): Promise<{
|
|
476
|
+
resetResult: TransactionResult,
|
|
477
|
+
approveResult: TransactionResult
|
|
478
|
+
}> => {
|
|
479
|
+
try {
|
|
480
|
+
console.log('Performing safe approve: reset then approve')
|
|
481
|
+
|
|
482
|
+
// First reset to zero
|
|
483
|
+
const resetResult = await resetAllowance(wallet, tokenAddress, spender, gasLimit, confirmations)
|
|
484
|
+
|
|
485
|
+
if (!resetResult.success) {
|
|
486
|
+
throw new Error('Failed to reset allowance to zero')
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Then approve the desired amount
|
|
490
|
+
const approveResult = await approveToken(wallet, tokenAddress, spender, amount, gasLimit, confirmations)
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
resetResult,
|
|
494
|
+
approveResult
|
|
495
|
+
}
|
|
496
|
+
} catch (error) {
|
|
497
|
+
console.error('Safe approve failed:', error)
|
|
498
|
+
throw error
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
//swaps
|
|
504
|
+
|
package/utils/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./svm"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./svm"
|
package/utils/svm/svm.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SVMChainWallet = exports.SVMVM = void 0;
|
|
7
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
8
|
+
const bip32_1 = require("../bip32");
|
|
9
|
+
const vm_1 = require("../vm");
|
|
10
|
+
const IChainWallet_1 = require("../IChainWallet");
|
|
11
|
+
const utils_1 = require("./utils");
|
|
12
|
+
const bn_js_1 = __importDefault(require("bn.js"));
|
|
13
|
+
class SVMVM extends vm_1.VM {
|
|
14
|
+
constructor(mnemonic) {
|
|
15
|
+
super(mnemonic, "SVM");
|
|
16
|
+
this.derivationPath = "m/44'/501'/"; // Default EVM derivation path
|
|
17
|
+
}
|
|
18
|
+
static validateAddress(address) {
|
|
19
|
+
return web3_js_1.PublicKey.isOnCurve(address.toBuffer());
|
|
20
|
+
}
|
|
21
|
+
static getNativeBalance(address, connection) {
|
|
22
|
+
return (0, utils_1.getSvmNativeBalance)(address, connection);
|
|
23
|
+
}
|
|
24
|
+
static async getTokenBalance(address, tokenAddress, connection) {
|
|
25
|
+
const balance = await (0, utils_1.getTokenBalance)(address, tokenAddress, connection);
|
|
26
|
+
if (balance === 0) {
|
|
27
|
+
return { balance: new bn_js_1.default(0), formatted: 0, decimal: 0 };
|
|
28
|
+
}
|
|
29
|
+
return { balance: new bn_js_1.default(balance.amount), formatted: balance.uiAmount || parseInt(balance.amount) / 10 ** balance.decimals, decimal: balance.decimals };
|
|
30
|
+
}
|
|
31
|
+
generatePrivateKey(index, seedPhrase = this.mnemonic, derivationPath = this.derivationPath) {
|
|
32
|
+
const seed = this.mnemonicToSeed(seedPhrase);
|
|
33
|
+
const privateKey = (0, bip32_1.SVMDeriveChildPrivateKey)(seed, index, derivationPath);
|
|
34
|
+
return { privateKey, index };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.SVMVM = SVMVM;
|
|
38
|
+
SVMVM.signAndSendTransaction = utils_1.signAndSendTransaction;
|
|
39
|
+
class SVMChainWallet extends IChainWallet_1.ChainWallet {
|
|
40
|
+
constructor(config, privateKey, index) {
|
|
41
|
+
super(config, privateKey, index);
|
|
42
|
+
this.address = privateKey.publicKey;
|
|
43
|
+
this.privateKey = privateKey;
|
|
44
|
+
this.connection = new web3_js_1.Connection(config.rpcUrl);
|
|
45
|
+
}
|
|
46
|
+
generateAddress() {
|
|
47
|
+
return this.address;
|
|
48
|
+
}
|
|
49
|
+
async getNativeBalance() {
|
|
50
|
+
// Implement native balance retrieval logic here
|
|
51
|
+
return await SVMVM.getNativeBalance(this.address, this.connection);
|
|
52
|
+
}
|
|
53
|
+
async getTokenBalance(tokenAddress) {
|
|
54
|
+
// Implement token balance retrieval logic here
|
|
55
|
+
return await SVMVM.getTokenBalance(this.address, (tokenAddress), this.connection);
|
|
56
|
+
}
|
|
57
|
+
async transferNative(to, amount) {
|
|
58
|
+
// Implement native transfer logic here
|
|
59
|
+
const transaction = await (0, utils_1.getTransferNativeTransaction)(this.privateKey, to, amount, this.connection);
|
|
60
|
+
const hash = await SVMVM.signAndSendTransaction(transaction, this.connection, [this.privateKey]);
|
|
61
|
+
return { success: true, hash }; // Placeholder
|
|
62
|
+
}
|
|
63
|
+
async transferToken(token, to, amount) {
|
|
64
|
+
// Implement token transfer logic here
|
|
65
|
+
const transaction = await (0, utils_1.getTransferTokenTransaction)(this.privateKey, new web3_js_1.PublicKey(to), token, (amount), this.connection);
|
|
66
|
+
const hash = await SVMVM.signAndSendTransaction(transaction, this.connection, [this.privateKey]);
|
|
67
|
+
return { success: true, hash }; // Placeholder
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.SVMChainWallet = SVMChainWallet;
|
|
71
|
+
//swaps jupiter swap here
|