
Gives Claude direct access to Ethereum transaction primitives without needing a node connection. You can build and sign both legacy and EIP-1559 transactions, estimate gas costs, work with RLP encoding, and handle ERC-20 transfers. It's part of a five-server toolkit that separates wallet operations, keystore management, signing, transactions, and validation into distinct tools. The transaction server specifically exposes 15 tools covering everything from building unsigned transactions to parsing calldata and serializing for broadcast. Useful when you need to construct transactions offline or want an AI assistant to help with transaction debugging, gas estimation, or building complex contract interactions before sending them on-chain.
A collection of Ethereum wallet tools: Python CLI utilities (eth_toolkit.py, wallet.py, keystore.py, sign.py, transaction.py, typed_data.py, validate.py, vanity.py), five Model Context Protocol (MCP) servers exposing wallet functionality to AI assistants, a fully offline single-file HTML wallet (offline.html), and an x402 payment facilitator. See the sections below for each component.
This directory contains the build system to create offline1.html using official ethereumjs libraries.
All libraries are from the official Ethereum ecosystem:
@ethereumjs/tx - Transaction signing (legacy + EIP-1559)@ethereumjs/util - Utilities (address validation, checksums, etc.)@ethereumjs/rlp - RLP encoding@ethereumjs/wallet - Wallet utilitiesethereum-cryptography - Official cryptographic primitives (secp256k1, keccak256)@scure/bip39 - BIP39 mnemonic (used by ethereumjs)@scure/bip32 - BIP32 HD derivation (used by ethereumjs)# Navigate to this directory
cd offline-build
# Install dependencies
npm install
# Build offline1.html
npm run build
The output file offline1.html will be created in the parent directory.
The build process uses esbuild to:
Typical bundle size: ~400-600 KB (varies by version)
All features match the CLI tool:
This repository includes 5 Model Context Protocol (MCP) servers that expose Ethereum wallet functionality to AI assistants like Claude.
| Server | Purpose | Tools | Tests |
|---|---|---|---|
| ethereum-wallet-mcp | Wallet generation, HD wallets, mnemonics | 6 | 111 |
| keystore-mcp-server | Encrypted keystore files (Web3 Secret Storage) | 9 | 74 |
| signing-mcp-server | Message signing, EIP-191, EIP-712 | 12 | 34 |
| transaction-mcp-server | Transaction building, encoding, signing | 15 | 65 |
| validation-mcp-server | Address/key validation, checksums, hashing | 15 | 64 |
Total: 57 tools, 348 tests
Each server is a standalone Python package:
# Install all servers
pip install -e ./ethereum-wallet-mcp
pip install -e ./keystore-mcp-server
pip install -e ./signing-mcp-server
pip install -e ./transaction-mcp-server
pip install -e ./validation-mcp-server
Add to your claude_desktop_config.json:
{
"mcpServers": {
"ethereum-wallet": {
"command": "ethereum-wallet-mcp"
},
"keystore": {
"command": "keystore-mcp-server"
},
"signing": {
"command": "signing-mcp-server"
},
"transaction": {
"command": "transaction-mcp-server"
},
"validation": {
"command": "validation-mcp-server"
}
}
}
Wallet Generation & HD Wallets
Tools:
generate_wallet - Random Ethereum walletgenerate_wallet_with_mnemonic - BIP39 mnemonic walletrestore_wallet_from_mnemonic - Restore from seed phraserestore_wallet_from_private_key - Restore from private keyderive_multiple_accounts - HD wallet derivationgenerate_vanity_address - Custom prefix/suffix addressesResources:
wallet://documentation/bip39 - BIP39 specwallet://documentation/derivation-paths - HD pathswallet://wordlist/{language} - BIP39 wordlistsEncrypted Keystore Files (Web3 Secret Storage)
Tools:
encrypt_keystore - Create encrypted keystore from private keydecrypt_keystore - Decrypt keystore to get private keychange_keystore_password - Re-encrypt with new passwordvalidate_keystore - Verify keystore structureget_keystore_address - Extract address without decryptiongenerate_encrypted_wallet - Create new wallet as keystoreread_keystore_file - Read keystore from filesystemwrite_keystore_file - Save keystore to filesystemSupports:
Message & Data Signing
Tools:
sign_message - EIP-191 personal_signverify_message - Verify signed messagehash_message - Create message hashsign_typed_data - EIP-712 structured dataverify_typed_data - Verify typed data signatureencode_typed_data - Encode without signingrecover_signer - Recover address from signatureTransaction Building & Signing
Tools:
build_transaction - Create unsigned transactionsign_transaction - Sign with private keydecode_transaction - Parse raw transactionencode_transaction - RLP encode transactionestimate_gas - Calculate gas requirementscalculate_transaction_hash - Pre-signing hashbuild_eip1559_transaction - Type 2 transactionsbuild_legacy_transaction - Type 0 transactionsbuild_access_list_transaction - Type 1 transactionsserialize_transaction - Convert to wire formatparse_transaction_input - Decode calldatavalidate_transaction - Check transaction validityValidation & Cryptographic Utilities
Tools:
validate_address - EIP-55 checksum validationvalidate_private_key - Key range checkingto_checksum_address - Convert to checksummedderive_address_from_private_key - Key → addressderive_address_from_public_key - Pubkey → addressvalidate_signature - Check v, r, s valuesvalidate_hex_data - Hex string validationcompare_addresses - Address equalitybatch_validate_addresses - Bulk validationgenerate_vanity_check - Pattern matchingkeccak256_hash - Compute Keccak-256encode_function_selector - Signature → selectordecode_function_selector - Lookup selectorsvalidate_ens_name - ENS format validationcalculate_storage_slot - Storage slot computationResources:
validation://eip55-specification - EIP-55 docsvalidation://secp256k1-constants - Curve paramsvalidation://function-selectors-db - 500+ selectorsvalidation://address-patterns - Known patternsRun all server tests:
# Individual servers
pytest ethereum-wallet-mcp/tests/ -v
pytest keystore-mcp-server/tests/ -v
pytest signing-mcp-server/tests/ -v
pytest transaction-mcp-server/tests/ -v
pytest validation-mcp-server/tests/ -v
# All at once
./run_all_tests.sh
All servers follow a consistent pattern:
server-name/
├── pyproject.toml # Package config
├── README.md # Server docs
├── src/
│ └── package_name/
│ ├── __init__.py
│ ├── __main__.py # Entry point
│ ├── server.py # MCP server setup
│ ├── tools/ # Tool implementations
│ ├── resources/ # Static resources
│ └── prompts/ # Interactive prompts
└── tests/
└── test_*.py # Pytest tests
Each tool has:
*_impl() function with pure business logic (for testing)⚠️ Warning: These tools handle sensitive cryptographic material. Review the code and understand the implications before using with real assets.
All servers use official Ethereum Foundation libraries:
eth-account - Key generation, signingeth-keys - ECDSA operationseth-utils - Utility functionseth-rlp - RLP encodingmnemonic - BIP39 implementationmcp - Model Context Protocol SDKAll rights reserved. See LICENSE.
This guide provides real-world prompt examples for interacting with the Ethereum Wallet Toolkit MCP servers through AI assistants like Claude.
Simple:
Generate a new Ethereum wallet for me
With mnemonic:
Create a new Ethereum wallet with a 24-word seed phrase
For development/testing:
Generate a test wallet for Sepolia testnet development
From mnemonic:
Restore my wallet from this seed phrase:
abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
From private key:
Import this private key and show me the wallet address:
0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318
Derive multiple accounts:
Derive 5 accounts from this mnemonic using the standard Ethereum path:
[your 12/24 word mnemonic]
Custom derivation path:
Derive an account at path m/44'/60'/1'/0/0 from my seed phrase
Simple prefix:
Generate a vanity address starting with "cafe"
Case-insensitive suffix:
Find me an address ending with "1337" (case insensitive)
Basic signing:
Sign this message with my private key:
Message: "Hello, Ethereum!"
Private key: 0x...
For verification:
Sign a message that proves I own wallet 0xABC...
The message should be: "I authorize login to MyDApp on 2024-01-15"
Verify message:
Verify this signed message:
- Message: "Hello, Ethereum!"
- Signature: 0x...
- Expected signer: 0x...
Recover signer:
Who signed this message?
- Message: "I agree to the terms"
- Signature: 0x...
Decompose signature:
Break down this signature into v, r, s components:
0x...
Normalize v value:
Convert this signature's v value from 0/1 format to 27/28 format:
0x...
Permit signature for token approval:
Sign an EIP-712 permit for USDC on Ethereum mainnet:
- Owner: 0x... (my address)
- Spender: 0x... (Uniswap router)
- Value: 1000000000 (1000 USDC, 6 decimals)
- Nonce: 0
- Deadline: 1735689600 (Unix timestamp)
- Private key: 0x...
- Contract address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
NFT marketplace order:
Sign this EIP-712 typed data for an NFT listing:
Domain:
- Name: "OpenSea"
- Version: "1.4"
- Chain ID: 1
- Verifying Contract: 0x00000000006c3852cbEf3e08E8dF289169EdE581
Message (Order):
- offerer: 0x... (my address)
- zone: 0x0000000000000000000000000000000000000000
- offer: [{ token: 0x..., identifier: 1234, amount: 1 }]
- consideration: [{ token: 0x0, amount: 1000000000000000000 }]
- orderType: 0
- startTime: 1704067200
- endTime: 1735689600
Private key: 0x...
Compute the EIP-712 hash for this typed data without signing:
[typed data structure]
Simple ETH transfer:
Build and sign a transaction to send 0.5 ETH:
- To: 0x742d35Cc6634C0532925a3b844Bc9e7595f5b4E2
- Chain ID: 1 (mainnet)
- Nonce: 42
- Max fee per gas: 30 gwei
- Max priority fee: 2 gwei
- Private key: 0x...
ERC-20 token transfer:
Create a signed transaction to transfer 100 USDT:
- Token contract: 0xdAC17F958D2ee523a2206206994597C13D831ec7
- To: 0x...
- Amount: 100000000 (100 USDT with 6 decimals)
- From nonce: 5
- Chain ID: 1
- Gas limit: 65000
- Max fee: 50 gwei
- Private key: 0x...
Contract interaction:
Build a transaction to call the 'approve' function:
- Contract: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 (USDC)
- Function: approve(address spender, uint256 amount)
- Spender: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D (Uniswap)
- Amount: max uint256 (unlimited approval)
- Sign with: 0x...
Decode raw transaction:
Decode this raw signed transaction and show me what it does:
0x02f8730180843b9aca00850...
Decode calldata:
Decode this calldata and tell me what function it calls:
0xa9059cbb000000000000000000000000...
Estimate cost:
Estimate the total cost in ETH for this transaction at current gas prices:
- Gas limit: 21000
- Max fee: 30 gwei
- Priority fee: 2 gwei
Compare transactions:
Compare these two transactions and highlight the differences:
Transaction 1: 0x...
Transaction 2: 0x...
From private key:
Create an encrypted keystore file for this private key:
- Private key: 0x...
- Password: MySecurePassword123!
- Use scrypt (more secure)
Generate new encrypted wallet:
Generate a new wallet and immediately encrypt it as a keystore:
- Password: MySecurePassword123!
- Return the keystore JSON
Get private key:
Decrypt this keystore to get the private key:
[paste keystore JSON]
Password: MySecurePassword123!
Just get the address:
What's the address in this keystore? (don't decrypt)
[paste keystore JSON]
Change password:
Change the password on this keystore:
[paste keystore JSON]
Old password: OldPassword123
New password: NewSecurePassword456!
Validate keystore:
Is this a valid Web3 Secret Storage keystore?
[paste keystore JSON]
Single address:
Is this a valid Ethereum address? 0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed
Check checksum:
Does this address have a valid EIP-55 checksum?
0x5aaEB6053f3e94C9b9A09f33669435e7ef1beaed
Convert to checksum:
Convert this address to checksummed format:
0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed
Batch validation:
Check which of these addresses are valid:
- 0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed
- 0x1234
- 0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359
- invalid
Validate key:
Is this a valid Ethereum private key?
0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318
Security check:
Check if this private key is secure (not a known weak key):
0x0000000000000000000000000000000000000000000000000000000000000001
Keccak256 hash:
Compute the keccak256 hash of "Hello, Ethereum!"
Function selector:
What's the function selector for transfer(address,uint256)?
Decode selector:
What function does selector 0xa9059cbb correspond to?
Calculate mapping slot:
Calculate the storage slot for a mapping at slot 2 with key 0xABC...
Dynamic array slot:
Calculate the storage slot for the first element of a dynamic array at slot 5
Help me create a complete backup of my wallet:
1. Generate a new wallet with mnemonic
2. Create an encrypted keystore as additional backup
3. Show me how to verify I can restore from both
Walk me through creating a gasless ERC-20 permit:
1. I want to approve Uniswap to spend my USDC
2. Generate the EIP-712 typed data
3. Sign it with my private key
4. Show me the permit parameters to submit on-chain
Set up a hierarchical wallet structure:
1. Generate a master seed phrase (24 words)
2. Derive 3 accounts:
- Account 0: Main spending wallet
- Account 1: Savings (cold storage)
- Account 2: DeFi interactions
3. Create keystores for each with different passwords
Audit the security of this private key:
0x...
Check for:
- Known weak keys
- Proper entropy
- Valid curve point
I have a transaction that failed. Help me debug it:
Raw transaction: 0x...
Please:
1. Decode the transaction completely
2. Validate all fields
3. Identify potential issues
4. Suggest fixes
✅ "Sign a transaction for Ethereum mainnet (chain ID 1)"
❌ "Sign a transaction"
✅ "Sign message 'Hello' with private key 0x..."
❌ "Sign a message"
✅ "Return the signature in v/r/s format as well as the packed format"
❌ "Sign and give me the signature"
✅ "Generate a test wallet for development"
❌ "Generate a wallet" (when just testing)
✅ "Generate a wallet, then create a signed message proving ownership"
❌ Two separate prompts that lose context
⚠️ When using these prompts:
DISCLAIMER: FOR EDUCATIONAL PURPOSES ONLY
This software is provided for educational and research purposes only. DO NOT USE WITH REAL FUNDS. The author accepts NO LIABILITY for any damages. All example addresses shown are for illustrative purposes only.
A comprehensive compilation of research papers, technical articles, and security analyses related to cryptocurrency vanity addresses, their generation, vulnerabilities, and associated attack vectors.
Vanity addresses are custom cryptocurrency wallet addresses created using specific algorithms to incorporate user-chosen character sequences. While they serve legitimate purposes such as gas optimization, protocol branding, and multichain reproducibility, they have also been associated with significant security vulnerabilities and attack vectors.
This document compiles research from academic papers, security analyses, and technical blog posts to provide a comprehensive understanding of vanity addresses in the cryptocurrency ecosystem.
When generating a cryptocurrency wallet, the system produces an address composed of a random string of characters. These default addresses lack personal significance. A vanity address is a custom address generated using specific algorithms that incorporate a user-chosen sequence of characters into the wallet address.
Gas Optimization: Transaction gas costs decrease if an address has leading zeros. According to the Ethereum yellowpaper, leading zeros are cheaper for gas calculations. Wintermute reportedly saved $15,000 in gas costs due to their EOA having leading zeros (foobar).
Protocol Branding: Companies use vanity addresses for easy recognition. For example, protocols often use addresses starting with recognizable patterns like 0x1111... or 0x0000... for branding.
Multichain Reproducibility: Protocols can deploy to multiple EVM chains with the same contract address, simplifying documentation and user experience.
Creating vanity addresses is computationally intensive. The algorithm must try many combinations before finding an address that includes the chosen string of characters. The higher the number of prefixes and suffixes requested, the more time and computational resources required.
For reference, using optimized GPU mining (RTX 3090 at ~2 billion attempts/second):
Profanity is an open-source vanity address generator. The basic workflow:
Critical Flaw: The profanity code uses a pseudo-random number generator called mt19937, which only outputs 8 bytes at a time and takes a 4-byte unsigned int seed (fed by a random_device call). Since Ethereum private keys are 32 bytes, the code must combine 4 outputs. The mt19937_64 generator is only seeded once, so outputs don't change if the input seed is reused. This reduces complexity from 2^256 to 2^32 (James).
CREATE (Default):
new_address = hash(sender, nonce)
CREATE2 (Recommended for Vanity):
new_address = hash(0xFF, sender, salt, bytecode)
The fundamental flaw in Profanity's random number generation:
// Vulnerable code from Dispatcher.cpp
// Uses mt19937 with only 4-byte seed
// Reduces keyspace from 2^256 to 2^32
Impact: All starting private keys that could be generated by this program can be generated and saved in just a few hours using less than 2TB of hard drive space (James).
EOAs (Externally Owned Accounts):
Smart Contract Accounts:
As stated by foobar: "EOA vanity is the road to bankruptcy, smart contract vanity is the road to success."
Research from UCSB identified 50,081 addresses as decentralized exchanges through arbitrage detection. Attribution was done by:
This research revealed 180 unique Uniswap v2 factories, demonstrating how vanity addresses can be used for exchange identification (McLaughlin et al. 3299).
Address poisoning aims to create a vanity address resembling a legitimate wallet that the target frequently interacts with. The attacker then:
Typically, the first 4-6 characters and last 4-6 characters are made to resemble the target address.
Example (FAKE illustrative addresses):
0xABCD1234ABCD1234ABCD1234ABCD1234ABCD12340xABCD5678000056780000567800005678ABCD5678(Notice first 4 and last 4 characters match)
Attackers send fake tokens (e.g., fake USDT) to wallets with addresses similar to ones that received legitimate tokens. These fake token contracts may:
With tokens like USDT, transferring 0 amount records on the ledger. Scammers:
A victim lost 1,155 WBTC (~$72M) by copying the wrong address from their transaction history. Remarkably, the stolen funds were eventually returned to the victim (CertiK).
Research from McLaughlin et al. at UCSB conducted a 28-month study (February 2020 - July 2022) analyzing the Ethereum arbitrage ecosystem. Key findings relevant to vanity addresses:
| Exchange | Usage % |
|---|---|
| Uniswap v2 | 44.9% |
| Uniswap v3 | 15.2% |
| Sushi Swap | 13.5% |
| Balancer v1 | 10.8% |
| Unknown | 5.4% |
The research identified threats to consensus stability:
Research from Soska et al. at Carnegie Mellon University provides context on how vanity addresses are used in the broader cryptocurrency trading ecosystem.
BitMEX uses vanity addresses for customer deposit accounts:
3BMEX prefix for all customer addressesThe researchers developed methods to cluster BitMEX accounts:
Results showed sophisticated traders operating multiple accounts:
Analysis of vanity address clusters revealed:
DO NOT use vanity generators for EOAs. The only truly reliable method is to generate addresses yourself with cryptographically secure randomness. Even then, vulnerabilities in generation software can compromise security.
Smart contract vanity addresses are safe when using CREATE2:
To fix the Profanity vulnerability, proper seeding is required:
// Instead of single 4-byte seed
// Use 624-word seed for mt19937
// Or use cryptographically secure RNG
The fix requires feeding a random seed sequence of at least 32 bits to ensure mt19937 produces cryptographically secure outputs (James).
0xba5ed... ("based")Using create2crunch on vast.ai (RTX 3090):
# Install
sudo apt install build-essential -y
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
git clone https://github.com/0age/create2crunch && cd create2crunch
sed -i 's/0x4/0x40/g' src/lib.rs
# Run search
export FACTORY="0xYourFactoryAddress"
export CALLER="0xYourCallerAddress"
export INIT_CODE_HASH="0xYourInitCodeHash"
export LEADING=5
export TOTAL=7
cargo run --release $FACTORY $CALLER $INIT_CODE_HASH 0 $LEADING $TOTAL
The CREATE2 Factory uses ENS founder Nick Johnson's "keyless transaction" approach:
This creates a single-use EOA that can only ever deploy one transaction.
CertiK. "Vanity Address and Address Poisoning." CertiK Resources, 29 July 2024, www.certik.com/resources/blog/vanity-address-and-address-poisoning.
foobar. "Vanity Addresses: The Only Safe Way to Do Permissionless Multichain Deployments." 0xfoobar Substack, 10 Jan. 2023, 0xfoobar.substack.com/p/vanity-addresses.
Garreau, Marc. "Web3.py Patterns: Address Mining." Snake Charmers (Ethereum Foundation), 4 Oct. 2021, snakecharmers.ethereum.org/web3-py-patterns-address-mining/.
James. "Fixing Other People's Code." Oregon State University Blogs, 6 Feb. 2023, blogs.oregonstate.edu/james/2023/02/.
McLaughlin, Robert, et al. "A Large Scale Study of the Ethereum Arbitrage Ecosystem." 32nd USENIX Security Symposium, 9-11 Aug. 2023, Anaheim, CA, USA, pp. 3295-3312. USENIX Association, www.usenix.org/conference/usenixsecurity23/presentation/mclaughlin.
Soska, Kyle, et al. "Towards Understanding Cryptocurrency Derivatives: A Case Study of BitMEX." Proceedings of the Web Conference 2021 (WWW '21), 19-23 Apr. 2021, Ljubljana, Slovenia. ACM, New York, NY, USA, doi.org/10.1145/3442381.3450059.
Daian, Philip, et al. "Flash Boys 2.0: Frontrunning in Decentralized Exchanges, Miner Extractable Value, and Consensus Instability." 2020 IEEE Symposium on Security and Privacy (SP), 2020, pp. 910-927.
Qin, Kaihua, et al. "Quantifying Blockchain Extractable Value: How Dark Is the Forest?" 2022 IEEE Symposium on Security and Privacy (SP), 2022, pp. 198-214.
Wang, Ye, et al. "Cyclic Arbitrage in Decentralized Exchanges." Companion Proceedings of the Web Conference 2022 (WWW '22), 2022, pp. 12-19. ACM.
Zhou, Liyi, et al. "On the Just-in-Time Discovery of Profit-Generating Transactions in DeFi Protocols." 2021 IEEE Symposium on Security and Privacy (SP), 2021, pp. 919-936.
Gandal, Neil, et al. "Price Manipulation in the Bitcoin Ecosystem." Journal of Monetary Economics, vol. 95, 2018, pp. 86-96.
Meiklejohn, Sarah, et al. "A Fistful of Bitcoins: Characterizing Payments Among Men with No Names." Proceedings of the 2013 Internet Measurement Conference, 2013, pp. 127-140.
Nakamoto, Satoshi. "Bitcoin: A Peer-to-Peer Electronic Cash System." 2008.
Document compiled: January 2026
For the ethereum-wallet-toolkit project
DISCLAIMER: FOR EDUCATIONAL PURPOSES ONLY
This software is provided for educational and research purposes only. DO NOT USE WITH REAL FUNDS. The author accepts NO LIABILITY for any damages. All example addresses and keys shown are FAKE - never use them for real funds.
This document covers the keystore encryption and decryption features of the Ethereum Wallet Toolkit.
Keystores are encrypted JSON files that securely store private keys. They follow the Web3 Secret Storage Definition (Version 3) and are compatible with all major Ethereum wallets including MetaMask, Geth, and MyEtherWallet.
The toolkit supports two KDF algorithms:
| KDF | Security | Speed | Recommended |
|---|---|---|---|
| scrypt | Higher | Slower | Yes (default) |
| pbkdf2 | Good | Faster | For compatibility |
scrypt (default):
pbkdf2:
# Encrypt a private key
python keystore.py encrypt --key 0x... --password mypassword --output wallet.json
# Encrypt with secure password prompt
python keystore.py encrypt --key 0x... --output wallet.json
# Use PBKDF2 instead of scrypt
python keystore.py encrypt --key 0x... --password secret --kdf pbkdf2 --output wallet.json
# Decrypt a keystore
python keystore.py decrypt --file wallet.json --password mypassword
# Decrypt with password prompt (more secure)
python keystore.py decrypt --file wallet.json
# View keystore info without decryption
python keystore.py info --file wallet.json
# Change keystore password
python keystore.py change-password --file wallet.json
# Encrypt
python eth_toolkit.py keystore --encrypt --key 0x... --password secret --output wallet.json
# Decrypt
python eth_toolkit.py keystore --decrypt --file wallet.json --password secret
{
"version": 3,
"id": "uuid-here",
"address": "abcdefghijkabcdefghijklmnopqrstuvwxyz",
"crypto": {
"ciphertext": "encrypted-private-key",
"cipherparams": {
"iv": "initialization-vector"
},
"cipher": "aes-128-ctr",
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"salt": "random-salt",
"n": 262144,
"r": 8,
"p": 1
},
"mac": "message-authentication-code"
}
}
from keystore import (
encrypt_keystore,
decrypt_keystore,
save_keystore,
load_keystore,
get_keystore_info
)
# Encrypt a private key
keystore = encrypt_keystore(
private_key='0x...',
password='my-secure-password',
kdf='scrypt'
)
# Save to file
filepath = save_keystore(keystore, 'my-wallet.json')
# Load from file
keystore = load_keystore('my-wallet.json')
# Get info without decryption
info = get_keystore_info(keystore)
print(f"Address: {info['address']}")
print(f"KDF: {info['kdf']}")
# Decrypt
private_key = decrypt_keystore(keystore, 'my-secure-password')
--password# Good - password prompted securely
python keystore.py encrypt --key 0x... --output wallet.json
# Avoid - password visible in shell history
python keystore.py encrypt --key 0x... --password secret --output wallet.json
Always test decryption after creating a keystore:
# Create keystore
python keystore.py encrypt --key 0x... --output test.json
# Verify decryption works
python keystore.py decrypt --file test.json
# Check the derived address matches expected
python keystore.py info --file test.json
MetaMask can import keystore files directly:
geth account import --datadir /path/to/data wallet.json
from web3 import Web3
with open('wallet.json', 'r') as f:
keystore = f.read()
private_key = Web3().eth.account.decrypt(keystore, 'password')
Incorrect Password
Error: Failed to decrypt keystore - MAC mismatch
Check that the password is correct.
Corrupted File
Error: Invalid keystore format
Wrong KDF Parameters
Error: Unsupported KDF algorithm
If you forget your password:
DISCLAIMER: FOR EDUCATIONAL PURPOSES ONLY
This software is provided for educational and research purposes only. DO NOT USE WITH REAL FUNDS. The author accepts NO LIABILITY for any damages. All example addresses and keys shown are FAKE - never use them for real funds.
This document covers the EIP-712 typed structured data signing features of the Ethereum Wallet Toolkit.
EIP-712 defines a standard for hashing and signing typed structured data. It's widely used in DeFi for:
Every EIP-712 message has four components:
{
"types": {
"EIP712Domain": [...],
"PrimaryType": [...]
},
"primaryType": "PrimaryType",
"domain": {...},
"message": {...}
}
Defines the structure of all types used:
{
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"}
],
"Permit": [
{"name": "owner", "type": "address"},
{"name": "spender", "type": "address"},
{"name": "value", "type": "uint256"},
{"name": "nonce", "type": "uint256"},
{"name": "deadline", "type": "uint256"}
]
}
}
The main type being signed:
{
"primaryType": "Permit"
}
Context that binds the signature to a specific contract:
{
"domain": {
"name": "USD Coin",
"version": "2",
"chainId": 1,
"verifyingContract": "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
}
}
The actual data being signed:
{
"message": {
"owner": "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"spender": "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"value": "1000000000000000000",
"nonce": 0,
"deadline": 1893456000
}
}
# ERC-20 Permit example
python typed_data.py example --type permit --output permit.json
# DEX Order example
python typed_data.py example --type order --output order.json
# Mail example (EIP-712 spec)
python typed_data.py example --type mail --output mail.json
# Sign a permit
python typed_data.py sign --file permit.json --key 0xaaa...
# Sign with verbose output
python typed_data.py sign --file order.json --key 0xaaa... --verbose
# Save signature to file
python typed_data.py sign --file permit.json --key 0xaaa... --output signed.json
# Verify a signature
python typed_data.py verify \
--file permit.json \
--signature 0xabc... \
--address 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# Get the signing hash (for debugging)
python typed_data.py hash --file permit.json
from typed_data import (
sign_typed_data,
verify_typed_data,
hash_typed_data,
load_typed_data,
EXAMPLES
)
# Load typed data from file
typed_data = load_typed_data('permit.json')
# Or use built-in examples
typed_data = EXAMPLES['permit']
# Sign
result = sign_typed_data(typed_data, '0xaaa...')
print(f"Signature: {result['signature']}")
print(f"v: {result['v']}, r: {result['r']}, s: {result['s']}")
# Verify
verification = verify_typed_data(
typed_data,
result['signature'],
'0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
)
print(f"Valid: {verification['is_valid']}")
# Calculate hash
hash_value = hash_typed_data(typed_data)
print(f"Hash: {hash_value}")
Instead of calling approve() (which costs gas), users sign a permit off-chain:
{
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"}
],
"Permit": [
{"name": "owner", "type": "address"},
{"name": "spender", "type": "address"},
{"name": "value", "type": "uint256"},
{"name": "nonce", "type": "uint256"},
{"name": "deadline", "type": "uint256"}
]
},
"primaryType": "Permit",
"domain": {
"name": "USD Coin",
"version": "2",
"chainId": 1,
"verifyingContract": "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
},
"message": {
"owner": "0xYourAddress",
"spender": "0xSpenderContract",
"value": "1000000000",
"nonce": 0,
"deadline": 1893456000
}
}
Off-chain limit orders for decentralized exchanges:
{
"types": {
"EIP712Domain": [...],
"Order": [
{"name": "maker", "type": "address"},
{"name": "taker", "type": "address"},
{"name": "makerToken", "type": "address"},
{"name": "takerToken", "type": "address"},
{"name": "makerAmount", "type": "uint256"},
{"name": "takerAmount", "type": "uint256"},
{"name": "expiry", "type": "uint256"},
{"name": "salt", "type": "uint256"}
]
},
"primaryType": "Order",
"domain": {
"name": "Exchange",
"version": "1.0",
"chainId": 1,
"verifyingContract": "0xExchangeContract"
},
"message": {
"maker": "0xYourAddress",
"taker": "0x0000000000000000000000000000000000000000",
"makerToken": "0xWETH",
"takerToken": "0xUSDC",
"makerAmount": "1000000000000000000",
"takerAmount": "3000000000",
"expiry": 1893456000,
"salt": 12345
}
}
Allow relayers to pay gas on behalf of users:
{
"types": {
"EIP712Domain": [...],
"ForwardRequest": [
{"name": "from", "type": "address"},
{"name": "to", "type": "address"},
{"name": "value", "type": "uint256"},
{"name": "gas", "type": "uint256"},
{"name": "nonce", "type": "uint256"},
{"name": "data", "type": "bytes"}
]
},
"primaryType": "ForwardRequest",
"message": {
"from": "0xUserAddress",
"to": "0xTargetContract",
"value": "0",
"gas": "100000",
"nonce": 0,
"data": "0x..."
}
}
| Solidity Type | EIP-712 Type |
|---|---|
| address | address |
| bool | bool |
| uint256 | uint256 |
| int256 | int256 |
| bytes32 | bytes32 |
| bytes | bytes |
| string | string |
| Custom struct | Custom type |
| Array | Type[] |
The domain separator prevents signature replay across:
domainSeparator = keccak256(
encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(name),
keccak256(version),
chainId,
verifyingContract
)
)
Always verify the domain matches the intended contract:
verifyingContract is the correct addresschainId matches your networkname and version match the contractBefore signing:
EIP-712 prevents cross-domain replay, but:
Malicious dApps may request signatures that:
Always review what you're signing!
function verify(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
bytes32 structHash = keccak256(abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
nonces[owner]++,
deadline
));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, v, r, s);
require(signer == owner, "Invalid signature");
require(block.timestamp <= deadline, "Expired");
}
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract MyContract is EIP712 {
constructor() EIP712("MyContract", "1") {}
function _verify(bytes memory signature, bytes32 hash, address signer)
internal view returns (bool)
{
return ECDSA.recover(_hashTypedDataV4(hash), signature) == signer;
}
}
If signatures fail to verify:
# Calculate hash for comparison
python typed_data.py hash --file permit.json
# Compare with contract's domain separator
cast call 0xContract "DOMAIN_SEPARATOR()(bytes32)"
DISCLAIMER: FOR EDUCATIONAL PURPOSES ONLY
This software is provided for educational and research purposes only. DO NOT USE WITH REAL FUNDS. The author accepts NO LIABILITY for any damages. All example addresses shown are FAKE illustrative examples.
This document outlines security best practices for using the Ethereum Wallet Toolkit.
The toolkit uses Python's secrets module and the operating system's cryptographically secure pseudorandom number generator (CSPRNG) for all key generation:
/dev/urandomCryptGenRandomThe eth-account library handles entropy generation internally using these secure sources.
BIP39 Mnemonic:
BIP32 HD Wallet:
For maximum security when generating wallets:
# Create isolated virtual environment
python -m venv --copies venv
source venv/bin/activate
# Verify no network-capable packages are installed
pip list
# Run the toolkit
python eth_toolkit.py generate --mnemonic
| Storage Method | Security Level | Use Case |
|---|---|---|
| Hardware wallet | Highest | Large holdings |
| Encrypted USB | High | Cold storage |
| Paper wallet | High | Long-term backup |
| Password manager | Medium | Day-to-day access |
| Plain text file | Dangerous | Never recommended |
# Generate wallet and save to encrypted file
python eth_toolkit.py generate --mnemonic --output wallet.json
# The output file should be:
# 1. Encrypted with a strong password
# 2. Stored on encrypted storage
# 3. Backed up securely
# 4. Deleted from unencrypted storage
The open-source vanity address generator "Profanity" contained a critical flaw that led to $160 million in losses for Wintermute and other victims.
Root Cause: Profanity used mt19937 pseudo-random number generator with only a 4-byte seed. Since Ethereum private keys are 32 bytes, this reduced the keyspace from 2^256 to 2^32 possible keys—easily brute-forceable.
How Attackers Exploited It:
This toolkit avoids the specific Profanity vulnerability by:
eth-account which relies on OS CSPRNG (/dev/urandom on Linux, CryptGenRandom on Windows)However, this does NOT guarantee safety. Other vulnerabilities may exist. This toolkit is for EDUCATIONAL PURPOSES ONLY. Always audit the code yourself and use at your own risk.
Reference: James. "Fixing Other People's Code." Oregon State University Blogs, February 2023.
Address poisoning is a social engineering attack where attackers:
Example (FAKE illustrative addresses):
0xABCD1234ABCD1234ABCD1234ABCD1234ABCD12340xABCD5678000056780000567800005678ABCD5678(Notice the first 4 and last 4 characters match, middle is different)
Notable Incident: Address poisoning attacks have resulted in millions of dollars in losses.
Protection:
Reference: CertiK. "Vanity Address and Address Poisoning." CertiK Resources, July 2024.
| Threat | Mitigation |
|---|---|
| Weak RNG | Uses OS CSPRNG via eth-account |
| Network interception | Run offline |
| Malicious dependencies | Uses only official Ethereum Foundation libs |
| Memory inspection | User responsibility (air-gapped system) |
| Supply chain attack | Verify source code before use |
| Profanity-style vulnerability | Fresh CSPRNG entropy per generation |
| Threat | User Responsibility |
|---|---|
| Compromised OS | Use trusted operating system |
| Hardware keyloggers | Verify physical security |
| Shoulder surfing | Ensure private environment |
| Social engineering | Verify all instructions |
| Malware on system | Run on clean system |
1. Compromised Random Number Generator
2. Modified Source Code
3. Dependency Confusion
Before using this toolkit for valuable assets, review:
Key Generation (eth_toolkit.py)
Account.create() is used for random generationAccount.create_with_mnemonic() for mnemonic generationDependencies (requirements.txt)
Output Handling
# Verify eth-account is official
pip show eth-account
# Check homepage: https://github.com/ethereum/eth-account
# Verify package integrity
pip hash eth-account
# Count lines of code (should be ~600-800)
wc -l eth_toolkit.py vanity.py
# Search for suspicious patterns
grep -r "http\|https\|request\|socket\|urllib" *.py
If you discover a security vulnerability:
This toolkit is provided as-is for educational and personal use. The authors are not responsible for:
Always verify generated addresses before depositing significant funds.
""" Documentation Resources for Ethereum Wallet MCP Server