# 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
```


The **wallet_dev** is the parent folder of the main project called **wallet-dev-01**

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


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.


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

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

### Account Recovery
Recovering Account by seed phrase, the ethers library function is **Wallet.fromMnemonic** in **V5** and **Wallet.fromPhrase** in **V6.2.2**

Hiding the seedphrase

Just psate seedphrase and press enter for Recovering the account, the seedphrase doesn't show itself.
Deposit coin on Sepolia testnet
Wallet Address : 0x977b8CcFf21132896bb420eD15bB47b2178a0906

### 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)
```

The Balance is 0 ETH on `0x7984Ebb42AbD280FA31A4144357adD5EB3A16884`

Transfering ETH
from `0x977b8CcFf21132896bb420eD15bB47b2178a0906`
to `0x7984Ebb42AbD280FA31A4144357adD5EB3A16884`

Transfer completed

Refresh Transactions

The balance on `0x7984Ebb42AbD280FA31A4144357adD5EB3A16884`

## 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)