# Directory Structure ``` ├── .env.example ├── .eslintrc │ └── index.js ├── .gitignore ├── .npmignore ├── .npmrc ├── jest.config.js ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── required-methods.md ├── src │ ├── handlers │ │ ├── utils.ts │ │ ├── wallet.ts │ │ └── wallet.types.ts │ ├── index.ts │ ├── tools.ts │ └── types.ts ├── tests │ └── handlers │ ├── network.test.ts │ ├── provider.test.ts │ └── wallet.test.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | node_modules/ 2 | build/ 3 | .env 4 | .DS_Store 5 | *.log 6 | ``` -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- ``` 1 | @dcspark:registry=https://npm.pkg.github.com ``` -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- ``` 1 | .eslintrc 2 | tests 3 | .gitignore 4 | .git 5 | .DS_Store 6 | .vscode 7 | .env ``` -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` 1 | # Optional private key for wallet operations 2 | PRIVATE_KEY=your_private_key_here 3 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP Crypto Wallet EVM 2 | 3 | This repository contains a Model Context Protocol (MCP) server that provides Claude with access to Ethereum and EVM-compatible blockchain operations via ethers.js v5. The server enables Claude to perform operations like creating wallets, checking balances, sending transactions, and interacting with smart contracts on EVM-compatible blockchains. 4 | 5 | <a href="https://glama.ai/mcp/servers/@dcSpark/mcp-cryptowallet-evm"> 6 | <img width="380" height="200" src="https://glama.ai/mcp/servers/@dcSpark/mcp-cryptowallet-evm/badge" alt="Crypto Wallet EVM MCP server" /> 7 | </a> 8 | 9 | ## Overview 10 | 11 | The MCP server exposes the following tools to Claude: 12 | 13 | ### Wallet Creation and Management 14 | - `wallet_create_random`: Create a new wallet with a random private key 15 | - `wallet_from_private_key`: Create a wallet from a private key 16 | - `wallet_from_mnemonic`: Create a wallet from a mnemonic phrase 17 | - `wallet_from_encrypted_json`: Create a wallet by decrypting an encrypted JSON wallet 18 | - `wallet_encrypt`: Encrypt a wallet with a password 19 | 20 | ### Wallet Properties 21 | - `wallet_get_address`: Get the wallet address 22 | - `wallet_get_public_key`: Get the wallet public key 23 | - `wallet_get_private_key`: Get the wallet private key (with appropriate security warnings) 24 | - `wallet_get_mnemonic`: Get the wallet mnemonic phrase (if available) 25 | 26 | ### Blockchain Methods 27 | - `wallet_get_balance`: Get the balance of the wallet 28 | - `wallet_get_chain_id`: Get the chain ID the wallet is connected to 29 | - `wallet_get_gas_price`: Get the current gas price 30 | - `wallet_get_transaction_count`: Get the number of transactions sent from this account (nonce) 31 | - `wallet_call`: Call a contract method without sending a transaction 32 | 33 | ### Transaction Methods 34 | - `wallet_send_transaction`: Send a transaction 35 | - `wallet_sign_transaction`: Sign a transaction without sending it 36 | - `wallet_populate_transaction`: Populate a transaction with missing fields 37 | 38 | ### Signing Methods 39 | - `wallet_sign_message`: Sign a message 40 | - `wallet_sign_typed_data`: Sign typed data (EIP-712) 41 | - `wallet_verify_message`: Verify a signed message 42 | - `wallet_verify_typed_data`: Verify signed typed data 43 | 44 | ### Provider Methods 45 | - `provider_get_block`: Get a block by number or hash 46 | - `provider_get_transaction`: Get a transaction by hash 47 | - `provider_get_transaction_receipt`: Get a transaction receipt 48 | - `provider_get_code`: Get the code at an address 49 | - `provider_get_storage_at`: Get the storage at a position for an address 50 | - `provider_estimate_gas`: Estimate the gas required for a transaction 51 | - `provider_get_logs`: Get logs that match a filter 52 | - `provider_get_ens_resolver`: Get the ENS resolver for a name 53 | - `provider_lookup_address`: Lookup the ENS name for an address 54 | - `provider_resolve_name`: Resolve an ENS name to an address 55 | 56 | ### Network Methods 57 | - `network_get_network`: Get the current network information 58 | - `network_get_block_number`: Get the current block number 59 | - `network_get_fee_data`: Get the current fee data (base fee, max priority fee, etc.) 60 | 61 | ## Prerequisites 62 | 63 | - Node.js (v16 or higher) 64 | - Claude Desktop application 65 | 66 | ## Installation 67 | 68 | ### Option 1: Using npx (Recommended) 69 | 70 | You can run the MCP server directly without installation using npx: 71 | 72 | ```bash 73 | npx @mcp-dockmaster/mcp-cryptowallet-evm 74 | ``` 75 | 76 | This will download and execute the server directly from npm. 77 | 78 | ### Option 2: Manual Installation 79 | 80 | 1. Clone this repository: 81 | ```bash 82 | git clone https://github.com/dcSpark/mcp-cryptowallet-evm.git 83 | cd mcp-cryptowallet-evm 84 | ``` 85 | 86 | 2. Install dependencies: 87 | ```bash 88 | npm ci 89 | ``` 90 | 91 | 3. Build the project: 92 | ```bash 93 | npm run build 94 | ``` 95 | 96 | ## Configuration 97 | 98 | ### Environment Variables 99 | 100 | The MCP server supports the following environment variables: 101 | 102 | - `PRIVATE_KEY`: Optional private key to use for wallet operations when no wallet is explicitly provided 103 | 104 | ### Configure Claude Desktop 105 | 106 | To configure Claude Desktop to use this MCP server: 107 | 108 | 1. Open Claude Desktop 109 | 2. Navigate to the Claude Desktop configuration file: 110 | - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` 111 | - Windows: `%APPDATA%\Claude\claude_desktop_config.json` 112 | - Linux: `~/.config/Claude/claude_desktop_config.json` 113 | 114 | 3. Add the MCP server configuration: 115 | 116 | ```json 117 | { 118 | "mcpServers": { 119 | "mcp-cryptowallet-evm": { 120 | "command": "npx", 121 | "args": [ 122 | "@mcp-dockmaster/mcp-cryptowallet-evm" 123 | ] 124 | } 125 | } 126 | } 127 | ``` 128 | 129 | Alternatively, if you installed the package locally: 130 | 131 | ```json 132 | { 133 | "mcpServers": { 134 | "mcp-cryptowallet-evm": { 135 | "command": "node", 136 | "args": [ 137 | "/path/to/your/mcp-cryptowallet-evm/build/index.js" 138 | ] 139 | } 140 | } 141 | } 142 | ``` 143 | 144 | ### Running Locally 145 | 146 | ```bash 147 | node build/index.js 148 | ``` 149 | 150 | ## Usage 151 | 152 | Once configured, restart Claude Desktop. Claude will now have access to the Ethereum and EVM-compatible blockchain tools. You can ask Claude to: 153 | 154 | 1. Create a new wallet: 155 | ``` 156 | Can you create a new Ethereum wallet for me? 157 | ``` 158 | 159 | 2. Check a wallet balance: 160 | ``` 161 | What's the balance of the Ethereum wallet address 0x742d35Cc6634C0532925a3b844Bc454e4438f44e? 162 | ``` 163 | 164 | 3. Send a transaction: 165 | ``` 166 | Can you help me send 0.1 ETH to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e? 167 | ``` 168 | 169 | Claude will use the MCP server to interact with the Ethereum blockchain directly. 170 | 171 | ## Development 172 | 173 | ### Adding New Tools 174 | 175 | To add new tools to the MCP server: 176 | 177 | 1. Define the tool in `src/tools.ts` 178 | 2. Create a handler function in the appropriate handler file 179 | 3. Add the handler to the `handlers` object in `src/tools.ts` 180 | 181 | ### Building 182 | 183 | ```bash 184 | npm run build 185 | ``` 186 | 187 | ## License 188 | 189 | MIT ``` -------------------------------------------------------------------------------- /.eslintrc/index.js: -------------------------------------------------------------------------------- ```javascript 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint'], 5 | extends: [ 6 | 'eslint:recommended', 7 | 'plugin:@typescript-eslint/recommended' 8 | ], 9 | rules: { 10 | '@typescript-eslint/explicit-module-boundary-types': 'off' 11 | } 12 | } 13 | ``` -------------------------------------------------------------------------------- /src/handlers/wallet.types.ts: -------------------------------------------------------------------------------- ```typescript 1 | export type fromPrivateKeyHandlerInput = { 2 | privateKey: string; 3 | provider?: string; 4 | }; 5 | 6 | export type createMnemonicPhraseHandlerInput = { 7 | locale?: string; 8 | length?: 12 | 15 | 18 | 21 | 24; 9 | }; 10 | 11 | export type fromMnemonicHandlerInput = { 12 | mnemonic: string; 13 | provider?: string; 14 | path?: string; 15 | locale?: string; 16 | }; 17 | ``` -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- ```javascript 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | export default { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | extensionsToTreatAsEsm: ['.ts'], 6 | moduleNameMapper: { 7 | '^(\\.{1,2}/.*)\\.js$': '$1', 8 | }, 9 | transform: { 10 | '^.+\\.tsx?$': [ 11 | 'ts-jest', 12 | { 13 | useESM: true, 14 | }, 15 | ], 16 | }, 17 | }; 18 | ``` -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | export type ToolResultSchema = { 2 | content: ToolResultContent[]; 3 | isError?: boolean; 4 | } 5 | 6 | export type ToolResultContent = { 7 | type: "text"; 8 | text: string; 9 | } | { 10 | type: "image"; 11 | data: string; // base64 encoded image data 12 | mimeType: string; 13 | } | { 14 | type: "resource"; 15 | resource: { 16 | url: string; 17 | mimeType: string; 18 | text: string; 19 | } 20 | } 21 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "esModuleInterop": true, 7 | "outDir": "build", 8 | "strict": true, 9 | "declaration": true, 10 | "sourceMap": true, 11 | "resolveJsonModule": true, 12 | "allowSyntheticDefaultImports": true, 13 | "skipLibCheck": true 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules", "build", "tests"] 17 | } 18 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | 3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 5 | import { 6 | CallToolRequestSchema, 7 | ErrorCode, 8 | ListToolsRequestSchema, 9 | McpError, 10 | } from "@modelcontextprotocol/sdk/types.js"; 11 | import { handlers, tools } from "./tools.js"; 12 | 13 | const server = new Server({ 14 | name: "mcp-cryptowallet-evm", 15 | version: "1.0.0", 16 | }, { 17 | capabilities: { 18 | tools: {} 19 | } 20 | }); 21 | 22 | const transport = new StdioServerTransport(); 23 | await server.connect(transport); 24 | 25 | server.setRequestHandler(ListToolsRequestSchema, async () => { 26 | return { tools }; 27 | }); 28 | 29 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 30 | const handler = handlers[request.params.name]; 31 | if (handler) { 32 | try { 33 | const input = request.params.arguments; 34 | return await handler(input); 35 | } catch (error) { 36 | return { toolResult: { error: (error as Error).message }, content: [], isError: true }; 37 | } 38 | } 39 | return { toolResult: { error: "Method not found" }, content: [], isError: true }; 40 | }); 41 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@mcp-dockmaster/mcp-cryptowallet-evm", 3 | "version": "1.0.6", 4 | "description": "MCP server for EVM crypto wallet operations using ethers.js v5", 5 | "main": "build/index.js", 6 | "type": "module", 7 | "bin": { 8 | "mcp-cryptowallet-evm": "./build/index.js" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "start": "node build/index.js", 13 | "test": "node --test ./tests/**/*.test.ts", 14 | "lint": "eslint src/**/*.ts" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/dcSpark/mcp-cryptowallet-evm.git" 19 | }, 20 | "keywords": [ 21 | "mcp", 22 | "ethereum", 23 | "evm", 24 | "wallet", 25 | "ethers" 26 | ], 27 | "author": "dcSpark", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/dcSpark/mcp-cryptowallet-evm/issues" 31 | }, 32 | "homepage": "https://github.com/dcSpark/mcp-cryptowallet-evm#readme", 33 | "dependencies": { 34 | "@modelcontextprotocol/sdk": "^1.6.1", 35 | "@scure/bip39": "^1.5.4", 36 | "ethers": "^5.7.2" 37 | }, 38 | "devDependencies": { 39 | "@types/jest": "^29.5.0", 40 | "@types/node": "^18.15.11", 41 | "@typescript-eslint/eslint-plugin": "^5.57.1", 42 | "@typescript-eslint/parser": "^5.57.1", 43 | "eslint": "^8.38.0", 44 | "jest": "^29.5.0", 45 | "ts-jest": "^29.1.0", 46 | "typescript": "^5.0.4" 47 | } 48 | } 49 | ``` -------------------------------------------------------------------------------- /tests/handlers/network.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ethers } from 'ethers'; 2 | import { 3 | getNetworkHandler, 4 | getBlockNumberHandler, 5 | getFeeDataHandler 6 | } from '../../src/handlers/wallet.js'; 7 | 8 | // Mock ethers.js functions 9 | jest.mock('ethers', () => { 10 | const originalModule = jest.requireActual('ethers'); 11 | 12 | // Create a mock provider 13 | const mockProvider = { 14 | getNetwork: jest.fn().mockResolvedValue({ 15 | name: 'homestead', 16 | chainId: 1, 17 | ensAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e' 18 | }), 19 | getBlockNumber: jest.fn().mockResolvedValue(1000000), 20 | getFeeData: jest.fn().mockResolvedValue({ 21 | gasPrice: originalModule.utils.parseUnits('50', 'gwei'), 22 | maxFeePerGas: originalModule.utils.parseUnits('100', 'gwei'), 23 | maxPriorityFeePerGas: originalModule.utils.parseUnits('2', 'gwei') 24 | }) 25 | }; 26 | 27 | return { 28 | ...originalModule, 29 | providers: { 30 | JsonRpcProvider: jest.fn().mockReturnValue(mockProvider) 31 | }, 32 | getDefaultProvider: jest.fn().mockReturnValue(mockProvider), 33 | utils: originalModule.utils 34 | }; 35 | }); 36 | 37 | describe('Network Methods Handlers', () => { 38 | test('getNetworkHandler should return network information', async () => { 39 | const result = await getNetworkHandler({ 40 | provider: 'https://mainnet.infura.io/v3/your-api-key' 41 | }); 42 | 43 | expect(result.isError).toBe(false); 44 | expect(result.toolResult).toHaveProperty('network'); 45 | expect(result.toolResult.network).toHaveProperty('name'); 46 | expect(result.toolResult.network).toHaveProperty('chainId'); 47 | expect(result.toolResult.network).toHaveProperty('ensAddress'); 48 | expect(result.toolResult.network.name).toBe('homestead'); 49 | expect(result.toolResult.network.chainId).toBe(1); 50 | }); 51 | 52 | test('getBlockNumberHandler should return the current block number', async () => { 53 | const result = await getBlockNumberHandler({ 54 | provider: 'https://mainnet.infura.io/v3/your-api-key' 55 | }); 56 | 57 | expect(result.isError).toBe(false); 58 | expect(result.toolResult).toHaveProperty('blockNumber'); 59 | expect(result.toolResult.blockNumber).toBe(1000000); 60 | }); 61 | 62 | test('getFeeDataHandler should return fee data', async () => { 63 | const result = await getFeeDataHandler({ 64 | provider: 'https://mainnet.infura.io/v3/your-api-key' 65 | }); 66 | 67 | expect(result.isError).toBe(false); 68 | expect(result.toolResult).toHaveProperty('feeData'); 69 | expect(result.toolResult.feeData).toHaveProperty('gasPrice'); 70 | expect(result.toolResult.feeData).toHaveProperty('maxFeePerGas'); 71 | expect(result.toolResult.feeData).toHaveProperty('maxPriorityFeePerGas'); 72 | }); 73 | }); 74 | ``` -------------------------------------------------------------------------------- /required-methods.md: -------------------------------------------------------------------------------- ```markdown 1 | # Required Methods for EVM Crypto Wallet MCP Server 2 | 3 | Based on the ethers.js v5 documentation and the example MCP servers, the following methods should be implemented for our crypto wallet MCP server: 4 | 5 | ## Wallet Creation and Management 6 | - `wallet_create_random` - Create a new wallet with a random private key 7 | - `wallet_from_private_key` - Create a wallet from a private key 8 | - `wallet_from_mnemonic` - Create a wallet from a mnemonic phrase 9 | - `wallet_from_encrypted_json` - Create a wallet by decrypting an encrypted JSON wallet 10 | - `wallet_encrypt` - Encrypt a wallet with a password 11 | 12 | ## Wallet Properties 13 | - `wallet_get_address` - Get the wallet address 14 | - `wallet_get_public_key` - Get the wallet public key 15 | - `wallet_get_private_key` - Get the wallet private key (with appropriate security warnings) 16 | - `wallet_get_mnemonic` - Get the wallet mnemonic phrase (if available) 17 | 18 | ## Blockchain Methods 19 | - `wallet_get_balance` - Get the balance of the wallet 20 | - `wallet_get_chain_id` - Get the chain ID the wallet is connected to 21 | - `wallet_get_gas_price` - Get the current gas price 22 | - `wallet_get_transaction_count` - Get the number of transactions sent from this account (nonce) 23 | - `wallet_call` - Call a contract method without sending a transaction 24 | 25 | ## Transaction Methods 26 | - `wallet_send_transaction` - Send a transaction 27 | - `wallet_sign_transaction` - Sign a transaction without sending it 28 | - `wallet_populate_transaction` - Populate a transaction with missing fields 29 | 30 | ## Signing Methods 31 | - `wallet_sign_message` - Sign a message 32 | - `wallet_sign_typed_data` - Sign typed data (EIP-712) 33 | - `wallet_verify_message` - Verify a signed message 34 | - `wallet_verify_typed_data` - Verify signed typed data 35 | 36 | ## Provider Methods 37 | - `provider_get_block` - Get a block by number or hash 38 | - `provider_get_transaction` - Get a transaction by hash 39 | - `provider_get_transaction_receipt` - Get a transaction receipt 40 | - `provider_get_code` - Get the code at an address 41 | - `provider_get_storage_at` - Get the storage at a position for an address 42 | - `provider_estimate_gas` - Estimate the gas required for a transaction 43 | - `provider_get_logs` - Get logs that match a filter 44 | - `provider_get_ens_resolver` - Get the ENS resolver for a name 45 | - `provider_lookup_address` - Lookup the ENS name for an address 46 | - `provider_resolve_name` - Resolve an ENS name to an address 47 | 48 | ## Network Methods 49 | - `network_get_network` - Get the current network information 50 | - `network_get_block_number` - Get the current block number 51 | - `network_get_fee_data` - Get the current fee data (base fee, max priority fee, etc.) 52 | 53 | These methods cover the core functionality needed for a comprehensive EVM crypto wallet implementation using ethers.js v5. 54 | ``` -------------------------------------------------------------------------------- /src/handlers/utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ethers } from "ethers"; 2 | import { ToolResultSchema } from "../types.js"; 3 | 4 | 5 | let provider: ethers.providers.Provider | undefined; 6 | try { 7 | provider = process.env.PROVIDER_URL ? 8 | ethers.providers.getDefaultProvider(process.env.PROVIDER_URL) : 9 | ethers.providers.getDefaultProvider('https://eth.llamarpc.com'); 10 | } catch (error) { 11 | console.error("Error initializing provider:", error); 12 | } 13 | 14 | /** 15 | * Creates a success response with the given message 16 | * @param message Optional message to include in the response 17 | * @returns A ToolResultSchema with the message 18 | */ 19 | export const createSuccessResponse = (message?: string): ToolResultSchema => { 20 | return { 21 | content: [ 22 | { 23 | type: "text", 24 | text: message || "Operation completed successfully" 25 | } 26 | ], 27 | isError: false, 28 | }; 29 | }; 30 | 31 | /** 32 | * Creates an error response with the given message 33 | * @param message The error message 34 | * @returns A ToolResultSchema with the error message 35 | */ 36 | export const createErrorResponse = (message: string): ToolResultSchema => { 37 | return { 38 | content: [ 39 | { 40 | type: "text", 41 | text: message 42 | } 43 | ], 44 | isError: true 45 | }; 46 | }; 47 | 48 | /** 49 | * Gets the provider setup in memory 50 | * @returns An ethers.js provider 51 | */ 52 | export const getProvider = (): ethers.providers.Provider => { 53 | if (!provider) { 54 | throw new Error(`Invalid provider URL: ${process.env.PROVIDER_URL}`); 55 | } 56 | return provider 57 | }; 58 | 59 | export const setProvider = (providerURL: string) => { 60 | try { 61 | provider = ethers.providers.getDefaultProvider(providerURL); 62 | } catch (error) { 63 | throw new Error(`Invalid provider URL: ${providerURL}`); 64 | } 65 | }; 66 | 67 | /** 68 | * Gets a wallet from a private key, mnemonic, or JSON wallet 69 | * @param walletData The wallet data (private key, mnemonic, or JSON) 70 | * @param password Optional password for encrypted JSON wallets 71 | * @param provider Optional provider to connect the wallet to 72 | * @returns An ethers.js wallet 73 | */ 74 | export const getWallet = async ( 75 | walletData?: string, 76 | password?: string, 77 | ): Promise<ethers.Wallet> => { 78 | const provider = getProvider() 79 | // If walletData is not provided, check for PRIVATE_KEY environment variable 80 | if (!walletData && process.env.PRIVATE_KEY) { 81 | const wallet = new ethers.Wallet(process.env.PRIVATE_KEY); 82 | return provider ? wallet.connect(provider) : wallet; 83 | } 84 | 85 | // If no walletData and no environment variable, throw an error 86 | if (!walletData) { 87 | throw new Error("Wallet data is required or set PRIVATE_KEY environment variable"); 88 | } 89 | 90 | try { 91 | // Try to parse as JSON first 92 | if (walletData.startsWith("{")) { 93 | if (!password) { 94 | throw new Error("Password is required for encrypted JSON wallets"); 95 | } 96 | 97 | const wallet = await ethers.Wallet.fromEncryptedJson(walletData, password); 98 | return provider ? wallet.connect(provider) : wallet; 99 | } 100 | 101 | // Check if it's a mnemonic (12, 15, 18, 21, or 24 words) 102 | const words = walletData.trim().split(/\s+/); 103 | if ([12, 15, 18, 21, 24].includes(words.length)) { 104 | const wallet = ethers.Wallet.fromMnemonic(walletData); 105 | return provider ? wallet.connect(provider) : wallet; 106 | } 107 | 108 | // Assume it's a private key 109 | const wallet = new ethers.Wallet(walletData); 110 | return provider ? wallet.connect(provider) : wallet; 111 | } catch (error) { 112 | throw new Error(`Invalid wallet data: ${(error as Error).message}`); 113 | } 114 | }; 115 | ``` -------------------------------------------------------------------------------- /tests/handlers/provider.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ethers } from 'ethers'; 2 | import { 3 | getBlockHandler, 4 | getTransactionHandler, 5 | getTransactionReceiptHandler, 6 | getCodeHandler, 7 | getStorageAtHandler, 8 | estimateGasHandler, 9 | getLogsHandler, 10 | getEnsResolverHandler, 11 | lookupAddressHandler, 12 | resolveNameHandler 13 | } from '../../src/handlers/wallet.js'; 14 | 15 | // Mock ethers.js functions 16 | jest.mock('ethers', () => { 17 | const originalModule = jest.requireActual('ethers'); 18 | 19 | // Create a mock provider 20 | const mockProvider = { 21 | getBlock: jest.fn().mockResolvedValue({ 22 | hash: '0xblock', 23 | number: 1000000, 24 | timestamp: Date.now() / 1000, 25 | transactions: ['0xtx1', '0xtx2'] 26 | }), 27 | getTransaction: jest.fn().mockResolvedValue({ 28 | hash: '0xtx', 29 | from: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', 30 | to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' 31 | }), 32 | getTransactionReceipt: jest.fn().mockResolvedValue({ 33 | status: 1, 34 | blockNumber: 1000000, 35 | gasUsed: originalModule.BigNumber.from(21000) 36 | }), 37 | getCode: jest.fn().mockResolvedValue('0x'), 38 | getStorageAt: jest.fn().mockResolvedValue('0x0000000000000000000000000000000000000000000000000000000000000000'), 39 | estimateGas: jest.fn().mockResolvedValue(originalModule.BigNumber.from(21000)), 40 | getLogs: jest.fn().mockResolvedValue([{ 41 | blockNumber: 1000000, 42 | blockHash: '0xblock', 43 | transactionIndex: 0, 44 | removed: false, 45 | address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', 46 | data: '0x', 47 | topics: ['0xtopic1', '0xtopic2'] 48 | }]), 49 | getResolver: jest.fn().mockResolvedValue({ 50 | address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', 51 | name: 'test.eth' 52 | }), 53 | lookupAddress: jest.fn().mockResolvedValue('test.eth'), 54 | resolveName: jest.fn().mockResolvedValue('0x742d35Cc6634C0532925a3b844Bc454e4438f44e') 55 | }; 56 | 57 | return { 58 | ...originalModule, 59 | providers: { 60 | JsonRpcProvider: jest.fn().mockReturnValue(mockProvider) 61 | }, 62 | getDefaultProvider: jest.fn().mockReturnValue(mockProvider), 63 | utils: originalModule.utils 64 | }; 65 | }); 66 | 67 | describe('Provider Methods Handlers', () => { 68 | test('getBlockHandler should return a block', async () => { 69 | const result = await getBlockHandler({ 70 | provider: 'https://mainnet.infura.io/v3/your-api-key', 71 | blockHashOrBlockTag: 'latest', 72 | includeTransactions: true 73 | }); 74 | 75 | expect(result.isError).toBe(false); 76 | expect(result.toolResult).toHaveProperty('block'); 77 | expect(result.toolResult.block).toHaveProperty('hash'); 78 | expect(result.toolResult.block).toHaveProperty('number'); 79 | expect(result.toolResult.block).toHaveProperty('timestamp'); 80 | expect(result.toolResult.block).toHaveProperty('transactions'); 81 | }); 82 | 83 | test('getTransactionHandler should return a transaction', async () => { 84 | const result = await getTransactionHandler({ 85 | provider: 'https://mainnet.infura.io/v3/your-api-key', 86 | transactionHash: '0xtx' 87 | }); 88 | 89 | expect(result.isError).toBe(false); 90 | expect(result.toolResult).toHaveProperty('transaction'); 91 | expect(result.toolResult.transaction).toHaveProperty('hash'); 92 | expect(result.toolResult.transaction).toHaveProperty('from'); 93 | expect(result.toolResult.transaction).toHaveProperty('to'); 94 | }); 95 | 96 | test('getTransactionReceiptHandler should return a transaction receipt', async () => { 97 | const result = await getTransactionReceiptHandler({ 98 | provider: 'https://mainnet.infura.io/v3/your-api-key', 99 | transactionHash: '0xtx' 100 | }); 101 | 102 | expect(result.isError).toBe(false); 103 | expect(result.toolResult).toHaveProperty('receipt'); 104 | expect(result.toolResult.receipt).toHaveProperty('status'); 105 | expect(result.toolResult.receipt).toHaveProperty('blockNumber'); 106 | expect(result.toolResult.receipt).toHaveProperty('gasUsed'); 107 | }); 108 | 109 | test('getCodeHandler should return code at an address', async () => { 110 | const result = await getCodeHandler({ 111 | provider: 'https://mainnet.infura.io/v3/your-api-key', 112 | address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' 113 | }); 114 | 115 | expect(result.isError).toBe(false); 116 | expect(result.toolResult).toHaveProperty('code'); 117 | expect(result.toolResult.code).toBe('0x'); 118 | }); 119 | 120 | test('getStorageAtHandler should return storage at a position', async () => { 121 | const result = await getStorageAtHandler({ 122 | provider: 'https://mainnet.infura.io/v3/your-api-key', 123 | address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', 124 | position: '0x0' 125 | }); 126 | 127 | expect(result.isError).toBe(false); 128 | expect(result.toolResult).toHaveProperty('storage'); 129 | expect(result.toolResult.storage).toBe('0x0000000000000000000000000000000000000000000000000000000000000000'); 130 | }); 131 | 132 | test('estimateGasHandler should estimate gas for a transaction', async () => { 133 | const result = await estimateGasHandler({ 134 | provider: 'https://mainnet.infura.io/v3/your-api-key', 135 | transaction: { 136 | to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', 137 | data: '0x' 138 | } 139 | }); 140 | 141 | expect(result.isError).toBe(false); 142 | expect(result.toolResult).toHaveProperty('gasEstimate'); 143 | expect(result.toolResult.gasEstimate).toBe('21000'); 144 | }); 145 | 146 | test('getLogsHandler should return logs matching a filter', async () => { 147 | const result = await getLogsHandler({ 148 | provider: 'https://mainnet.infura.io/v3/your-api-key', 149 | filter: { 150 | address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', 151 | fromBlock: 'latest', 152 | toBlock: 'latest' 153 | } 154 | }); 155 | 156 | expect(result.isError).toBe(false); 157 | expect(result.toolResult).toHaveProperty('logs'); 158 | expect(Array.isArray(result.toolResult.logs)).toBe(true); 159 | expect(result.toolResult.logs.length).toBe(1); 160 | expect(result.toolResult.logs[0]).toHaveProperty('blockNumber'); 161 | expect(result.toolResult.logs[0]).toHaveProperty('topics'); 162 | }); 163 | 164 | test('getEnsResolverHandler should return an ENS resolver', async () => { 165 | const result = await getEnsResolverHandler({ 166 | provider: 'https://mainnet.infura.io/v3/your-api-key', 167 | name: 'test.eth' 168 | }); 169 | 170 | expect(result.isError).toBe(false); 171 | expect(result.toolResult).toHaveProperty('resolver'); 172 | expect(result.toolResult.resolver).toHaveProperty('address'); 173 | expect(result.toolResult.resolver).toHaveProperty('name'); 174 | }); 175 | 176 | test('lookupAddressHandler should lookup an ENS name for an address', async () => { 177 | const result = await lookupAddressHandler({ 178 | provider: 'https://mainnet.infura.io/v3/your-api-key', 179 | address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' 180 | }); 181 | 182 | expect(result.isError).toBe(false); 183 | expect(result.toolResult).toHaveProperty('name'); 184 | expect(result.toolResult.name).toBe('test.eth'); 185 | }); 186 | 187 | test('resolveNameHandler should resolve an ENS name to an address', async () => { 188 | const result = await resolveNameHandler({ 189 | provider: 'https://mainnet.infura.io/v3/your-api-key', 190 | name: 'test.eth' 191 | }); 192 | 193 | expect(result.isError).toBe(false); 194 | expect(result.toolResult).toHaveProperty('address'); 195 | expect(result.toolResult.address).toBe('0x742d35Cc6634C0532925a3b844Bc454e4438f44e'); 196 | }); 197 | }); 198 | ``` -------------------------------------------------------------------------------- /tests/handlers/wallet.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ethers } from 'ethers'; 2 | import { 3 | createWalletHandler, 4 | fromPrivateKeyHandler, 5 | fromMnemonicHandler, 6 | fromEncryptedJsonHandler, 7 | encryptWalletHandler, 8 | getAddressHandler, 9 | getPublicKeyHandler, 10 | getPrivateKeyHandler, 11 | getMnemonicHandler, 12 | getBalanceHandler, 13 | getChainIdHandler, 14 | getGasPriceHandler, 15 | getTransactionCountHandler, 16 | callHandler, 17 | signMessageHandler, 18 | verifyMessageHandler 19 | } from '../../src/handlers/wallet.js'; 20 | 21 | // Mock ethers.js functions 22 | jest.mock('ethers', () => { 23 | const originalModule = jest.requireActual('ethers'); 24 | 25 | // Create a mock wallet 26 | const mockWallet = { 27 | address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', 28 | privateKey: '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 29 | publicKey: '0x04a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6', 30 | mnemonic: { 31 | phrase: 'test test test test test test test test test test test junk', 32 | path: "m/44'/60'/0'/0/0", 33 | locale: 'en' 34 | }, 35 | connect: jest.fn().mockImplementation((provider) => { 36 | mockWallet.provider = provider; 37 | return mockWallet; 38 | }), 39 | getBalance: jest.fn().mockResolvedValue(originalModule.utils.parseEther('1.0')), 40 | getChainId: jest.fn().mockResolvedValue(1), 41 | getGasPrice: jest.fn().mockResolvedValue(originalModule.utils.parseUnits('50', 'gwei')), 42 | getTransactionCount: jest.fn().mockResolvedValue(5), 43 | call: jest.fn().mockResolvedValue('0x0000000000000000000000000000000000000000000000000000000000000001'), 44 | signMessage: jest.fn().mockResolvedValue('0xsignature'), 45 | encrypt: jest.fn().mockResolvedValue(JSON.stringify({ version: 3, id: 'test', address: '742d35cc6634c0532925a3b844bc454e4438f44e' })), 46 | provider: null 47 | }; 48 | 49 | // Create a mock provider 50 | const mockProvider = { 51 | getBalance: jest.fn().mockResolvedValue(originalModule.utils.parseEther('1.0')), 52 | getBlock: jest.fn().mockResolvedValue({ 53 | hash: '0xblock', 54 | number: 1000000, 55 | timestamp: Date.now() / 1000 56 | }), 57 | getTransaction: jest.fn().mockResolvedValue({ 58 | hash: '0xtx', 59 | from: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', 60 | to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' 61 | }), 62 | getTransactionReceipt: jest.fn().mockResolvedValue({ 63 | status: 1, 64 | blockNumber: 1000000 65 | }), 66 | getCode: jest.fn().mockResolvedValue('0x'), 67 | getStorageAt: jest.fn().mockResolvedValue('0x0000000000000000000000000000000000000000000000000000000000000000'), 68 | estimateGas: jest.fn().mockResolvedValue(ethers.BigNumber.from(21000)), 69 | getLogs: jest.fn().mockResolvedValue([]), 70 | getResolver: jest.fn().mockResolvedValue(null), 71 | lookupAddress: jest.fn().mockResolvedValue(null), 72 | resolveName: jest.fn().mockResolvedValue(null), 73 | getNetwork: jest.fn().mockResolvedValue({ name: 'homestead', chainId: 1 }), 74 | getBlockNumber: jest.fn().mockResolvedValue(1000000), 75 | getFeeData: jest.fn().mockResolvedValue({ 76 | gasPrice: ethers.utils.parseUnits('50', 'gwei'), 77 | maxFeePerGas: ethers.utils.parseUnits('100', 'gwei'), 78 | maxPriorityFeePerGas: ethers.utils.parseUnits('2', 'gwei') 79 | }) 80 | }; 81 | 82 | return { 83 | ...originalModule, 84 | Wallet: { 85 | createRandom: jest.fn().mockReturnValue(mockWallet), 86 | fromMnemonic: jest.fn().mockReturnValue(mockWallet), 87 | fromEncryptedJson: jest.fn().mockResolvedValue(mockWallet) 88 | }, 89 | providers: { 90 | JsonRpcProvider: jest.fn().mockReturnValue(mockProvider) 91 | }, 92 | getDefaultProvider: jest.fn().mockReturnValue(mockProvider), 93 | utils: originalModule.utils 94 | }; 95 | }); 96 | 97 | describe('Wallet Creation and Management Handlers', () => { 98 | test('createWalletHandler should create a random wallet', async () => { 99 | const result = await createWalletHandler({}); 100 | 101 | expect(result.isError).toBe(false); 102 | expect(result.toolResult).toHaveProperty('address'); 103 | expect(result.toolResult).toHaveProperty('privateKey'); 104 | expect(result.toolResult).toHaveProperty('publicKey'); 105 | expect(result.toolResult).toHaveProperty('mnemonic'); 106 | }); 107 | 108 | test('createWalletHandler should encrypt wallet if password is provided', async () => { 109 | const result = await createWalletHandler({ password: 'test123' }); 110 | 111 | expect(result.isError).toBe(false); 112 | expect(result.toolResult).toHaveProperty('encryptedWallet'); 113 | }); 114 | 115 | test('fromPrivateKeyHandler should create a wallet from a private key', async () => { 116 | const result = await fromPrivateKeyHandler({ 117 | privateKey: '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' 118 | }); 119 | 120 | expect(result.isError).toBe(false); 121 | expect(result.toolResult).toHaveProperty('address'); 122 | expect(result.toolResult).toHaveProperty('privateKey'); 123 | expect(result.toolResult).toHaveProperty('publicKey'); 124 | }); 125 | 126 | test('fromMnemonicHandler should create a wallet from a mnemonic', async () => { 127 | const result = await fromMnemonicHandler({ 128 | mnemonic: 'test test test test test test test test test test test junk' 129 | }); 130 | 131 | expect(result.isError).toBe(false); 132 | expect(result.toolResult).toHaveProperty('address'); 133 | expect(result.toolResult).toHaveProperty('privateKey'); 134 | expect(result.toolResult).toHaveProperty('publicKey'); 135 | expect(result.toolResult).toHaveProperty('mnemonic'); 136 | }); 137 | 138 | test('fromEncryptedJsonHandler should create a wallet from encrypted JSON', async () => { 139 | const result = await fromEncryptedJsonHandler({ 140 | json: JSON.stringify({ version: 3, id: 'test', address: '742d35cc6634c0532925a3b844bc454e4438f44e' }), 141 | password: 'test123' 142 | }); 143 | 144 | expect(result.isError).toBe(false); 145 | expect(result.toolResult).toHaveProperty('address'); 146 | expect(result.toolResult).toHaveProperty('privateKey'); 147 | expect(result.toolResult).toHaveProperty('publicKey'); 148 | }); 149 | 150 | test('encryptWalletHandler should encrypt a wallet', async () => { 151 | const result = await encryptWalletHandler({ 152 | wallet: '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 153 | password: 'test123' 154 | }); 155 | 156 | expect(result.isError).toBe(false); 157 | expect(result.toolResult).toHaveProperty('encryptedWallet'); 158 | }); 159 | }); 160 | 161 | describe('Wallet Properties Handlers', () => { 162 | test('getAddressHandler should return the wallet address', async () => { 163 | const result = await getAddressHandler({ 164 | wallet: '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' 165 | }); 166 | 167 | expect(result.isError).toBe(false); 168 | expect(result.toolResult).toHaveProperty('address'); 169 | expect(result.toolResult.address).toBe('0x742d35Cc6634C0532925a3b844Bc454e4438f44e'); 170 | }); 171 | 172 | test('getPublicKeyHandler should return the wallet public key', async () => { 173 | const result = await getPublicKeyHandler({ 174 | wallet: '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' 175 | }); 176 | 177 | expect(result.isError).toBe(false); 178 | expect(result.toolResult).toHaveProperty('publicKey'); 179 | }); 180 | 181 | test('getPrivateKeyHandler should return the wallet private key', async () => { 182 | const result = await getPrivateKeyHandler({ 183 | wallet: '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' 184 | }); 185 | 186 | expect(result.isError).toBe(false); 187 | expect(result.toolResult).toHaveProperty('privateKey'); 188 | expect(result.toolResult.privateKey).toBe('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'); 189 | }); 190 | 191 | test('getMnemonicHandler should return the wallet mnemonic', async () => { 192 | const result = await getMnemonicHandler({ 193 | wallet: 'test test test test test test test test test test test junk' 194 | }); 195 | 196 | expect(result.isError).toBe(false); 197 | expect(result.toolResult).toHaveProperty('mnemonic'); 198 | expect(result.toolResult.mnemonic).toBe('test test test test test test test test test test test junk'); 199 | }); 200 | }); 201 | 202 | describe('Blockchain Methods Handlers', () => { 203 | test('getBalanceHandler should return the wallet balance', async () => { 204 | const result = await getBalanceHandler({ 205 | wallet: '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 206 | provider: 'https://mainnet.infura.io/v3/your-api-key' 207 | }); 208 | 209 | expect(result.isError).toBe(false); 210 | expect(result.toolResult).toHaveProperty('balance'); 211 | expect(result.toolResult).toHaveProperty('balanceInEth'); 212 | expect(result.toolResult.balanceInEth).toBe('1.0'); 213 | }); 214 | 215 | test('getChainIdHandler should return the chain ID', async () => { 216 | const result = await getChainIdHandler({ 217 | wallet: '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 218 | provider: 'https://mainnet.infura.io/v3/your-api-key' 219 | }); 220 | 221 | expect(result.isError).toBe(false); 222 | expect(result.toolResult).toHaveProperty('chainId'); 223 | expect(result.toolResult.chainId).toBe(1); 224 | }); 225 | 226 | test('getGasPriceHandler should return the gas price', async () => { 227 | const result = await getGasPriceHandler({ 228 | wallet: '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 229 | provider: 'https://mainnet.infura.io/v3/your-api-key' 230 | }); 231 | 232 | expect(result.isError).toBe(false); 233 | expect(result.toolResult).toHaveProperty('gasPrice'); 234 | expect(result.toolResult).toHaveProperty('gasPriceInGwei'); 235 | expect(result.toolResult.gasPriceInGwei).toBe('50.0'); 236 | }); 237 | 238 | test('getTransactionCountHandler should return the transaction count', async () => { 239 | const result = await getTransactionCountHandler({ 240 | wallet: '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 241 | provider: 'https://mainnet.infura.io/v3/your-api-key' 242 | }); 243 | 244 | expect(result.isError).toBe(false); 245 | expect(result.toolResult).toHaveProperty('transactionCount'); 246 | expect(result.toolResult.transactionCount).toBe(5); 247 | }); 248 | 249 | test('callHandler should call a contract method', async () => { 250 | const result = await callHandler({ 251 | wallet: '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 252 | provider: 'https://mainnet.infura.io/v3/your-api-key', 253 | transaction: { 254 | to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', 255 | data: '0x70a08231000000000000000000000000742d35cc6634c0532925a3b844bc454e4438f44e' 256 | } 257 | }); 258 | 259 | expect(result.isError).toBe(false); 260 | expect(result.toolResult).toHaveProperty('result'); 261 | }); 262 | }); 263 | 264 | describe('Signing Methods Handlers', () => { 265 | test('signMessageHandler should sign a message', async () => { 266 | const result = await signMessageHandler({ 267 | wallet: '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', 268 | message: 'Hello, world!' 269 | }); 270 | 271 | expect(result.isError).toBe(false); 272 | expect(result.toolResult).toHaveProperty('signature'); 273 | expect(result.toolResult).toHaveProperty('message'); 274 | expect(result.toolResult.signature).toBe('0xsignature'); 275 | expect(result.toolResult.message).toBe('Hello, world!'); 276 | }); 277 | 278 | test('verifyMessageHandler should verify a signed message', async () => { 279 | const result = await verifyMessageHandler({ 280 | message: 'Hello, world!', 281 | signature: '0xsignature', 282 | address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' 283 | }); 284 | 285 | expect(result.isError).toBe(false); 286 | expect(result.toolResult).toHaveProperty('isValid'); 287 | expect(result.toolResult).toHaveProperty('recoveredAddress'); 288 | }); 289 | }); 290 | ``` -------------------------------------------------------------------------------- /src/tools.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | createWalletHandler, 3 | fromPrivateKeyHandler, 4 | fromMnemonicHandler, 5 | fromEncryptedJsonHandler, 6 | encryptWalletHandler, 7 | getAddressHandler, 8 | getPublicKeyHandler, 9 | getPrivateKeyHandler, 10 | getBalanceHandler, 11 | getChainIdHandler, 12 | getGasPriceHandler, 13 | getTransactionCountHandler, 14 | callHandler, 15 | sendTransactionHandler, 16 | signTransactionHandler, 17 | populateTransactionHandler, 18 | signMessageHandler, 19 | signTypedDataHandler, 20 | verifyMessageHandler, 21 | verifyTypedDataHandler, 22 | getBlockHandler, 23 | getTransactionHandler, 24 | getTransactionReceiptHandler, 25 | getCodeHandler, 26 | getStorageAtHandler, 27 | estimateGasHandler, 28 | getLogsHandler, 29 | getEnsResolverHandler, 30 | lookupAddressHandler, 31 | resolveNameHandler, 32 | getNetworkHandler, 33 | getBlockNumberHandler, 34 | getFeeDataHandler, 35 | createMnemonicPhraseHandler, 36 | setProviderHandler 37 | } from "./handlers/wallet.js"; 38 | 39 | export const tools = [ 40 | { 41 | name: "wallet_provider_set", 42 | description: "Set the provider URL. By default, the provider URL is set to the ETH mainnet or the URL set in the PROVIDER_URL environment variable.", 43 | inputSchema: { 44 | type: "object", 45 | properties: { 46 | providerURL: { type: "string", description: "The provider RPC URL" } 47 | }, 48 | required: ["providerURL"] 49 | } 50 | }, 51 | // Wallet Creation and Management 52 | { 53 | name: "wallet_create_random", 54 | description: "Create a new wallet with a random private key", 55 | inputSchema: { 56 | type: "object", 57 | properties: { 58 | password: { type: "string", description: "Optional password to encrypt the wallet" }, 59 | path: { type: "string", description: "Optional HD path" }, 60 | locale: { type: "string", description: "Optional locale for the wordlist" } 61 | }, 62 | required: [] 63 | } 64 | }, 65 | { 66 | name: "wallet_from_private_key", 67 | description: "Create a wallet from a private key", 68 | inputSchema: { 69 | type: "object", 70 | properties: { 71 | privateKey: { type: "string", description: "The private key" } 72 | }, 73 | required: ["privateKey"] 74 | } 75 | }, 76 | { 77 | name: "wallet_create_mnemonic_phrase", 78 | description: "Create a mnemonic phrase", 79 | inputSchema: { 80 | type: "object", 81 | properties: { 82 | length: { type: "number", description: "The length of the mnemonic phrase", enum: [12, 15, 18, 21, 24] }, 83 | locale: { type: "string", description: "Optional locale for the wordlist" } 84 | }, 85 | required: ["length"] 86 | } 87 | }, 88 | { 89 | name: "wallet_from_mnemonic", 90 | description: "Create a wallet from a mnemonic phrase", 91 | inputSchema: { 92 | type: "object", 93 | properties: { 94 | mnemonic: { type: "string", description: "The mnemonic phrase" }, 95 | path: { type: "string", description: "Optional HD path" }, 96 | locale: { type: "string", description: "Optional locale for the wordlist" } 97 | }, 98 | required: ["mnemonic"] 99 | } 100 | }, 101 | { 102 | name: "wallet_from_encrypted_json", 103 | description: "Create a wallet by decrypting an encrypted JSON wallet", 104 | inputSchema: { 105 | type: "object", 106 | properties: { 107 | json: { type: "string", description: "The encrypted JSON wallet" }, 108 | password: { type: "string", description: "The password to decrypt the wallet" } 109 | }, 110 | required: ["json", "password"] 111 | } 112 | }, 113 | { 114 | name: "wallet_encrypt", 115 | description: "Encrypt a wallet with a password", 116 | inputSchema: { 117 | type: "object", 118 | properties: { 119 | wallet: { type: "string", description: "The wallet to encrypt (private key, mnemonic, or JSON)" }, 120 | password: { type: "string", description: "The password to encrypt the wallet" }, 121 | options: { 122 | type: "object", 123 | description: "Optional encryption options", 124 | properties: { 125 | scrypt: { 126 | type: "object", 127 | properties: { 128 | N: { type: "number" }, 129 | r: { type: "number" }, 130 | p: { type: "number" } 131 | } 132 | } 133 | } 134 | } 135 | }, 136 | required: ["wallet", "password"] 137 | } 138 | }, 139 | 140 | // Wallet Properties 141 | { 142 | name: "wallet_get_address", 143 | description: "Get the wallet address", 144 | inputSchema: { 145 | type: "object", 146 | properties: { 147 | wallet: { type: "string", description: "The wallet (private key, mnemonic, or JSON). If not provided, uses PRIVATE_KEY environment variable if set." } 148 | }, 149 | required: [] 150 | } 151 | }, 152 | { 153 | name: "wallet_get_public_key", 154 | description: "Get the wallet public key", 155 | inputSchema: { 156 | type: "object", 157 | properties: { 158 | wallet: { type: "string", description: "The wallet (private key, mnemonic, or JSON). If not provided, uses PRIVATE_KEY environment variable if set." } 159 | }, 160 | required: [] 161 | } 162 | }, 163 | { 164 | name: "wallet_get_private_key", 165 | description: "Get the wallet private key (with appropriate security warnings)", 166 | inputSchema: { 167 | type: "object", 168 | properties: { 169 | wallet: { type: "string", description: "The wallet (private key, mnemonic, or JSON). If not provided, uses PRIVATE_KEY environment variable if set." }, 170 | password: { type: "string", description: "The password to decrypt the wallet if it's encrypted" } 171 | }, 172 | required: [] 173 | } 174 | }, 175 | // Blockchain Methods 176 | { 177 | name: "wallet_get_balance", 178 | description: "Get the balance of the wallet", 179 | inputSchema: { 180 | type: "object", 181 | properties: { 182 | wallet: { type: "string", description: "The wallet (private key, mnemonic, or JSON). If not provided, uses PRIVATE_KEY environment variable if set." }, 183 | blockTag: { type: "string", description: "Optional block tag (latest, pending, etc.)" } 184 | }, 185 | required: [] 186 | } 187 | }, 188 | { 189 | name: "wallet_get_chain_id", 190 | description: "Get the chain ID the wallet is connected to", 191 | inputSchema: { 192 | type: "object", 193 | properties: { 194 | wallet: { type: "string", description: "The wallet (private key, mnemonic, or JSON). If not provided, uses PRIVATE_KEY environment variable if set." } 195 | }, 196 | required: [] 197 | } 198 | }, 199 | { 200 | name: "wallet_get_gas_price", 201 | description: "Get the current gas price", 202 | inputSchema: { 203 | type: "object", 204 | properties: { 205 | wallet: { type: "string", description: "The wallet (private key, mnemonic, or JSON). If not provided, uses PRIVATE_KEY environment variable if set." } 206 | }, 207 | required: [] 208 | } 209 | }, 210 | { 211 | name: "wallet_get_transaction_count", 212 | description: "Get the number of transactions sent from this account (nonce)", 213 | inputSchema: { 214 | type: "object", 215 | properties: { 216 | wallet: { type: "string", description: "The wallet (private key, mnemonic, or JSON). If not provided, uses PRIVATE_KEY environment variable if set." }, 217 | blockTag: { type: "string", description: "Optional block tag (latest, pending, etc.)" } 218 | }, 219 | required: [] 220 | } 221 | }, 222 | { 223 | name: "wallet_call", 224 | description: "Call a contract method without sending a transaction", 225 | inputSchema: { 226 | type: "object", 227 | properties: { 228 | wallet: { type: "string", description: "The wallet (private key, mnemonic, or JSON). If not provided, uses PRIVATE_KEY environment variable if set." }, 229 | transaction: { 230 | type: "object", 231 | description: "The transaction to call", 232 | properties: { 233 | to: { type: "string" }, 234 | from: { type: "string" }, 235 | data: { type: "string" }, 236 | value: { type: "string" }, 237 | gasLimit: { type: "string" }, 238 | gasPrice: { type: "string" } 239 | }, 240 | required: ["to"] 241 | }, 242 | blockTag: { type: "string", description: "Optional block tag (latest, pending, etc.)" } 243 | }, 244 | required: ["transaction"] 245 | } 246 | }, 247 | 248 | // Transaction Methods 249 | { 250 | name: "wallet_send_transaction", 251 | description: "Send a transaction", 252 | inputSchema: { 253 | type: "object", 254 | properties: { 255 | wallet: { type: "string", description: "The wallet (private key, mnemonic, or JSON). If not provided, uses PRIVATE_KEY environment variable if set." }, 256 | transaction: { 257 | type: "object", 258 | description: "The transaction to send", 259 | properties: { 260 | to: { type: "string" }, 261 | from: { type: "string" }, 262 | data: { type: "string" }, 263 | value: { type: "string" }, 264 | gasLimit: { type: "string" }, 265 | gasPrice: { type: "string" }, 266 | nonce: { type: "number" }, 267 | type: { type: "number" }, 268 | maxFeePerGas: { type: "string" }, 269 | maxPriorityFeePerGas: { type: "string" } 270 | }, 271 | required: ["to"] 272 | } 273 | }, 274 | required: ["transaction"] 275 | } 276 | }, 277 | { 278 | name: "wallet_sign_transaction", 279 | description: "Sign a transaction without sending it", 280 | inputSchema: { 281 | type: "object", 282 | properties: { 283 | wallet: { type: "string", description: "The wallet (private key, mnemonic, or JSON). If not provided, uses PRIVATE_KEY environment variable if set." }, 284 | transaction: { 285 | type: "object", 286 | description: "The transaction to sign", 287 | properties: { 288 | to: { type: "string" }, 289 | from: { type: "string" }, 290 | data: { type: "string" }, 291 | value: { type: "string" }, 292 | gasLimit: { type: "string" }, 293 | gasPrice: { type: "string" }, 294 | nonce: { type: "number" }, 295 | type: { type: "number" }, 296 | maxFeePerGas: { type: "string" }, 297 | maxPriorityFeePerGas: { type: "string" } 298 | }, 299 | required: ["to"] 300 | } 301 | }, 302 | required: ["transaction"] 303 | } 304 | }, 305 | { 306 | name: "wallet_populate_transaction", 307 | description: "Populate a transaction with missing fields", 308 | inputSchema: { 309 | type: "object", 310 | properties: { 311 | wallet: { type: "string", description: "The wallet (private key, mnemonic, or JSON). If not provided, uses PRIVATE_KEY environment variable if set." }, 312 | transaction: { 313 | type: "object", 314 | description: "The transaction to populate", 315 | properties: { 316 | to: { type: "string" }, 317 | from: { type: "string" }, 318 | data: { type: "string" }, 319 | value: { type: "string" }, 320 | gasLimit: { type: "string" }, 321 | gasPrice: { type: "string" }, 322 | nonce: { type: "number" }, 323 | type: { type: "number" }, 324 | maxFeePerGas: { type: "string" }, 325 | maxPriorityFeePerGas: { type: "string" } 326 | }, 327 | required: ["to"] 328 | } 329 | }, 330 | required: ["transaction"] 331 | } 332 | }, 333 | 334 | // Signing Methods 335 | { 336 | name: "wallet_sign_message", 337 | description: "Sign a message", 338 | inputSchema: { 339 | type: "object", 340 | properties: { 341 | wallet: { type: "string", description: "The wallet (private key, mnemonic, or JSON). If not provided, uses PRIVATE_KEY environment variable if set." }, 342 | message: { type: "string", description: "The message to sign" } 343 | }, 344 | required: ["message"] 345 | } 346 | }, 347 | { 348 | name: "wallet_sign_typed_data", 349 | description: "Sign typed data (EIP-712)", 350 | inputSchema: { 351 | type: "object", 352 | properties: { 353 | wallet: { type: "string", description: "The wallet (private key, mnemonic, or JSON). If not provided, uses PRIVATE_KEY environment variable if set." }, 354 | domain: { type: "object", description: "The domain data" }, 355 | types: { type: "object", description: "The type definitions" }, 356 | value: { type: "object", description: "The value to sign" } 357 | }, 358 | required: ["domain", "types", "value"] 359 | } 360 | }, 361 | { 362 | name: "wallet_verify_message", 363 | description: "Verify a signed message", 364 | inputSchema: { 365 | type: "object", 366 | properties: { 367 | message: { type: "string", description: "The original message" }, 368 | signature: { type: "string", description: "The signature to verify" }, 369 | address: { type: "string", description: "The address that supposedly signed the message" } 370 | }, 371 | required: ["message", "signature", "address"] 372 | } 373 | }, 374 | { 375 | name: "wallet_verify_typed_data", 376 | description: "Verify signed typed data", 377 | inputSchema: { 378 | type: "object", 379 | properties: { 380 | domain: { type: "object", description: "The domain data" }, 381 | types: { type: "object", description: "The type definitions" }, 382 | value: { type: "object", description: "The value that was signed" }, 383 | signature: { type: "string", description: "The signature to verify" }, 384 | address: { type: "string", description: "The address that supposedly signed the data" } 385 | }, 386 | required: ["domain", "types", "value", "signature", "address"] 387 | } 388 | }, 389 | 390 | // Provider Methods 391 | { 392 | name: "provider_get_block", 393 | description: "Get a block by number or hash", 394 | inputSchema: { 395 | type: "object", 396 | properties: { 397 | blockHashOrBlockTag: { type: "string", description: "Block hash or block tag (latest, pending, etc.)" }, 398 | includeTransactions: { type: "boolean", description: "Whether to include full transactions or just hashes" } 399 | }, 400 | required: ["blockHashOrBlockTag"] 401 | } 402 | }, 403 | { 404 | name: "provider_get_transaction", 405 | description: "Get a transaction by hash", 406 | inputSchema: { 407 | type: "object", 408 | properties: { 409 | transactionHash: { type: "string", description: "The transaction hash" } 410 | }, 411 | required: ["transactionHash"] 412 | } 413 | }, 414 | { 415 | name: "provider_get_transaction_receipt", 416 | description: "Get a transaction receipt", 417 | inputSchema: { 418 | type: "object", 419 | properties: { 420 | transactionHash: { type: "string", description: "The transaction hash" } 421 | }, 422 | required: ["transactionHash"] 423 | } 424 | }, 425 | { 426 | name: "provider_get_code", 427 | description: "Get the code at an address", 428 | inputSchema: { 429 | type: "object", 430 | properties: { 431 | address: { type: "string", description: "The address to get code from" }, 432 | blockTag: { type: "string", description: "Optional block tag (latest, pending, etc.)" } 433 | }, 434 | required: ["address"] 435 | } 436 | }, 437 | { 438 | name: "provider_get_storage_at", 439 | description: "Get the storage at a position for an address", 440 | inputSchema: { 441 | type: "object", 442 | properties: { 443 | address: { type: "string", description: "The address to get storage from" }, 444 | position: { type: "string", description: "The storage position" }, 445 | blockTag: { type: "string", description: "Optional block tag (latest, pending, etc.)" } 446 | }, 447 | required: ["address", "position"] 448 | } 449 | }, 450 | { 451 | name: "provider_estimate_gas", 452 | description: "Estimate the gas required for a transaction", 453 | inputSchema: { 454 | type: "object", 455 | properties: { 456 | transaction: { 457 | type: "object", 458 | description: "The transaction to estimate gas for", 459 | properties: { 460 | to: { type: "string" }, 461 | from: { type: "string" }, 462 | data: { type: "string" }, 463 | value: { type: "string" } 464 | } 465 | } 466 | }, 467 | required: ["transaction"] 468 | } 469 | }, 470 | { 471 | name: "provider_get_logs", 472 | description: "Get logs that match a filter", 473 | inputSchema: { 474 | type: "object", 475 | properties: { 476 | filter: { 477 | type: "object", 478 | description: "The filter to apply", 479 | properties: { 480 | address: { type: "string" }, 481 | topics: { type: "array", items: { type: "string" } }, 482 | fromBlock: { type: "string" }, 483 | toBlock: { type: "string" } 484 | } 485 | } 486 | }, 487 | required: ["filter"] 488 | } 489 | }, 490 | { 491 | name: "provider_get_ens_resolver", 492 | description: "Get the ENS resolver for a name", 493 | inputSchema: { 494 | type: "object", 495 | properties: { 496 | name: { type: "string", description: "The ENS name" } 497 | }, 498 | required: ["name"] 499 | } 500 | }, 501 | { 502 | name: "provider_lookup_address", 503 | description: "Lookup the ENS name for an address", 504 | inputSchema: { 505 | type: "object", 506 | properties: { 507 | address: { type: "string", description: "The address to lookup" } 508 | }, 509 | required: ["address"] 510 | } 511 | }, 512 | { 513 | name: "provider_resolve_name", 514 | description: "Resolve an ENS name to an address", 515 | inputSchema: { 516 | type: "object", 517 | properties: { 518 | name: { type: "string", description: "The ENS name to resolve" } 519 | }, 520 | required: ["name"] 521 | } 522 | }, 523 | 524 | // Network Methods 525 | { 526 | name: "network_get_network", 527 | description: "Get the current network information", 528 | inputSchema: { 529 | type: "object", 530 | properties: {}, 531 | required: [] 532 | } 533 | }, 534 | { 535 | name: "network_get_block_number", 536 | description: "Get the current block number", 537 | inputSchema: { 538 | type: "object", 539 | properties: {}, 540 | required: [] 541 | } 542 | }, 543 | { 544 | name: "network_get_fee_data", 545 | description: "Get the current fee data (base fee, max priority fee, etc.)", 546 | inputSchema: { 547 | type: "object", 548 | properties: {}, 549 | required: [] 550 | } 551 | } 552 | ]; 553 | 554 | type HandlerDictionary = Record<string, (input: any) => any>; 555 | 556 | export const handlers: HandlerDictionary = { 557 | // Provider Methods 558 | "wallet_provider_set": setProviderHandler, 559 | // Wallet Creation and Management 560 | "wallet_create_random": createWalletHandler, 561 | "wallet_from_private_key": fromPrivateKeyHandler, 562 | "wallet_from_mnemonic": fromMnemonicHandler, 563 | "wallet_from_encrypted_json": fromEncryptedJsonHandler, 564 | "wallet_encrypt": encryptWalletHandler, 565 | 566 | // Wallet Properties 567 | "wallet_get_address": getAddressHandler, 568 | "wallet_get_public_key": getPublicKeyHandler, 569 | "wallet_get_private_key": getPrivateKeyHandler, 570 | 571 | // Blockchain Methods 572 | "wallet_get_balance": getBalanceHandler, 573 | "wallet_get_chain_id": getChainIdHandler, 574 | "wallet_get_gas_price": getGasPriceHandler, 575 | "wallet_get_transaction_count": getTransactionCountHandler, 576 | "wallet_call": callHandler, 577 | 578 | // Transaction Methods 579 | "wallet_send_transaction": sendTransactionHandler, 580 | "wallet_sign_transaction": signTransactionHandler, 581 | "wallet_populate_transaction": populateTransactionHandler, 582 | 583 | // Signing Methods 584 | "wallet_sign_message": signMessageHandler, 585 | "wallet_sign_typed_data": signTypedDataHandler, 586 | "wallet_verify_message": verifyMessageHandler, 587 | "wallet_verify_typed_data": verifyTypedDataHandler, 588 | 589 | // Provider Methods 590 | "provider_get_block": getBlockHandler, 591 | "provider_get_transaction": getTransactionHandler, 592 | "provider_get_transaction_receipt": getTransactionReceiptHandler, 593 | "provider_get_code": getCodeHandler, 594 | "provider_get_storage_at": getStorageAtHandler, 595 | "provider_estimate_gas": estimateGasHandler, 596 | "provider_get_logs": getLogsHandler, 597 | "provider_get_ens_resolver": getEnsResolverHandler, 598 | "provider_lookup_address": lookupAddressHandler, 599 | "provider_resolve_name": resolveNameHandler, 600 | 601 | // Network Methods 602 | "network_get_network": getNetworkHandler, 603 | "network_get_block_number": getBlockNumberHandler, 604 | "network_get_fee_data": getFeeDataHandler, 605 | 606 | // Mnemonic Methods 607 | "wallet_create_mnemonic_phrase": createMnemonicPhraseHandler 608 | }; 609 | ``` -------------------------------------------------------------------------------- /src/handlers/wallet.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ethers, providers } from "ethers"; 2 | import { ToolResultSchema } from "../types.js"; 3 | import { createSuccessResponse, createErrorResponse, getProvider, getWallet, setProvider } from "./utils.js"; 4 | import { fromPrivateKeyHandlerInput, createMnemonicPhraseHandlerInput } from "./wallet.types.js"; 5 | import { generateMnemonic, } from '@scure/bip39'; 6 | 7 | // Provider handlers 8 | 9 | export const setProviderHandler = async (input: any): Promise<ToolResultSchema> => { 10 | try { 11 | setProvider(input.providerURL); 12 | return createSuccessResponse(`Provider set successfully: 13 | Provider URL: ${input.providerURL} 14 | `); 15 | } catch (error) { 16 | return createErrorResponse(`Failed to set provider: ${(error as Error).message}`); 17 | } 18 | }; 19 | 20 | 21 | // Wallet Creation and Management 22 | export const createWalletHandler = async (input: any): Promise<ToolResultSchema> => { 23 | try { 24 | const options: any = {}; 25 | 26 | if (input.path) { 27 | options.path = input.path; 28 | } 29 | 30 | if (input.locale) { 31 | options.locale = input.locale; 32 | } 33 | 34 | const wallet = ethers.Wallet.createRandom(options); 35 | 36 | let result: any = { 37 | address: wallet.address, 38 | mnemonic: wallet.mnemonic?.phrase, 39 | privateKey: wallet.privateKey, 40 | publicKey: wallet.publicKey 41 | }; 42 | 43 | if (input.password) { 44 | // If a password is provided, encrypt the wallet 45 | const encryptedWallet = await wallet.encrypt(input.password); 46 | result.encryptedWallet = encryptedWallet; 47 | } 48 | 49 | return createSuccessResponse(`Wallet created successfully: 50 | Address: ${wallet.address} 51 | Private Key: ${wallet.privateKey} 52 | Public Key: ${wallet.publicKey} 53 | Mnemonic: ${wallet.mnemonic?.phrase} 54 | Encrypted Wallet: ${result.encryptedWallet ? "Yes" : "No"} 55 | `); 56 | } catch (error) { 57 | return createErrorResponse(`Failed to create wallet: ${(error as Error).message}`); 58 | } 59 | }; 60 | 61 | export const fromPrivateKeyHandler = async (input: fromPrivateKeyHandlerInput): Promise<ToolResultSchema> => { 62 | try { 63 | if (!input.privateKey) { 64 | return createErrorResponse("Private key is required"); 65 | } 66 | 67 | const provider = getProvider() 68 | const wallet = new ethers.Wallet(input.privateKey, provider); 69 | 70 | return createSuccessResponse( 71 | `Wallet created from private key successfully: 72 | Address: ${wallet.address} 73 | Private Key: ${wallet.privateKey} 74 | Public Key: ${wallet.publicKey} 75 | `); 76 | } catch (error) { 77 | return createErrorResponse(`Failed to create wallet from private key: ${(error as Error).message}`); 78 | } 79 | }; 80 | 81 | export const createMnemonicPhraseHandler = async (input: createMnemonicPhraseHandlerInput): Promise<ToolResultSchema> => { 82 | try { 83 | const { wordlist } = await import(`@scure/bip39/wordlists/${input.locale || 'english'}`); 84 | if (!wordlist) { 85 | return createErrorResponse("Invalid locale"); 86 | } 87 | // Convert length to entropy bits (12 words = 128 bits, 15 words = 160 bits, etc) 88 | const entropyBits = ((input.length ?? 12) / 3) * 32; 89 | const mnemonic = generateMnemonic(wordlist, entropyBits); 90 | 91 | return createSuccessResponse( 92 | `Mnemonic phrase created successfully: 93 | Mnemonic: "${mnemonic}" 94 | `); 95 | } catch (error) { 96 | return createErrorResponse(`Failed to create mnemonic phrase: ${(error as Error).message}`); 97 | } 98 | }; 99 | 100 | export const fromMnemonicHandler = async (input: any): Promise<ToolResultSchema> => { 101 | try { 102 | if (!input.mnemonic) { 103 | return createErrorResponse("Mnemonic is required"); 104 | } 105 | 106 | const options: any = { 107 | path: input.path, 108 | wordlist: (input.locale && ethers.wordlists[input.locale]) || ethers.wordlists.en 109 | }; 110 | 111 | const provider = getProvider(); 112 | const wallet = ethers.Wallet.fromMnemonic(input.mnemonic, options.path, options.wordlist); 113 | 114 | if (provider) wallet.connect(provider); 115 | 116 | return createSuccessResponse( 117 | `Wallet created from mnemonic successfully: 118 | Address: ${wallet.address} 119 | Mnemonic: ${input.mnemonic} 120 | Private Key: ${wallet.privateKey} 121 | Public Key: ${wallet.publicKey} 122 | `); 123 | } catch (error) { 124 | return createErrorResponse(`Failed to create wallet from mnemonic: ${(error as Error).message}`); 125 | } 126 | }; 127 | 128 | export const fromEncryptedJsonHandler = async (input: any): Promise<ToolResultSchema> => { 129 | try { 130 | if (!input.json) { 131 | return createErrorResponse("Encrypted JSON is required"); 132 | } 133 | 134 | if (!input.password) { 135 | return createErrorResponse("Password is required"); 136 | } 137 | 138 | const wallet = await ethers.Wallet.fromEncryptedJson(input.json, input.password); 139 | const provider = getProvider() 140 | 141 | if (provider) { 142 | wallet.connect(provider); 143 | } 144 | 145 | return createSuccessResponse( 146 | `Wallet created from encrypted JSON successfully 147 | Address: ${wallet.address} 148 | Private Key: ${wallet.privateKey} 149 | Public Key: ${wallet.publicKey} 150 | `); 151 | } catch (error) { 152 | return createErrorResponse(`Failed to create wallet from encrypted JSON: ${(error as Error).message}`); 153 | } 154 | }; 155 | 156 | export const encryptWalletHandler = async (input: any): Promise<ToolResultSchema> => { 157 | try { 158 | if (!input.wallet) { 159 | return createErrorResponse("Wallet is required"); 160 | } 161 | 162 | if (!input.password) { 163 | return createErrorResponse("Password is required"); 164 | } 165 | 166 | const wallet = await getWallet(input.wallet); 167 | const encryptedWallet = await wallet.encrypt(input.password, input.options); 168 | 169 | return createSuccessResponse( 170 | `Wallet encrypted successfully 171 | Encrypted Wallet: ${encryptedWallet} 172 | `); 173 | } catch (error) { 174 | return createErrorResponse(`Failed to encrypt wallet: ${(error as Error).message}`); 175 | } 176 | }; 177 | 178 | // Wallet Properties 179 | 180 | export const getAddressHandler = async (input: any): Promise<ToolResultSchema> => { 181 | try { 182 | const wallet = await getWallet(input.wallet); 183 | 184 | return createSuccessResponse( 185 | `Wallet address retrieved successfully: 186 | Address: ${wallet.address} 187 | `); 188 | } catch (error) { 189 | return createErrorResponse(`Failed to get wallet address: ${(error as Error).message}`); 190 | } 191 | }; 192 | 193 | export const getPublicKeyHandler = async (input: any): Promise<ToolResultSchema> => { 194 | try { 195 | const wallet = await getWallet(input.wallet); 196 | 197 | return createSuccessResponse( 198 | `Wallet public key retrieved successfully: 199 | Public Key: ${wallet.publicKey} 200 | `); 201 | } catch (error) { 202 | return createErrorResponse(`Failed to get wallet public key: ${(error as Error).message}`); 203 | } 204 | }; 205 | 206 | export const getPrivateKeyHandler = async (input: any): Promise<ToolResultSchema> => { 207 | try { 208 | const wallet = await getWallet(input.wallet, input.password); 209 | 210 | return createSuccessResponse( 211 | `Wallet private key retrieved successfully: 212 | Private Key: ${wallet.privateKey} 213 | `); 214 | } catch (error) { 215 | return createErrorResponse(`Failed to get wallet private key: ${(error as Error).message}`); 216 | } 217 | }; 218 | // Blockchain Methods 219 | 220 | export const getBalanceHandler = async (input: any): Promise<ToolResultSchema> => { 221 | try { 222 | const wallet = await getWallet(input.wallet, input.password); 223 | 224 | const balance = await wallet.getBalance(input.blockTag ?? "latest"); 225 | 226 | return createSuccessResponse( 227 | `Wallet balance retrieved successfully 228 | Balance: ${balance.toString()} 229 | Balance in ETH: ${ethers.utils.formatEther(balance)} 230 | `); 231 | } catch (error) { 232 | return createErrorResponse(`Failed to get wallet balance: ${(error as Error).message}`); 233 | } 234 | }; 235 | 236 | export const getChainIdHandler = async (input: any): Promise<ToolResultSchema> => { 237 | try { 238 | const wallet = await getWallet(input.wallet, input.password); 239 | 240 | if (!wallet.provider) { 241 | return createErrorResponse("Provider is required to get chain ID, please set the provider URL"); 242 | } 243 | 244 | const chainId = await wallet.getChainId(); 245 | 246 | return createSuccessResponse( 247 | `Chain ID retrieved successfully 248 | Chain ID: ${chainId.toString()} 249 | `); 250 | } catch (error) { 251 | return createErrorResponse(`Failed to get chain ID: ${(error as Error).message}`); 252 | } 253 | }; 254 | 255 | export const getGasPriceHandler = async (input: any): Promise<ToolResultSchema> => { 256 | try { 257 | const wallet = await getWallet(input.wallet, input.password); 258 | 259 | if (!wallet.provider) { 260 | return createErrorResponse("Provider is required to get gas price, please set the provider URL"); 261 | } 262 | 263 | const gasPrice = await wallet.getGasPrice(); 264 | 265 | return createSuccessResponse( 266 | `Gas price retrieved successfully 267 | Gas price: ${gasPrice.toString()} 268 | Gas price in Gwei: ${ethers.utils.formatUnits(gasPrice, "gwei")} 269 | `); 270 | } catch (error) { 271 | return createErrorResponse(`Failed to get gas price: ${(error as Error).message}`); 272 | } 273 | }; 274 | 275 | export const getTransactionCountHandler = async (input: any): Promise<ToolResultSchema> => { 276 | try { 277 | const wallet = await getWallet(input.wallet, input.password); 278 | 279 | if (!wallet.provider) { 280 | return createErrorResponse("Provider is required to get transaction count, please set the provider URL"); 281 | } 282 | 283 | const transactionCount = await wallet.getTransactionCount(input.blockTag); 284 | 285 | return createSuccessResponse( 286 | `Transaction count retrieved successfully 287 | Transaction count: ${transactionCount.toString()} 288 | `); 289 | } catch (error) { 290 | return createErrorResponse(`Failed to get transaction count: ${(error as Error).message}`); 291 | } 292 | }; 293 | 294 | export const callHandler = async (input: any): Promise<ToolResultSchema> => { 295 | try { 296 | if (!input.transaction) { 297 | return createErrorResponse("Transaction is required"); 298 | } 299 | 300 | const wallet = await getWallet(input.wallet, input.password); 301 | 302 | if (!wallet.provider) { 303 | return createErrorResponse("Provider is required to call a contract, please set the provider URL"); 304 | } 305 | 306 | const result = await wallet.call(input.transaction, input.blockTag); 307 | 308 | return createSuccessResponse( 309 | `Contract call executed successfully 310 | Result: ${result} 311 | `); 312 | } catch (error) { 313 | return createErrorResponse(`Failed to call contract: ${(error as Error).message}`); 314 | } 315 | }; 316 | 317 | // Transaction Methods 318 | 319 | export const sendTransactionHandler = async (input: any): Promise<ToolResultSchema> => { 320 | try { 321 | if (!input.transaction) { 322 | return createErrorResponse("Transaction is required"); 323 | } 324 | 325 | const wallet = await getWallet(input.wallet, input.password); 326 | if (!wallet.provider) { 327 | return createErrorResponse("Provider is required to send a transaction, please set the provider URL"); 328 | } 329 | 330 | const tx = await wallet.sendTransaction(input.transaction); 331 | 332 | return createSuccessResponse( 333 | `Transaction sent successfully 334 | Hash: ${tx.hash} 335 | Nonce: ${tx.nonce.toString()} 336 | Gas limit: ${tx.gasLimit.toString()} 337 | Gas price: ${tx.gasPrice?.toString()} 338 | Data: ${tx.data} 339 | `); 340 | } catch (error) { 341 | return createErrorResponse(`Failed to send transaction: ${(error as Error).message}`); 342 | } 343 | }; 344 | 345 | export const signTransactionHandler = async (input: any): Promise<ToolResultSchema> => { 346 | try { 347 | if (!input.transaction) { 348 | return createErrorResponse("Transaction is required"); 349 | } 350 | 351 | const wallet = await getWallet(input.wallet, input.password); 352 | 353 | // For signing a transaction, we need to populate it first 354 | const populatedTx = await wallet.populateTransaction(input.transaction); 355 | const signedTx = await wallet.signTransaction(populatedTx); 356 | 357 | return createSuccessResponse( 358 | `Transaction signed successfully 359 | Signed transaction: ${signedTx} 360 | `); 361 | } catch (error) { 362 | return createErrorResponse(`Failed to sign transaction: ${(error as Error).message}`); 363 | } 364 | }; 365 | 366 | export const populateTransactionHandler = async (input: any): Promise<ToolResultSchema> => { 367 | try { 368 | if (!input.transaction) { 369 | return createErrorResponse("Transaction is required"); 370 | } 371 | 372 | const wallet = await getWallet(input.wallet, input.password); 373 | 374 | if (!wallet.provider) { 375 | return createErrorResponse("Provider is required to populate a transaction, please set the provider URL"); 376 | } 377 | 378 | const populatedTx = await wallet.populateTransaction(input.transaction); 379 | 380 | return createSuccessResponse( 381 | `Transaction populated successfully 382 | To: ${populatedTx.to} 383 | From: ${populatedTx.from} 384 | Nonce: ${populatedTx.nonce?.toString() ?? "Not specified"} 385 | Gas limit: ${populatedTx.gasLimit?.toString() ?? "Not specified"} 386 | Gas price: ${populatedTx.gasPrice?.toString() ?? "Not specified"} 387 | `); 388 | } catch (error) { 389 | return createErrorResponse(`Failed to populate transaction: ${(error as Error).message}`); 390 | } 391 | }; 392 | 393 | // Signing Methods 394 | 395 | export const signMessageHandler = async (input: any): Promise<ToolResultSchema> => { 396 | try { 397 | if (!input.message) { 398 | return createErrorResponse("Message is required"); 399 | } 400 | 401 | const wallet = await getWallet(input.wallet, input.password); 402 | const signature = await wallet.signMessage(input.message); 403 | 404 | return createSuccessResponse(`Message signed successfully 405 | Signature: ${signature} 406 | Message: "${input.message}" 407 | `); 408 | } catch (error) { 409 | return createErrorResponse(`Failed to sign message: ${(error as Error).message}`); 410 | } 411 | }; 412 | 413 | export const signTypedDataHandler = async (input: any): Promise<ToolResultSchema> => { 414 | try { 415 | if (!input.wallet) { 416 | return createErrorResponse("Wallet is required"); 417 | } 418 | 419 | if (!input.domain || !input.types || !input.value) { 420 | return createErrorResponse("Domain, types, and value are required"); 421 | } 422 | 423 | const wallet = await getWallet(input.wallet, input.password); 424 | 425 | // Use ethers._signTypedData for EIP-712 signing 426 | // @ts-ignore - _signTypedData is not in the type definitions but is available 427 | const signature = await wallet._signTypedData(input.domain, input.types, input.value); 428 | 429 | return createSuccessResponse( 430 | `Typed data signed successfully 431 | Signature: ${signature} 432 | Domain: ${input.domain} 433 | Types: ${input.types} 434 | Value: ${input.value} 435 | `); 436 | } catch (error) { 437 | return createErrorResponse(`Failed to sign typed data: ${(error as Error).message}`); 438 | } 439 | }; 440 | 441 | export const verifyMessageHandler = async (input: any): Promise<ToolResultSchema> => { 442 | try { 443 | if (!input.message || !input.signature || !input.address) { 444 | return createErrorResponse("Message, signature, and address are required"); 445 | } 446 | 447 | const recoveredAddress = ethers.utils.verifyMessage(input.message, input.signature); 448 | const isValid = recoveredAddress.toLowerCase() === input.address.toLowerCase(); 449 | 450 | return createSuccessResponse( 451 | isValid ? `Signature verified successfully 452 | Message: "${input.message}" 453 | Signature: ${input.signature} 454 | Address: ${input.address} 455 | ` : `Signature verification failed 456 | Message: "${input.message}" 457 | Signature: ${input.signature} 458 | Address: ${input.address} 459 | `); 460 | } catch (error) { 461 | return createErrorResponse(`Failed to verify message: ${(error as Error).message}`); 462 | } 463 | }; 464 | 465 | export const verifyTypedDataHandler = async (input: any): Promise<ToolResultSchema> => { 466 | try { 467 | if (!input.domain || !input.types || !input.value || !input.signature || !input.address) { 468 | return createErrorResponse("Domain, types, value, signature, and address are required"); 469 | } 470 | 471 | // Use ethers.utils.verifyTypedData for EIP-712 verification 472 | const recoveredAddress = ethers.utils.verifyTypedData( 473 | input.domain, 474 | input.types, 475 | input.value, 476 | input.signature 477 | ); 478 | 479 | const isValid = recoveredAddress.toLowerCase() === input.address.toLowerCase(); 480 | 481 | return createSuccessResponse( 482 | isValid ? `Typed data signature is valid 483 | Domain: ${input.domain} 484 | Types: ${input.types} 485 | Value: ${input.value} 486 | Signature: ${input.signature} 487 | Address: ${input.address} 488 | ` : "Typed data signature is invalid"); 489 | } catch (error) { 490 | return createErrorResponse(`Failed to verify typed data: ${(error as Error).message}`); 491 | } 492 | }; 493 | 494 | // Provider Methods 495 | 496 | export const getBlockHandler = async (input: any): Promise<ToolResultSchema> => { 497 | try { 498 | if (!input.blockHashOrBlockTag) { 499 | return createErrorResponse("Block hash or block tag is required"); 500 | } 501 | 502 | const provider = getProvider(); 503 | // In ethers.js v5, getBlock can take includeTransactions as a second parameter 504 | // but TypeScript definitions might not reflect this 505 | const block = await (provider as any).getBlock(input.blockHashOrBlockTag, input.includeTransactions); 506 | 507 | return createSuccessResponse( 508 | `Block retrieved successfully 509 | Block hash: ${block.hash} 510 | Block number: ${block.number?.toString() ?? "Not specified"} 511 | Block timestamp: ${block.timestamp?.toString() ?? "Not specified"} 512 | Block transactions: ${block.transactions?.length ?? "Not specified"} 513 | `); 514 | } catch (error) { 515 | return createErrorResponse(`Failed to get block: ${(error as Error).message}`); 516 | } 517 | }; 518 | 519 | export const getTransactionHandler = async (input: any): Promise<ToolResultSchema> => { 520 | try { 521 | if (!input.transactionHash) { 522 | return createErrorResponse("Transaction hash is required"); 523 | } 524 | 525 | const provider = getProvider(); 526 | const transaction = await provider.getTransaction(input.transactionHash); 527 | 528 | return createSuccessResponse( 529 | `Transaction retrieved successfully 530 | Transaction hash: ${input.transactionHash} 531 | Transaction: ${transaction} 532 | `); 533 | } catch (error) { 534 | return createErrorResponse(`Failed to get transaction: ${(error as Error).message}`); 535 | } 536 | }; 537 | 538 | export const getTransactionReceiptHandler = async (input: any): Promise<ToolResultSchema> => { 539 | try { 540 | if (!input.transactionHash) { 541 | return createErrorResponse("Transaction hash is required"); 542 | } 543 | 544 | const provider = getProvider(); 545 | const receipt = await provider.getTransactionReceipt(input.transactionHash); 546 | 547 | return createSuccessResponse( 548 | `Transaction receipt retrieved successfully 549 | Transaction hash: ${input.transactionHash} 550 | Transaction receipt: ${receipt} 551 | `); 552 | } catch (error) { 553 | return createErrorResponse(`Failed to get transaction receipt: ${(error as Error).message}`); 554 | } 555 | }; 556 | 557 | export const getCodeHandler = async (input: any): Promise<ToolResultSchema> => { 558 | try { 559 | if (!input.address) { 560 | return createErrorResponse("Address is required"); 561 | } 562 | 563 | const provider = getProvider(); 564 | const code = await provider.getCode(input.address, input.blockTag); 565 | 566 | return createSuccessResponse( 567 | `Code retrieved successfully 568 | Address: ${input.address} 569 | Code: ${code} 570 | `); 571 | } catch (error) { 572 | return createErrorResponse(`Failed to get code: ${(error as Error).message}`); 573 | } 574 | }; 575 | 576 | export const getStorageAtHandler = async (input: any): Promise<ToolResultSchema> => { 577 | try { 578 | if (!input.address) { 579 | return createErrorResponse("Address is required"); 580 | } 581 | 582 | if (!input.position) { 583 | return createErrorResponse("Position is required"); 584 | } 585 | 586 | const provider = getProvider(); 587 | const storage = await provider.getStorageAt(input.address, input.position, input.blockTag); 588 | 589 | return createSuccessResponse( 590 | `Storage retrieved successfully 591 | Address: ${input.address} 592 | Position: ${input.position} 593 | Storage: ${storage} 594 | `); 595 | } catch (error) { 596 | return createErrorResponse(`Failed to get storage: ${(error as Error).message}`); 597 | } 598 | }; 599 | 600 | export const estimateGasHandler = async (input: any): Promise<ToolResultSchema> => { 601 | try { 602 | if (!input.transaction) { 603 | return createErrorResponse("Transaction is required"); 604 | } 605 | 606 | const provider = getProvider(); 607 | if (!provider) { 608 | return createErrorResponse("Provider is required to estimate gas, please set the provider URL"); 609 | } 610 | const gasEstimate = await provider.estimateGas(input.transaction); 611 | 612 | return createSuccessResponse( 613 | `Gas estimate retrieved successfully 614 | Gas estimate: ${gasEstimate.toString()} 615 | `); 616 | } catch (error) { 617 | return createErrorResponse(`Failed to estimate gas: ${(error as Error).message}`); 618 | } 619 | }; 620 | 621 | export const getLogsHandler = async (input: any): Promise<ToolResultSchema> => { 622 | try { 623 | if (!input.filter) { 624 | return createErrorResponse("Filter is required"); 625 | } 626 | 627 | const provider = getProvider(); 628 | if (!provider) { 629 | return createErrorResponse("Provider is required to get logs, please set the provider URL"); 630 | } 631 | const logs = await provider.getLogs(input.filter); 632 | 633 | return createSuccessResponse( 634 | `Logs retrieved successfully 635 | Logs: ${logs} 636 | `); 637 | } catch (error) { 638 | return createErrorResponse(`Failed to get logs: ${(error as Error).message}`); 639 | } 640 | }; 641 | 642 | export const getEnsResolverHandler = async (input: any): Promise<ToolResultSchema> => { 643 | try { 644 | if (!input.name) { 645 | return createErrorResponse("ENS name is required"); 646 | } 647 | 648 | const provider = getProvider(); 649 | if (!provider) { 650 | return createErrorResponse("Provider is required to get ENS resolver, please set the provider URL"); 651 | } 652 | // In ethers.js v5, getResolver might not be directly on the provider type 653 | // but it's available in the implementation 654 | const resolver = await (provider as any).getResolver(input.name); 655 | 656 | return createSuccessResponse( 657 | resolver ? `ENS resolver retrieved successfully 658 | Address: ${resolver.address} 659 | Name: ${resolver.name} 660 | ` : "No resolver found for this ENS name"); 661 | } catch (error) { 662 | return createErrorResponse(`Failed to get ENS resolver: ${(error as Error).message}`); 663 | } 664 | }; 665 | 666 | export const lookupAddressHandler = async (input: any): Promise<ToolResultSchema> => { 667 | try { 668 | if (!input.address) { 669 | return createErrorResponse("Address is required"); 670 | } 671 | 672 | const provider = getProvider(); 673 | if (!provider) { 674 | return createErrorResponse("Provider is required to lookup ENS name, please set the provider URL"); 675 | } 676 | const name = await provider.lookupAddress(input.address); 677 | 678 | return createSuccessResponse( 679 | name ? `ENS name retrieved successfully 680 | Name: ${name} 681 | ` : "No ENS name found for this address"); 682 | } catch (error) { 683 | return createErrorResponse(`Failed to lookup ENS name: ${(error as Error).message}`); 684 | } 685 | }; 686 | 687 | export const resolveNameHandler = async (input: any): Promise<ToolResultSchema> => { 688 | try { 689 | if (!input.name) { 690 | return createErrorResponse("ENS name is required"); 691 | } 692 | 693 | const provider = getProvider(); 694 | if (!provider) { 695 | return createErrorResponse("Provider is required to resolve ENS name, please set the provider URL"); 696 | } 697 | const address = await provider.resolveName(input.name); 698 | 699 | return createSuccessResponse( 700 | address ? `ENS name resolved successfully 701 | Name: ${input.name} 702 | Address: ${address} 703 | ` : "Could not resolve this ENS name"); 704 | } catch (error) { 705 | return createErrorResponse(`Failed to resolve ENS name: ${(error as Error).message}`); 706 | } 707 | }; 708 | 709 | // Network Methods 710 | 711 | export const getNetworkHandler = async (input: any): Promise<ToolResultSchema> => { 712 | try { 713 | const provider = getProvider(); 714 | if (!provider) { 715 | return createErrorResponse("Provider is required to get network information, please set the provider URL"); 716 | } 717 | const network = await provider.getNetwork(); 718 | 719 | return createSuccessResponse(`Network information retrieved successfully 720 | Network name: ${network.name} 721 | Chain ID: ${network.chainId} 722 | ENS address: ${network.ensAddress} 723 | `); 724 | } catch (error) { 725 | return createErrorResponse(`Failed to get network information: ${(error as Error).message}`); 726 | } 727 | }; 728 | 729 | export const getBlockNumberHandler = async (input: any): Promise<ToolResultSchema> => { 730 | try { 731 | const provider = getProvider(); 732 | if (!provider) { 733 | return createErrorResponse("Provider is required to get block number, please set the provider URL"); 734 | } 735 | const blockNumber = await provider.getBlockNumber(); 736 | 737 | return createSuccessResponse( 738 | `Block number retrieved successfully 739 | Block number: ${blockNumber.toString()} 740 | `); 741 | } catch (error) { 742 | return createErrorResponse(`Failed to get block number: ${(error as Error).message}`); 743 | } 744 | }; 745 | 746 | export const getFeeDataHandler = async (input: any): Promise<ToolResultSchema> => { 747 | try { 748 | const provider = getProvider(); 749 | if (!provider) { 750 | return createErrorResponse("Provider is required to get fee data, please set the provider URL"); 751 | } 752 | // getFeeData is available in ethers v5.5.0+ 753 | // @ts-ignore - getFeeData might not be in the type definitions depending on the version 754 | const feeData = await provider.getFeeData(); 755 | 756 | return createSuccessResponse(`Fee data retrieved successfully 757 | Gas price: ${feeData.gasPrice?.toString()} 758 | Max fee per gas: ${feeData.maxFeePerGas?.toString()} 759 | Max priority fee per gas: ${feeData.maxPriorityFeePerGas?.toString()} 760 | `); 761 | } catch (error) { 762 | return createErrorResponse(`Failed to get fee data: ${(error as Error).message}`); 763 | } 764 | }; 765 | ```