UNPKG

1inch-agent-kit

Version:

AI Agent Kit for 1inch - Connect any LLM to 1inch DeFi protocols

549 lines 20.6 kB
"use strict"; /** * Wallet connection utility for 1inch Agent Kit * Handles both local JSON wallet (for scripts) and frontend wallet connections */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WalletUtils = exports.walletManager = exports.WalletManager = void 0; exports.withWallet = withWallet; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const ethers_1 = require("ethers"); const logger_1 = require("./logger"); /** * Wallet manager class */ class WalletManager { constructor() { this.currentWallet = null; this.walletSource = 'none'; this.ethersWallet = null; this.provider = null; // Look for wallet.json in the project root this.localWalletPath = path_1.default.resolve(process.cwd(), 'wallet.json'); } /** * Initialize wallet - automatically detects if running in script mode or frontend mode */ async initialize(frontendWallet) { try { if (frontendWallet) { // Frontend wallet provided logger_1.logger.info('Using frontend wallet:', frontendWallet.address); this.currentWallet = frontendWallet; this.walletSource = 'frontend'; } else { // Try to load local wallet for scripts await this.loadLocalWallet(); } // Initialize ethers wallet if we have a private key if (this.currentWallet?.privateKey) { await this.initializeEthersWallet(); } return this.getWalletContext(); } catch (error) { logger_1.logger.error('Failed to initialize wallet:', error); return { wallet: null, isConnected: false, source: 'none' }; } } /** * Load local wallet from JSON file */ async loadLocalWallet() { try { if (fs_1.default.existsSync(this.localWalletPath)) { const walletData = JSON.parse(fs_1.default.readFileSync(this.localWalletPath, 'utf8')); // Validate wallet data if (!walletData.address || !walletData.chainId) { throw new Error('Invalid wallet.json format - missing address or chainId'); } this.currentWallet = { ...walletData, isTestWallet: true }; this.walletSource = 'local'; logger_1.logger.info(`Loaded local wallet: ${walletData.address} on chain ${walletData.chainId}`); } else { logger_1.logger.warn('No wallet.json found and no frontend wallet provided'); this.currentWallet = null; this.walletSource = 'none'; } } catch (error) { logger_1.logger.error('Error loading local wallet:', error); throw error; } } /** * Initialize ethers wallet for transaction signing */ async initializeEthersWallet() { if (!this.currentWallet?.privateKey) { logger_1.logger.warn('No private key available for ethers wallet initialization'); return; } try { // Create provider based on chain ID const rpcUrl = this.getRpcUrl(this.currentWallet.chainId); this.provider = new ethers_1.ethers.JsonRpcProvider(rpcUrl); // Create ethers wallet this.ethersWallet = new ethers_1.ethers.Wallet(this.currentWallet.privateKey, this.provider); logger_1.logger.info(`Initialized ethers wallet: ${this.ethersWallet.address}`); } catch (error) { logger_1.logger.error('Error initializing ethers wallet:', error); throw error; } } /** * Get RPC URL for a given chain ID */ getRpcUrl(chainId) { const rpcUrls = { 1: process.env.ETHEREUM_RPC_URL || 'https://eth.llamarpc.com', 10: process.env.OPTIMISM_RPC_URL || 'https://mainnet.optimism.io', 56: process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org', 100: process.env.GNOSIS_RPC_URL || 'https://rpc.gnosischain.com', 137: process.env.POLYGON_RPC_URL || 'https://polygon-rpc.com', 324: process.env.ZKSYNC_RPC_URL || 'https://mainnet.era.zksync.io', 42161: process.env.ARBITRUM_RPC_URL || 'https://arb1.arbitrum.io/rpc', 43114: process.env.AVALANCHE_RPC_URL || 'https://api.avax.network/ext/bc/C/rpc', 8453: process.env.BASE_RPC_URL || 'https://mainnet.base.org', 7565164: process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com' }; return rpcUrls[chainId] || process.env.DEFAULT_RPC_URL || 'https://eth.llamarpc.com'; } /** * Set frontend wallet */ setFrontendWallet(wallet) { logger_1.logger.info('Setting frontend wallet:', wallet.address); this.currentWallet = wallet; this.walletSource = 'frontend'; return this.getWalletContext(); } /** * Get current wallet context */ getWalletContext() { return { wallet: this.currentWallet, isConnected: this.currentWallet !== null, source: this.walletSource }; } /** * Get wallet for specific chain (with validation) */ getWalletForChain(chainId) { if (!this.currentWallet) { logger_1.logger.error('No wallet connected'); return null; } if (this.currentWallet.chainId !== chainId) { logger_1.logger.warn(`Wallet chain mismatch. Required: ${chainId}, Current: ${this.currentWallet.chainId}`); // For test wallets, we can be more flexible if (this.currentWallet.isTestWallet) { logger_1.logger.info('Using test wallet for different chain'); return { ...this.currentWallet, chainId: chainId }; } return null; } return this.currentWallet; } /** * Check if wallet is connected */ isConnected() { return this.currentWallet !== null; } /** * Get wallet address */ getAddress() { return this.currentWallet?.address || null; } /** * Get wallet chain ID */ getChainId() { return this.currentWallet?.chainId || null; } /** * Disconnect wallet */ disconnect() { logger_1.logger.info('Disconnecting wallet'); this.currentWallet = null; this.walletSource = 'none'; this.ethersWallet = null; this.provider = null; } /** * Create wallet parameters for function calls */ createWalletParams(overrides = {}) { if (!this.currentWallet) { return null; } return { ...this.currentWallet, ...overrides }; } /** * Sign and send a transaction * For local wallets: uses ethers.js to sign and send * For frontend wallets: returns transaction data for external signing */ async signAndSendTransaction(params) { if (!this.currentWallet) { throw new Error('No wallet connected'); } try { if (this.walletSource === 'local' && this.ethersWallet) { // Local wallet with private key - sign and send directly return await this.signAndSendLocalTransaction(params); } else if (this.walletSource === 'frontend') { // Frontend wallet (MetaMask, etc.) - return transaction data for external signing return await this.prepareTransactionForFrontend(params); } else { throw new Error('No signing method available for current wallet type'); } } catch (error) { logger_1.logger.error('Error signing and sending transaction:', error); throw error; } } /** * Sign and send transaction using local ethers wallet */ async signAndSendLocalTransaction(params) { if (!this.ethersWallet || !this.provider) { throw new Error('Ethers wallet not initialized'); } try { // Prepare transaction parameters const txParams = { to: params.to, data: params.data, value: typeof params.value === 'string' ? BigInt(params.value) : params.value, gasPrice: params.gasPrice ? (typeof params.gasPrice === 'string' ? BigInt(params.gasPrice) : params.gasPrice) : undefined, gasLimit: params.gas ? (typeof params.gas === 'string' ? BigInt(params.gas) : params.gas) : undefined, nonce: params.nonce, chainId: params.chainId || this.currentWallet?.chainId }; // Estimate gas if not provided if (!txParams.gasLimit) { txParams.gasLimit = await this.provider.estimateGas(txParams); logger_1.logger.info(`Estimated gas: ${txParams.gasLimit.toString()}`); } // Get gas price if not provided if (!txParams.gasPrice) { txParams.gasPrice = await this.provider.getFeeData().then((fee) => fee.gasPrice); logger_1.logger.info(`Gas price: ${txParams.gasPrice?.toString()}`); } // Get nonce if not provided if (txParams.nonce === undefined) { txParams.nonce = await this.provider.getTransactionCount(this.ethersWallet.address, 'pending'); logger_1.logger.info(`Nonce: ${txParams.nonce}`); } // Sign and send transaction const tx = await this.ethersWallet.sendTransaction(txParams); logger_1.logger.info(`Transaction sent: ${tx.hash}`); // Wait for transaction to be mined const receipt = await tx.wait(); logger_1.logger.info(`Transaction confirmed in block ${receipt?.blockNumber}`); return { hash: tx.hash, from: tx.from, to: tx.to || '', data: tx.data, value: tx.value.toString(), gasPrice: tx.gasPrice?.toString() || '0', gas: tx.gasLimit.toString(), nonce: tx.nonce, chainId: Number(tx.chainId) || 0 }; } catch (error) { logger_1.logger.error('Error in local transaction signing:', error); throw error; } } /** * Prepare transaction data for frontend wallet signing */ async prepareTransactionForFrontend(params) { if (!this.provider) { // Initialize provider for the current chain const rpcUrl = this.getRpcUrl(this.currentWallet.chainId); this.provider = new ethers_1.ethers.JsonRpcProvider(rpcUrl); } try { // Prepare transaction parameters const txParams = { to: params.to, data: params.data, value: typeof params.value === 'string' ? BigInt(params.value) : params.value, gasPrice: params.gasPrice ? (typeof params.gasPrice === 'string' ? BigInt(params.gasPrice) : params.gasPrice) : undefined, gasLimit: params.gas ? (typeof params.gas === 'string' ? BigInt(params.gas) : params.gas) : undefined, nonce: params.nonce, chainId: params.chainId || this.currentWallet?.chainId }; // Estimate gas if not provided if (!txParams.gasLimit) { txParams.gasLimit = await this.provider.estimateGas(txParams); logger_1.logger.info(`Estimated gas: ${txParams.gasLimit.toString()}`); } // Get gas price if not provided if (!txParams.gasPrice) { txParams.gasPrice = await this.provider.getFeeData().then((fee) => fee.gasPrice); logger_1.logger.info(`Gas price: ${txParams.gasPrice?.toString()}`); } // Get nonce if not provided if (txParams.nonce === undefined) { txParams.nonce = await this.provider.getTransactionCount(this.currentWallet.address, 'pending'); logger_1.logger.info(`Nonce: ${txParams.nonce}`); } // Return transaction data for frontend signing return { hash: '', // Will be set after frontend signing from: this.currentWallet.address, to: params.to, data: params.data, value: params.value.toString(), gasPrice: txParams.gasPrice?.toString() || '0', gas: txParams.gasLimit?.toString() || '0', nonce: txParams.nonce || 0, chainId: Number(txParams.chainId) || 0 }; } catch (error) { logger_1.logger.error('Error preparing transaction for frontend:', error); throw error; } } /** * Sign a message */ async signMessage(message) { try { if (!this.currentWallet) { throw new Error('No wallet connected'); } if (this.currentWallet.privateKey && this.ethersWallet) { // Local wallet signing const messageHash = ethers_1.ethers.hashMessage(message); const signature = await this.ethersWallet.signMessage(message); return signature; } else { // Frontend wallet - return a placeholder for now // In a real implementation, this would trigger a wallet popup logger_1.logger.warn('Frontend wallet signing not implemented - returning placeholder'); return '0x'; } } catch (error) { logger_1.logger.error('Failed to sign message:', error); throw new Error(`Failed to sign message: ${error instanceof Error ? error.message : String(error)}`); } } /** * Sign EIP-712 typed data (for Fusion+ orders) */ async signTypedData(typedData) { try { if (!this.currentWallet) { throw new Error('No wallet connected'); } if (this.currentWallet.privateKey && this.ethersWallet) { // Local wallet signing const signature = await this.ethersWallet.signTypedData(typedData.domain, { [typedData.primaryType]: typedData.types[typedData.primaryType] }, typedData.message); return signature; } else if (this.walletSource === 'frontend') { // Frontend wallet - return typed data for frontend signing // The frontend will need to call MetaMask's eth_signTypedData_v4 logger_1.logger.info('Frontend wallet detected - typed data needs to be signed by frontend'); throw new Error('FRONTEND_SIGNING_REQUIRED: Typed data needs to be signed by frontend wallet. Please implement eth_signTypedData_v4 in your frontend.'); } else { // No signing method available logger_1.logger.warn('No signing method available - returning placeholder'); return '0x'; } } catch (error) { logger_1.logger.error('Failed to sign typed data:', error); throw new Error(`Failed to sign typed data: ${error instanceof Error ? error.message : String(error)}`); } } /** * Get wallet balance */ async getBalance() { if (!this.currentWallet) { throw new Error('No wallet connected'); } if (!this.provider) { const rpcUrl = this.getRpcUrl(this.currentWallet.chainId); this.provider = new ethers_1.ethers.JsonRpcProvider(rpcUrl); } const balance = await this.provider.getBalance(this.currentWallet.address); return balance.toString(); } /** * Get transaction count (nonce) */ async getTransactionCount() { if (!this.currentWallet) { throw new Error('No wallet connected'); } if (!this.provider) { const rpcUrl = this.getRpcUrl(this.currentWallet.chainId); this.provider = new ethers_1.ethers.JsonRpcProvider(rpcUrl); } return await this.provider.getTransactionCount(this.currentWallet.address, 'pending'); } } exports.WalletManager = WalletManager; /** * Global wallet manager instance */ exports.walletManager = new WalletManager(); /** * Utility functions for wallet operations */ class WalletUtils { /** * Validate Ethereum address */ static isValidAddress(address) { return /^0x[a-fA-F0-9]{40}$/.test(address); } /** * Format address for display */ static formatAddress(address, startChars = 6, endChars = 4) { if (!address || address.length < startChars + endChars) { return address; } return `${address.slice(0, startChars)}...${address.slice(-endChars)}`; } /** * Get chain name from chain ID */ static getChainName(chainId) { const chainNames = { 1: 'Ethereum Mainnet', 10: 'Optimism', 56: 'BNB Chain', 100: 'Gnosis', 137: 'Polygon', 324: 'zkSync Era', 42161: 'Arbitrum One', 43114: 'Avalanche C-Chain', 8453: 'Base', 7565164: 'Solana' }; return chainNames[chainId] || `Chain ${chainId}`; } /** * Convert Wei to Ether */ static weiToEther(wei) { try { const weiNum = BigInt(wei); const etherNum = Number(weiNum) / 1e18; return etherNum.toFixed(6); } catch (error) { return '0'; } } /** * Convert Ether to Wei */ static etherToWei(ether) { try { const etherNum = parseFloat(ether); const weiNum = BigInt(Math.floor(etherNum * 1e18)); return weiNum.toString(); } catch (error) { return '0'; } } /** * Create a new wallet with private key */ static createWallet(privateKey, chainId = 1) { const ethersWallet = new ethers_1.ethers.Wallet(privateKey); return { address: ethersWallet.address, chainId: chainId, privateKey: privateKey, isTestWallet: true, name: 'Generated Wallet', walletType: 'generated' }; } /** * Generate a new random wallet */ static generateWallet(chainId = 1) { const ethersWallet = ethers_1.ethers.Wallet.createRandom(); return { address: ethersWallet.address, chainId: chainId, privateKey: ethersWallet.privateKey, isTestWallet: true, name: 'Generated Wallet', walletType: 'generated' }; } } exports.WalletUtils = WalletUtils; /** * Decorator function to inject wallet into function parameters */ function withWallet(fn, requireWallet = true) { return ((...args) => { const wallet = exports.walletManager.getWalletContext().wallet; if (requireWallet && !wallet) { throw new Error('Wallet connection required for this operation'); } // Add wallet to the first parameter if it's an object if (args.length > 0 && typeof args[0] === 'object' && args[0] !== null) { args[0] = { ...args[0], wallet: wallet }; } else if (wallet) { // If first arg is not an object, prepend wallet args.unshift({ wallet }); } return fn(...args); }); } exports.default = exports.walletManager; //# sourceMappingURL=wallet.js.map