Building a crypto wallet from scratch empowers developers to create secure, customizable tools for managing digital assets. This comprehensive guide walks you through the process, focusing on a Node.js-based Ethereum wallet prototype using libraries like ethereum-cryptography and ethers.js, while covering key concepts, security best practices, and advanced features.[1][2]
Whether you’re a beginner aiming for a simple prototype or an experienced developer targeting production-ready apps, you’ll find actionable steps, code examples, and curated resources here.
Understanding Crypto Wallets: Types and Fundamentals
Crypto wallets are software or hardware tools that generate, store, and manage public-private key pairs for interacting with blockchains. They don’t store cryptocurrencies themselves but control access via private keys.[2][6]
Key Wallet Types
- Custodial Wallets: Managed by third parties (e.g., exchanges); easier but less secure as you don’t control keys.[3]
- Non-Custodial Wallets: You control keys; ideal for self-sovereignty (e.g., MetaMask clones).[5]
- Hot Wallets: Online/software-based for quick access.[1]
- Cold Wallets: Offline/hardware for maximum security (e.g., Ledger, Trezor).[3]
- HD Wallets: Hierarchical Deterministic, using seed phrases for multiple accounts (BIP-39 standard).[6]
For this tutorial, we’ll build a non-custodial hot wallet prototype supporting Ethereum and ERC-20 tokens, similar to a MetaMask clone.[1][5]
Core Components:
- Key generation and storage.
- Blockchain connection (e.g., via RPC providers).
- Transaction signing and broadcasting.
- Balance querying and UI integration.[2][6]
Prerequisites and Tech Stack
Before coding, ensure you have:
- Node.js (v18+).
- Yarn or npm.
- Basic JavaScript knowledge.
- A testnet RPC (e.g., Alchemy or Infura for Ethereum Sepolia).[1][5]
Essential Libraries:
ethereum-cryptography: For low-level key operations.ethers.js: For wallet interactions, providers, and transactions.- Optional: React/Next.js for frontend UI.[1][2]
Security Note: Never expose private keys in production. Use hardware security modules (HSMs) or secure enclaves for real apps.[2][6]
Step 1: Set Up Your Development Environment
Create a new project folder and initialize it.
mkdir my-crypto-wallet
cd my-crypto-wallet
yarn init -y
yarn add ethereum-cryptography ethers
yarn add -D typescript @types/node # Optional for TypeScript
For a full example, clone the Chainlink repo:
git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/my-crypto-wallet
yarn install
[1]
This sets up a backend prototype. For frontend, initialize a React app:
npx create-react-app wallet-frontend --template typescript
cd wallet-frontend
yarn add ethers
[2]
Step 2: Generate Keys and Addresses (HD Wallet Basics)
Wallets derive addresses from a seed phrase using BIP-39/44 standards. Start with a root key and generate child accounts.
Create 01_generate.js:
const { hdkey } = require('ethereum-cryptography/hdkey');
const { toHex } = require('ethereum-cryptography/utils');
const bip39 = require('ethereum-cryptography/bip39'); // Add: yarn add ethereum-cryptography/bip39
async function main() {
const mnemonic = bip39.generateMnemonic(); // 12-24 word seed
console.log('Seed Phrase:', mnemonic);
const seed = await bip39.mnemonicToSeed(mnemonic);
const hdRootKey = hdkey.fromMasterSeed(seed);
const accountOneIndex = "m/44'/60'/0'/0/0"; // BIP-44 path for ETH
const accountOnePrivateKey = hdRootKey.derive(accountOneIndex).privateKey;
const accountOnePublicKey = /* Derive public key */;
const accountOneAddress = /* Get ETH address */;
console.log('Account Address:', `0x${toHex(accountOneAddress)}`);
}
main().catch(console.error);
[1][6]
Run: node 01_generate.js. Securely back up the seed phrase—it’s your wallet recovery key.[3]
Step 3: Connect to Blockchain and Query Balances
Use ethers.js to connect to a provider (e.g., public RPC).
Create 02_balance.js:
const { ethers } = require('ethers');
async function getBalance(address, providerUrl = 'https://sepolia.infura.io/v3/YOUR_KEY') {
const provider = new ethers.JsonRpcProvider(providerUrl);
const balance = await provider.getBalance(address);
console.log('Balance:', ethers.formatEther(balance), 'ETH');
}
getBalance('YOUR_ADDRESS');
[1][2]
For ERC-20 tokens, add contract ABI queries.[5]
Step 4: Implement Send/Receive Transactions
Sign and broadcast transactions with a Wallet instance.
Create 03_send.js:
const { ethers } = require('ethers');
async function main(receiverAddress, ethAmount) {
const provider = new ethers.JsonRpcProvider('YOUR_RPC_URL');
const accountData = JSON.parse(fs.readFileSync('account.json')); // Load securely
const privateKey = Object.values(accountData.privateKey);
const signer = new ethers.Wallet(privateKey, provider);
const tx = await signer.sendTransaction({
to: receiverAddress,
value: ethers.parseEther(ethAmount),
});
console.log('Transaction:', tx.hash);
await tx.wait(); // Confirm
}
main(process.argv[2], process.argv[3]);
[1]
Run: node 03_send.js 0xReceiverAddress 0.01
Pro Tip: Always simulate transactions (provider.call) before signing to prevent errors.[2]
Step 5: Build a Frontend UI (React Example)
Integrate into React for a MetaMask-like interface.
In App.js:
import { ethers } from 'ethers';
import { useState } from 'react';
function App() {
const [provider, setProvider] = useState(null);
const [signer, setSigner] = useState(null);
const [balance, setBalance] = useState('');
const connectWallet = async () => {
if (window.ethereum) {
const prov = new ethers.BrowserProvider(window.ethereum);
await prov.send('eth_requestAccounts', []);
const sign = await prov.getSigner();
setProvider(prov);
setSigner(sign);
const bal = await prov.getBalance(await sign.getAddress());
setBalance(ethers.formatEther(bal));
}
};
return (
<div>
<button onClick={connectWallet}>Connect Wallet</button>
<p>Balance: {balance} ETH</p>
</div>
);
}
[2][5]
Add transaction forms for send/receive with QR codes.[4]
Step 6: Advanced Features and Security Hardening
- Multi-Chain Support: Use libraries like
wagmifor EVM chains.[2] - Seed Phrase Recovery: Implement BIP-39 validation.
- Transaction History: Query via
provider.getLogsor TheGraph. - Security:
- Encrypt private keys with user passwords (use
crypto.subtle). - Multi-sig for high-value wallets.
- Audit for reentrancy, phishing vectors.[6]
- Encrypt private keys with user passwords (use
- Testing: Use Anvil (Foundry) for local blockchain:
anvil.[5]
| Feature | Library/Tool | Purpose |
|---|---|---|
| Key Gen | ethereum-cryptography | Secure primitives[1] |
| Provider | ethers.js | RPC interactions[1][2] |
| Local Chain | Foundry Anvil | Testing[5] |
| UI | React + wagmi | User-friendly dApp[2] |
Common Pitfalls and Best Practices
- Never hardcode keys; use environment variables or vaults.
- Test on testnets first (Sepolia, Goerli).
- Comply with regulations (KYC for custodial).[3][7]
- Open-source and audit code for trust.
Conclusion
Building a crypto wallet from scratch demystifies blockchain interaction and equips you with production-grade skills. Start with the Node.js prototype above, expand to a full React dApp, and iterate based on user needs. Prioritize security—lost keys mean lost funds.
This guide provides a functional starting point; scale it with multi-currency support and hardware integration for real-world use. Happy coding!
Resources and Further Reading
- Chainlink Tutorial: Full Node.js Wallet Code[1]
- Webisoft Guide: Frontend Integration[2]
- PixelPlex Development Roadmap[6]
- YouTube: MetaMask Clone Tutorial[5]
- GitHub: Smart Contract Examples Repo[1]
- Ethers.js Docs: https://docs.ethers.org/v6/
Deploy your prototype today and join the decentralized future!