在上一次开发中,我们完成了钱包的ETH转账功能,这次开发我们计划完成账号导出功能。
一、本次开发计划因为我们的钱包为网页版,账号私钥加密后保存在本地存储中,当用户清理浏览器本地存储时便会丢失账号。所以需要一个导出账号功能,将用户的私钥或者助记词导出,用户可以复制后保存在别处,防止账号丢失。计划开发中的页面如下:
用户登录后,点击右上角的账号选项,会弹出一个用选择菜单: 点击
在EtherScan上查看
便会打开一个新浏览器窗口并转到EtherScan显示账号详情。如果我们点击账户详情,则显示如下页面: 微信扫描那个二维码会显示账号地址,这个页面同样也同样提供了
在EtherScan上查看
的功能,最下方是导出助记词和导出私钥按钮。
提示:
只有在使用助记词导入账号时才会显示导出助记词按钮,使用私钥导入账号是获取不了助记词的。因为我们的钱包只保存了私钥,所以在登录时是使用私钥登录的,获取不了助记词,也就无法导出助记词。
点击导出私钥,会出现如下界面: 输入你的密码后点击确定,就会在文本框中显示你的私钥。点击私钥可以复制到粘贴板,可以将它保存在别处。
点击完成返回到钱包主界面,点击左上角返回按钮退到账号详情界面。
我们在MetaMask中可以看到,每个账号都有一个不同的圆球形的icon来表示,比如下图中的主网账号、Kovan账号1和2等。 这个功能我们使用
jazzicon
库来实现,这是它的文档地址: => https://www.npmjs.com/package/jazzicon。它返回一个HTML节点对象(一个包含了svg
的div
)。通常使用父元素(一般是一个div
)的appendChild
方法来添加icon。
在React中,定位一个元素通常使用如下的方式:
const ref = useRef()
......
然后再添加子节点,使用ref.current.appendChild(node)
。只不过在我们的钱包中,账号详情界面是使用一个Dialog
组件来实现的。由于未知原因,在Dialog
组件里子元素的ref
属性无法绑定(Dialog
组件本身也不行),所以也就无法使用上面的方法来显示账号icon。我们采取了一个绕行的办法:先将节点对象转换成字符串,然后再设置父对象的innerHTML属性。下面列出相关的代码:
- 产生icon的代码
import {useMemo} from 'react';
import Jazzicon from 'jazzicon'
export function useAddressIcon(address,size) {
return useMemo(() => {
return Jazzicon(size, parseInt(address.slice(2, 8), 100))
}, [address,size])
}
从代码中可以看出,给定一个地址和icon大小,它都会返回一个节点对象。因为使用了useMemo
,所以如果作为参数的地址和大小不变,它直接返回上次的返回值,并不重新计算。
- 节点对象转字符串,这个参考了别人的实现
//将一个html节点对象转换成字符串
function nodeToString(node) {
let tmpNode = document.createElement( "div" );
tmpNode.appendChild( node.cloneNode( true ) );
let str = tmpNode.innerHTML;
tmpNode = node = null; // prevent memory leaks in IE
return str;
}
- 将Icon字符串在
div
中显示
这里对比 MetaMask还有一个小小的不同,这是MetaMask的账号详情界面: 可以看到账号icon有一半是显示在详情底层
paper
的上方。因为我们这里直接使用了Dialog
组件,将icon移出paper
的边框后就无法再显示,所以该icon并未上移。还希望各位读者指出问题的原因或者给出解决的办法。
二维码的实现很简单,使用qrcode-react
这个库即可。下面是相关代码:
import QRCode from 'qrcode-react';
......
直接设置size
属性和显示的value
就OK了。
从最开始的计划图中我们可以看到,点击按钮后菜单背景颜色为黑色透明,而菜单默认背景是白色的。这需要对Material UI组件的一些属性进行修改,这里是官方文档的自定义组件详细指南:https://material-ui.com/zh/customization/components/。
我们这里采取文档中提到的用类覆盖样式
这种方法。应用到本次开发中就是更改菜单的背景颜色。先打开菜单组件的API指南:https://material-ui.com/zh/api/menu/。在页面的最下方,列出了可以重写的CSS样式表规则: 可以看到,
paper
规则是应用于Paper
元素的(包括了菜单背景),所以我们需要设置它,相关代码为:
const useStyles = makeStyles(theme => ({
menuPaper:{
border: '1px solid #d3d4d5',
backgroundColor:"#000000BB",
color:'white'
}
}));
......
const classes = useStyles();
......
这里菜单组件设置了很多属性,大家可以在菜单API里看各属性的具体含义,链接上面已经给出。现在让我们只关注classes
属性,这里将paper
规则设置成为我们预设的名为menuPaper
样式。
- 因为导出账号时需要验证密码,为了简化,我们将账号登录(包括创建账号、导入账号)时的密码保存在全局变量中,也就是
GlobalProvider.js
中,使用时直接比较。 - 我们新建了一个
hooks\index.js
用来存放用户自定义Hook,比如生成地址Icon的Hook等。它和工具类utils\index.js
是不同的,需要分开。 - 因为我们的钱包在桌面端只占web页面中间一小部分,此时消息条显示在左下角有些远。所以桌面端我们的消息条改成显示在下方中间的位置。
- 账号导出就是直接把钱包的私钥或者助记词显示出来。当然也可以进一步拓展,选择导出为一个加密json钱包,这里并未列入开发计划。
本次开发我们实现了账号导出的功能,并且更改了弹出菜单的背景颜色。应用的知识点主要是地址Icon的生成和添加、二维码的生成和用类覆盖样式来自定义组件外观等。
在开发中也遇到了两个问题:一是Dialog组件子元素里(也包括它本身)无法绑定ref
属性。二是Dialog组件里子元素超过了底层Paper
的边框就无法显示。由于时间问题,没有进一步深入研究,留到以后有空了慢慢来看。也希望有经验的读者能够指出根源所在或者给出解决办法,不胜感激。
下一次开发我们计划实现用户添加ERC20代币的功能。
本钱包码云(gitee)仓库地址为: => https://gitee.com/TianCaoJiangLin/khwallet
欢迎读者留言指出错误或者提出改进意见。