1inch-agent-kit
Version:
AI Agent Kit for 1inch - Connect any LLM to 1inch DeFi protocols
549 lines • 20.6 kB
JavaScript
"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