文:nobodyjack (Twitter: @ethcrap)
最近开始了解web3 DAPP开发,了解到一个完整DAPP开发主要包含如下两部分:
智能合约:通常指代运行在 EVM 兼容网络中的 Solidity 或其他合约语言代码,负责与用户交易我们发行的资产并储存 DApp 的链上状态。
DAPP前端:负责应用程序界面及与智能合约交互,它们大部分是 Web APP,可以用原生前端语言或流行框架例如 React/Vue 来进行编写。
我没什么编程基础,所以放弃直接从学习智能合约入门,转而先从DAPP前端开始学习,这样入门学习会简单很多,反馈也更快,可以避免自己中途放弃(挑软柿子捏更容易成功,持续反馈是坚持学习的关键)。
我一直非常推崇费曼学习法,简化概念、以教代学,因此后面我会持续更新本系列教程,希望能帮助自己和他人学习。
我也是小白,如有错误,欢迎指教!
为了让大家对DAPP开发有个全貌了解,先祭一张DAPP架构神图
对于DAPP前端开发,图中有几点至关重要的概念需要理解:
Provider:节点提供者,这是一个 DApp 架构中特殊的角色,它负责与区块链进行通信,它提供对区块链及其状态的只读访问。
Signer:签名者,一个至关重要的角色,它负责签署消息和交易,链上所有交易都需要签名才能执行。
Ethereum JSON-RPC:以太坊通信协议接口,所有的节点客户端都需要实现该接口,该接口提供一组统一的方法供DAPP调用,实现DAPP与链进行交互(读取区块链数据或发送交易信息到链上)。
Metamask作为一个浏览器扩展插件,既是Provider,也是一个Signer。
Chrome浏览器(不建议用其他浏览器)
安装MetaMask钱包插件,并创建或导入账号,切换到测试网Ropsten节点
测试网领水:https://faucet.paradigm.xyz https://faucet.metamask.io/
etherscan:以太坊区块浏览器
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
//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. ');
}
}
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:
Medium:

