Creating a Basic Ethereum Wallet App with Ethers.js and SolidJS

·

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-ts

This 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-wallet

Then start the development server:

npm run dev

You now have a live development environment ready for building.


Installing Required Dependencies

Our app needs two core packages:

Install them using npm:

npm i ethers crypto-js

With 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:

  1. Password setup and wallet creation
  2. Displaying the recovery phrase
  3. 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:

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:

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.