Learn and use--use the Material UI framework under React to develop a simple MetaMask-like web version of the Ethereum wallet (7)

       In the previous article, we completed the account export function of the wallet. This time we plan to develop the function of the wallet to display the list of ERC20 tokens. Because we are imitating MetaMask, we can only manually add Erc20 tokens.

1. Main function demonstration

       After the user logs in to the wallet, click the menu button in the upper left corner, and the My Account interface will appear:
Insert picture description here

       This interface is implemented in MetaMask with a drawer, that is, there is a drawer animation. Drawer animations in Material UI are all full screen, and our wallet occupies only a small part of the center of the screen in the Web page, so after many attempts, due to personal ability problems, the drawer cannot be implemented. So the commonly used routing function is used to jump to simplify the processing of this part. Interested readers can study the drawer animation of Material UI.

       Click the account address in the interface to copy it, and click the details button to display the user details interface implemented in the previous article. Let's click to add tokens (here, ERC20 tokens), and the following interface appears:
Insert picture description here
       here we have also made some simplifications compared to the MetaMask interface. Enter the token contract address of the corresponding network in the top text box and click Query. After the query is over, the token's symbol, precision and your token balance will be displayed. If the address you entered is not a valid token address, you will be prompted that the address is invalid.
Insert picture description here
       Click the Add button to add it to our token list. Note that the list here can be scrolled up and down. If you click Cancel, you will return to the main wallet interface. If the token has been added, there will be a prompt.
Insert picture description here
       You can see that there are already GEC tokens in our list. Click the expansion button on the right of GEC tokens, and a menu will appear, which contains hidden tokens and viewing on EtherScan:
Insert picture description here
       we click on hidden tokens (hidden is Is not displayed in the list, and you will not lose your tokens), a confirmation screen will appear:
Insert picture description here
       Click Cancel to return to the list, let’s click Hide, and GEC tokens will be deleted from your list and returned to the token Currency list, you will see the same screen as when it was not added. If you want to display GEC tokens again, you can only add it again.

       Let us issue an air coin KHC (the wallet is called KHWallet, and the coin is called KHCoins of course), and add it and GEC to the list:
Insert picture description here
       you can now add all your tokens to the list. If you want to know the relevant information of a certain token that has been added, click the extension button on the right, and then click the view button on etherscan, you can see it in the browser. Note that the address bar contains the token address.
Insert picture description here
       This development only completes the addition, display and hiding of ERC20 tokens in the list. The function of clicking tokens to display and transfer tokens on the main interface of the wallet has not been developed. This will be put into the next development, but the token balance monitoring of the current account is implemented, which can be used to keep the token balance updated in real time.

2. Test token balance display

       Let's test whether the token balance can be updated in real time, let's switch to the Kovan testnet for free operation. I have mentioned the test ETH acquisition method of Kovan testnet in the previous article. Readers are welcome to check my entire series of articles to get a comprehensive understanding of the wallet.

       Let's open both the wallet and MetaMask, and prepare two accounts, one for use in the wallet and one for use in MetaMask, both of which add the same token, as shown in the figure below: As
Insert picture description here
       you can see, my account on the left has 988,004,400 tests Tokens, the Kovan account 2 on the right has 200 test tokens. Let us transfer 100 test tokens from Kovan2 to my account.

       Click on the test coin in the MetaMask token list interface (the way to open it is the same as our wallet), enter the following interface (this is what we will implement in the next step):
Insert picture description here
       You can see that the main interface of MetaMask displays 200 test tokens, in our Click the account address in the wallet to copy, and then click the send button in MetaMask (you may need to reopen the page):
Insert picture description here
       Paste my account directly in the address bar, and enter 100 in the quantity field. Click Next, click Confirm on the next screen, wait for the transaction to complete, and stare at the wallet interface:
Insert picture description here
       Before the MetaMask tokens have been sent 确认, the token balance in the wallet on the left has been updated. This is because the token balance is updated by monitoring and filtering transaction events, and the receipt of the event will be earlier than the confirmation of the receipt of the transaction.

       You can also perform a reverse test, which is to log in to my account with MetaMask and send 100 test tokens to Kovan account 2. Before the transaction is confirmed, the token balance in the left wallet will be updated. Here we will not demonstrate anymore.

Three, the main points of this development

       In addition to the relatively large workload of UI splicing, refreshing, or display, this development also has some design or code writing points:

  1. Design of local storage. The token list of the account must be stored in the local storage. It needs to be carefully designed in what format, how to update, and how to read it. The specific implementation is src\contexts\StorageProvider.jshere, and readers can also have better design ideas. Here is one of the notes:
/**  本地存储计划示例,
{

    "0x1234....":{
                    crypt:"ifajfay08",
                    erc20Tokens:{
                                    homestead:[
                                                {
                                                    address:'0x1234....'
                                                    balance:0x78,
                                                    symbol:'',
                                                    decimals:''

                                                },
                                                {
                                                    address:'0x1234....'
                                                    symbol:'',
                                                    decimals:''
                                                }
                                            ]
                                }
                  }
}
*/

       The local storage is in Json format, and each address corresponds to an encrypted key (used to log in) and a list of 20 tokens. Among them, the list of 20 tokens is distinguished according to the network. All 20 tokens under a certain network are an array. Each element of the array is a 20 token object, including its address, account balance, symbol and precision.

       There is a small detail here: because in the Js library, the integer value returned by Ethereum is in the bigNumberformat, it is converted into a hexadecimal string format when it is saved locally, and it needs to be converted into it when it is read and used locally bigNumber.

  1. ERC20 token contract. Since it is necessary to deal with tokens, token contracts must be involved. The main elements of a token contract are the address of the token, the ABI of the token contract, and of course which network it belongs to. Among them, the ABI of the token contract can be the ABI generated by an ordinary compiler, or a human-readable ABI ( ethersboth libraries can be used, the author has not used other libraries, please verify by yourself if necessary). The ABI of the ERC20 token contract used in this wallet is a readable ABI, the code is as follows:
[
  "function balanceOf(address owner) view returns (uint)",
  "function decimals() view returns (uint8)",
  "function symbol() view returns (string)",
  "function allowance(address tokenOwner, address spender) view returns (uint)",
  "function transfer(address to, uint amount)",
  "function approve(address spender, uint amount)",
  "event Transfer(address indexed from, address indexed to, uint amount)",
  "event Approval(address indexed tokenOwner, address indexed spender, uint amount)"
]

       The code snippet to obtain the contract is as follows:

//获取ERC20代币合约
export function getErc20Token(tokenAddress,network,wallet) {
    
    
    if(!isAddress(tokenAddress) || !network) {
    
    
        return null;
    }
    try{
    
    
        let provider = ethers.getDefaultProvider(network);
        if(wallet) {
    
    
            provider = wallet.connect(provider)
        }
        return new ethers.Contract(tokenAddress,ERC20_ABI,provider)
    }catch{
    
    
        return null
    }
}

       Here again: if readers are interested in developing Dapp on Ethereum, please read the etherslibrary first.

  1. To obtain and refresh the account ERC20 token balance, let's first look at the obtaining code:
//获取某个地址在某个token余额
export async function getTokenBalance(tokenContract,address) {
    
    
    return tokenContract.balanceOf(address).catch(error => {
    
    
        error.code = ERROR_CODES.TOKEN_BALANCE
        throw error
    })
}

       This code is very simple, directly call the balanceOfmethod of the contract , note that it returns a promise.

       Let's take a look at the implementation of refresh. This is relatively complicated, and there may be a better way or optimization:

//监听用户代币变化
useEffect(()=>{
    
    
    if(tokens.length > 1) {
    
    
        let stale = false
        let allContracts = []
        for (let token of tokens) {
    
    
            if(token.symbol !== 'ETH'){
    
    
                allContracts.push(getErc20Token(token.address,network,wallet))
            }
        }
        for (let contract of allContracts) {
    
    
            let filter1 = contract.filters.Transfer(wallet.address,null)
            let filter2 = contract.filters.Transfer(null,wallet.address)
            // eslint-disable-next-line
            contract.on(filter1,(from,to,amount,event) => {
    
    
                getTokenBalance(contract,wallet.address).then(_balance => {
    
    
                    if(!stale) {
    
    
                        updateTokenBalance(wallet.address,network,contract.address,_balance)
                    }
                })
            })
            // eslint-disable-next-line
            contract.on(filter2,(from,to,amount,event) => {
    
    
                getTokenBalance(contract,wallet.address).then(_balance => {
    
    
                    if(!stale) {
    
    
                        updateTokenBalance(wallet.address,network,contract.address,_balance)
                    }
                })
            })
        }

        return () => {
    
    
            stale = true
            for (let contract of allContracts) {
    
    
                contract.removeAllListeners('Transfer')
            }
        }
    }
},[tokens,network,wallet,updateTokenBalance])

       Here we set up listeners for all token contracts to listen to its Transferevents. Set filters separately to filter out events where the sender or receiver is a user account. After listening to the event, get the user's latest balance to update. When the interface exits, cancel all listeners.

       There is also little tips, for a pure array of arrays (no additional property) is concerned, for (let key of arrays)and for (let key in arrays)terms are different, the former acquisition is an element, which is obtained under standard, when you want to use careful not to use the wrong .

Four, summary

       Compared with the previous few times, this development is relatively more complicated, and time is tight. There is no detailed test. If there is any imperfection or error, readers are welcome to give me corrections during the reading or use process. Thank you very much.

       As the development progresses, you will feel more and more that you need to clarify every attribute of every component of the Material UI framework, and even every CSS rule. However, this is a long-term process, and it is difficult for you to go deep into the underlying implementation. Therefore, it is very necessary to keep learning every component in the Material UI framework, and learn the new by reviewing the past. Not only need to know the usage of commonly used attributes of components, but also need to know what the commonly used CSS rules are and how to modify them. I think even if you are not learning Material UI, even if you are learning some frameworks in Vue, the same is true.

       The specific UI code is more cumbersome and complicated. I will not give an example here. You can download my code and take a look.

       In addition, recommend an article: CSS, art, science or nightmare (everything you should know) . I personally think this is a must-read article for every front-end developer, although I am not a front-end developer.

       In the next development, we plan to implement the transfer function of ERC20 tokens

       This wallet code cloud (gitee) warehouse address is: => https://gitee.com/TianCaoJiangLin/khwallet

       Readers are welcome to leave a message to point out errors or suggest improvements.

Guess you like

Origin blog.csdn.net/weixin_39430411/article/details/104145456