React / React Native / Blockchain

Building a Web3 Wallet Example App with React and React Native

September 07, 202443 min read
Blockchain wallet and cryptocurrency concept
Photo by Allef Vinicius

1. Introduction

The world of Web3 is rapidly transforming how we interact with the internet, offering a more decentralized and user-controlled approach to online applications. One of the most exciting areas within this space is the development of Web3 wallets, which allow users to manage their crypto assets, connect to multiple blockchains, and interact seamlessly with decentralized applications (dApps).

In this tutorial, we'll guide you through building your own Web3 wallet using React for the web and React Native for mobile. By the end of this article, you'll have a fully functional wallet that can connect to the Ethereum blockchain, manage wallet connections, interact with smart contracts, and handle user transactions—all while providing a secure and user-friendly experience.

Whether you're a frontend developer eager to expand your skills into blockchain or someone looking to better understand how Web3 technologies work, this guide will walk you through the essential steps, from setting up your project to deploying it for real-world use.

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Project Setup
  4. Connecting to the Ethereum Blockchain
  5. Wallet Integration
  6. Interacting with Smart Contracts
  7. UI/UX Design
  8. Security Considerations
  9. Testing and Deployment
  10. Advanced Features
  11. Conclusion

2. Prerequisites

Before diving into the code, let's cover the prerequisites for this project. While we aim to make this guide accessible, a basic understanding of the following topics will help you get the most out of the tutorial:

  • JavaScript and React: Familiarity with JavaScript ES6+ and React basics is essential, as these form the backbone of our Web3 wallet.
  • Frontend Development Tools: Knowledge of using Node.js, npm/yarn, Git, and an IDE (like Visual Studio Code) will be assumed.
  • Basic Understanding of Blockchain Concepts: Although we will cover the essentials, a foundational understanding of blockchain, Ethereum, and smart contracts will be beneficial.
Tools and Environment Setup
  1. Node.js and npm/yarn: Ensure that Node.js (version 14 or later) and npm or yarn are installed on your machine. These are necessary for running our JavaScript-based tools and managing project dependencies.

  2. Git: Version control with Git will be used throughout the project. Make sure Git is installed and configured on your system.

  3. Development Environment: We recommend using Visual Studio Code with relevant extensions for JavaScript, React, and linting to enhance your coding experience.

  4. React and React Native CLI: We'll be setting up separate environments for web and mobile development:

    • For web, we'll use Create React App to scaffold our React project quickly.
    • For mobile, we'll utilize the React Native CLI or Expo for setting up the React Native project.

With these prerequisites in place, you'll be ready to follow along as we build out the Web3 wallet, integrating blockchain technologies with familiar frontend tools.


3. Project Setup

To get started, we'll set up two separate projects: one for the web version using React and another for the mobile version using React Native. This approach will allow us to create a cohesive experience across platforms, leveraging the strengths of each framework.

Setting Up the React Project (Web Version)

  1. Create React App:

    • Open your terminal and run the following command to create a new React project using Create React App:

      npx create-react-app web3-wallet-web
      cd web3-wallet-web
  2. Install Dependencies:

    • We'll use ethers.js for interacting with the Ethereum blockchain, Web3Modal for wallet connections, and styled-components for styling. Install these dependencies by running:

      npm install ethers web3modal styled-components
  3. Project Structure:

    • Organize your project by creating folders for components, services (blockchain interactions), and styles. Here's a basic structure to follow:

      web3-wallet-web/
      ├── public/
      ├── src/
      │   ├── components/
      │   ├── services/
      │   ├── styles/
      │   ├── App.js
      │   └── index.js
      └── package.json
  4. Basic Setup:

    • In App.js, set up a basic structure with a placeholder for wallet connections. You can start with a simple heading to verify everything is working:

      import React from 'react';
      
      function App() {
        return (
          <div>
            <h1>Web3 Wallet</h1>
          </div>
        );
      }
      
      export default App;
  5. Run the Application:

    • Start your web application with:

      npm start
    • This should launch a development server at http://localhost:3000 displaying the "Web3 Wallet" heading.

Setting Up the React Native Project (Mobile Version)

  1. React Native CLI or Expo:

    • Choose between React Native CLI or Expo. For simplicity, we'll use Expo, which streamlines the setup process and includes built-in support for testing on both iOS and Android.

    • Run the following commands to set up your mobile project:

      npx expo-cli init web3-wallet-mobile
      cd web3-wallet-mobile
    • Choose the blank template when prompted.

  2. Install Dependencies:

    • Similar to the web project, we need ethers.js for blockchain interactions and react-native-elements for UI components:

      npx expo install ethers
      npm install react-native-elements
  3. Project Structure:

    • Set up a directory structure similar to the web version for consistency:

      web3-wallet-mobile/
      ├── assets/
      ├── components/
      ├── services/
      ├── styles/
      ├── App.js
      └── app.json
  4. Basic Setup:

    • In App.js, add a simple layout with a heading, similar to the web project:

      import React from 'react';
      import { Text, View, StyleSheet } from 'react-native';
      
      export default function App() {
        return (
          <View style={styles.container}>
            <Text style={styles.title}>Web3 Wallet</Text>
          </View>
        );
      }
      
      const styles = StyleSheet.create({
        container: {
          flex: 1,
          justifyContent: 'center',
          alignItems: 'center',
          backgroundColor: '#f5f5f5',
        },
        title: {
          fontSize: 24,
          fontWeight: 'bold',
        },
      });
  5. Run the Application:

    • Start the Expo server and test the app on your device or simulator:

      npm start
    • Use the Expo Go app on your mobile device to scan the QR code and view your app.

Summary of Project Setup

At this point, you should have both the web and mobile versions of your Web3 wallet project set up and running. With this foundation in place, we can move on to integrating blockchain functionalities and creating the core features of our Web3 wallet.


4. Connecting to the Ethereum Blockchain

Now that our project is set up, the next step is to connect to the Ethereum blockchain. This will allow our Web3 wallet to interact with the blockchain, enabling functionalities like checking balances, sending transactions, and interacting with smart contracts. We will use ethers.js for this purpose, as it provides a user-friendly API for working with Ethereum.

Understanding Providers

Before diving into the code, it's important to understand the role of providers in blockchain applications. A provider in Web3 is a connection point to the Ethereum network, which allows our application to interact with the blockchain. Providers can be wallets like MetaMask or services like Infura and Alchemy, which provide access to Ethereum nodes.

For this tutorial, we'll use Infura as our primary provider. Infura is a popular service that provides scalable access to Ethereum and other blockchains without needing to run your own nodes.

