# Web3 开发极简教程001:一文快速学会DAPP前端开发 **Published by:** [nobodyjack.eth](https://paragraph.com/@nobodyjack-eth/) **Published on:** 2022-08-13 **URL:** https://paragraph.com/@nobodyjack-eth/web3-001-dapp ## Content 文:nobodyjack (Twitter: @ethcrap) 前言 最近开始了解web3 DAPP开发,了解到一个完整DAPP开发主要包含如下两部分: 智能合约:通常指代运行在 EVM 兼容网络中的 Solidity 或其他合约语言代码,负责与用户交易我们发行的资产并储存 DApp 的链上状态。 DAPP前端:负责应用程序界面及与智能合约交互,它们大部分是 Web APP,可以用原生前端语言或流行框架例如 React/Vue 来进行编写。 我没什么编程基础,所以放弃直接从学习智能合约入门,转而先从DAPP前端开始学习,这样入门学习会简单很多,反馈也更快,可以避免自己中途放弃(挑软柿子捏更容易成功,持续反馈是坚持学习的关键)。 我一直非常推崇费曼学习法,简化概念、以教代学,因此后面我会持续更新本系列教程,希望能帮助自己和他人学习。 我也是小白,如有错误,欢迎指教! 基础&准备 1)理解Signer、JSON RPC、Provider与Metamask 为了让大家对DAPP开发有个全貌了解,先祭一张DAPP架构神图 对于DAPP前端开发,图中有几点至关重要的概念需要理解: Provider:节点提供者,这是一个 DApp 架构中特殊的角色,它负责与区块链进行通信,它提供对区块链及其状态的只读访问。 Signer:签名者,一个至关重要的角色,它负责签署消息和交易,链上所有交易都需要签名才能执行。 Ethereum JSON-RPC:以太坊通信协议接口,所有的节点客户端都需要实现该接口,该接口提供一组统一的方法供DAPP调用,实现DAPP与链进行交互(读取区块链数据或发送交易信息到链上)。 Metamask作为一个浏览器扩展插件,既是Provider,也是一个Signer。 2)环境与工具 Chrome浏览器(不建议用其他浏览器) 安装MetaMask钱包插件,并创建或导入账号,切换到测试网Ropsten节点 测试网领水:https://faucet.paradigm.xyz https://faucet.metamask.io/ etherscan:以太坊区块浏览器 3)技术栈 html、css javascript ethers.js:以太坊区块链及其生态系统进行交互的JavaScript库 需求分析 一个简单的DAPP 前端Demo,主要涉及如下几个通用功能: 登录 连接钱包(MetaMask) 签署消息 转账 调用智能合约只读方法 调用智能合约写方法 页面 本教程为了简便,直接用HTML+CSS,并在HTML中引入ethers.js <script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js" type="application/javascript"></script> 做了一个毫无设计的页面,大家可以不用参照,可以自己设计😂 登录:连接钱包、签署消息 登录是最常见的功能之一,不少应用都会要求用户登录才能使用应用或部分功能。与传统应用不同的是,对于一些 DAPP,你只需要一个钱包就可以访问它,它完全没有帐户、用户名、密码、个人数据。 也就是说,对于DAPP来说,登录意味着“连接钱包、访问钱包中的账户” 另外,为了保证安全,DAPP通常也会要求用户在钱包签署一条消息 ,以验证账户控制权限,而不是他人使用用户的钱包/资金进行欺诈行为。 简化流程: 登录 —> 连接钱包 —> 签署消息 —> 使用DAPP 1)连接钱包,获取当前账户 //HTML <button class="btn btn-primary btn-lg btn-block mb-3" id="connectButton" > Connect Wallet</button> <p class="info-text alert alert-success">Account : <span id="showAccount"></span> </p> MetaMask将一个全局Provider注入浏览器每个页面的window.ethereum,ethers库提供一个方法可将该Provider包装成一个新的可供 ethers.js 使用的Web3Provider。 “连接”到 MetaMask 实际上意味着“访问用户的以太坊帐户”: //javacriptconst connectButton = document.querySelector('#connectButton');const showAccount = document.querySelector('#showAccount'); var currentAccount =''; connectButton.addEventListener('click', () => { getAccount();}); async function getAccount() { // const accounts = await ethereum.request({ method: 'eth_requestAccounts' }); if (window.ethereum) { const provider = new ethers.providers.Web3Provider(window.ethereum); const accounts = await provider.send("eth_requestAccounts", []); currentAccount = accounts[0]; showAccount.innerHTML = currentAccount; } else { alert('Please install MetaMask '); } } 签署消息 要签署消息,必须先获得一个signer实例,然后通过signMessage方法签署消息。 //HTML <button class="btn btn-primary btn-lg btn-block mb-3" id="signButton">Sign Message</button> <p class="info-text alert alert-warning">Result : <span id="signResult"></span> </p> //javacriptconst signButton = document.querySelector('#signButton') const signResult = document.querySelector('#signResult') signButton.addEventListener('click', () => { signMessage();}); async function signMessage() { if (currentAccount !== '') { console.log(typeof account); const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner(); const result = await signer.signMessage("Hello World"); signResult.innerHTML = result; } else { alert('Please connect to MetaMask. '); } } 转账:发送ETH signer提供了一个sendTransaction方法用于发送交易: //HTML <div class="card full-width"> <div class="card-body"> <h4 class="card-title"> Send ETH </h4> <div class="form-group"> <label>To</label> <input class="form-control" type="text" id="toInput" value="0x83BFFEe57cE413f846d54bb8d7A55b4F6d475F8E" /> </div> <div class="form-group"> <label>Amount</label> <input class="form-control" type="text" id="amountInput" value="0" /> </div> <div class="form-group"> <label>Data</label> <input class="form-control" type="text" id="dataInput" /> </div> <button class="btn btn-primary btn-lg btn-block mb-3" id="submitForm">Submit</button> </div> </div> //javacript const toInput = document.querySelector('#toInput'); const amountInput = document.querySelector('#amountInput'); const dataInput = document.querySelector('#dataInput'); const submitForm = document.querySelector('#submitForm'); submitForm.addEventListener('click', () => { sendETH(); }); async function sendETH() { if (window.ethereum) { const amount = amountInput.value.trim(); const to = toInput.value.trim(); let data = dataInput.value.trim(); if (to !== '' && amount !== '') { const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner(); const tx = {}; tx.to = to; tx.value = ethers.utils.parseEther(amount); if (data != '') { tx.data = ethers.utils.isHexString(data) ? data : '0x' + data; } await signer.sendTransaction(tx).catch(err =>{ alert(err.message); }); } else { alert('to or amount can not blank'); } } else { alert('Please connect to MetaMask. '); }} 调用合约的只读方法 调用只读方法(由Solidity 表示pure或view在 Solidity 中表示),可以由节点返回结果,因此它是免费的,但不能更改区块链状态。 调用合约的方法,需要合约的ABI和地址,我们直接在etherscan.io找个Ropsten测试网的开源合约,可以查到合约的源代码、ABI及合约,这里以NFT Doodles 的合约为例: https://ropsten.etherscan.io/address/0xa86ea6e910ed86ad53807f533e1550b569b47ad4#code 调用合约的balanceof方法,查询地址持有该NFT的mint数量: //HTML <div class="card full-width"> <div class="card-body"> <h4 class="card-title"> Mint </h4> <div class="form-group"> <label>Num</label> <input class="form-control" type="text" id="numInput" value="0x83BFFEe57cE413f846d54bb8d7A55b4F6d475F8E" /> </div> <button class="btn btn-primary btn-lg btn-block mb-3" id="mintButton"> Mint </button> <p class="info-text alert alert-warning"> Hash Result: <span id="mintResult"></span> </p> </div> </div> //javacript queryButton.addEventListener('click', () => { const owner = ownerInput.value.trim(); queryAvailableNum(owner);}); async function queryAvailableNum(owner) { const provider = new ethers.providers.Web3Provider(window.ethereum); const url = 'abi.json'; const abi = await getABI(url); console.log(abi); const contract_adress = '0xA86eA6e910ED86aD53807F533e1550B569B47AD4'; const contract = new ethers.Contract(contract_adress, abi, provider); const num = await contract.balanceOf(owner) console.log(num); nftNum.innerHTML = num;} async function getABI(url) { const res = await fetch(url); return await res.json();} 调用合约的写方法 调用写方法签署交易,并需要支付给矿工费用,该交易将由整个网络上的每个节点以及矿工验证,矿工将在针对当前状态执行区块链后计算区块链的新状态。 //HTML <div class="card full-width"> <div class="card-body"> <h4 class="card-title"> Query Doodle Num </h4> <div class="form-group"> <label>owner</label> <input class="form-control" type="text" id="ownerInput" value="0x83BFFEe57cE413f846d54bb8d7A55b4F6d475F8E" /> </div> <button class="btn btn-primary btn-lg btn-block mb-3" id="queryButton"> Query </button> <p class="info-text alert alert-warning"> Doodle Num: <span id="nftNum"></span> </p> </div> </div> //javacript <div class="card full-width"> <div class="card-body"> <h4 class="card-title"> Mint </h4> <div class="form-group"> <label>Num</label> <input class="form-control" type="text" id="numInput" value="0x83BFFEe57cE413f846d54bb8d7A55b4F6d475F8E" /> </div> <button class="btn btn-primary btn-lg btn-block mb-3" id="mintButton"> Mint </button> <p class="info-text alert alert-warning"> Hash Result: <span id="mintResult"></span> </p> </div> </div> 完整实现 把以上的功能组合起来就可以实现一个简单完整的DAPP前端Demo,这里我只是抛砖引玉,剩下的工作就交给你们了。 如果你觉得对你有用,请关注我 Mirror: https://mirror.xyz/0x83BFFEe57cE413f846d54bb8d7A55b4F6d475F8E Twitter: https://twitter.com/ethcrap Medium: https://medium.com/@nobodyjack2050 ## Publication Information - [nobodyjack.eth](https://paragraph.com/@nobodyjack-eth/): Publication homepage - [All Posts](https://paragraph.com/@nobodyjack-eth/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@nobodyjack-eth): Subscribe to updates