Skip to content

Commit 9e2a0ac

Browse files
committed
feat: Add flexible wallet configuration supporting private key or mnemonic phrase, centralize wallet logic, and update documentation.
1 parent 8cee62f commit 9e2a0ac

4 files changed

Lines changed: 102 additions & 31 deletions

File tree

README.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ All services are exposed through a consistent interface of MCP tools, resources,
8383

8484
### Comprehensive Transaction Support
8585

86+
- **Flexible Wallet Support** - Configure with Private Key or Mnemonic (BIP-39) with HD path support
8687
- **Native token transfers** across all supported networks
8788
- **Gas estimation** for transaction planning
8889
- **Transaction status** and receipt information
@@ -185,18 +186,40 @@ npm install
185186

186187
The server uses the following environment variables. For write operations and ABI fetching, you must configure these variables:
187188

188-
#### Private Key (For Write Operations)
189+
#### Wallet Configuration (For Write Operations)
190+
191+
You can configure your wallet using **either** a private key or a mnemonic phrase:
192+
193+
**Option 1: Private Key**
189194

190195
```bash
191196
export EVM_PRIVATE_KEY="0x..." # Your private key in hex format (with or without 0x prefix)
192197
```
193198

194-
This private key is used for:
199+
**Option 2: Mnemonic Phrase (Recommended for HD Wallets)**
200+
201+
```bash
202+
export EVM_MNEMONIC="word1 word2 word3 ... word12" # Your 12 or 24 word BIP-39 mnemonic
203+
export EVM_ACCOUNT_INDEX="0" # Optional: Account index for HD wallet derivation (default: 0)
204+
```
205+
206+
The mnemonic option supports hierarchical deterministic (HD) wallet derivation:
207+
- Uses BIP-39 standard mnemonic phrases (12 or 24 words)
208+
- Supports BIP-44 derivation path: `m/44'/60'/0'/0/{accountIndex}`
209+
- `EVM_ACCOUNT_INDEX` allows you to derive different accounts from the same mnemonic
210+
- Default account index is 0 (first account)
211+
212+
**Wallet is used for:**
195213
- Transferring native tokens (`transfer_native` tool)
196214
- Transferring ERC20 tokens (`transfer_erc20` tool)
197215
- Approving token spending (`approve_token_spending` tool)
216+
- Writing to smart contracts (`write_contract` tool)
198217

199-
⚠️ **Security**: Never commit your private key to version control. Use environment variables or a secure key management system.
218+
⚠️ **Security**:
219+
- Never commit your private key or mnemonic to version control
220+
- Use environment variables or a secure key management system
221+
- Store mnemonics securely - they provide access to all derived accounts
222+
- Consider using different account indices for different purposes
200223

201224
#### API Keys (For ABI Fetching)
202225

src/core/services/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ export * from './contracts.js';
88
export * from './tokens.js';
99
export * from './ens.js';
1010
export * from './abi.js';
11+
export * from './wallet.js';
1112
export { utils as helpers } from './utils.js';
1213

