与传统的 App(包括 Web App 与 Mobile App)最大的不同点在于,DApp 的大量功能依赖直接与智能合约(以下简称合约)进行交互。我们无法直接使用前端代码调用合约,因此,在开发 DApp 之前,我们必须理解这一技术栈中存在哪些技术细节以及它们分别扮演何种角色。
- 智能合约:通常指代运行在 EVM 兼容网络中的 Solidity 或其他合约语言代码,他们负责与用户交易我们发行的资产并储存 DApp 的链上状态。
- Provider/Signer: 这是一个 DApp 架构中特殊的角色,它负责与区块链进行通信,并进行合约的读/写操作。Metamask 是一个流行的 InjectProvider(Web3Provider)你也可以使用其他 JSON-RPC Provider 与区块链进行通信。
- Relay: 这个角色隐藏在 Provider/Signer 之后,是真正负责我们与区块链的某一个节点同步状态的服务器集群,它保存了所有账本(全节点)它通常是 Infura、Alchemy、Quicknode、Moralis 或者 Pocket 提供的服务。
- 服务端(可选):大部分 DApp 仍然有他们的服务端逻辑,这意味着,你需要自己搭建服务环境,或使用流行的 BasS/FaaS 服务,你可以使用深度整合区块链的 Moralis 来完成服务端的开发,也可以使用成熟的 Firebase 体系。当然,你也可以挑战完全不依赖服务端的方式来构建 DApp,就像 Uniswap 所做的那样。
现在,我们知道编写一个 DApp 大概需要哪些领域的知识,如果你已经决定迈向下一代互联网并打算闯荡一番,我会在接下来的内容中仔细介绍这些角色分别需要理解哪些编程语言,框架和库。
让我们先进入最重要的部分,智能合约。大量程序员望而却步的重要门槛是,他们认为智能合约需要学习一门新的编程语言,Solidity,这毫无疑问,我非常推荐入门 Web3 的程序员—— 无论你是从哪一个软件开发领域转型而来 —— 从 Solidity 入手学习 DApp 开发。
在智能合约的编码方面,我们目前有许多工具,但认识和理解 Solidity 非常有必要,大量的已经存在的,和流行的合约都使用它进行编码,因此,学习 Solidity 不但有助于帮助你理解区块链开发的基本知识和概念,还能让你在许多优秀的开发者已有的卓越工程上快速起步。
就编程语言而言,在目前的 EVM 兼容链上,你可以使用 Solidity 或 Vyper 进行开发,在其他 L1s 区块链上,例如 Solana,你可以使用 Rust 来进行合约的开发;在 Layer2 方案 StarkNet 中,你可以使用 Cairo 来进行开发;在 Arweave 储存网络中,也存在着类似 3em 这样的运行环境支持你使用 JavaScript 来编写合约。
在这些百花齐放的方案中,实际上存在着两种不同的合约运行环境,EVM 或非 EVM 方案,前者的代码都会被编译成 EVM bytecode,而后者则会采用各种各样的 runtime,各显神通。
这篇文章不会在合约编程语言上讨论太多,我认为,我们目前正处于合约 runtime 的战国时代,没有人能断言哪种合约编程语言的地位会成为 Web 世界的 JavaScript。但对于智能合约编码来说,我们必须要了解和熟悉 Solidity,这是毫无疑问的。
关于 Solidity,我推荐你从 Solidity by Example 教程开始学习:
这一教程没有繁琐的语法介绍,而根据范例帮助读者掌握基本知识,因此,完成这一教程大约只需要不到一个工作日。Solidity 并不是一个特别复杂的语言,在使用它时,我们可以逐步理解每一项语句的语义,我推荐你设置好编码环境后按照网站上的范例来进行实践。当你已经掌握所有范例的写法之后,可以打开 Solidity 语言官方文档(中文)对照编码中的错误来进行针对性的学习:
理解并掌握智能合约后,我们可以进入 DApp 的编码,这是许多互联网行业从业者的强项,我不会在此赘述关于前端编码的经验,如上所述,我们可以使用流行的前端框架,例如 React 或者 Vue 来进行 DApp 的编码。毫无疑问,你会需要一些前端的技术栈知识,主要是 JavaScript 与 CSS。
在此,我想向大家推荐一些优秀的前端库,使用这些代码库来进行合约交互,会使我们的开发效率事半功倍。
以 React 为例,我们可以使用 wagmi 来帮助我们更好的操作合约,它集成了大量基础但够用的 hooks,并提供了与外部
Provider/Signer
交互的快捷函数。与此同时,wgami 没有过多的外部依赖,它的核心依赖只有 ethers.js一般来说,我们并不需要其他的库为我们提供专门的
Provider/Signer
支持,如果你打算支持更多复杂的 Provider,或者同时支持多网络 Provider/Signer 的读写功能,类似 Apeboard 为它的用户提供跨区块链的数据展现,可以参考 react-web3 或者 w3modal 两个流行的模块,这些模块提供了一些好用的功能,但他们的设计不够解耦,有时会带来不必要的 bug,对此,我保持谨慎推荐。进一步,如果你想不想支持外部
Provider/Signer
,而为自己的用户构建一个 Web 钱包,你可以使用 ethers.js 从零开始构建。如果你想为用户提供一个 onboard 体验更好(但更不去中心化)的托管钱包系统,让他们可以从普通的账户密码或者社交网络账户来登录你的 DApp,可以选择采用 Web3Auth 或 MagicLink 的方案。托管钱包系统是一个非常大的话题,感兴趣的读者可以参考上述两个解决方案自行研究。
在理解合约以及 DApp 使用何种方式与区块链进行交互后,开发者很快会意识到,我们并没有通过在本地建立一个节点的方式来与区块链进行操作。如果你在本地部署过 IPFS,你会很快发现它会默认在本地同步节点,就像 BT 下载软件那样。这是否意味着我们的 DApp 不够「去中心化」呢?
实际上,仍然有大量的软件基于本地的全节点来进行交互,只是,对于大部分开发者而言,他们放弃了这样的权利,而转而使用更便利的 Relay Network 与区块链进行通信,通过这种方式,我们节省了部署成本,并且不再需要维护节点的状态缓存,对于快速构建 DApp 来说,选择一个靠谱的 Relay,是无可非议的方案。
使用 Relay Network 不需要特殊的知识,在前端,我们使用上述提及的代码库(ethers.js 或者 web3.js)与 Relay 进行交互;在服务端,如果你使用 Node 运行环境,也可以直接拷贝前端的代码来使用。如果你使用其他的运行环境,你可能会需要一些特定的 JSON-RPC 函数包装,以访问这些 Relay。
Infura 是世界上最早和最大的以太坊 Relay Network,它提供一些公开的 Gateway 节点,但一般来说,我们需要获取属于自己的 DApp Access Key 并为这些访问权限设置 origin 和 IP 限制,以提升使用我们自己的 DApp 用户的访问速度体验。Infura 目前支持 ETH,ETH2 网络,以及 IPFS 和 Filecoin 两个分布式储存方案。