Setting Up Ethers.js in the Web Project

  1. Create an Infura Account:

    • Sign up at Infura and create a new project to get your API key. This key will be used to connect to the Ethereum network.
  2. Configure Ethers.js:

    • In your React web project, create a new file services/ethersService.js to handle blockchain interactions. This file will set up the provider and expose functions to interact with the blockchain.

    • Add the following code to configure the provider using your Infura project ID:

      // src/services/ethersService.js
      import { ethers } from 'ethers';
      
      // Replace with your Infura project ID
      const infuraProjectId = 'YOUR_INFURA_PROJECT_ID';
      
      // Create a provider using Infura
      const provider = new ethers.providers.InfuraProvider('mainnet', infuraProjectId);
      
      // Export provider for use in other components
      export default provider;
  3. Test the Connection:

    • To verify the connection, we can add a simple function to fetch the latest block number from the Ethereum blockchain:

      // src/services/ethersService.js
      import { ethers } from 'ethers';
      
      const infuraProjectId = 'YOUR_INFURA_PROJECT_ID';
      const provider = new ethers.providers.InfuraProvider('mainnet', infuraProjectId);
      
      // Function to get the latest block number
      export async function getLatestBlock() {
        try {
          const blockNumber = await provider.getBlockNumber();
          console.log('Latest Block Number:', blockNumber);
          return blockNumber;
        } catch (error) {
          console.error('Error fetching block number:', error);
        }
      }
    • Call this function in your App.js file to ensure everything is working correctly:

      // src/App.js
      import React, { useEffect } from 'react';
      import { getLatestBlock } from './services/ethersService';
      
      function App() {
        useEffect(() => {
          getLatestBlock();
        }, []);
      
        return (
          <div>
            <h1>Web3 Wallet</h1>
          </div>
        );
      }
      
      export default App;
    • When you run your application, you should see the latest block number logged in the console, confirming that your connection to the Ethereum network is successful.

Setting Up Ethers.js in the React Native Project

  1. Configure Ethers.js:

    • In your React Native project, create a similar service file services/ethersService.js to handle blockchain connections.

    • The setup is identical to the web version, except it will be within the React Native environment:

      // src/services/ethersService.js
      import { ethers } from 'ethers';
      
      const infuraProjectId = 'YOUR_INFURA_PROJECT_ID';
      const provider = new ethers.providers.InfuraProvider('mainnet', infuraProjectId);
      
      export async function getLatestBlock() {
        try {
          const blockNumber = await provider.getBlockNumber();
          console.log('Latest Block Number:', blockNumber);
          return blockNumber;
        } catch (error) {
          console.error('Error fetching block number:', error);
        }
      }
  2. Testing the Connection:

    • Use the getLatestBlock function in your main App.js file to verify the setup:

      // App.js
      import React, { useEffect } from 'react';
      import { Text, View, StyleSheet } from 'react-native';
      import { getLatestBlock } from './services/ethersService';
      
      export default function App() {
        useEffect(() => {
          getLatestBlock();
        }, []);
      
        return (
          <View style={styles.container}>
            <Text style={styles.title}>Web3 Wallet</Text>
          </View>
        );
      }
      
      const styles = StyleSheet.create({
        container: {
          flex: 1,
          justifyContent: 'center',
          alignItems: 'center',
          backgroundColor: '#f5f5f5',
        },
        title: {
          fontSize: 24,
          fontWeight: 'bold',
        },
      });
    • Running your React Native app should now log the latest block number in the console, confirming that your React Native app can also connect to the Ethereum network.

Summary of Connecting to the Ethereum Blockchain

With these steps, both your web and mobile applications are now connected to the Ethereum blockchain through Infura. You've verified the connection by fetching the latest block number, setting the stage for integrating wallet functionalities and smart contract interactions.


5. Wallet Integration

With your connection to the Ethereum blockchain established, the next crucial step is integrating wallet functionality into your application. This will enable users to connect their crypto wallets, view balances, and interact with the blockchain directly from your Web3 wallet app. For this, we will use Web3Modal, a popular library that provides a simple and user-friendly way to connect with multiple wallets such as MetaMask, WalletConnect, and more.

Setting Up Web3Modal in the Web Project

  1. Install Web3Modal:

    • If you haven't already installed Web3Modal, you can do so by running:

      npm install web3modal
  2. Configure Web3Modal:

    • In your React web project, create a new file services/walletService.js. This file will handle wallet connections using Web3Modal.

    • Start by importing necessary modules and setting up a basic configuration:

      // src/services/walletService.js
      import Web3Modal from 'web3modal';
      import { ethers } from 'ethers';
      
      // Provider options for Web3Modal (e.g., MetaMask, WalletConnect)
      const providerOptions = {
        /* See: https://github.com/Web3Modal/web3modal#provider-options */
      };
      
      // Create a Web3Modal instance
      const web3Modal = new Web3Modal({
        cacheProvider: false, // Optional: enable caching of chosen provider
        providerOptions, // Required: provider options for Web3Modal
      });
      
      let provider;
      let signer;
      
      // Function to connect wallet
      export async function connectWallet() {
        try {
          provider = await web3Modal.connect(); // Prompt user to select wallet
          const ethersProvider = new ethers.providers.Web3Provider(provider); // Wrap with ethers
          signer = ethersProvider.getSigner();
          console.log('Wallet connected:', await signer.getAddress());
          return { ethersProvider, signer };
        } catch (error) {
          console.error('Failed to connect wallet:', error);
        }
      }
      
      // Function to disconnect wallet
      export async function disconnectWallet() {
        if (provider && provider.disconnect) {
          await provider.disconnect(); // Disconnect wallet if supported
        }
        web3Modal.clearCachedProvider();
        provider = null;
        signer = null;
        console.log('Wallet disconnected');
      }
  3. Integrating Web3Modal in the App Component:

    • Now, update your App.js to include buttons for connecting and disconnecting the wallet, and display the connected wallet address:

      // src/App.js
      import React, { useState } from 'react';
      import { connectWallet, disconnectWallet } from './services/walletService';
      
      function App() {
        const [walletAddress, setWalletAddress] = useState(null);
      
        const handleConnect = async () => {
          const { signer } = await connectWallet();
          if (signer) {
            setWalletAddress(await signer.getAddress());
          }
        };
      
        const handleDisconnect = () => {
          disconnectWallet();
          setWalletAddress(null);
        };
      
        return (
          <div>
            <h1>Web3 Wallet</h1>
            {walletAddress ? (
              <div>
                <p>Connected: {walletAddress}</p>
                <button onClick={handleDisconnect}>Disconnect Wallet</button>
              </div>
            ) : (
              <button onClick={handleConnect}>Connect Wallet</button>
            )}
          </div>
        );
      }
      
      export default App;
    • When you click "Connect Wallet," Web3Modal will prompt you to select a wallet provider (e.g., MetaMask). Upon successful connection, the wallet address will be displayed.

Setting Up Web3Modal in the React Native Project

  1. Install Web3Modal and Web3Modal React Native Adapter:

    • Install Web3Modal along with a compatible adapter for React Native. Since Web3Modal doesn’t natively support React Native out-of-the-box, we need a specific adapter, @walletconnect/react-native-dapp.

    • Install these by running:

      npm install web3modal @walletconnect/react-native-dapp
  2. Configure Web3Modal in React Native:

    • Create a new file services/walletService.js similar to the web setup:

      // src/services/walletService.js
      import Web3Modal from 'web3modal';
      import { ethers } from 'ethers';
      import WalletConnectProvider from '@walletconnect/react-native-dapp';
      
      const providerOptions = {
        walletconnect: {
          package: WalletConnectProvider,
          options: {
            infuraId: 'YOUR_INFURA_PROJECT_ID', // Required
          },
        },
      };
      
      const web3Modal = new Web3Modal({
        providerOptions,
      });
      
      let provider;
      let signer;
      
      export async function connectWallet() {
        try {
          provider = await web3Modal.connect();
          const ethersProvider = new ethers.providers.Web3Provider(provider);
          signer = ethersProvider.getSigner();
          console.log('Wallet connected:', await signer.getAddress());
          return { ethersProvider, signer };
        } catch (error) {
          console.error('Failed to connect wallet:', error);
        }
      }
      
      export async function disconnectWallet() {
        if (provider && provider.disconnect) {
          await provider.disconnect();
        }
        web3Modal.clearCachedProvider();
        provider = null;
        signer = null;
        console.log('Wallet disconnected');
      }
  3. Integrate Wallet Connection in React Native App:

    • Update your App.js file to provide UI components for connecting and disconnecting the wallet, similar to the web version:

      // App.js
      import React, { useState } from 'react';
      import { Text, View, Button, StyleSheet } from 'react-native';
      import { connectWallet, disconnectWallet } from './services/walletService';
      
      export default function App() {
        const [walletAddress, setWalletAddress] = useState(null);
      
        const handleConnect = async () => {
          const { signer } = await connectWallet();
          if (signer) {
            setWalletAddress(await signer.getAddress());
          }
        };
      
        const handleDisconnect = () => {
          disconnectWallet();
          setWalletAddress(null);
        };
      
        return (
          <View style={styles.container}>
            <Text style={styles.title}>Web3 Wallet</Text>
            {walletAddress ? (
              <View>
                <Text>Connected: {walletAddress}</Text>
                <Button title="Disconnect Wallet" onPress={handleDisconnect} />
              </View>
            ) : (
              <Button title="Connect Wallet" onPress={handleConnect} />
            )}
          </View>
        );
      }
      
      const styles = StyleSheet.create({
        container: {
          flex: 1,
          justifyContent: 'center',
          alignItems: 'center',
          backgroundColor: '#f5f5f5',
        },
        title: {
          fontSize: 24,
          fontWeight: 'bold',
        },
      });
    • When you click "Connect Wallet," Web3Modal will prompt you to connect via WalletConnect in the React Native app. The wallet address will be displayed once connected.

