Building a self-custodial cryptocurrency wallet is a powerful way to understand blockchain interactions at a deeper level. In this guide, you’ll learn how to create a simple Ethereum wallet application using ethers.js for blockchain operations and SolidJS for the frontend interface. This wallet will support mnemonic phrase generation, balance viewing, and ETH transfers—core functionalities found in popular tools like MetaMask.
By the end of this tutorial, you’ll have a working prototype that runs in the browser and interacts with the Ethereum testnet, making it ideal for learning and experimentation.
Understanding Mnemonic Phrases
A mnemonic phrase (also known as a seed phrase or recovery phrase) is a human-readable representation of a wallet’s cryptographic private key. Typically composed of 12 or 24 random words, this phrase allows users to back up and restore their wallets across devices. If the original device is lost, the mnemonic can regenerate the exact same private key and, therefore, access the same funds.
In our app, we’ll generate a secure mnemonic during wallet creation and prompt the user to store it safely—emphasizing security while maintaining simplicity.
Setting Up the Project
To begin, we’ll use Vite to scaffold a new SolidJS project with TypeScript support. Open your terminal and run:
npm create vite@latest my-wallet -- --template solid-tsThis command sets up a modern, fast development environment with reactive signals and type safety. After the setup completes, navigate into the project folder:
cd my-walletThen start the development server:
npm run devYou now have a live development environment ready for building.
Installing Required Dependencies
Our app needs two core packages:
ethers: To interact with the Ethereum blockchain (create wallets, send transactions, check balances).crypto-js: To encrypt the private key before storing it locally in the browser.
Install them using npm:
npm i ethers crypto-jsWith these libraries, we can securely handle cryptographic operations and blockchain communication.
👉 Build your own secure Ethereum wallet interface today using modern tools.
Building the User Interface
We’ll structure our app in three steps:
- Password setup and wallet creation
- Displaying the recovery phrase
- Viewing balance and sending ETH
We start by defining reactive signals for state management:
import { createSignal } from 'solid-js';
import { Wallet, HDNodeWallet } from 'ethers';
function App() {
const [step, setStep] = createSignal(1);
const [password, setPassword] = createSignal('');
const [phrase, setPhrase] = createSignal('');
const [wallet, setWallet] = createSignal<HDNodeWallet | null>(null);
// UI JSX follows...
}These signals track user progress, input values, and wallet state throughout the flow.
Step 1: Creating or Loading a Wallet
When the app loads, we first check if an encrypted private key already exists in localStorage. If so, we show a "Load Wallet" option; otherwise, we prompt for wallet creation.
const key = localStorage.getItem('encryptedPrivateKey');The createWallet function generates a new mnemonic and derives an HD wallet:
const createWallet = () => {
const mnemonic = Wallet.createRandom().mnemonic;
setPhrase(mnemonic.phrase);
const wallet = HDNodeWallet.fromMnemonic(mnemonic!);
setWallet(wallet);
encryptAndStorePrivateKey();
setStep(2);
};We connect to the Sepolia testnet via Infura (replace API_KEY with your own):
const provider = new JsonRpcProvider('https://sepolia.infura.io/v3/YOUR_API_KEY');
wallet().connect(provider);🔒 Security Note: Never expose API keys in production. Use environment variables or backend services instead.
The private key is encrypted using the user’s password and stored locally:
const encryptAndStorePrivateKey = () => {
const encryptedPrivateKey = CryptoJS.AES.encrypt(
wallet()!.privateKey,
password()
).toString();
localStorage.setItem('encryptedPrivateKey', encryptedPrivateKey);
};👉 Securely manage private keys and blockchain interactions with best practices.
Step 2: Showing the Recovery Phrase
After generating the wallet, we display the mnemonic phrase and urge the user to save it securely:
{step() === 2 && (
<div>
<h2>Save Your Recovery Phrase</h2>
<p>Write down these words in order. Do not share them with anyone.</p>
<pre>{phrase()}</pre>
<button onClick={() => setStep(3)}>I've Saved It</button>
</div>
)}This step is critical—losing the phrase means losing access to funds permanently.
Step 3: Viewing Balance and Sending ETH
Now that the wallet is ready, we show:
- Wallet address
- Current ETH balance
- A form to send transactions
First, load the balance when loading an existing wallet:
const loadWallet = async () => {
const bytes = CryptoJS.AES.decrypt(key!, password());
const privateKey = bytes.toString(CryptoJS.enc.Utf8);
const wallet = new Wallet(privateKey, provider);
setWallet(wallet);
const balance = await wallet.provider.getBalance(wallet.address);
setBalance(formatEther(balance));
setStep(3);
};Display balance and address:
<p>Address: {wallet()?.address}</p>
<p>Balance: {balance()} ETH</p>Allow transfers:
const [recipientAddress, setRecipientAddress] = createSignal('');
const [amount, setAmount] = createSignal('');
const [etherscanLink, setEtherscanLink] = createSignal('');
const transfer = async () => {
try {
const tx = await wallet()?.sendTransaction({
to: recipientAddress(),
value: parseEther(amount()),
});
setEtherscanLink(`https://sepolia.etherscan.io/tx/${tx.hash}`);
} catch (error) {
console.error('Transaction failed:', error);
}
};After sending, users can click the Etherscan link to verify the transaction on-chain.
Frequently Asked Questions
How does a mnemonic phrase work?
A mnemonic phrase is generated from entropy and used to derive a hierarchical deterministic (HD) wallet. It ensures that all keys are reproducible from the same seed—making backups reliable and standardized.
Is it safe to store encrypted keys in localStorage?
For demo purposes, yes—but in production, consider more secure alternatives like browser key stores or hardware integration. Local storage is vulnerable to XSS attacks.
Can I use this wallet on mainnet?
Yes. Just switch the provider URL from Sepolia to Ethereum mainnet (e.g., https://mainnet.infura.io/v3/...) and fund your wallet with real ETH. Always test thoroughly before going live.
What is ethers.js used for?
Ethers.js is a comprehensive library for interacting with Ethereum. It handles wallet creation, transaction signing, contract calls, and blockchain queries with minimal boilerplate.
How do I recover a wallet from a phrase?
Use Wallet.fromMnemonic() to rebuild the wallet from a saved phrase:
function recoverWalletFromPhrase(phrase: string) {
return HDNodeWallet.fromMnemonic(phrase);
}Ensure proper validation of user input to avoid errors.
Why use SolidJS instead of React?
SolidJS offers fine-grained reactivity without virtual DOM diffing, resulting in faster performance and simpler state logic—ideal for lightweight dApps.
What’s Next?
Now that you’ve built a basic Ethereum wallet, consider enhancing it with features like:
- Chrome Extension Packaging: Turn your app into a browser extension for broader usability.
- Token Support: Add ERC-20 token balance tracking and transfers.
- Transaction History: Fetch past transactions via Etherscan API.
- Password Reset & Recovery Flow: Let users restore wallets using their mnemonic.
With these improvements, your app can evolve into a full-featured self-custodial wallet.
👉 Explore advanced blockchain development techniques and expand your dApp toolkit.
Core Keywords: Ethereum wallet, ethers.js, SolidJS, mnemonic phrase, blockchain development, cryptocurrency wallet, HD wallet, decentralized application (dApp)
By combining modern frontend frameworks with robust blockchain libraries, you can build secure, user-friendly tools that empower users in the Web3 ecosystem. Start small, iterate often, and always prioritize security.