如何构建以太坊钱包应用程序

本文是对eth-hot-wallet中一部分的技术评论,这是一个具有erc20 token原生支持的以太坊钱包Web应用程序。

当软件部署为Web应用程序时,首先想到的是可访问性。毕竟,网络是最广泛可访问的跨设备平台。Eth-hot-wallet是一种PWA(渐进式网络应用程序),可以在任何现代网络浏览器中使用。Eth-hot-wallet源代码可以在GitHub (MIT许可证)上找到。

此外,最近PWA支持的改进显著改善了移动用户体验。

优点:

  • 无需其他软件
  • 不需要任何安装
  • 能够使用现代Web开发工具。
  • 易于部署和升级

缺点:

  • 更容易发生网络钓鱼攻击。
  • 浏览器插件可能会将恶意代码注入页面。
  • 在慢速互联网连接上的高加载时间
  • 对设备存储的访问受限

恶意浏览器扩展可能会在尝试提取密钥时注入JavaScript代码这一事实非常重要。要迁移此风险,应鼓励用户关闭扩展程序(即通过隐身模式使用)或将Web与外部Web3提供程序(如MetaMask或Trust浏览器)集成。将Web应用程序转换为桌面应用程序也是一种可行的选择。

对于网络钓鱼,应鼓励用户将页面加入书签并通过谷歌搜索进行访问。网络钓鱼站点在搜索结果中排名高于真实站点的可能性极小。

结论:Web应用程序可让你以最小的摩擦力覆盖最广泛的受众。在我看来,使用web开发是应用程序的最佳方式。

堆栈

大部分代码专用于前端:

最终的包由许多包组成,可以在package.json中看到。

顶层的组件包括:

  • Eth-lightwallet:用于Node的轻量级JS钱包和用于密钥库管理的浏览器。
  • React,Redux,saga.js,immutableJS和离线优先重选的react-boilerplate
  • Ant design:用于react的优秀UI组件集。
  • Webpack:JavaScript封装打包器。

而对于后端:

最终包从存储库中的专用分支直接部署到GitHub页面。在传统场景中不需要后端。

要创建Testnet以太坊水龙头,我们将使用无服务器框架Serverless framework。使用AWS Lambda时,它显着改善了开发人员的体验。这是一种非常具有成本效益的解决方案,可以消除维护基础架构的需求,特别是在小批量应用程序上。

eth-hot-wallet的容器

当使用React,Redux,Saga.js和Reselect的组合时,每个容器(可能)由以下成分组成:

  • index.js,用于呈现GUI。
  • actions.js
  • reducer.js
  • saga.js
  • selectors.js
  • constants.js

正如Dan Abramov所说,无论是使用组件还是容器,都有多种方法。根据我的经验,如果一个组件在应用程序状态中有超过8个属性,它应该被分成一个新容器。这只是一个经验法则。随着时间的推移,属性的数量可能会膨胀。对于复杂的组件,最好拥有一个独特的容器,而不是聚集主Container的状态。

并非每个容器都需要所有成分。在eth-hot-wallet中,sendToken容器不使用自己的Saga.js. 我们把它分开,以免给主页组件的状态带来负担。

主页容器

发生大部分操作的主容器是主页容器。在主页容器中,Saga.js负责处理其他问题。除了GUI,它的主要职责是处理密钥库操作。

ETH-Lightwallet包提供密钥库。所有相关操作,包括密钥,种子,加密,导入,导出都在这里中完成。

Header容器

Header演示了容器不仅仅是GUI组件的事实:

这个容器最初看起来很简单,只有一个徽标和一个网络选择器。它甚至需要在自己的容器中吗?答案是,在eth-hot-wallet中,每个与网络通信相关的操作和状态管理都在Header容器内完成。对于任何容器来说都足够了。

SendToken容器

SendToken是用户选择发送Ether/Token时显示的一个模态。

模态包括一些基本的输入验证,如金额和以太坊地址检查。它不使用Saga.js来启动,而是使用主页和Header容器提供的操作。

我们将它分成一个新的容器,以减少聚类主页容器的状态。

TokenChooser容器

当用户想要选择钱包将管理的Token时,将出现Token选择器。

选择TokenChooser这个名称是为了不与术语selector混淆,后者在不同的上下文中通过钱包代码多次出现(reduxjs/reselect: Selector library for Redux)。

SendToken容器相同,TokenChooser不使用自己的Saga.js文件,但在需要时从主页容器调用操作。

以太坊钱包的统一设计

自ERC20标准(EIP20)出现以来,很明显Token将成为以太坊生态系统的重要组成部分。以太坊钱包的设计考虑了统一的设计方法。从用户的角度来看,应该平等对待以太和Token。

在hood下,用于发送以太和发送Token的API是完全不同的。但是检查余额时它们在GUI上显示相同。

要发送以太网,我们需要使用web3.js库提供的本机功能,而发送Token和检查余额则涉及与智能合约的交互。稍后会详细介绍此问题。

Redux和Redux-Saga

