# Cryptocurrencies wallet development ## The test is still working on ..., the completion is about 50%. ## Test Goals To create a cryptocurrencies wallet for React practice and understanding Ethers library usage. ## Dev and Test Environment | OS | VS Code | Node.js | ether.js | react | axios | | -------- | -------- | -------- | -------- | -------- | -------- | | Ubuntu 22.04 | 1.76.2 | 18.15.0 | 6.2.0 | 18.2.0 | 1.3.5 | ## Table of contents: * Getting Started * Create Account Function * Ethers Library (Seed Phrase, Private Key, Public Key and Address) * Account Recovery * Getting Transactions * Multisig (working on...) * References ### Getting Started First of all, create a vite project on VS Code ``` npm create vite@latest wallet-dev-01 ``` ![](https://i.imgur.com/VN2zuev.png) ![](https://i.imgur.com/R2WZvVT.png) The **wallet_dev** is the parent folder of the main project called **wallet-dev-01** ![](https://i.imgur.com/9RO49pi.png) ### Create Account Function The page needs a button for creating an account, so ask ChatGPT : >please create react component called CreateAccount that has a button called Create Account, when the button is clicked, it call a createAccount function which is in the component ![](https://i.imgur.com/s5WSH3r.png) ![](https://i.imgur.com/Gc9nSWh.png) Make a folder in src called **scenes** and a folder **Account** in the scenes folder. According to the informations from ChatGPT create a file **src/scenes/Account/CreateAccount.jsx** ``` import React from 'react'; function CreateAccount() { function createAccount() { // Your create account logic here console.log('Create account clicked!'); } return ( <div> <button onClick={createAccount}>Create Account</button> </div> ); } export default CreateAccount; ``` and update the **src/App.jsx** ``` import React from 'react' import './App.css' import CreateAccount from './scenes/Account/CreateAccount'; function App() { return ( <div className="App"> <head>Wallet Dev 01</head> <CreateAccount /> </div> ) } export default App ``` ### Ethers Library (Seed Phrase, Private Key, Public Key and Address) Ask ChatGPT: >Please create a react function that generate seed phrase, private key, public key and address using the ethers library. ![](https://i.imgur.com/D7ry587.png) ![](https://i.imgur.com/O4LDPxu.png) Create jsx file **src/utils/AccountUtils.jsx** ``` import React, { useState } from "react"; import { ethers } from "ethers"; function EthAddressGenerator() { const [seedPhrase, setSeedPhrase] = useState(""); const [privateKey, setPrivateKey] = useState(""); const [publicKey, setPublicKey] = useState(""); const [address, setAddress] = useState(""); const generateAddress = () => { // Generate a new random wallet const wallet = ethers.Wallet.createRandom(); // Set the values in the state setSeedPhrase(wallet.mnemonic.phrase); setPrivateKey(wallet.privateKey); setPublicKey(wallet.publicKey); setAddress(wallet.address); }; return ( <div> <button onClick={generateAddress}>Generate Address</button> <p>Seed Phrase: {seedPhrase}</p> <p>Private Key: {privateKey}</p> <p>Public Key: {publicKey}</p> <p>Address: {address}</p> </div> ); } export default EthAddressGenerator; ``` And update the **src/App.jsx** ``` import React from 'react' import './App.css' import CreateAccount from './scenes/Account/CreateAccount'; import AccountUtils from './utils/AccountUtils'; function App() { return ( <div className="App"> <CreateAccount /> <AccountUtils /> </div> ) } export default App ``` Run the command `npm run dev` and press the button **Generate Address**, then we got the seed phrase, private key, public key and address ![](https://i.imgur.com/QJ5iFMP.png) According to the data on **Aqua Wallet Github** and **Ethers Library V6** the codes have been updated as follows **src/utils/AccountUtils.jsx** ``` import React, { useState } from "react"; import { Wallet } from "ethers"; export function EthAddressGenerator() { // const [seedPhrase, setSeedPhrase] = useState(""); // const [privateKey, setPrivateKey] = useState(""); // const [publicKey, setPublicKey] = useState(""); // const [address, setAddress] = useState(""); // const generateAddress = () => { // Generate a new random wallet const wallet = Wallet.createRandom(); // Set the values in the state // setSeedPhrase(wallet.mnemonic.phrase); // setPrivateKey(wallet.privateKey); // setPublicKey(wallet.publicKey); // setAddress(wallet.address); // }; const account = {"address": wallet.address, "privateKey": wallet.privateKey} const seedPhrase = wallet.mnemonic.phrase; console.log("return account: ", account) return {account, seedPhrase}; // return ( // <div> // <button onClick={generateAddress}>Generate Address</button> // <p>Seed Phrase: {seedPhrase}</p> // <p>Private Key: {privateKey}</p> // <p>Public Key: {publicKey}</p> // <p>Address: {address}</p> // </div> // ); } export async function RecoverAccount(seedPhrase, index) { const wallet = (seedPhrase.includes(" ")) ? Wallet.fromPhrase(seedPhrase, `m/44'/60'/0'/0/${index}`) : new Wallet(seedPhrase); const account = {"address": wallet.address, "privateKey": wallet.privateKey} console.log("return account: ", account) return {account, seedPhrase}; } ``` **src/scenes/Account/CreateAccount.jsx** ``` import React, { useState, useCallback } from 'react'; import { EthAddressGenerator, RecoverAccount } from '../../utils/AccountUtils' const recoveryPhraseKeyName = 'recoveryPhrase'; function CreateAccount() { // Declare a new state variable, which we'll call "seedphrase" const [seedphrase, setSeedphrase] = useState(''); // Declare a new state variable, which we'll call "account" const [account, setAccount] = useState(null); // Declare a new state variable, which we'll call "showRecoverInput" // and initialize it to false const [showRecoverInput, setShowRecoverInput] = useState(false); function createAccount() { // Your create account logic here console.log('Create account clicked!'); const result = EthAddressGenerator(); console.log(result.account) console.log(result.seedPhrase) setAccount(result.account) setSeedphrase(result.seedPhrase) } function handleChange(event) { // Update the seedphrase state with the value from the text input setSeedphrase(event.target.value); } const handleKeyDown = async (event) => { if (event.keyCode === 13) { event.preventDefault(); recoverAccount(seedphrase); } } const recoverAccount = useCallback( // recoverAccount could be used without recoveryPhrase as an arguement but then we would have to // put it in a deps array. async (recoveryPhrase) => { // Call the RecoverAccount function with no arguments // Call the RecoverAccount function and pass it 0 and the current seedphrase const result = await RecoverAccount(recoveryPhrase); // Update the account state with the newly recovered account setAccount(result.account); if (localStorage.getItem(recoveryPhraseKeyName) !== recoveryPhrase) { localStorage.setItem(recoveryPhraseKeyName, recoveryPhrase); } }, [] ); return ( <div> <form onSubmit={event => event.preventDefault()}> <button type="button" className="btn btn-primary" onClick={createAccount}> Create Account </button> {/* Add a button to toggle showing the recover account input and button */} {/* If show recover input is visible, clicking the button again will submit the phrase in the input */} <button type="button" className="btn btn-outline-primary ml-3" onClick={() => showRecoverInput ? recoverAccount(seedphrase) : setShowRecoverInput(true)} // if the recoveryinput is showing but there is no seedphrase, disable the ability to recover account disabled={showRecoverInput && !seedphrase} > Recover account </button> {/* Show the recover account input and button if showRecoverInput is true */} {showRecoverInput && ( <div className="form-group mt-3"> <input type="text" placeholder='Seedphrase or private key for recovery' className="form-control" value={seedphrase} onChange={handleChange} onKeyDown={handleKeyDown} /> </div> )} </form> <p>Address: {account === null? "": account.address}</p> <p>Address: {seedphrase === null? "": seedphrase}</p> </div> ); } export default CreateAccount; ``` **src/App.jsx** ``` import React from 'react' import './App.css' import CreateAccount from './scenes/Account/CreateAccount'; function App() { return ( <div className="App"> <CreateAccount /> </div> ) } export default App ``` The result of rapid testing Creating Account ![](https://i.imgur.com/3c586he.png) ### Account Recovery Recovering Account by seed phrase, the ethers library function is **Wallet.fromMnemonic** in **V5** and **Wallet.fromPhrase** in **V6.2.2** ![](https://i.imgur.com/N1pHy1D.png) Hiding the seedphrase ![](https://i.imgur.com/NLa548O.png) Just psate seedphrase and press enter for Recovering the account, the seedphrase doesn't show itself. Deposit coin on Sepolia testnet Wallet Address : 0x977b8CcFf21132896bb420eD15bB47b2178a0906 ![](https://i.imgur.com/t2gNOki.png) ### Getting Transactions Adding Provider and Transactions and the state stored in localstorage and apply a Moralis API key for requesting the transactions The ether.js V6 is changed a lot. ``` V5 const provider = new ethers.providers.JsonRpcProvider(goerli.rpcUrl); V6 const provider = new ethers.JsonRpcProvider(sepolia.rpcUrl); V5 ethers.utils.formatEther(accountBalance) V6 ethers.formatEther(accountBalance) ``` ![](https://i.imgur.com/oqkSz1V.png) The Balance is 0 ETH on `0x7984Ebb42AbD280FA31A4144357adD5EB3A16884` ![](https://i.imgur.com/Qs11KX3.png) Transfering ETH from `0x977b8CcFf21132896bb420eD15bB47b2178a0906` to `0x7984Ebb42AbD280FA31A4144357adD5EB3A16884` ![](https://i.imgur.com/pQGWnK0.png) Transfer completed ![](https://i.imgur.com/IQbFv7B.png) Refresh Transactions ![](https://i.imgur.com/Q22BeXC.png) The balance on `0x7984Ebb42AbD280FA31A4144357adD5EB3A16884` ![](https://i.imgur.com/dfiiurh.png) ## References 1. Vite Guide : [https://vitejs.dev/guide/](https://vitejs.dev/guide/) 2. Ethers Library V5 : [https://docs.ethers.org/v5/getting-started/](https://docs.ethers.org/v5/getting-started/) 3. Ethers Library V6 : [https://docs.ethers.org/v6/](https://docs.ethers.org/v6/) 4. Wallet development article : [https://atila.ca/blog/tomiwa/how-to-make-a-blockchain-crypto-wallet-like-metamask-with-chatgpt-react-typescript-and-ethersjs](https://atila.ca/blog/tomiwa/how-to-make-a-blockchain-crypto-wallet-like-metamask-with-chatgpt-react-typescript-and-ethersjs) 5. Wallet development video : [https://www.youtube.com/watch?v=CPxekpYYDDY](https://www.youtube.com/watch?v=CPxekpYYDDY) 6. ChatGPT : [https://openai.com/blog/chatgpt](https://openai.com/blog/chatgpt) 7. Multisig knowledge : [https://academy.binance.com/en/articles/what-is-a-multisig-wallet](https://academy.binance.com/en/articles/what-is-a-multisig-wallet) 8. Aqua Wallet Github : [https://github.com/atilatech/aqua-wallet](https://github.com/atilatech/aqua-wallet)