Alchemy 也是一个非常流行的 Relay Network,它在 Infura 的功能上更近一步,为开发者提供了相当多实用的功能,例如调试工具,区块状态推送与丰富的 Webhooks。从某种意义上说,Alchemy 不是一个单纯的 Relay Network,它更像是一个 SaaS 服务,它提供了丰富的自定义 JSON-RPC 方法,实际上,我们的函数库与它的缓存网络进行交互,而不是直接与区块链节点进行交互,这在很大程度上提升了视图(view)方法的访问速度,但依赖 Alchemy 独有的 JSON-RPC 方法,也让 DApp 变得更加中心化了。

我不会在这里评判去中心化的「道德问题」,各位读者可以根据自己的开发时间周期,风险偏好和使用习惯来决定何种服务适合自己,并为自己的客户与用户提供更好的服务。
在 Relay Network 方面,我想再推荐一个服务:

Moralis 集成了许多 FaaS 的功能到他们的 Relay Network 中,这使得你可以快速在服务端访问区块链的状态,而不需要反复调用第三方网络的 API,这是一个非常有趣而实用的方案,他们的定位是 Web3 的 Firebase,我希望他们能够将软件质量和可用性真正提升到 Firebase 的水平,那这就会是一件非常棒的事儿。
在本文编写的过程中,我得知 Google Cloud Platform 也正在组建他们的 Web3 团队,这意味着我们有可能在不久的将来能在 Firebase 或者 GAE 服务上使用到 Google 的 Relay 服务,我们可以保持适当的关注。
服务端方面,你可以使用任何你喜欢的编程语言,运行环境和软件架构,没有什么特殊的限制,只要保证你选择的技术栈能和本地节点或者(通常是)Relay Network 进行交互即可。
一般来说,我会选择 Node 运行环境。说些题外话,由于大部分合约使用 NPM 来进行包管理,并使用 hardhat 来做编译和测试工作流,使用 JavaScript 已经成为智能合约编码中必不可少的一个环节。既然如此,在服务端同时使用 JavaScript 语言有助于我们复用代码,留出更多的时间享受人生。
编写服务端并不意味着我们需要做完所有事,通常,我们使用 DApp 的服务端代码来储存没必要储存在合约中的「链下状态」。在合约中储存数据是十分昂贵的选择(至少目前看来)这种昂贵不仅涉及到我们部署合约中产生的费用,还涉及到每一次修改状态的函数请求带来的,用户需要付出的 gas 成本。所以,大部分时候,我们会使用自己的服务端来储存这些「链下状态」
使用一个健壮的 FaaS 对许多工程师来说是简单而且实用的选择,我推荐 Firebase,如果你想体验深度集成区块链的 FaaS,也可以参考上述提及的 Moralis。

我选择 Firebase 的主要原因是他们提供成本低廉,服务完善和稳定的健壮 API,同时,他们针对开发者开发了功能齐全的本地模拟测试套件,这会节省我们相当多的时间。

FaaS 在市面上有太多可选的方案,你可以依赖一个全功能 FaaS,也可以将自己为数不多的「链下状态」储存在 headless CMS 当中,例如 Vercel 或者 Netlify。


或者,如果你希望自己搭建 FaaS 服务器,以获得更完善的控制与更低的成本,我向你推荐一些 Firebase 的开源替代品,例如 Supabase: