您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關如何使用Ether.js構建一個簡單的DApp,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
如果你已經在以太坊上開發過DApp,那你在前端JavaScript中可能用過web3.js。Ethers.js則是一個輕量級的web3.js替代品,我們將學習如何使用Ether.js構建一個簡單的DApp。
與Web3.js相比,Ethers.js有很多優點,其中我最喜歡的一個特性是Ethers.js提供的狀態和密鑰管理。Web3的設計場景是DApp應該連接到一個本地節點,由這個節點負責保存密鑰、簽名交易并與以太坊區塊鏈交互。現實并不是這樣的,絕大多數用戶不會在本地運行一個geth節點。Metamask在瀏覽器 應用中有效地模擬了這種節點環境,因此絕大多數web3應用需要使用Metamask來保存密鑰、簽名交易并完成與以太坊的交互。
Ethers.js采取了不同的設計思路,它提供給開發者更多的靈活性。Ethers.js將“節點”拆分為兩個不同的角色:
錢包:負責密鑰保存和交易簽名
提供器:負責以太坊網絡的匿名連接、狀態檢查和交易發送
在這個教程中我們將與一個ERC20智能合約交互,你需要在機器里先安裝nodejs和npm。
首先創建一個文件夾ethers-template,然后再這個文件夾里再創建另一個contracts文件夾:
~$ mkdir -p ethers-template/contracts
然后進入ethers-template目錄初始化npm配置:
~$ cd ethers-template ~/ethers-template$ npm init -y
接下來創建一個config.json文件保存你的項目配置:
{ "private_key": "24C4FE6063E62710EAD956611B71825B778B041B18ED53118CE5DA5F02E494BA", "network": "kovan", "ERC20": "0x0DEd9F7D82a24099F09AF7831CaB61B31Df10487", "name": "Kanchan Coin", "symbol": "SNK", "total_supply": "1000000000000000000000000", "decimals": 18 }
說明如下:
private_key:賬戶私鑰,將使用這個私鑰對應的賬戶在指定網絡上部署智能合約。
network:要接入的以太坊網絡,ethers.js支持以下網絡:
homestead:主網
rinkeby
ropsten
kovan
goerli
ERC20:聲明要交互的已部署合約,可選
name/symbol/decimals:ERC20合約的參數
現在可以安裝ethers.js:
~/ethers-template$ npm install --save ethers
為了編譯合約,我們還需要安裝solc和fs-extra:
~/ethers-template$ npm install fs-extra@8.1.0 solc@0.5.11 --save
在contracts目錄下創建文件erc20.sol:
~/ethers-template$ touch contracts/erc20.sol
并按如下內容修改:
pragma solidity ^0.5.0; contract ERC20 { using SafeMath for uint256; event Approval(address indexed tokenOwner, address indexed spender, uint tokens); event Transfer(address indexed from, address indexed to, uint tokens); mapping(address => uint256) balances; mapping(address => mapping (address => uint256)) allowed; string public symbol; uint8 public decimals; string public name; uint256 private _totalSupply; constructor(uint8 _decimals, string memory _symbol, string memory _name, uint256 _total_supply) public{ decimals = _decimals; symbol = _symbol; name = _name; _totalSupply = _total_supply; balances[msg.sender] = _totalSupply; } function totalSupply() public view returns (uint256) { return _totalSupply; } function balanceOf(address tokenOwner) public view returns (uint) { return balances[tokenOwner]; } function transfer(address receiver, uint numTokens) public returns (bool) { require(numTokens <= balances[msg.sender]); balances[msg.sender] = balances[msg.sender].sub(numTokens); balances[receiver] = balances[receiver].add(numTokens); emit Transfer(msg.sender, receiver, numTokens); return true; } function approve(address delegate, uint numTokens) public returns (bool) { allowed[msg.sender][delegate] = numTokens; emit Approval(msg.sender, delegate, numTokens); return true; } function allowance(address owner, address delegate) public view returns (uint) { return allowed[owner][delegate]; } function transferFrom(address owner, address buyer, uint numTokens) public returns (bool) { require(numTokens <= balances[owner]); require(numTokens <= allowed[owner][msg.sender]); balances[owner] = balances[owner].sub(numTokens); allowed[owner][msg.sender] = allowed[owner][msg.sender].sub(numTokens); balances[buyer] = balances[buyer].add(numTokens); emit Transfer(owner, buyer, numTokens); return true; } } library SafeMath { function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } }
下面的代碼使用solc編譯合約文件,將其保存為compile.js:
const path = require('path'); const fs = require('fs-extra'); const solc = require('solc'); const config = require('./config.json'); const sourceFolderPath = path.resolve(__dirname, 'contracts'); const buildFolderPath = path.resolve(__dirname, 'build'); const getContractSource = contractFileName => { const contractPath = path.resolve(__dirname, 'contracts', contractFileName); const source = fs.readFileSync(contractPath, 'utf8'); return source; }; let sources = {}; fs.readdirSync(sourceFolderPath).forEach(contractFileName => { sources = { ...sources, [contractFileName]: { content: getContractSource(contractFileName) } } }); const input = { language: 'Solidity', sources, settings: { outputSelection: { '*': { '*': [ '*' ] } } } } console.log('\nCompiling contracts...'); const output = JSON.parse(solc.compile(JSON.stringify(input))); console.log('Done'); let shouldBuild = true; if (output.errors) { console.error(output.errors); // throw '\nError in compilation please check the contract\n'; for(error of output.errors) { if(error.severity === 'error') { shouldBuild = false; throw 'Error found'; break; } } } if(shouldBuild) { console.log('\nBuilding please wait...'); fs.removeSync(buildFolderPath); fs.ensureDirSync(buildFolderPath); for (let contractFile in output.contracts) { for(let key in output.contracts[contractFile]) { fs.outputJsonSync( path.resolve(buildFolderPath, `${key}.json`), { abi: output.contracts[contractFile][key]["abi"], bytecode: output.contracts[contractFile][key]["evm"]["bytecode"]["object"] }, { spaces:2, EOL: "\n" } ); } } console.log('Build finished successfully!\n'); } else { console.log('\nBuild failed\n'); }
上面的代碼將讀入并編譯contracts目錄中的所有合約文件,然后將編譯得到的abi和字節碼保存為json文件。
現在我們使用compile.js來編譯erc20.sol合約:
~/ethers-template$ node compile.js
編譯結束后,我們得到如下的目錄結構;
+ethers-template +compile.js +contracts -erc20.sol +build -ERC.json -Context.json -IERC20.sjon -SafeMath.json -package.json
創建文件deploy.js:
~/ethers-template$ touch deploy.js
然后按如下內容修改:
const startTimestamp = Date.now(); const ethers = require('ethers'); const config = require('./config.json'); const fs = require('fs-extra'); const provider = ethers.getDefaultProvider(config["network"]); const wallet = new ethers.Wallet(config["private_key"], provider); console.log(`Loaded wallet ${wallet.address}`); let compiled = require(`./build/${process.argv[2]}.json`); (async() => { console.log(`\nDeploying ${process.argv[2]} in ${config["network"]}...`); let contract = new ethers.ContractFactory( compiled.abi, compiled.bytecode, wallet ); let instance = await contract.deploy(config["decimals"], config["symbol"], config["name"], config["total_supply"]); console.log(`deployed at ${instance.address}`) config[`${process.argv[2]}`] = instance.address console.log("Waiting for the contract to get mined...") await instance.deployed() console.log("Contract deployed") fs.outputJsonSync( 'config.json', config, { spaces:2, EOL: "\n" } ); })();
注意:
上面代碼中的默認網絡是kovan測試網
在這個測試網中,你的賬號需要一些以太幣來支付部署交易的手續費
將使用config.json中的private_key來部署合約
運行deploy.js腳本時,需要在命令行傳入要部署的合約名稱ERC20:
~/ethers-template$ node deploy.js ERC20
輸出結果如下:
Loaded wallet 0xC8e1F3B9a0CdFceF9fFd2343B943989A22517b26 Deploying ERC20 in kovan... deployed at 0x77Bb3546f5ee356E4026BaA96b7DDf22141bd77B Waiting for the contract to get mined... Contract deployed
在與合約交互時需要合約部署地址,上面的代碼會自動將合約部署地址保存到config.json文件中。
在這個教程中,我們使用ES6來編寫合約交互代碼,然后使用webpack和babel將ES6代碼轉換為ES5代碼。
首先安裝這些依賴項:
~/ethers-template$ npm i webpack webpack-cli @babel/core \ @babel/plugin-proposal-object-rest-spread \ @babel/preset-env babel-loader \ babel-polyfill -D
創建一個文件app.js:
~/ethers-template$ touch app.js
然后按照如下代碼修改:
const ethers = require('ethers'); const config = require('./config.json'); // Import the json file from build to get the abi const erc_json = require('./build/ERC20.json'); //import the json of the contract which you want to interact // You can use any standard network name // - "homestead" // - "rinkeby" // - "ropsten" // - "kovan" // - "goerli" const provider = ethers.getDefaultProvider(config['network']); // Make a wallet instance using private key and provider const wallet = new ethers.Wallet(config['private_key'] , provider); const address = config["ERC20"]; const abi = erc_json.abi; erc20 = new ethers.Contract( address , abi , wallet ); document.getElementById("send").onsubmit = async function(e) { e.preventDefault(); let address = document.getElementById("address").value; document.getElementById("status").innerText = "Waiting for transaction to get published..."; let tx = await erc20.functions.transfer(address, "1000000000000000000"); let tx_hash = tx.hash; let node = document.createElement("LI"); let link = document.createElement("A"); link.target = "_blank"; link.href = `https://${config["network"]}.etherscan.io/tx/` + tx_hash; let textnode = document.createTextNode(tx_hash); link.appendChild(textnode); node.appendChild(link); document.getElementById("transactions").appendChild(node); document.getElementById("status").innerText = "Waiting for transaction to be mined..."; await tx.wait(); document.getElementById("status").innerText = "Transaction confirmed"; return false; };
首先我們需要指定要使用的網絡/提供器:
const provider = ethers.getDefaultProvider(config['network']);
為了與合約交互,我們需要2個東西:
合約部署地址
合約ABI接口
在上面app.js中,我們從配置文件引入了合約地址,從合約編譯結果目錄中引入了合約ABI:
//import the json of the contract which you want to interact const erc_json = require('./build/ERC20.json'); const config = require('./config.json'); const address = config["ERC20"]; const abi = erc_json.abi;
為了創建合約實例,我們需要先創建一個錢包實例,這樣不管什么時候調用setter方法,都需要一個私鑰來簽名交易。在ethers.js中,你只需要創建錢包,所有的setter方法就會由這個錢包簽名。
const wallet = new ethers.Wallet(config['private_key'] , provider);
你也可以使用keystore和助記詞來創建一個錢包。如果你希望用這個錢包與智能合約交互,你還需要傳入提供器。如果你只是想用私鑰簽名消息,那么就不需要提供器了。
erc20 = new ethers.Contract( address , abi , wallet );
上面的代碼創建了一個合約實例,然后你就可以像這樣調用合約函數:
erc20.functions.function_name_in_smart_contract(parameters);
小白備注:在ERC20中定義了一個函數transfer,它的參數是轉賬地址和代幣數量。下面的代碼調用了合約的transfer函數,錢包將簽名這個交易,然后發布到指定的網絡中:
erc20.functions.transfer(address, "1000000000000000000");
注意:無論任何時候你要創建一個交易,錢包里都需要以太幣來支付交易手續費。
在package.json中添加如下內容:
"deploy": "node compile.js && node deploy.js ERC20", "build": "webpack — mode production',
修改后的package.json看起來是這樣:
{ "name": "ethers-template", "version": "1.0.0", "description": "", "main": "webpack.config.js", "scripts": { "deploy": "node compile.js && node deploy.js ERC20", "build": "webpack --mode production", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "ethers": "^4.0.37", "fs-extra": "^8.1.0", "solc": "^0.5.11" }, "devDependencies": { "@babel/core": "^7.6.0", "@babel/plugin-proposal-object-rest-spread": "^7.5.5", "@babel/preset-env": "^7.6.0", "babel-loader": "^8.0.6", "babel-polyfill": "^6.26.0", "webpack": "^4.40.2", "webpack-cli": "^3.3.9", "webpack-dev-server": "^3.8.1" } }
如果你后續修改了智能合約,就需要重新編譯和部署。可以用一個命令來完成合約的編譯和部署:
~/ethers-template$ npm run deploy
這個命令會自動修改配置文件和合約構件文件。這樣在與合約交互時,你就不需要修改合約地址或者ABI接口了。
如果你修改了app.js,也需要重新構建前端代碼:
~/ethers-template$ npm run build
這會生成新的發布版本:dist/bundle.js。
創建一個新的文件index.html:
~/ethers-template$ touch index.html
按如下內容修改:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"/> <title>Ethers Template</title> <style> body { padding-top: 75px; } .search-container { width: 490px; display: block; margin: 0 auto; } input#address { margin: 0 auto; width: 100%; height: 45px; padding: 0 20px; font-size: 1rem; border: 1px solid #D0CFCE; outline: none; } .center { text-align: center; } ol { counter-reset: list; list-style: none; } li { counter-increment: list; margin-bottom: 10px; } li::before { content: counter(list, decimal-leading-zero); background: #2b4353; font-family: Arial, sans-serif; color: #fff; font-size: 13px; text-align: center; border-radius: 50%; width: 2.2em; height: 2.2em; line-height: 2.3em; display: inline-block; margin-right: 1em; } .button { background-color: #4CAF50; border: none; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 19px; cursor: pointer; } </style> </head> <body> <div class="center"> <h2>Ethers Template</h2> <form class="search-container" id="send"> <input type="text" id="address" placeholder="Enter you address to get kanchan Coin"> <button type="submit" class="button">Send</button> </form> <h3>Status:</h3> <p id="status"></p> <h3>Transactions</h3> <ol id="transactions"> </ol> </div> <script src="dist/bundle.js"></script> </body> </html>
現在你的文件夾看起來就是這樣了:
其中build文件夾將在運行compile.js之后自動創建,而dist 文件夾是在npm run build
執行后自動創建。
用瀏覽器訪問看起來是這樣:
看完上述內容,你們對如何使用Ether.js構建一個簡單的DApp有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。