使用Redux存储作为唯一的数据来源极大地有利于钱包。可以通过Redux提供的操作和reducers相对容易地管理GUI操作和用户触发的流。

除了保持UI状态之外,Redux存储还保存密钥存储对象(eth-lightwallet提供的部分加密的JavaScript对象)。这使得通过使用选择器可以在整个应用程序中访问密钥库。

Redux-Saga是整个设置很牛的原因。

redux-saga是一个旨在使应用程序(诸如数据获取之类的异步事件和访问浏览器缓存之类的)更易于管理,执行更高效,易于测试以及更好地处理故障的库。Saga.js使用Generators使异步流程易于读写。通过这样做,这些异步流看起来像你的标准同步JavaScript代码(有点像async/await但有更多自定义选项)。

对于以太坊钱包,通过使用Saga,我们可以轻松地处理异步操作,例如rest API调用,密钥库操作,通过web3.js进行的以太坊区块链调用等等。所有请求都在一个地方干净地管理,以及非常直观的API,没有烦人的回调。

安全密码生成器

为了充分保护用户的密钥库,我们需要使用强密码对其进行加密。使用eth-lightwallet时,需要在启动硬钱包期间提供密码。

让我们假设我们有一个名为generateString的函数,它可以提供任意长度的真正随机字符串。

如果用户想要生成新钱包,我们将生成以下参数:

我们可以要求用户确认密码和种子或代表它生成新的集合。或者,我们可以询问用户他们自己的现有种子和密码。

generateString实现:我们将使用相对较新的window.crypto API来获取随机值(目前所有主流浏览器都支持)。

Eth-hot-wallet实现基于以下代码生成随机但我们可读的字符串:

用户接受密码和种子后,我们可以使用这些值并生成新的钱包。

eth-lightwallet和SignerProvider

  • 1.LightWallet旨在成为Hooked Web3提供商的签名提供商。
  • 2.Hooked Web3提供程序已被弃用,目前作者建议使用ethjs-provider-signer包作为替代方法。
  • 3.在撰写本文时,ethjs-provider-signer中存在一个错误,可以防止显示错误消息。该错误已修复但未合并回主分支。这些错误消息对于此设置正常运行至关重要。

底线:将eth-lightwallet与此版本的ethjs-provider-signer一起使用:https://github.com/ethjs/ethjs-provider-signer/pull/3以节省试用和调试错误的时间。

加密的离线存储

lightwallet密钥库保险库JSON对象已加密,它要求我们使用外部passwordProvider来安全地保留加密密钥。keystrore对象始终是加密的。该应用程序负责保护密码并为其提供任何操作。

eth-hot-wallet使用Store.js,跨网络使用的所有用例的跨浏览器存储。Store.js允许我们轻松存储加密的密钥库,并在访问网页时将其从存储中提取出来。

首次加载钱包时,它将检查本地存储中是否存在密钥库,如果是,则自动将其加载到Redux状态。

此时,我们可以读取密钥库的公共数据,但不能读取密钥。要在用户输入加密密码之前显示公共数据,我们需要一个额外的操作模式:加载和锁定。在此模式下,钱包将显示地址并获取余额,但无法执行发送交易甚至生成新地址等操作。触发任何这些操作都会提示输入用户密码。

使用Web3.js发送以太坊

使用Web3.js@0.2.x,函数sendTransaction以下列形式提供:

1
web3.eth.sendTransaction(transactionObject [, callback])

如果成功,回调将返回TX。

但是,为了将它正确地集成到我们的saga.js流中,我们需要使用promise封装sendTransaction函数

这样我们在调用sendTransaction后继续执行常规的Saga.js执行。

使用Web3.js发送erc20 Token

以太坊区块链不提供封装Token功能的原语,也不应该。事实上,部署在以太坊上的每个Token都是与eip20规范相对应的程序。由于以太坊虚拟机(EVM)是图灵完备(有一些限制),因此每个Token可能具有不同的实现(即使对于相同的功能)。在Token一词下统一所有这些程序的是它们提供与规范定义的API相同的API。

当我们在以太坊上发送Token时,我们正在与智能合约进行交互。 要与智能合约进行通信,我们需要了解其API,即共享合约API的格式,称为以太坊合约ABI 。

我们将erc20 ABI存储为JavaScript包的一部分,并在程序运行时实例化一个合约:

1
const erc20Contract = web3.eth.contract(erc20Abi);

合约设置后,我们可以使用Web3.js合约API以编程方式轻松地与其进行交互。

对于每个Token,我们需要一个专用的合约实例:

1
const tokenContract = erc20Contract.at(tokenContractAddress);

在创建合约实例之后,我们可以直接从JavaScript调用所需的函数来访问合约函数:

我们将promisify tokenContract.transfer.sendTransaction与我们的redux-saga流一起使用:

可以使用es6-promisify或类似代码而不是直接编写承诺,但在这种情况下我更喜欢直接方法。

使用Web3.js V1和redux-saga 通道订阅以太坊交易生命周期

eth-hot-wallet使用web3.js v0.2.x,目前不支持此功能。该示例由另一个项目提供。这是一个重要的特征,应该广泛使用。

新版本的Web3.js(V1.0.0)附带了一个新的合约API,可以告知我们有关交易生命周期的更改。

输入PromiEvent:一个promise组合事件发射器 。

1
web3.eth.sendTransaction({...}).once('transactionHash', function(hash){ ... }).once('receipt', function(receipt){ ... }).on('confirmation', function(number, receipt){ ... }).on('error', function(error){ ... }).then(function(receipt){    //fired once the receipt is mined});

这个API比0.2.x版本的Web3.js提供的API更具信息性和优雅性。我们将在Saga.js频道的帮助下看到我们如何将其集成到我们的网络应用程序中。一旦检测到对交易状态的更改,动机是更新应用程序状态(Redux存储)。

在以下示例中,我们将为任意智能合约创建提交交易,并在收到transactionHashreceipterror事件时更新应用程序状态。

我们需要初始化新通道并分叉处理程序:

处理程序将捕获所有通道事件,并将调用相应的Redux操作创建器。

一旦通道和处理程序都准备好并且用户启动交易,我们需要注册到生成的事件:

实际上,我们不需要为每个交易建立新的通道,并且可以对所有类型的交易使用相同的通道。

可以在此处找到此示例的完整源代码

使用redux-saga轮询以太坊区块链和价格数据

有几种方法可以监视区块链的变化。可以使用Web3.js订阅事件,或者我们可以自己轮询区块链,并且可以更好地控制轮询的某些方面。

在eth-hot-wallet中,钱包定期轮询区块链以进行余额变更,并使用Coinmarketcap API进行价格变动。

这个redux-saga模式将允许我们轮询任何数据源或API:

在默认saga看到CHECK_BALANCES操作后,checkAllBalances函数被调用。它可以有两种可能结果:CHECK_BALANCES_SUCCESSCHECK_BALANCES_ERRORwatchPollData()将捕获它们中的每一个以等待X秒并再次调用checkAllBalance。此例程将继续,直到STOP_POLL_BALANCES捕获watchPollData。之后,可以通过再次提交CHECK_BALANCES操作来恢复轮询。

密切关注bundle大小

使用JavaScript和npm构建Web应用程序时,可能很容易在不分析footprint增加的情况下添加新软件包。Eth-hot-wallet使用webpack-monitor显示所有依赖项的图表以及每个构建之间的差异。它允许开发人员在添加每个新包后清楚地看到包大小的变化。

Webpack监视器还可以帮助找到最苛刻的依赖项,甚至可能通过突出显示对应用程序做的很少但对包大小有很大贡献的依赖项来让开发人员感到惊讶。

Webpack-monitor易于集成,绝对值得包含在任何基于webpack的Web应用程序中。

结论

本文中提出的问题只是构建以太坊钱包时需要解决的挑战的一部分。但是,克服这些问题将创造一个坚实的基础,并将使我们能够继续并创造一个成功的钱包。

构建钱包也可以成为进入以太坊世界的一个很好的开始,因为大多数分布式应用程序(DApps)需求从前端和区块链的角度来看是类似的一组功能。

======================================================================

分享一些比特币、以太坊、EOS、Fabric等区块链相关的交互式在线编程实战教程:

  • java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
  • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
  • c#比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在C#代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是C#工程师不可多得的比特币开发学习课程。
  • java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
  • 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • ERC721以太坊通证实战,课程以一个数字艺术品创作与分享DApp的实战开发为主线,深入讲解以太坊非同质化通证的概念、标准与开发方案。内容包含ERC-721标准的自主实现,讲解OpenZeppelin合约代码库二次开发,实战项目采用Truffle,IPFS,实现了通证以及去中心化的通证交易所。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
  • EOS入门教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
  • 深入浅出玩转EOS钱包开发,本课程以手机EOS钱包的完整开发过程为主线,深入学习EOS区块链应用开发,课程内容即涵盖账户、计算资源、智能合约、动作与交易等EOS区块链的核心概念,同时也讲解如何使用eosjs和eosjs-ecc开发包访问EOS区块链,以及如何在React前端应用中集成对EOS区块链的支持。课程内容深入浅出,非常适合前端工程师深入学习EOS区块链应用开发。
  • Hyperledger Fabric 区块链开发详解,本课程面向初学者,内容即包含Hyperledger Fabric的身份证书与MSP服务、权限策略、信道配置与启动、链码通信接口等核心概念,也包含Fabric网络设计、nodejs链码与应用开发的操作实践,是Nodejs工程师学习Fabric区块链开发的最佳选择。
  • Hyperledger Fabric java 区块链开发详解,课程面向初学者,内容即包含Hyperledger Fabric的身份证书与MSP服务、权限策略、信道配置与启动、链码通信接口等核心概念,也包含Fabric网络设计、java链码与应用开发的操作实践,是java工程师学习Fabric区块链开发的最佳选择。
  • tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。

汇智网原创翻译,转载请标明出处。这里是如何构建以太坊钱包应用程序