昨天晚上受到 LXDAO Vdel 老师的邀请给 web3 实习计划的同学们讲一下 DAPP 开发。乍一想,其实连钱包的需要链上交互的不就是 DAPP 呗,也没多复杂,更何况现在的 AI 这么强,想写个 DAPP 不是分分钟的事情。于是我便答应了下来,心想,这不就是个 web3 的 hello world 吗?
后来,忙完工作后想了下,我可能是受到 "知识的诅咒" 了。回忆了一下自己第一次打黑客松,做 LXDAO Marry3 的仿品 Ring3 时的迷茫无助,想想这几年打黑客松、做项目在不同的链摸爬滚打的经历,突然觉得还是值得一说的。同时,这也是一个非常好的机会整理下我这几年的经验,准备课程的同时,写一篇博客。
什么是 DAPP?#
通常的定义是:DAPP,全称为 Decentralized Application,中文翻译为去中心化应用,是运行在区块链上的应用,与传统的中心化应用不同,DAPP 不依赖于中心化的服务器,而是依赖于区块链的去中心化特性,因此 DAPP 具有去中心化、去信任、去中介的特性。
以上是一个非常官方的介绍,但实际上如果严格按着官方服务器去做 DAPP,让 everything decentralized 用户体验会非常差,比如难以访问、速度慢、流程复杂等等。事实上,就算是区块链最伟大的发明之一 —— 去中心化交易所 Uniswap,也不是完全去中心化的,具体来说,比如 uniswap 上的代币列表,如果每次都从区块链上读,可能得等到猴年马月;还有一些价格、滑点、交易量、TVL、流动性池等等诸如此类偏数据查询的数据都是通过中心化服务检索整理存储区块链数据后,提供 web api/GraphQL 接口,再通过前端调用这些接口,提供给用户使用的。
那么话又说回来,到底什么是去中心化应用(DAPP)呢?用我的一句话来说,需要链上交互的,进行写入 / 读取操作的,都可以算作是 DAPP,只不过有着程度之分。DAPP 和我们传统的 web 开发不是对立的,还是可以彼此兼容的,我们可以通过维恩图来分析一个产品的去中心化程度:
整个紫色区域我认为是可以被定义为 DAPP 的区域,重点关注几个区域:
- 棕色区域是绝大多数 DAPP:如 uniswap、pendle、opensea、ens 等大多数知名的 DAPP 都是前端后端既有去中心化技术也有中心化技术的。前端通常不会使用 IPFS 这样的技术部署服务,但可能通过去中心化的技术 CDN 去提升用户体验。后端服务的底层是基于去中心化的区块链技术的,核心服务用户是直接与链交互的,但都会通过中心化的数据库服务器同步区块链上数据,形成索引,提升数据查询速度,本质也是提升用户体验。
- 再看棕色区域下面的 "简单 DAPP" 区域,这个区域的应用通常功能相对简单,具体来说可能只需要读取某个地址的资产情况,或者进行一些简单的写入操作,比如 mint nft、transfer nft 等等。这类 DAPP 在 2021 年左右尤为盛行,当时是 NFT 的旺季,很多项目方会做一些酷炫的网页来提升自己 NFT 的品牌形象,以卖出更多的 NFT。这些 DAPP 会去判断用户有没有资格 Mint,能 mint 几个 NFT,然后让用户调用智能合约进行 Mint,再将 Mint 后的 NFT 展示给用户。顺便一提,LXDAO 的 0 号项目良心 NFT 也是诞生在那个时期的,但良心 NFT 在整个市场中独树一帜,网页风格朴实但功能完善,客观介绍 NFT 的功能,没有花里胡哨的噱头,可以理解为是一场单纯的艺术品销售网站。当时的价格只要 0.01ETH 一个,现在一个难求
- 然后看棕色区域的 "极端 DAPP" 区域。在这里咱们先不讨论 CDN 的内容分发网络,这服务本身确实是去中心化的,但仍然面临 CDN 服务商中心化的问题,但大多数时候我们去中心化应用可以接受没有交互界面的前提下使用。这里讨论的去中心化前端就是想避免被网络服务商中心化的问题,比如:Tornado.cash,这个说白了就是利用区块链技术洗钱的应用,谁来维护他就会有法律风险,任何服务器提供商都不会希望自己的服务器会用来部署这样的东西的。但技术是无罪的,社区就想到了一个绝妙的主意把 Tornado.cash 的前端代码部署到了 IPFS 上(下一章有介绍),简单地说像把商店复制成传单,分发给很多人保存,封了一个地方,其他地方还有。当然代价就是这个网站访问起来巨慢无比。所以这也不是常见的方案。
- 宽泛的说,其实 DAPP 的核心就是一整套的智能合约,只有智能合约就可以构成一个 DAPP,但区块链不是只有程序员,想要有更多的用户,还是得有基础的交互界面。
总结一下,有没有和链交互、核心是不是围绕智能合约展开是判断一个 DAPP 的最基本标准,中心化的前端后端是可以与 DAPP 兼容的,对提升用户体验有非常重要的作用。
DAPP 的基本架构#
综合我们上文对 DAPP 的理解,其实 DAPP 的基本架构简单的来看可以分为前端、后端、智能合约三个部分,前端和后端是中心化的,智能合约是去中心化的。哈哈,当然没这么简单啦,要这么简单就没什么可以写的了。除了上文的内容,还有一些重要的基础服务支持 DAPP 的运行,我给出了详细的解释,他们的关系如下图,也没多少好说的:
IPFS#
中文名叫星际文件系统,可以理解为一个去中心化的文件存储系统,但没有区块链复杂的共识机制,会通过核对哈希值的方式区别 / 检索文件。
具体但简单的说,你可以理解为,有一群好心人在互联网上维护了一套免费的存储设施,上传文件会被分布式的存储在很多台全球各地的存储设施上(就是部分服务器上都有一份),通过计算文件哈希值来避免重复存储,同时用户也可以通过文件哈希值找到对应文件的完整数据。
当然天下没有那么好的事情,免费的代价就是你的文件可能随时被删除,但是,IPFS 也提供了一些付费的存储服务,通过 Filecoin(区块链)来保证文件的存储。
RPC 服务商#
事实上普通应用无法也无需直接与以太坊网络进行交互,详细的区块链底层知识就不在这里细说了,有兴趣的朋友可以自己查一下。
DAPP 只需要 "使用" 区块链,不需要 "维护" 区块链,"维护" 区块链才是要与链直接交互的操作,也就是参与区块链的挖矿 / 共识,这需要同步庞大的区块链数据,但 "使用" 区块链不需要关心那么庞大的数据,和普通的 web 服务相似,只需要开放一些接口就可以了。
RPC(远程过程调用 remote procedure call)服务商本质上就是提供一个 API 接口,只不过有一点特殊的格式要求,RPC 服务商通常运行着不同类型的以太坊节点(包括全节点、轻节点或归档节点),并将节点功能通过 JSON-RPC 接口暴露出来。这些接口遵循以太坊的 JSON-RPC 规范,让开发者可以通过 HTTP 或 WebSocket 请求来查询区块链数据、发送交易、调用智能合约等。
索引服务商#
可以先看看 RPC 再来理解索引服务。这需要说得再细一点,当你用 RPC 去读取区块链数据时,你不能像使用数据库一样自由地查询你需要的数据。通常你只能读取一个合约或一个钱包当前的状态(比如余额),或者查询单个交易 / 区块的详细信息,或者监听一些事件的发生(比如有没有新的 NFT 被 mint,有没有新的交易)。
但作为一个 DApp,通常会需要查询某个合约从部署到现在所有的交易情况。具体点说,可能要查某个 NFT 合约的所有持仓者。如果没有索引服务,你就需要从合约部署的区块开始,一个区块一个区块地去记录相关 NFT 的 mint、transfer、burn 等交易。
理论上讲这是一个没那么 "必要" 的服务,如果你的项目体量不大,完全可以自己做这件事情。但是这种服务的需求是相似的、通用的,所以就有了索引服务。它们检查每一个区块,把相关交易记录到数据库里,方便开发者用常规的数据查询语句(通常会提供 GraphQL API)进行查询,帮大家减少开发的负担。
预言机(Oracle)#
Oracle 这个名字听起来很高大上,但其实就是区块链与现实世界的桥梁。思考一个问题,区块链怎么知道当前 ETH 的价格是多少呢?直观的说是不是可以部署一个智能合约,然后找一个可以信任的角色把价格告诉智能合约,智能合约再根据这个价格进行后续操作。但问题来了,谁来保证这个角色是可信的呢?我们当然不能只根据一个角色的消息就判断消息的真假,结果的验证也得去中心化,预言机就应运而生了。
预言机的基本原理是通过多个数据源和多个数据提供者来确保数据的可靠性。以 ETH 价格为例,预言机网络会从 Coinbase、Binance、Kraken 等多个交易所获取 ETH/USD 的价格数据,然后通过加权平均算法计算出一个最终价格。如果某个交易所提供的价格与其他大多数交易所差异过大,会被系统识别并排除。同时,数据提供者(预言机节点)需要质押代币作为保证金,如果提供错误的价格数据会被罚没代币,这样就形成了经济激励来保证数据准确性。
最著名的预言机项目是 Chainlink,它建立了一个庞大的预言机网络,为几千个 DeFi DAPP 提供实时的价格数据。当你在 Uniswap 上交易时,或者在 Aave 上借贷时,这些协议都需要知道各种代币的准确价格来计算交易比率或清算条件,而这些价格数据就是通过 Chainlink 等预言机提供的。
工具链推荐#
在这一部分我会首先介绍比较通用的前后端开发工具链,再介绍我接触到的一些链的工具链。
通用技术#
个人非常推荐搞 DApp 开发的优先考虑学好 JS,因为你会了 JS 几乎就可以一个人跑通全流程了。
前端技术栈:
- 前端框架:Next.js
- 样式库:Tailwind CSS
- UI 库:shadcn/ui
- 替代样式方案:bruce 老师的推文
- 状态管理:Zustand
- 表单验证:Zod
- 新兴技术栈:TanStack
部署和工具:
- 简单部署:Vercel
- 运维方案:Coolify
- 设计工具:Figma
- 原型工具:Excalidraw
后端技术栈:
- 简单后端:Next.js 的 API Routes + Vercel serverless 函数
- 复杂后端:NestJS
- ORM:Prisma
- 数据库:PostgreSQL
- 部署平台:NorthFlank
存储服务:
成本优化:
- 推荐搜索 "独立开发 穷鬼套餐" 获取更多经济方案
生态工具链#
区块链 | 钱包插件链接 | 合约 / 链交互库 | 合约开发框架 / 语言 | RPC 提供商 | 索引服务 |
---|---|---|---|---|---|
ETH | RainbowKit, ConnectKit, Web3Modal | ethers.js, viem, web3.js | Foundry, Hardhat, Truffle (Solidity 语言) | Alchemy, Infura, QuickNode | The Graph, Moralis, Alchemy |
Solana | @solana/wallet-adapter | @solana/web3.js, @solana/spl-token | Anchor (框架), Rust (语言) | Helius, QuickNode, Alchemy | Helius, Simple Hash |
BTC | UniSat, Xverse | bitcoinjs-lib, @scure/btc-signer | Bitcoin Script (脚本语言) | BlockCypher, Blockstream API | Ordiscan, Blockstream |
Cosmos | @cosmos-kit/react | @cosmjs/stargate, cosmjs | Cosmos SDK (框架), CosmWasm (Rust 语言) | All Nodes, Stakely | Mintscan, Big Dipper |
Aptos | @aptos-labs/wallet-adapter | @aptos-labs/ts-sdk | Move (语言), Aptos Framework | Aptos Labs, Nodereal | Aptos Labs, Aptoscan |
TON | @tonconnect/ui-react | @ton/ton, @ton/crypto | FunC (语言), Blueprint (框架) | TonCenter, GetBlock | TonAPI, Toncenter |
不管怎么说以太坊是目前最成熟的链,咱们接下来的章节会以以太坊为例,介绍 DAPP 开发的全流程。
NFT mint DAPP Demo#
合约开发及部署(以下选择其一即可)#
开发前准备#
- 制作图片:可以考虑用自己的头像,也可以找个 AI 生成一个
- 上传到Pinata IPFS
- 注册登录
- 上传文件
- 记录 CID like this:bafkreigfpdewysnl5fq2ir57r26fhxpa6bg3qoy3sbkxlajq6dkf4wye3u
- 你可以通过 IPFS Gateway 访问图片,like this:https://ipfs.io/ipfs/bafkreigfpdewysnl5fq2ir57r26fhxpa6bg3qoy3sbkxlajq6dkf4wye3u
- 准备字符串 ipfs://{CID},like this:ipfs://bafkreigfpdewysnl5fq2ir57r26fhxpa6bg3qoy3sbkxlajq6dkf4wye3u
- 准备 metadata
- 参考下面的内容,写一个自己的 Metadata,名字(name)、描述(description)、属性 (attributes) 随便写 (注意:属性是一个固定内容为 trait_type 和 value 的数组)
- 复制刚刚的内容,做成一个 json 文件
- 参考之前的步骤上传到 ipfs
- 记录 CID,准备 IPFS URL,like this:ipfs://bafkreid7msiyufvgilrlkt6244psudmaycbbzika2aq57kou3xha5u36pe
{
"name": "My First Handmade NFT",
"description": "My First Handmade NFT by @hardman_eth",
"image": "ipfs://bafkreigfpdewysnl5fq2ir57r26fhxpa6bg3qoy3sbkxlajq6dkf4wye3u",
"attributes": [
{
"trait_type": "IQ",
"value": "80"
},
{
"trait_type": "Hat",
"value": "Pot"
}
]
}
- 准备 etherscan API key
- https://etherscan.io/login 注册登录,主网 API key 和测试网通用
- 把 API key 复制下来,放到环境变量.env 里
- 加上计划用来部署的私钥 (可以考虑自己用脚本生成一个 / 直接创建一个,不建议用线上工具生成,有安全问题)
# Private key for deployment
PRIVATE_KEY=your_private_key_here
# Public Sepolia RPC URL, You can change it to your own private RPC
SEPOLIA_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com
# Etherscan API key for contract verification (free from etherscan.io)
ETHERSCAN_API_KEY=your_etherscan_api_key
- 领水(测试币)
区块链上的任何写入操作都需要支付 Gas 费用,咱们使用测试网,可以领取一些测试币,也就是 "领水"。
https://www.alchemy.com/faucets/ethereum-sepolia
也可以关注我推特,私信地址,送你 0.1 个。
基于 foundry 框架#
- 安装 foundry,我就不详细写了,大家看官方文档
- 初始化 foundry 项目
forge init foundry-nft
- 把刚才准备好的.env 复制到文件目录里
- 安装依赖(openzeppelin)
forge install OpenZeppelin/openzeppelin-contracts
- 编写合约,可以直接叫 AI 生成,提示词参考 “写一个简单的 nft 合约,免费 mint,限量 999 个,ipfs://bafkreid7msiyufvgilrlkt6244psudmaycbbzika2aq57kou3xha5u36pe”,也可以直接参考 foundry-simple-nft 仓库 中的合约代码
- 编写单元测试,参考 foundry-nft/test/SimpleNFT.t.sol
- 准备部署脚本,参考 foundry-nft/script/DeploySimpleNFT.s.sol
- 部署合约,验证合约
# 1. 复制环境变量文件并填写私钥
cp .env.example .env
# 编辑 .env 文件,填入你的私钥
# 2. 部署到 Sepolia 测试网
source .env && forge script script/DeploySimpleNFT.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast --verify
# 或者使用公共 RPC 直接部署
forge script script/DeploySimpleNFT.s.sol --rpc-url https://ethereum-sepolia-rpc.publicnode.com --broadcast
# 3. 或者分步执行
# 先部署
source .env && forge script script/DeploySimpleNFT.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast
# 后验证 (需要合约地址)
source .env && forge verify-contract <CONTRACT_ADDRESS> src/SimpleNFT.sol:SimpleNFT \
--etherscan-api-key $ETHERSCAN_API_KEY \
--chain sepolia
前端开发#
https://github.com/0xhardman/handmade-nft-frontend master 分支
集成钱包插件#
- 安装 rainbowkit,wagmi,viem
npm install @rainbow-me/rainbowkit wagmi [email protected] @tanstack/react-query
- 配置测试网网络
- 封装 provider
- 简单了解 RainbowKit 的配置
粗略了解常用 wagmi hook#
- useReadContract:读取单个合约的数据
- useWriteContract:向合约里写入数据,如 mint transfer
- useAccount:获取连接账户的基本信息(地址,当前所在的网络)
- useSignMessage:给消息签名
- useBalance:获取账户的 gas token 余额,或 erc20 代币余额
- useWaitForTransactionReceipt:等待确认交易成功
- useSwitchChain:切换所在的链
- useSwitchAccount:切换账户
获取 abi 文件#
- 如果合约已验证,可以从区块链浏览器上下载
- 如果是自己部署的,可以从编译文件里找
- 如 foundry 的可以从 out/SimpleNFT.out/SimpleNFT.json 取到
功能实现#
- 检查网络,并提示切换。
- 检查钱包余额,提示用户充值。
- 点击按钮,调用合约 mint。
- 等待交易确认,提示成功。
- 我的 nft 页面展示
其他小白可能不知道的概念#
Mint#
可以理解为初次购买,比如购买一件艺术品,你直接从艺术家手上买就叫 Mint,你的钱会直接打给艺术家,以支持艺术家,区别从二手商或者其他买家手上买,二手商或者买家会赚取差价。从技术角度说,mint 是指在区块链网络上从零地址 (0x0000...) 创造新的数字资产并分配给用户,区别于 transfer,transfer 是将一个资产从一个地址转移到另一个地址。
什么是 Gas? 为什么要 Gas?#
这个问题非常经典,我第一次接触区块链的时候也是一脸懵逼,为啥转个账还要手续费?而且这个手续费还忽高忽低的,有时候几块钱有时候几十块钱,简直比银行还黑!
其实 Gas 就是以太坊网络的 "手续费",但它不是简单的手续费这么简单。想象一下,以太坊就像一个全球共享的超级计算机,你要让这台计算机帮你做事(比如转账、执行智能合约),就需要付费给那些维护这台计算机的人(矿工 / 验证者)。
技术的说,每个操作都需要消耗计算资源,Gas 就是衡量这些资源消耗的单位。转个账可能只需要 21,000 Gas,但如果你要执行一个复杂的智能合约(比如在 Uniswap 上交易),可能就需要几十万 Gas。Gas 价格(Gas Price)会根据网络拥堵程度实时变化,网络忙的时候价格就高,闲的时候价格就低,就像打车的峰时价格一样。
所以为什么要 Gas?简单说就是防止有人恶意攻击网络(比如无限循环的代码),同时激励矿工 / 验证者维护网络安全。没有 Gas,整个网络早就被各种垃圾交易搞瘫痪了。
为什么要等待区块确认?#
为什么我点了按钮,还要等个十几秒甚至几分钟才能看到结果?这体验也太差了吧!
但其实这是区块链去中心化的必然结果。传统的中心化系统,比如支付宝,你转账的时候只需要支付宝的服务器确认一下就行了,秒到账。但区块链不一样,它没有一个中央服务器,而是由全世界成千上万个节点共同维护的。
具体来说,当你发起一笔交易时,这笔交易会被广播到整个网络,然后等待被矿工(或验证者)打包到新的区块里。以太坊大概每 12-15 秒产生一个新区块,所以你的交易最快也要等一个区块时间才能被确认。
但这还没完,为了防止链重组(简单说就是区块链的分叉回滚,为什么链会重组就说来话长了... 大家可以自己查一下吧),通常建议等待多个区块确认。比如交易所通常要求 12 个区块确认才认为一笔交易是最终的,这就是为什么有时候要等几分钟的原因。
不过对于 DAPP 来说,通常等 1-2 个区块确认就够了,大概就是十几秒到一分钟的时间。虽然比中心化系统慢,但换来的是去中心化、抗审查、全球无界的特性,我觉得这个 trade-off 还是值得的。
推荐资源#
EVM 生态 DApp 脚手架
Learn Blockchain, Solidity, and Full Stack Web3 Development with JavaScript
web3 实习计划
关于将助记词转换为 ETH 地址,你所需要知道的一切
结语#
其实想说的还是很多的,但围绕 “入门” 展开的话又说不了太深。想入门区块链没那么简单,半小时只能带大家 “略读” 一下 DApp 开发这本厚书,还是得多实战,多尝试!
欢迎私信我的推特,一起交流!