Summary of Wallet Integration

By integrating Web3Modal, your Web3 wallet app can now connect to various wallets, providing a gateway for users to interact with the Ethereum blockchain. This setup allows users to connect or disconnect their wallets easily, setting the stage for further interactions with smart contracts and blockchain transactions.


6. Interacting with Smart Contracts

With wallet integration set up, the next key feature for our Web3 wallet is enabling interaction with smart contracts. Smart contracts are self-executing contracts with the terms of the agreement directly written into code, allowing users to perform various blockchain operations like transferring tokens or interacting with decentralized applications (dApps). In this section, we’ll cover how to interact with a basic ERC-20 token contract using Ethers.js.

Understanding Smart Contracts and the ERC-20 Standard

An ERC-20 token is a type of smart contract on the Ethereum blockchain that implements a common set of functions for tokens. These functions include getting the total supply of tokens, checking the balance of an account, and transferring tokens between accounts. We'll use these functions in our example to demonstrate how your wallet can interact with an ERC-20 contract.

Interacting with Smart Contracts in the Web Project

  1. Identify the Smart Contract:

    • For this example, we’ll interact with the DAI stablecoin contract, a popular ERC-20 token on Ethereum. The DAI contract address on the Ethereum mainnet is 0x6b175474e89094c44da98b954eedeac495271d0f, and its ABI (Application Binary Interface) can be found on Etherscan.
  2. Set Up Smart Contract Interaction:

    • Create a new file services/tokenService.js in your React web project to handle interactions with the DAI contract. The ABI and contract address will be used to create a contract instance.

    • Add the following code to set up the contract and define functions for reading the token balance and sending transactions:

      // src/services/tokenService.js
      import { ethers } from 'ethers';
      import provider from './ethersService'; // Import the existing provider
      
      // DAI contract details
      const DAI_ABI = [
        // Only include the ABI entries you need
        "function balanceOf(address owner) view returns (uint256)",
        "function transfer(address to, uint amount) returns (boolean)",
      ];
      
      const DAI_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f';
      
      // Create a contract instance
      const daiContract = new ethers.Contract(DAI_ADDRESS, DAI_ABI, provider);
      
      // Function to get the balance of a given address
      export async function getTokenBalance(address) {
        try {
          const balance = await daiContract.balanceOf(address);
          console.log('Token Balance:', ethers.utils.formatUnits(balance, 18)); // DAI has 18 decimals
          return ethers.utils.formatUnits(balance, 18);
        } catch (error) {
          console.error('Error fetching token balance:', error);
        }
      }
      
      // Function to transfer tokens
      export async function sendTokens(signer, to, amount) {
        try {
          const daiWithSigner = daiContract.connect(signer);
          const tx = await daiWithSigner.transfer(to, ethers.utils.parseUnits(amount, 18));
          await tx.wait(); // Wait for transaction to be mined
          console.log('Transaction successful:', tx);
          return tx;
        } catch (error) {
          console.error('Error sending tokens:', error);
        }
      }
  3. Integrate Smart Contract Functions in the App Component:

    • Use the smart contract functions in your App.js to display the connected user’s DAI balance and allow them to send DAI tokens.

      // src/App.js
      import React, { useState, useEffect } from 'react';
      import { connectWallet, disconnectWallet } from './services/walletService';
      import { getTokenBalance, sendTokens } from './services/tokenService';
      
      function App() {
        const [walletAddress, setWalletAddress] = useState(null);
        const [balance, setBalance] = useState('0');
        const [receiver, setReceiver] = useState('');
        const [amount, setAmount] = useState('');
      
        const handleConnect = async () => {
          const { signer } = await connectWallet();
          if (signer) {
            const address = await signer.getAddress();
            setWalletAddress(address);
            fetchBalance(address);
          }
        };
      
        const handleDisconnect = () => {
          disconnectWallet();
          setWalletAddress(null);
          setBalance('0');
        };
      
        const fetchBalance = async (address) => {
          const balance = await getTokenBalance(address);
          setBalance(balance);
        };
      
        const handleSendTokens = async () => {
          const { signer } = await connectWallet();
          if (signer && receiver && amount) {
            await sendTokens(signer, receiver, amount);
            fetchBalance(walletAddress); // Refresh balance after sending
          }
        };
      
        return (
          <div>
            <h1>Web3 Wallet</h1>
            {walletAddress ? (
              <div>
                <p>Connected: {walletAddress}</p>
                <p>DAI Balance: {balance}</p>
                <input
                  type="text"
                  placeholder="Receiver Address"
                  value={receiver}
                  onChange={(e) => setReceiver(e.target.value)}
                />
                <input
                  type="number"
                  placeholder="Amount"
                  value={amount}
                  onChange={(e) => setAmount(e.target.value)}
                />
                <button onClick={handleSendTokens}>Send Tokens</button>
                <button onClick={handleDisconnect}>Disconnect Wallet</button>
              </div>
            ) : (
              <button onClick={handleConnect}>Connect Wallet</button>
            )}
          </div>
        );
      }
      
      export default App;
    • This setup allows the user to view their DAI balance and send DAI tokens to another address directly from the wallet interface.

Interacting with Smart Contracts in the React Native Project

  1. Configure Smart Contract Interaction:

    • In your React Native project, create a similar file services/tokenService.js to handle interactions with the DAI contract using Ethers.js.

    • The code is similar to the web version, adjusted for the React Native environment:

      // src/services/tokenService.js
      import { ethers } from 'ethers';
      import provider from './ethersService';
      
      const DAI_ABI = [
        "function balanceOf(address owner) view returns (uint256)",
        "function transfer(address to, uint amount) returns (boolean)",
      ];
      
      const DAI_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f';
      const daiContract = new ethers.Contract(DAI_ADDRESS, DAI_ABI, provider);
      
      export async function getTokenBalance(address) {
        try {
          const balance = await daiContract.balanceOf(address);
          console.log('Token Balance:', ethers.utils.formatUnits(balance, 18));
          return ethers.utils.formatUnits(balance, 18);
        } catch (error) {
          console.error('Error fetching token balance:', error);
        }
      }
      
      export async function sendTokens(signer, to, amount) {
        try {
          const daiWithSigner = daiContract.connect(signer);
          const tx = await daiWithSigner.transfer(to, ethers.utils.parseUnits(amount, 18));
          await tx.wait();
          console.log('Transaction successful:', tx);
          return tx;
        } catch (error) {
          console.error('Error sending tokens:', error);
        }
      }
  2. Integrate Functions in React Native App:

    • Update your App.js to provide UI elements for displaying the DAI balance and sending tokens:

      // App.js
      import React, { useState, useEffect } from 'react';
      import { Text, View, Button, TextInput, StyleSheet } from 'react-native';
      import { connectWallet, disconnectWallet } from './services/walletService';
      import { getTokenBalance, sendTokens } from './services/tokenService';
      
      export default function App() {
        const [walletAddress, setWalletAddress] = useState(null);
        const [balance, setBalance] = useState('0');
        const [receiver, setReceiver] = useState('');
        const [amount, setAmount] = useState('');
      
        const handleConnect = async () => {
          const { signer } = await connectWallet();
          if (signer) {
            const address = await signer.getAddress();
            setWalletAddress(address);
            fetchBalance(address);
          }
        };
      
        const handleDisconnect = () => {
          disconnectWallet();
          setWalletAddress(null);
          setBalance('0');
        };
      
        const fetchBalance = async (address) => {
          const balance = await getTokenBalance(address);
          setBalance(balance);
        };
      
        const handleSendTokens = async () => {
          const { signer } = await connectWallet();
          if (signer && receiver && amount) {
            await sendTokens(signer, receiver, amount);
            fetchBalance(walletAddress);
          }
        };
      
        return (
          <View style={styles.container}>
            <Text style={styles.title}>Web3 Wallet</Text>
            {walletAddress ? (
              <View>
                <Text>Connected: {walletAddress}</Text>
                <Text>DAI Balance: {balance}</Text>
                <TextInput
                  placeholder="Receiver Address"
                  value={receiver}
                  onChangeText={setReceiver}
                  style={styles.input}
                />
                <TextInput
                  placeholder="Amount"
                  value={amount}
                  onChangeText={setAmount}
                  keyboardType="numeric"
                  style={styles.input}
                />
                <Button title="Send Tokens" onPress={handleSendTokens} />
                <Button title="Disconnect Wallet" onPress={handleDisconnect} />
              </View>
            ) : (
              <Button title="Connect Wallet" onPress={handleConnect} />
            )}
          </View>
        );
      }
      
      const styles = StyleSheet.create({
        container: {
          flex: 1,
          justifyContent: 'center',
          alignItems: 'center',
          backgroundColor: '#f5f5f5',
        },
        title: {
          fontSize: 24,
          fontWeight: 'bold',
        },
        input: {
          height: 40,
          borderColor: 'gray',
          borderWidth: 1,
          marginTop: 10,
          padding: 5,
          width: '80%',
        },
      });
    • This implementation allows the React Native app to display the user’s DAI balance and send DAI tokens through a simple interface.

Summary of Smart Contract Interaction

By setting up smart contract interactions in both the web and mobile versions of your Web3 wallet, you've empowered your app to handle essential blockchain tasks, such as checking balances and sending transactions. This functionality opens up the possibility for a wide range of features, from simple token transfers to more complex DeFi integrations in the future.


7. UI/UX Design

A well-designed user interface (UI) and user experience (UX) are critical for any application, especially for Web3 wallets, where the interaction between users and complex blockchain functionalities needs to be intuitive and seamless. In this section, we’ll enhance the design of our wallet app using styling libraries and best practices to create a secure, user-friendly interface that guides users through wallet connections and smart contract interactions.

Design Principles for Web3 Wallets

  1. Simplicity: Keep the interface clean and straightforward. Users should be able to connect their wallets, view balances, and perform actions without unnecessary complexity.

  2. Security Awareness: Integrate subtle reminders and warnings about security, such as keeping seed phrases safe or confirming transaction details carefully.

  3. Responsiveness: Ensure that the design is fully responsive on web and mobile, providing a consistent experience across devices.

  4. Feedback: Provide clear feedback for user actions, such as loading indicators during transactions or confirmation messages after successful actions.

UI Enhancements in the Web Project

  1. Adding Styled Components:

    • We will use styled-components to enhance the UI with reusable and modular styles. Ensure styled-components is installed in your web project:

      npm install styled-components
  2. Styling the App Component:

    • Update App.js to use styled components, creating a more polished and cohesive look:

      // src/App.js
      import React, { useState, useEffect } from 'react';
      import styled from 'styled-components';
      import { connectWallet, disconnectWallet } from './services/walletService';
      import { getTokenBalance, sendTokens } from './services/tokenService';
      
      // Styled Components
      const Container = styled.div`
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        min-height: 100vh;
        background-color: #f0f2f5;
        padding: 20px;
      `;
      
      const Title = styled.h1`
        font-size: 2.5rem;
        color: #333;
      `;
      
      const Button = styled.button`
        padding: 10px 20px;
        margin: 10px;
        background-color: #007bff;
        color: white;
        border: none;
        border-radius: 5px;
        cursor: pointer;
      
        &:hover {
          background-color: #0056b3;
        }
      `;
      
      const Input = styled.input`
        padding: 10px;
        margin: 5px;
        border: 1px solid #ccc;
        border-radius: 5px;
        width: 300px;
      `;
      
      const AddressText = styled.p`
        font-size: 1rem;
        color: #555;
      `;
      
      function App() {
        const [walletAddress, setWalletAddress] = useState(null);
        const [balance, setBalance] = useState('0');
        const [receiver, setReceiver] = useState('');
        const [amount, setAmount] = useState('');
      
        const handleConnect = async () => {
          const { signer } = await connectWallet();
          if (signer) {
            const address = await signer.getAddress();
            setWalletAddress(address);
            fetchBalance(address);
          }
        };
      
        const handleDisconnect = () => {
          disconnectWallet();
          setWalletAddress(null);
          setBalance('0');
        };
      
        const fetchBalance = async (address) => {
          const balance = await getTokenBalance(address);
          setBalance(balance);
        };
      
        const handleSendTokens = async () => {
          const { signer } = await connectWallet();
          if (signer && receiver && amount) {
            await sendTokens(signer, receiver, amount);
            fetchBalance(walletAddress);
          }
        };
      
        return (
          <Container>
            <Title>Web3 Wallet</Title>
            {walletAddress ? (
              <>
                <AddressText>Connected: {walletAddress}</AddressText>
                <AddressText>DAI Balance: {balance}</AddressText>
                <Input
                  type="text"
                  placeholder="Receiver Address"
                  value={receiver}
                  onChange={(e) => setReceiver(e.target.value)}
                />
                <Input
                  type="number"
                  placeholder="Amount"
                  value={amount}
                  onChange={(e) => setAmount(e.target.value)}
                />
                <Button onClick={handleSendTokens}>Send Tokens</Button>
                <Button onClick={handleDisconnect}>Disconnect Wallet</Button>
              </>
            ) : (
              <Button onClick={handleConnect}>Connect Wallet</Button>
            )}
          </Container>
        );
      }
      
      export default App;
  3. Adding Feedback Elements:

    • Add visual feedback such as loading spinners or success messages when users interact with the wallet or smart contracts. Consider using libraries like react-toastify for toast notifications or react-loader-spinner for loading indicators.

UI Enhancements in the React Native Project

  1. Using React Native Elements:

    • We will use react-native-elements to enhance the UI with well-designed, cross-platform components. Ensure it is installed in your React Native project:

      npm install react-native-elements
  2. Styling the App Component:

    • Update your App.js to use components from react-native-elements like Button, Input, and Text to create a polished mobile interface:

      // App.js
      import React, { useState } from 'react';
      import { View, StyleSheet } from 'react-native';
      import { Button, Input, Text } from 'react-native-elements';
      import { connectWallet, disconnectWallet } from './services/walletService';
      import { getTokenBalance, sendTokens } from './services/tokenService';
      
      export default function App() {
        const [walletAddress, setWalletAddress] = useState(null);
        const [balance, setBalance] = useState('0');
        const [receiver, setReceiver] = useState('');
        const [amount, setAmount] = useState('');
      
        const handleConnect = async () => {
          const { signer } = await connectWallet();
          if (signer) {
            const address = await signer.getAddress();
            setWalletAddress(address);
            fetchBalance(address);
          }
        };
      
        const handleDisconnect = () => {
          disconnectWallet();
          setWalletAddress(null);
          setBalance('0');
        };
      
        const fetchBalance = async (address) => {
          const balance = await getTokenBalance(address);
          setBalance(balance);
        };
      
        const handleSendTokens = async () => {
          const { signer } = await connectWallet();
          if (signer && receiver && amount) {
            await sendTokens(signer, receiver, amount);
            fetchBalance(walletAddress);
          }
        };
      
        return (
          <View style={styles.container}>
            <Text h1>Web3 Wallet</Text>
            {walletAddress ? (
              <>
                <Text h4>Connected: {walletAddress}</Text>
                <Text h4>DAI Balance: {balance}</Text>
                <Input
                  placeholder="Receiver Address"
                  value={receiver}
                  onChangeText={setReceiver}
                  containerStyle={styles.input}
                />
                <Input
                  placeholder="Amount"
                  value={amount}
                  onChangeText={setAmount}
                  keyboardType="numeric"
                  containerStyle={styles.input}
                />
                <Button title="Send Tokens" onPress={handleSendTokens} buttonStyle={styles.button} />
                <Button title="Disconnect Wallet" onPress={handleDisconnect} buttonStyle={styles.button} />
              </>
            ) : (
              <Button title="Connect Wallet" onPress={handleConnect} buttonStyle={styles.button} />
            )}
          </View>
        );
      }
      
      const styles = StyleSheet.create({
        container: {
          flex: 1,
          justifyContent: 'center',
          alignItems: 'center',
          padding: 20,
          backgroundColor: '#f0f2f5',
        },
        button: {
          backgroundColor: '#007bff',
          marginVertical: 10,
          width: 200,
        },
        input: {
          width: '80%',
          marginVertical: 10,
        },
      });
  3. Adding Feedback Elements:

    • For React Native, consider using react-native-toast-message for toast notifications or react-native-loading-spinner-overlay for loading states, to provide feedback on actions like sending tokens or connecting/disconnecting wallets.

Summary of UI/UX Design

With these UI/UX improvements, your Web3 wallet now offers a more professional and user-friendly interface, making it easier for users to interact with their wallets and the blockchain. These enhancements help ensure that users can perform actions confidently and securely, with clear feedback on their interactions.


8. Security Considerations

Security is one of the most critical aspects of any Web3 application, particularly when dealing with users' crypto assets and private keys. A Web3 wallet must prioritize protecting sensitive information and educating users about best practices to keep their assets safe. In this section, we’ll cover key strategies and considerations for securing your Web3 wallet app.

Key Security Principles for Web3 Wallets

  1. Never Store Private Keys on the Frontend:

    • Private keys should never be exposed to the frontend or stored in local storage, session storage, or cookies. Instead, use secure wallet connections via libraries like MetaMask, WalletConnect, or hardware wallets.
  2. Educate Users on Security Best Practices:

    • Users should be aware of the importance of securing their seed phrases and private keys. Incorporate educational prompts and warnings throughout the app to reinforce this message.
  3. Use Secure Storage Solutions:

    • For any sensitive data that needs to be stored (like session information), use secure storage solutions. On web, consider sessionStorage for non-critical data, and for React Native, use libraries like react-native-secure-storage to store sensitive information securely.
  4. Handle Transactions Safely:

    • Always show transaction details clearly before user confirmation. Include checks to verify that transaction data hasn’t been tampered with.
  5. Monitor and Update Dependencies:

    • Regularly monitor and update your dependencies to ensure that you are using the latest, most secure versions of libraries. Tools like npm audit can help identify and fix security vulnerabilities.

Implementing Security Measures in the Web Project

  1. Using Secure Connections:

    • Ensure all wallet connections are made securely using trusted libraries like Web3Modal. Avoid handling private keys directly in your application code.
  2. Displaying Security Warnings:

    • Add clear warnings in your UI regarding the handling of private keys and seed phrases. You can use react-toastify for notifications or modals to alert users to potential security concerns.

    • Example of a security notice:

      // src/components/SecurityNotice.js
      import React from 'react';
      import styled from 'styled-components';
      
      const NoticeContainer = styled.div`
        background-color: #fff3cd;
        border: 1px solid #ffeeba;
        padding: 15px;
        margin: 20px 0;
        border-radius: 5px;
        color: #856404;
        font-size: 0.9rem;
      `;
      
      const SecurityNotice = () => {
        return (
          <NoticeContainer>
            <p><strong>Security Notice:</strong> Never share your seed phrase or private keys with anyone. Always verify the details of your transactions before confirming them.</p>
          </NoticeContainer>
        );
      };
      
      export default SecurityNotice;
    • Integrate this component into your main app view where wallet interactions occur to ensure users are regularly reminded of security best practices.

  3. Handling Transactions with User Confirmation:

    • Before sending transactions, ensure that users confirm the details. This can be implemented with a modal that shows the transaction details before the user approves it.

      // src/components/TransactionModal.js
      import React from 'react';
      import styled from 'styled-components';
      
      const ModalContainer = styled.div`
        display: ${({ show }) => (show ? 'block' : 'none')};
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        justify-content: center;
        align-items: center;
      `;
      
      const ModalContent = styled.div`
        background-color: white;
        padding: 20px;
        border-radius: 10px;
        text-align: center;
      `;
      
      const TransactionModal = ({ show, onClose, onConfirm, transactionDetails }) => {
        return (
          <ModalContainer show={show}>
            <ModalContent>
              <h3>Confirm Transaction</h3>
              <p>Receiver: {transactionDetails.receiver}</p>
              <p>Amount: {transactionDetails.amount} DAI</p>
              <button onClick={onConfirm}>Confirm</button>
              <button onClick={onClose}>Cancel</button>
            </ModalContent>
          </ModalContainer>
        );
      };
      
      export default TransactionModal;
    • This approach ensures that users are fully aware of the transaction details before committing, reducing the risk of accidental or malicious transactions.