1314
// Re-export common types for convenience
14-
export type {
15-
Address,
16-
Hash,
15+
export type {
16+
Address,
17+
Hash,
1718
Hex,
1819
Block,
1920
TransactionReceipt,

src/core/services/wallet.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { type Address, type Hex } from 'viem';
2+
import { privateKeyToAccount, mnemonicToAccount } from 'viem/accounts';
3+
4+
/**
5+
* Get the configured account from environment (private key or mnemonic)
6+
*
7+
* Configuration options:
8+
* - EVM_PRIVATE_KEY: Hex private key (with or without 0x prefix)
9+
* - EVM_MNEMONIC: BIP-39 mnemonic phrase (12 or 24 words)
10+
* - EVM_ACCOUNT_INDEX: Optional account index for HD wallet derivation (default: 0)
11+
*/
12+
export const getConfiguredAccount = () => {
13+
const privateKey = process.env.EVM_PRIVATE_KEY;
14+
const mnemonic = process.env.EVM_MNEMONIC;
15+
const accountIndex = parseInt(process.env.EVM_ACCOUNT_INDEX || '0');
16+
17+
if (privateKey) {
18+
// Use private key if provided
19+
const key = (privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`) as Hex;
20+
return privateKeyToAccount(key);
21+
} else if (mnemonic) {
22+
// Use mnemonic if provided
23+
return mnemonicToAccount(mnemonic, { accountIndex });
24+
} else {
25+
throw new Error(
26+
"Neither EVM_PRIVATE_KEY nor EVM_MNEMONIC environment variable is set. " +
27+
"Configure one of them to enable write operations.\n" +
28+
"- EVM_PRIVATE_KEY: Your private key in hex format\n" +
29+
"- EVM_MNEMONIC: Your 12 or 24 word mnemonic phrase\n" +
30+
"- EVM_ACCOUNT_INDEX: (Optional) Account index for HD wallet (default: 0)"
31+
);
32+
}
33+
};
34+
35+
/**
36+
* Helper to get the configured private key (for services that need it)
37+
*/
38+
export const getConfiguredPrivateKey = (): Hex => {
39+
const account = getConfiguredAccount();
40+
// For mnemonic-based accounts, we need to extract the private key
41+
// Viem accounts have a privateKey property but it's not always in the type definition
42+
const accountWithKey = account as any;
43+
if (!accountWithKey.privateKey) {
44+
throw new Error("Unable to extract private key from account");
45+
}
46+
return accountWithKey.privateKey;
47+
};
48+
49+
/**
50+
* Helper to get wallet address
51+
*/
52+
export const getWalletAddressFromKey = (): Address => {
53+
const account = getConfiguredAccount();
54+
return account.address;
55+
};
56+
57+
/**
58+
* Helper to get configured wallet object
59+
*/
60+
export const getConfiguredWallet = (): { address: Address } => {
61+
return { address: getWalletAddressFromKey() };
62+
};

src/core/tools.ts

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,28 @@ import { z } from "zod";
33
import { getSupportedNetworks, getRpcUrl } from "./chains.js";
44
import * as services from "./services/index.js";
55
import { type Address, type Hex, type Hash } from 'viem';
6-
import { privateKeyToAccount } from 'viem/accounts';
76
import { normalize } from 'viem/ens';
87

98
/**
109
* Register all EVM-related tools with the MCP server
1110
*
12-
* SECURITY: The EVM_PRIVATE_KEY environment variable must be set for write operations.
13-
* Private keys are never passed as tool arguments for security reasons.
11+
* SECURITY: Either EVM_PRIVATE_KEY or EVM_MNEMONIC environment variable must be set for write operations.
12+
* Private keys and mnemonics are never passed as tool arguments for security reasons.
1413
* Tools will use the configured wallet for all transactions.
1514
*
15+
* Configuration options:
16+
* - EVM_PRIVATE_KEY: Hex private key (with or without 0x prefix)
17+
* - EVM_MNEMONIC: BIP-39 mnemonic phrase (12 or 24 words)
18+
* - EVM_ACCOUNT_INDEX: Optional account index for HD wallet derivation (default: 0)
19+
*
1620
* All tools that accept addresses also support ENS names (e.g., 'vitalik.eth').
1721
* ENS names are automatically resolved to addresses using the Ethereum Name Service.
1822
*
1923
* @param server The MCP server instance
2024
*/
2125
export function registerEVMTools(server: McpServer) {
22-
// Helper to get the configured private key from environment
23-
const getConfiguredPrivateKey = (): Hex => {
24-
const privateKey = process.env.EVM_PRIVATE_KEY;
25-
if (!privateKey) {
26-
throw new Error("EVM_PRIVATE_KEY environment variable is not set. Configure it to enable write operations.");
27-
}
28-
// Ensure 0x prefix
29-
return (privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`) as Hex;
30-
};
31-
32-
// Helper to get wallet address from private key
33-
const getWalletAddressFromKey = (): Address => {
34-
const privateKey = getConfiguredPrivateKey();
35-
const account = privateKeyToAccount(privateKey);
36-
return account.address;
37-
};
38-
39-
// Helper to get configured wallet object
40-
const getConfiguredWallet = (): { address: Address } => {
41-
return { address: getWalletAddressFromKey() };
42-
};
26+
// Helpers are now imported from services/wallet.ts
27+
const { getConfiguredPrivateKey, getWalletAddressFromKey, getConfiguredWallet } = services;
4328

4429
// ============================================================================
4530
// WALLET INFORMATION TOOLS (Read-only)
@@ -768,7 +753,7 @@ export function registerEVMTools(server: McpServer) {
768753
server.registerTool(
769754
"write_contract",
770755
{
771-
description: "Execute state-changing functions on a smart contract. Automatically fetches ABI from block explorer if not provided (requires ETHERSCAN_API_KEY). Use this to call any write function on verified contracts. Requires EVM_PRIVATE_KEY to be configured.",
756+
description: "Execute state-changing functions on a smart contract. Automatically fetches ABI from block explorer if not provided (requires ETHERSCAN_API_KEY). Use this to call any write function on verified contracts. Requires wallet to be configured (via private key or mnemonic).",
772757
inputSchema: {
773758
contractAddress: z.string().describe("The contract address"),
774759
functionName: z.string().describe("Function name to call (e.g., 'mint', 'swap', 'stake', 'approve')"),

0 commit comments

Comments
 (0)