web3全栈开发指南(web全栈开发进阶之路)-ag凯发k8国际

本文将详细介绍web3 dapp开发的架构、技术,以及使用哪些工具,并给出完整示例。

我们先介绍web3用到的技术:

区块链,以以太坊(ethereum)为主流,也包括solana、aptos等其他非evm链。区块链本身是软件,需要运行在一系列节点上,这些节点组成p2p网络或者半中心化网络。节点不仅负责接收新的输入并生成新的区块,还需要存储区块链运行时产生的所有数据,并负责同步、对外提供查询等rpc服务。

钱包:帮助用户管理自己在区块链上资产的软件,加密存储用户的私钥。当用户需要和区块链交互时,就需要用到私钥签名;

智能合约:运行在区块链上的一段托管程序,主要用来和外部账户交互;

ui:这里特指前端页面,因为直接通过rpc调用合约仅限个别高级用户,普通用户仍需要一个前端页面,并通过javascript脚本配合钱包签名与合约交互。

因为区块链上所有数据全透明,要查询任意区块的数据,可以通过etherscan这个网站查。其他公链,无论是与ethereum兼容的bsc、polygon,还是不兼容的solana、aptos等,也提供类似xxxscan这样的查询网站。这些scan能提供基本的读写合约的能力,有助于开发阶段的测试。

区块链本身以及钱包、etherscan等属于基础设施,如何基础设施不在本文讨论范围之内。本文仅限定如何开发dapp。

一个完整的dapp需要开发以下部分:

智能合约:将逻辑写入合约,并部署在链上;
ui:为用户提供一个交互式ui,配合钱包完成特定功能。
此外,对后端开发有经验的同学应该知道,通常来说,app数据会存储在数据库中,前端与后端交互,离不开后端对数据的查询和修改。在dapp中,同样需要一个查询的“后端”,但这个后端通常不是数据库。

有的同学会认为,既然节点本身提供了查询链上全部数据的prc接口,那么,前端直接查询节点是否可行?答案是不行。因为节点提供的数据,是用户产生的原始日志。

举个例子,假设有个nft合约,允许用户创建nft,那么,一段时间内,节点产生的日志如下:

用户a创建了nft-1;
用户b创建了nft-2;
用户b把nft-2转移给了用户x;
用户c创建了nft-3;

这些未经聚合处理的数据没法生成一个不断更新的数据库表:

owner nft id
用户a 1
用户x 2
用户c 3
所以,一个dapp除了前端外,还需要一个后端服务,它主要功能是不断聚合链上产生的数据,并根据dapp的业务需求设计表结构以方便查询。

一个直观的想法是用java或者go语言等编写一个后端服务,再配上一个数据库,就可以为前端提供rest api来实现查询。只不过自己维护后端服务比较麻烦,还需要租用云端服务器、数据库等资源,费时费力。

我们推荐另一种后端服务:the graph。它本身也可看作是一个基础设置。the graph可以让我们部署一个graph查询服务,如何定义表结构以及如何更新则由我们提供一个预编译的wasm。整个配置、wasm代码以及查询服务都托管在the graph中,无需自己搭建服务器,非常方便。

因此,一个完整的dapp架构如下:

┌───────┐
┌───────────│ dapp │───────────┐
│ └───────┘ │
│ read/write query │
│ contract data │
▼ ▼
┌───────┐ ┌───────┐
│wallet │ │ graph │
└───────┘ └───────┘
│ ▲
│ sign index │
│ broadcast data │
│ │
│ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ ┌────┐ ┌────┐ ┌────┐ │ │
└──┼▶│node│ │node│ … │node│───┘
└────┘ └────┘ └────┘ │
│ ethereum
─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
我们看看开发各个组件所需的技能树需求。由于本文仅讨论eth以及eth兼容链的dapp开发,所以,以下技能树仅适用于eth系:

合约开发:使用solidity语言;
合约部署工具:可以选择hardhat、truffle或foundry,建议使用hardhat;
数据聚合服务:选择the graph提供的托管服务;
数据聚合开发:the graph给出的模板代码是typescript,因此这里使用typescript;
前端页面:html javascript/typescript,也可配合任意前端框架如vue、react等;
合约交互框架:虽然理论上使用json rpc就可以读写合约,但使用ethers.js可以大大简化开发;
钱包支持:如果仅支持metamask,则使用ethers.js已足够,如果要支持多种钱包,尤其是需要连接手机钱包,则需要使用web3modal。
综上所述,我们可以总结一个基本的web3全栈开发技术需求:

solidity语言;
javascript语言;
typescript语言;
html/css等前端技能。
以及用到的服务:

将所有源码托管在github
使用the graph提供的hosted service;
使用github page实现静态页托管;
可选:绑定一个域名。
下面我们以一个具体的项目来演示web3全栈开发的完整流程。该项目允许用户在polygon上创建属于自己的nft卡片,并可在页面查看链上铸造的所有nft卡片:

图片

polygon是兼容以太坊的pos链,特点是gas便宜,速度快,领测试币方便。

编写合约

创建web3 dapp的第一步是编写合约。我们使用hardhat工具,它是node.js开发的,确保本机安装了node.js和npm,先安装solidity编译器:

$ npm install -g solc
$ solc –version
solc, the solidity compiler commandline interface
version: 0.8.17
然后创建目录web3stack并安装hardhat:

$ mkdir web3stack
$ cd web3stack
$ npm install –save-dev hardhat
然后输入命令npx hardhat开始创建一个新的合约项目:

$ npx hardhat
hardhat提示选择项目类型:

? what do you want to do? …
❯ create a javascript project
create a typescript project
create an empty hardhat.config.js
quit
这里选择javascript项目。后续接着提示项目根目录、是否添加.gitignore、是否安装相关依赖等:

✔ what do you want to do? · create a javascript project
✔ hardhat project root: · /path/to/web3stack
✔ do you want to add a .gitignore? (y/n) · y
✔ do you want to install this sample project's dependencies with npm (@nomicfoundation/hardhat-toolbox)? (y/n) · y
全部按默认值来。完成后查看package.json应该有两个dev依赖:

{
"devdependencies": {
"@nomicfoundation/hardhat-toolbox": "^2.0.1",
"hardhat": "^2.12.7"
}
}
hardhat默认创建了一个lock合约,以及相关配置。我们可以自己再写一个card合约:

// spdx-license-identifier: gpl-v3
pragma solidity =0.8.17;

import "@openzeppelin/contracts/token/erc721/erc721.sol";

contract card is erc721 {

}
直接编译:

$ npx hardhat compile
error hh411: the library @openzeppelin/contracts, imported from contracts/card.sol, is not installed. try installing it using npm.
编译提示找不到library报错,因为我们引用了openzeppelin的库,所以要先用npm安装一下:

npm install –save @openzeppelin/contracts
这条命令会在package.json中添加一个新的依赖:

{

"dependencies": {
"@openzeppelin/contracts": "^4.8.1"
}
}
再次编译:

npx hardhat compile
生成的合约存放在artifacts/contracts/card.sol/card.json,它包括了abi接口、字节码等所有信息。部署合约就是把字节码部署到链上。

hardhat提供了一个示例代码script/deploy.js用于部署lock合约,我们可以仿照这个脚本,复制一份script/deploy-card.js来部署card合约:

const hre = require("hardhat");

async function main() {
// 合约工厂:
const card = await hre.ethers.getcontractfactory("card");
// 部署:
const card = await card.deploy();
await card.deployed();
// 打印部署的地址:
console.log(`card deployed to ${card.address}`);
}

main().catch((error) => {
console.error(error);
process.exitcode = 1;
});
部署时,直接运行脚本:

$ npx hardhat run scripts/deploy-card.js
但是,我们并没有在本地配置任何关于链的信息,也没有配置私钥等账户信息,这个合约是不可能部署到链上的,那它部署到哪了?

实际上合约默认部署到hardhat提供的“虚拟javascript环境”,它可以在本地用node执行合约代码,主要用于测试。要部署到真实的链上,我们首先要在hardhat.config.js中加一点关于链的配置:

module.exports = {

// 定义所有的链:
networks: {
// 定义名为testnet的链:
testnet: {
// 配置私钥:
accounts: ['0x72b3…bd2b'],
// 配置为polygon testnet节点的prc:
url: "https://matic-mumbai.chainstacklabs.com"
}
},

}
这里为了方便我们把私钥直接写进配置里,实际开发可从环境变量读取。在部署前,确保私钥对应的地址有一点matic测试币。可以从这里领测试币。然后用带–network参数的命令部署:

$ npx hardhat run scripts/deploy-card.js –network testnet
如果部署成功,则显示card合约被部署的地址:

card deployed to 0x8131aa1b766966f9f8ec3e1132d9d29d92311ab0
在polygonscan上就能查看该合约。顺便可以将合约源码在polygonscan上验证,验证后即可在polygonscan对合约进行基本的读写操作。

开发dapp ui

dapp ui就是前端页面,既可以手写html javascript,也可以使用react、vue等任何前端框架与webpack等前端工具。为了简化开发流程,这里我们直接手写一个index.html页,让用户能在页面创建一个nft。

页面引入的第三方库包括jquerybootstrap css、vue,以及用于合约交互的ethers.js:


安装了metamask插件后,页面会获得一个注入的window.ethereum全局变量,通过此变量与钱包进行交互,例如,连接钱包:

await window.ethereum.request({
method: 'eth_requestaccounts',
});
调用合约方法则使用ethers.js。下面的代码创建了card合约并调用mint()方法创建nft:

async function () {
// 创建合约:
let contract = new ethers.contract(
// 合约地址:
'0x8131aa1b766966f9f8ec3e1132d9d29d92311ab0',
// 合约的abi接口
'[{"inputs":…]',
// 钱包签名对象:
new ethers.providers.web3provider(window.ethereum, "any").getsigner()
);
// 调用mint()方法:
let tx = await contract.mint();
// 等待1个确认:
await tx.wait(1);
// todo: 解析tx的日志并拿到tokenid
}
异步调用mint()方法时,会拉起metamask,提示用户对交易进行签名。签名后返回tx对象代表已发送的交易。等待1个确认后,如果要获取交易信息,则需要解析tx的日志以便拿到token id等信息。

最后注意到合约的abi接口包含了合约完整的读写方法以及输入输出,它是一个json对象,这里以字符串形式传入。card合约的abi可以在card.json中找到并复制出来,不过我们可以使用hardhat的一个插件直接输出abi文件。我们先用npm安装插件:

$ npm install –save-dev hardhat-abi-exporter
然后在hardhat.config.js中添加插件配置:

// 用require引入插件:
require('hardhat-abi-exporter');

module.exports = {

// 使用abi exporter插件:
abiexporter: {
// 输出到abi目录:
path: "./abi",
clear: false,
flat: true,
pretty: false,
// 编译时输出:
runoncompile: true,
}
};
再运行一次编译,我们就可在abi目录下看到若干.json文件。找到card.json,整理下格式,变成一个字符串粘贴至html:


window.nft_abi = '[{"inputs":[],"statemutability":"nonpayable","type":"constructor"}…';

这样,通过前端页面,就可以调用合约方法。通过mint()方法写入后,nft已经生成,在polygonscan查找对应的tx可查看详细信息。通过polygonscan的read contract页面调用getimage()可获得nft图片信息:

图片

把返回的data:image/svg…复制到浏览器的地址栏,可查看图片:

图片

也可在opensea等第三方nft市场看到该nft的图片。

不过我们还有最后一个问题,就是如何在我们自己的页面上展示用户创建的nft。有的同学会想到在页面调用card合约的读方法,依次读出每个nft的信息,这种方式会非常慢,因为需要反复多次调用读方法,且无法实现条件查询,比如根据地址查询该地址拥有的nft,或者创建于一个月内的nft。因此,我们还需要用到the graph提供的数据聚合服务。

创建graph查询

为了创建graph查询,我们需要使用the graph提供的托管服务。先注册the graph,然后安装全局命令行工具,只需运行一次:

npm install -g @graphprotocol/graph-cli
安装后可使用命令graph,例如查看版本:

$ graph –version
0.38.0
第二步是在the graph的hosted service – my dashboard – add subgraph创建一个项目,创建成功后the graph显示状态为未部署(not deployed)。为了把subgraph部署上去,我们在本地项目根目录创建一个subgraph目录,然后在此目录下执行命令:

$ graph init –product hosted-service michaelliao/web3stack
注意将登录名替换为你的github用户名,将项目名替换为the graph上创建的项目名。

接下来依次输入信息:

选择协议的类型:选ethereum;
填写subgraph名称:用默认的名称;
填写目录名:用默认目录名;
选择链:选mumbai(polygon的测试链);
填写合约地址:填入部署的地址0x8131…1ab0;
尝试自动获取abi,一般都能成功;
填写从指定的块开始:查看合约部署的tx所在块填入区块高度;
填写合约名字:与合约代码保持一致,此处必须为card;
是否索引事件:默认是。
接下来会安装一系列依赖。如果填写的信息有问题,或者npm运行出错,删掉subgraph目录再来一遍即可。

然后按照提示,先运行graph auth输入the graph给的一个access token,然后进入subgraph/web3stack目录,运行:

npm run deploy
几秒钟后,就可以在the graph提供的playground输入查询并查看结果:

图片

为什么我们可以直接查询transfers?首先,我们查看schema.graphql,默认有3个entity,把entity看作是数据库表,这3个entity是the graph根据合约定义的event自动生成的:

type approval @entity(immutable: true) {

}

type approvalforall @entity(immutable: true) {

}

type transfer @entity(immutable: true) {

}
但并不是我们的业务需要的。我们需要的是card类型,包括owner、image等信息。因此,删掉自动生成的代码,换成我们自定义的card:

type card @entity(immutable: false) {
id: string!
owner: bytes!
blocknumber: bigint!
blocktimestamp: bigint!
transactionhash: bytes!
image: string!
}
其中,id是唯一主键,这里用nft的token id即可,但类型是string而不是bigint

接下来,在subgraph.yaml中定义了如何处理事件,需要修改的有两处,一是entities,删除原有的3个entity,换成我们定义的card:

datasources:
– kind: ethereum

mapping:

entities:
– card
二是在eventhandlers中删除我们不关心的approval和approvalforall事件,仅保留transfer:

datasources:
– kind: ethereum

mapping:

eventhandlers:
– event: transfer(indexed address,indexed address,indexed uint256)
handler: handletransfer
当the graph的节点扫描到我们部署的合约产生了transfer事件后,它将调用handletransfer处理,这个函数定义在src/card.ts中,自动生成的代码如下:

export function handletransfer(event: transferevent): void {
let entity = new transfer(
event.transaction.hash.concati32(event.logindex.toi32())
)
entity.from = event.params.from
entity.to = event.params.to
entity.tokenid = event.params.tokenid

entity.blocknumber = event.block.number
entity.blocktimestamp = event.block.timestamp
entity.transactionhash = event.transaction.hash

entity.save()
}
因此,每捕获到一个transfer事件,会保存一个transfer的entity,这就是我们前面在the graph的playground中能查询transfers的原因。

现在我们不需要transfer这个entity,改成card,先定义card这个entity:

export class card extends entity {

}
再修改handletransfer()的代码:

export function handletransfer(event: transferevent): void {
let tokenid = event.params.tokenid;
let contract = cardcontract.bind(event.address);
if (event.params.from.equals(address.zero())) {
// 如果from=0,表示创建了新的nft:
let nft = new card(tokenid.tostring());
nft.owner = event.params.to;
nft.image = contract.getimage(tokenid);

nft.blocknumber = event.block.number;
nft.blocktimestamp = event.block.timestamp;
nft.transactionhash = event.transaction.hash;

nft.save();
} else {
// from!=0,表示nft发生了转移,需要更新owner和image:
let nft = card.load(tokenid.tostring());
if (nft === null) {
log.error('failed load nft by token: {}', [tokenid.tostring()]);
} else {
nft.owner = event.params.to;
nft.image = contract.getimage(tokenid);
nft.save();
}
}
}
再次运行npm run deploy,我们可以在the graph的playground中查询到cards:

图片

这样,支持页面显示的后端查询服务就准备就绪。

下一步,我们在页面中添加一点查询代码:

async function query() {
let query = {
"query":
// 输入为graph查询字符串:
`
{
cards(first: 20, orderby: blocktimestamp, orderdirection: desc) {
id
owner
image
}
}`
};
// 调用jquery发送post请求并获得json结果:
let opt = {
type: 'post',
datatype: 'json',
contenttype: 'application/json',
// 与graph服务接口保持一致:
url: 'https://api.thegraph.com/subgraphs/name/michaelliao/web3stack',
data: json.stringify(query)
};
let result = await $.ajax(opt);
let cards = result.data.cards;
}
拿到查询结果,我们就能直接在页面展示:

图片

最后一步就是把页面发布到github pages,然后绑一个域名,就可以让用户访问了:

https://web3stack.itranswarp.com

至此,一个完整的dapp就开发完毕。

faq

q:可以不用the graph,自己写后端服务吗?

a:可以,很多需求,例如用户实名认证、发送email是the graph服务无法支持的,必须自己编写后端服务,配合数据库实现。

q:可以同时支持多链吗?

a:可以,用户在钱包切换链时,dapp可以通过chainchanged事件拿到链id,提前配置好链id与不同链的合约地址,就可以正常在不同链调用不同合约。

q:可以支持多种钱包吗?

a:可以,需要使用web3modal这个库,能简化连接多个钱包的代码。

小结

从本文给出的完整示例来看,web3全栈开发,最适合前端开发人员。当年国外有个前端开发叫hayden,在17年失业了,他决定自学solidity并花了几个月的时间为以太坊开发了一个defi应用,后来这个应用火爆了,它叫uniswap。

ag凯发k8国际的版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

(0)
上一篇 2023年4月25日 上午8:12
下一篇 2023年4月25日 上午8:22

相关推荐

  • 中华人民共和国民政部令 第66号 《养老机构管理办法》已经2020年8月21日民政部部务会议通过,现予公布,自2020年11月1日起施行。 2020年9月1日 第一章 总 则 第一…

    科研百科 2024年6月14日
    22
  •   今天小编推荐一个基于php开发的开源任务管理工具,该工具会提供各类文档协作功能、在线思维导图、在线流程图、项目管理、任务分发、即时 im,文件管理等等。该开源项目使用到 vue…

    科研百科 2022年12月10日
    189
  • 中证网讯(记者 彭扬)近期,数字人民币结合智能合约的应用实践得到了广泛关注。中国证券报记者从公开数据了解到,人民银行数字货币研究所申请了8篇标题含有“智能合约”字眼的专利,大部分于…

    科研百科 2023年5月20日
    201
  •   编者按:0iy   “十二五”规划即将落幕,“十三五”规划渐行渐近。回望“十二五”,我省农牧业发展提质增…

    科研百科 2022年5月31日
    339
  • 客户关系管理app:提升客户体验的数字化工具 随着数字化时代的到来,客户关系管理(crm)应用程序已经成为企业获取、跟踪和管理客户信息和需求的不可或缺的工具。客户关系管理应用程序可…

    科研百科 2024年6月5日
    32
  • 3月29日,2022房地产开发企业综合实力测评成果正式发布,备受关注的“房地产开发企业综合实力top500”等测评榜单揭晓。这项由中国房地产业协会、上海易居房地产研究院共同主办的测…

    2022年7月4日
    1.7k
  • 在工作中,我们经常会推进一些项目,每个事项需要按时完成,整体才能正常推进,为了更好的管理项目推进,我们可以用excel甘特图来监督每个过程的进度,效果如下: 当我们日期更新的时候,…

    科研百科 2024年6月11日
    31
  • 剑桥大学经济系ag凯发k8国际官网宣布:将在24fall开设全新的跨学科硕士学位课程——mphil in economics and data science! 目前该学科已获得批准,将于2024…

    科研百科 2024年6月22日
    12
  • 来源:【四川日报-川观新闻】 川观新闻记者 王眉灵 近日,四川省公路生态环境工程技术研究中心正式揭牌成立。这是四川省首个公路生态环境科研平台,获得了省科技厅认定。该平台的诞生,将给…

    科研百科 2022年7月27日
    246
  • 游戏美术生产工业化:项目管理-u0026数据管理

    以下文章来源于腾讯游戏学堂 ,作者gandalf mu 笔者曾在影视后期pipeline领域工作多年,后来转至游戏行业并继续从事pipeline方向的工作。影视pipeline百花…

    科研百科 2022年12月18日
    192
网站地图