Implementing Security Measures in the React Native Project

  1. Using Secure Storage:

    • For storing sensitive data in React Native, use secure storage libraries like react-native-secure-storage to ensure data encryption at rest.

    • Install the library:

      npm install react-native-secure-storage
    • Example usage:

      import SecureStorage from 'react-native-secure-storage';
      
      // Store data securely
      SecureStorage.setItem('sessionToken', 'your_session_token', {
        accessible: SecureStorage.ACCESSIBLE.WHEN_UNLOCKED,
      });
      
      // Retrieve data securely
      SecureStorage.getItem('sessionToken').then((token) => {
        console.log('Retrieved session token:', token);
      });
  2. User Education and Warnings:

    • Use React Native Elements to display security notices in a visually appealing way. This could be a Text component styled to stand out, or a Card that provides additional security tips.

    • Example:

      // SecurityNotice.js
      import React from 'react';
      import { Card, Text } from 'react-native-elements';
      import { StyleSheet } from 'react-native';
      
      const SecurityNotice = () => {
        return (
          <Card containerStyle={styles.card}>
            <Text style={styles.notice}>
              <Text style={styles.bold}>Security Notice:</Text> Never share your seed phrase or private keys. Verify transaction details before confirmation.
            </Text>
          </Card>
        );
      };
      
      const styles = StyleSheet.create({
        card: {
          backgroundColor: '#fff3cd',
          borderColor: '#ffeeba',
        },
        notice: {
          color: '#856404',
          fontSize: 14,
        },
        bold: {
          fontWeight: 'bold',
        },
      });
      
      export default SecurityNotice;
  3. Confirming Transactions:

    • Use modals or confirmation dialogs to let users review and confirm transactions before proceeding, reducing the risk of errors or unintended actions.

    • Example using react-native-elements:

      // TransactionModal.js
      import React from 'react';
      import { Modal, Text, Button } from 'react-native-elements';
      import { View, StyleSheet } from 'react-native';
      
      const TransactionModal = ({ visible, onClose, onConfirm, transactionDetails }) => {
        return (
          <Modal isVisible={visible}>
            <View style={styles.modalContent}>
              <Text h4>Confirm Transaction</Text>
              <Text>Receiver: {transactionDetails.receiver}</Text>
              <Text>Amount: {transactionDetails.amount} DAI</Text>
              <Button title="Confirm" onPress={onConfirm} />
              <Button title="Cancel" onPress={onClose} type="outline" />
            </View>
          </Modal>
        );
      };
      
      const styles = StyleSheet.create({
        modalContent: {
          backgroundColor: 'white',
          padding: 20,
          borderRadius: 10,
          alignItems: 'center',
        },
      });
      
      export default TransactionModal;

Summary of Security Considerations

By implementing these security measures, your Web3 wallet will provide a safer experience for users interacting with their crypto assets. Protecting private keys, educating users about security, and confirming actions before proceeding are crucial steps to prevent security breaches and build user trust in your application.


9. Testing and Deployment

Testing is an essential part of the development process, especially for applications like Web3 wallets that handle sensitive user data and financial transactions. In this section, we’ll cover how to write tests for your React and React Native components, verify blockchain interactions, and deploy your app to production environments.

Testing Your Web3 Wallet

To ensure your Web3 wallet functions correctly, we’ll use testing libraries like Jest for unit testing and React Testing Library for component testing. For blockchain interactions, we’ll mock providers and contracts to simulate blockchain responses.

Setting Up Testing in the Web Project
  1. Install Testing Dependencies:

    • Ensure Jest and React Testing Library are installed in your web project:

      npm install --save-dev jest @testing-library/react @testing-library/jest-dom @testing-library/user-event
  2. Testing React Components:

    • Create a test file for your main App.js component, App.test.js, in the src folder:

      // src/App.test.js
      import React from 'react';
      import { render, screen, fireEvent } from '@testing-library/react';
      import '@testing-library/jest-dom';
      import App from './App';
      
      test('renders the connect wallet button', () => {
        render(<App />);
        const connectButton = screen.getByText(/connect wallet/i);
        expect(connectButton).toBeInTheDocument();
      });
      
      test('connects wallet and displays address', async () => {
        render(<App />);
        const connectButton = screen.getByText(/connect wallet/i);
      
        // Simulate clicking the connect button
        fireEvent.click(connectButton);
      
        // Mock wallet connection and address fetching
        const address = '0x123...abc';
        const addressDisplay = await screen.findByText(`Connected: ${address}`);
        expect(addressDisplay).toBeInTheDocument();
      });
    • These tests check for the presence of UI elements and simulate user actions like clicking buttons, providing a basic validation of your UI logic.

  3. Testing Blockchain Interactions:

    • To test blockchain interactions, mock the blockchain provider and contract functions. You can use libraries like jest-mock or manually mock the functions:

      // src/services/__mocks__/ethersService.js
      export const getLatestBlock = jest.fn().mockResolvedValue(12345678);
      
      // src/services/__mocks__/walletService.js
      export const connectWallet = jest.fn().mockResolvedValue({
        signer: {
          getAddress: jest.fn().mockResolvedValue('0x123...abc'),
        },
      });
      
      export const disconnectWallet = jest.fn();
    • Include these mocks in your test setup to simulate blockchain responses without making actual network calls.

  4. Run Your Tests:

    • Run the tests using Jest to verify your component and service functionality:

      npm test
Setting Up Testing in the React Native Project
  1. Install Testing Dependencies:

    • For React Native, you’ll use @testing-library/react-native alongside Jest. Install the necessary packages:

      npm install --save-dev @testing-library/react-native @testing-library/jest-native jest
  2. Testing React Native Components:

    • Create a test file for your App.js component, App.test.js, in the src folder:

      // src/App.test.js
      import React from 'react';
      import { render, fireEvent, waitFor } from '@testing-library/react-native';
      import App from './App';
      
      test('renders the connect wallet button', () => {
        const { getByText } = render(<App />);
        const connectButton = getByText(/connect wallet/i);
        expect(connectButton).toBeTruthy();
      });
      
      test('connects wallet and displays address', async () => {
        const { getByText, findByText } = render(<App />);
        const connectButton = getByText(/connect wallet/i);
      
        fireEvent.press(connectButton);
      
        // Mock wallet connection and address fetching
        const address = '0x123...abc';
        const addressDisplay = await findByText(`Connected: ${address}`);
        expect(addressDisplay).toBeTruthy();
      });
  3. Mocking Blockchain Interactions:

    • Similarly to the web project, mock your blockchain interactions in the React Native project by creating mock files in the __mocks__ folder.
  4. Run Your Tests:

    • Use Jest to run your tests and verify component and service behavior:

      npm test

Deploying Your Web3 Wallet

Once your app is thoroughly tested, it’s time to deploy it to production environments. We’ll use Vercel or Netlify for the web version and the respective app stores for the mobile version.

Deploying the Web Project
  1. Deploying to Vercel:

    • Vercel provides a seamless deployment process for React applications. To deploy:

      • Push your code to a GitHub repository.
      • Sign up or log in to Vercel and import your GitHub repository.
      • Configure build settings (typically npm run build for React) and click "Deploy."
    • Vercel will automatically build and deploy your application, providing a live URL.

  2. Deploying to Netlify:

    • Alternatively, use Netlify for deployment:

      • Push your code to a GitHub repository.
      • Sign up or log in to Netlify and link your GitHub repository.
      • Set build settings (build command: npm run build, publish directory: build).
      • Click "Deploy Site" to initiate deployment.
    • Netlify will handle the build process and host your application on a live URL.

Deploying the React Native Project
  1. Deploying to Google Play Store:

    • Follow these steps to publish your app to the Google Play Store:
      • Ensure your app is production-ready with all required icons, splash screens, and app information configured.
      • Generate a signed APK or AAB using Android Studio or Expo.
      • Create a Google Play Developer account, and fill out all necessary details for your app (description, screenshots, etc.).
      • Upload the APK/AAB, set your pricing and distribution, and submit for review.
  2. Deploying to Apple App Store:

    • To publish on the Apple App Store:
      • Ensure your app is production-ready with necessary assets and configuration.
      • Build your app for iOS using Xcode or Expo, generating an IPA file.
      • Create an Apple Developer account, and configure App Store Connect with your app details.
      • Upload the IPA using Xcode or Transporter, fill out app information, and submit for review.
  3. Handling CI/CD for Mobile Apps:

    • Implement continuous integration and continuous deployment (CI/CD) pipelines using tools like GitHub Actions, CircleCI, or Bitrise to automate builds and deployments. This will streamline the update process and ensure consistent delivery of new features and bug fixes.

Summary of Testing and Deployment

By thoroughly testing your Web3 wallet app and deploying it to production environments, you ensure that users have a reliable and secure experience. Continuous testing and automated deployment processes help maintain app quality and keep your Web3 wallet updated with the latest features and security patches.


10. Advanced Features

With the core functionalities of your Web3 wallet in place, it’s time to explore advanced features that can set your wallet apart and provide additional value to your users. This section covers how to expand your wallet to support multiple blockchains, integrate additional DeFi functionalities like staking or swapping, and handle scaling considerations for growing user bases.

Multi-Chain Support

Supporting multiple blockchains allows your wallet to interact with a wider array of decentralized applications (dApps) and digital assets beyond Ethereum. Adding multi-chain support involves configuring your wallet to connect to different blockchains, such as Binance Smart Chain, Polygon, or Solana.

Adding Support for Additional Blockchains
  1. Configuring Providers for Multiple Blockchains:

    • To support multiple blockchains, you’ll need to set up providers for each one. For Ethereum-compatible chains like Binance Smart Chain or Polygon, you can use similar methods to what you’ve already implemented with Ethers.js by configuring different provider URLs.

    • Example: Configuring providers for Ethereum, Binance Smart Chain, and Polygon:

      // src/services/multiChainService.js
      import { ethers } from 'ethers';
      
      const PROVIDERS = {
        ethereum: new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID'),
        bsc: new ethers.providers.JsonRpcProvider('https://bsc-dataseed.binance.org/'),
        polygon: new ethers.providers.JsonRpcProvider('https://polygon-rpc.com/'),
      };
      
      export function getProvider(chain) {
        return PROVIDERS[chain];
      }
  2. Switching Between Blockchains:

    • Allow users to switch between blockchains using a dropdown or buttons in your app’s UI. Update the wallet’s provider based on the selected blockchain:

      // src/components/ChainSwitcher.js
      import React, { useState } from 'react';
      import styled from 'styled-components';
      import { getProvider } from '../services/multiChainService';
      
      const Container = styled.div`
        margin: 20px 0;
      `;
      
      const ChainSwitcher = ({ onChainChange }) => {
        const [selectedChain, setSelectedChain] = useState('ethereum');
      
        const handleChainChange = (event) => {
          const chain = event.target.value;
          setSelectedChain(chain);
          onChainChange(getProvider(chain));
        };
      
        return (
          <Container>
            <label htmlFor="chain-select">Select Blockchain: </label>
            <select id="chain-select" value={selectedChain} onChange={handleChainChange}>
              <option value="ethereum">Ethereum</option>
              <option value="bsc">Binance Smart Chain</option>
              <option value="polygon">Polygon</option>
            </select>
          </Container>
        );
      };
      
      export default ChainSwitcher;
    • Integrate the ChainSwitcher component into your main app, updating the provider as users switch chains.

  3. Handling Blockchain-Specific Smart Contracts:

    • Ensure that your smart contract interactions account for blockchain differences. Some contracts might have different addresses or ABI formats on various chains, so always check compatibility and update addresses accordingly.

Integrating Additional DeFi Functionalities

DeFi (Decentralized Finance) features can greatly enhance the appeal of your wallet by allowing users to participate in activities like staking, swapping tokens, and earning yield. Here are some ways to integrate these functionalities:

1. Staking Integration

Staking allows users to lock their tokens in a smart contract to earn rewards. Here's a basic example of how to integrate staking into your wallet:

  • Staking Contract Interaction:

    • Identify or deploy a staking contract and configure its ABI and address in your service files.

    • Example: Staking interaction in your wallet:

      // src/services/stakingService.js
      import { ethers } from 'ethers';
      import provider from './ethersService';
      
      const STAKING_CONTRACT_ADDRESS = '0xYourStakingContractAddress';
      const STAKING_CONTRACT_ABI = [
        "function stake(uint256 amount) public",
        "function unstake(uint256 amount) public",
        "function getStakedBalance(address owner) view returns (uint256)",
      ];
      
      const stakingContract = new ethers.Contract(STAKING_CONTRACT_ADDRESS, STAKING_CONTRACT_ABI, provider);
      
      export async function stakeTokens(signer, amount) {
        try {
          const stakingWithSigner = stakingContract.connect(signer);
          const tx = await stakingWithSigner.stake(ethers.utils.parseUnits(amount, 18));
          await tx.wait();
          console.log('Tokens staked successfully:', tx);
          return tx;
        } catch (error) {
          console.error('Error staking tokens:', error);
        }
      }
      
      export async function getStakedBalance(address) {
        try {
          const balance = await stakingContract.getStakedBalance(address);
          console.log('Staked Balance:', ethers.utils.formatUnits(balance, 18));
          return ethers.utils.formatUnits(balance, 18);
        } catch (error) {
          console.error('Error fetching staked balance:', error);
        }
      }
  • Staking UI:

    • Add a simple UI component for users to stake and unstake tokens:

      // src/components/Staking.js
      import React, { useState } from 'react';
      import styled from 'styled-components';
      import { stakeTokens, getStakedBalance } from '../services/stakingService';
      
      const Container = styled.div`
        margin: 20px 0;
        padding: 20px;
        border: 1px solid #ddd;
        border-radius: 8px;
        background-color: #f9f9f9;
      `;
      
      const Staking = ({ signer, walletAddress }) => {
        const [amount, setAmount] = useState('');
        const [stakedBalance, setStakedBalance] = useState('0');
      
        const handleStake = async () => {
          if (signer && amount) {
            await stakeTokens(signer, amount);
            fetchStakedBalance(walletAddress);
          }
        };
      
        const fetchStakedBalance = async (address) => {
          const balance = await getStakedBalance(address);
          setStakedBalance(balance);
        };
      
        return (
          <Container>
            <h3>Staking</h3>
            <p>Staked Balance: {stakedBalance} DAI</p>
            <input
              type="number"
              placeholder="Amount to stake"
              value={amount}
              onChange={(e) => setAmount(e.target.value)}
            />
            <button onClick={handleStake}>Stake Tokens</button>
          </Container>
        );
      };
      
      export default Staking;
2. Token Swapping Integration

Token swapping allows users to exchange one token for another directly within the wallet. Integrating a swap feature involves connecting to a decentralized exchange (DEX) like Uniswap or PancakeSwap.

  • Swap Contract Interaction:

    • Use a DEX aggregator API like 1inch or direct DEX integration using Uniswap’s SDK.

    • Example of setting up a swap function using Uniswap:

      // src/services/swapService.js
      import { ethers } from 'ethers';
      import { Fetcher, Route, Token, Trade, TradeType, WETH } from '@uniswap/sdk';
      
      export async function getSwapPrice(provider, inputToken, outputToken, amount) {
        const pair = await Fetcher.fetchPairData(inputToken, outputToken, provider);
        const route = new Route([pair], WETH[inputToken.chainId]);
        const trade = new Trade(route, new TokenAmount(WETH[inputToken.chainId], amount), TradeType.EXACT_INPUT);
      
        console.log('Swap Price:', trade.executionPrice.toSignificant(6));
        return trade.executionPrice.toSignificant(6);
      }
  • Swap UI:

    • Create a simple interface for users to select tokens and amounts for swapping:

      // src/components/TokenSwap.js
      import React, { useState } from 'react';
      import styled from 'styled-components';
      import { getSwapPrice } from '../services/swapService';
      
      const Container = styled.div`
        margin: 20px 0;
        padding: 20px;
        border: 1px solid #ddd;
        border-radius: 8px;
        background-color: #f9f9f9;
      `;
      
      const TokenSwap = ({ provider }) => {
        const [inputToken, setInputToken] = useState('');
        const [outputToken, setOutputToken] = useState('');
        const [amount, setAmount] = useState('');
        const [swapPrice, setSwapPrice] = useState('');
      
        const handleGetPrice = async () => {
          if (inputToken && outputToken && amount) {
            const price = await getSwapPrice(provider, inputToken, outputToken, amount);
            setSwapPrice(price);
          }
        };
      
        return (
          <Container>
            <h3>Token Swap</h3>
            <input
              type="text"
              placeholder="Input Token (e.g., DAI)"
              value={inputToken}
              onChange={(e) => setInputToken(e.target.value)}
            />
            <input
              type="text"
              placeholder="Output Token (e.g., ETH)"
              value={outputToken}
              onChange={(e) => setOutputToken(e.target.value)}
            />
            <input
              type="number"
              placeholder="Amount"
              value={amount}
              onChange={(e) => setAmount(e.target.value)}
            />
            <button onClick={handleGetPrice}>Get Swap Price</button>
            {swapPrice && <p>Swap Price: {swapPrice}</p>}
          </Container>
        );
      };
      
      export default TokenSwap;

Scaling and Maintenance Considerations

As your Web3 wallet grows, it’s important to plan for scaling and ongoing maintenance:

  1. Scaling for High Traffic:

    • Consider using a load balancer and deploying your application across multiple regions for better performance and reliability.
    • For blockchain interactions, use scalable provider services like Infura or Alchemy to handle increased request loads.
  2. Regular Security Audits:

    • Perform regular security audits of your smart contracts and application code. Use tools like MythX for smart contract analysis and Snyk for dependency vulnerability scanning.
  3. Updating and Expanding Features:

    • Stay updated with the latest blockchain developments and continuously add new features to keep your wallet competitive. Regularly solicit feedback from users to guide future enhancements.

Summary of Advanced Features

By integrating multi-chain support and DeFi functionalities, your Web3 wallet can become a versatile tool for users, allowing them to manage a wider array of assets and participate in the decentralized economy. Scaling and maintaining your app will ensure it continues to provide a secure and high-performance experience as your user base grows.


11. Conclusion

Building a Web3 wallet is an exciting project that combines modern frontend development skills with the dynamic and rapidly evolving world of blockchain technology. In this tutorial, we’ve walked through the process of creating a comprehensive Web3 wallet using React for web and React Native for mobile, equipping developers with the tools and knowledge needed to navigate the complexities of blockchain integration.

Recap of What We’ve Covered

  1. Project Setup:

    • We started by setting up React and React Native projects, installing necessary dependencies, and structuring our applications for efficient development and maintenance.
  2. Connecting to the Ethereum Blockchain:

    • Using Ethers.js, we connected to the Ethereum blockchain, set up providers, and verified our connections by fetching data from the blockchain.
  3. Wallet Integration:

    • By integrating Web3Modal, we enabled users to connect their crypto wallets securely, manage wallet sessions, and display their wallet addresses within the app.
  4. Interacting with Smart Contracts:

    • We implemented functionalities to interact with smart contracts, such as checking token balances and executing token transfers, demonstrating how users can interact with decentralized applications directly from their wallets.
  5. UI/UX Design:

    • We enhanced the user interface using styled-components for web and React Native Elements for mobile, focusing on creating a clean, responsive, and intuitive design.
  6. Security Considerations:

    • Emphasizing security, we discussed best practices for protecting private keys, securing data, and educating users on safeguarding their assets.
  7. Testing and Deployment:

    • We covered testing strategies using Jest and React Testing Library to validate our components and interactions. We also walked through deploying the web application using Vercel or Netlify and the mobile app to the Google Play Store and Apple App Store.
  8. Advanced Features:

    • To expand the wallet’s capabilities, we explored multi-chain support and DeFi functionalities such as staking and token swapping, providing users with a more versatile and comprehensive tool for managing their digital assets.

Next Steps for Developers

As blockchain technology continues to evolve, there are countless opportunities to expand upon what we’ve built:

  • Explore Other Blockchains: Beyond Ethereum, consider integrating other chains like Solana, Avalanche, or Cardano to provide even more options for your users.
  • Implement More DeFi Features: Dive deeper into decentralized finance by adding functionalities like liquidity pooling, yield farming, or participating in decentralized autonomous organizations (DAOs).
  • Optimize for Performance: Focus on optimizing your wallet for performance and scalability. Techniques like lazy loading, code splitting, and efficient state management can significantly enhance the user experience.
  • Enhance Security Features: Explore advanced security measures like multi-factor authentication (MFA), biometric authentication, and enhanced transaction monitoring to provide additional layers of protection for your users.
  • Contribute to Open Source: Consider contributing your project or components of it to the open-source community. Sharing your code can help others learn and build upon your work, fostering collaboration and innovation in the Web3 space.

Call to Action

Web3 is more than just a technology; it's a movement towards decentralization, user empowerment, and greater control over personal data and assets. By building tools like this Web3 wallet, you're contributing to the future of the internet. Keep exploring, keep building, and keep pushing the boundaries of what's possible in the decentralized world.

If you found this tutorial helpful, share it with your fellow developers, contribute to open-source Web3 projects, or start your own blockchain journey. The future of the internet is decentralized, and your next big idea could be the one that makes a significant impact.

This concludes our comprehensive guide to building a Web3 wallet app with React and React Native. If you have any questions, feedback, or topics you'd like to see covered in future tutorials, feel free to reach out or leave a comment. Happy coding, and welcome to the Web3 revolution!


View the Complete Project

If you'd like to see the complete, finished application that we've built throughout this tutorial, you can view the source code and project structure on GitHub:

View the TigerVault Web3 Wallet on GitHub

This repository showcases the entire TigerVault application, including all the components we've discussed. It's an excellent resource for reviewing the code structure, understanding how different parts of the application interact, and seeing how concepts like wallet integration, transaction handling, and blockchain interactions are implemented in a real-world scenario.

By exploring the repository, you can:

  1. Examine the full project structure
  2. Review the implementation of wallet connection and transaction functionality
  3. See how WalletConnect is integrated for external wallet connections
  4. Understand the configuration for both real and mock wallet functionality
  5. Explore additional features like transaction history and tab-based navigation

Feel free to clone the repository, experiment with the code, and use it as a foundation for your own Web3 projects or further learning. As you continue to develop and expand your Web3 wallet, consider the next steps outlined in the repository's README, such as implementing testnet integration, enhancing the transaction history, and adding support for token transfers.

Remember, building a Web3 wallet is an ongoing process, and there's always room for improvement and new features. Keep exploring, learning, and contributing to the exciting world of Web3 development!

Web3 WalletReactReact NativeBlockchainEthers.jsWalletConnectMulti-ChainDeFi