區塊鏈研究實驗室|使用JavaScript進行智慧合約的編譯、部署和互動

買賣虛擬貨幣

如果您已經在以太坊上構建了daap,那麼很可能您使用web3.js構建了javascript前端。Ethers.js是一個輕量級的JavaScript庫,可以替代Web3.js。在這篇文章/教程中,我將展示如何使用Ethers.js來構建一個簡單的dApp。

與web3.js相比,ethers.js有許多優勢。我想集中討論ethers.js中的一個主要特性,即狀態和金鑰管理。web3假設有一個本地節點連線到應用程式。假設該節點儲存金鑰、簽署事務、與以太坊區塊鏈互動和讀取。實際上情況並非如此,大多數使用者都沒有在本地執行geth。Metamask透過瀏覽器應用程式有效地模擬該環境,因此大多數Web3應用程式需要Metamask來儲存金鑰、簽署事務,並與以太坊主網進行互動。

Ethers.js採用了不同的方法,為開發人員提供了更大的靈活性。 Ethers.js將“節點”分為兩個單獨的角色:

  1. 持有鑰匙並簽署交易的“錢包”。

  2. 一個“provider”用作與以太坊網路的匿名連線,檢查狀態併傳送交易。

編譯和部署智慧合約

在我們的教程中,我們將與ERC20智慧合約進行互動。 您需要在裝置中安裝nodejs和npm。

1、建立一個名為ethers-template的資料夾,並在ethers-template內建立另一個名為contract的資料夾。

mkdir ethers-template && cd ethers-template && mkdir contracts

2、執行npm init命令建立一個package.json檔案,該檔案將儲存專案依賴項:

`npm init -y`

3、建立一個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” (main network)

  • “rinkeby”

  • “ropsten”

  • “kovan”

  • “goerli”

ERC20:“ 0x0b0Ce2d3f67b4482BD830Ac24ECa5E8d022Fd32f”行是可選的。 如果要與已部署的智慧合約進行互動,則可以在此處寫地址。名稱,符號,小數與ERC20引數有關。

4、使用npm安裝ethers.js

npm install --save ethers

5、要編寫合同,請安裝fs-extra和solc

npm install [email protected] [email protected] --save

6、在合約資料夾中將以下程式碼另存為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;
}
}

7、現在為了編譯上面的程式碼,您需要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');
}

node compile.js

上面的程式碼將讀取Contracts目錄中的所有智慧合約,並將abi和bytecode儲存為json檔案。 所有的json檔案將儲存在構建目錄中。 編譯後,我們得到以下結構。

+ethers-template
+compile.js
+contracts
-erc20.sol
+build
-ERC.json
-Context.json
-IERC20.sjon
-SafeMath.json
-package.json

8、建立一個名為deploy.json的新檔案並貼上以下程式碼

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"
}
);

})();

執行部署檔案時,應將合同名稱作為命令列引數。

注意:

1.上面程式碼中的預設網路是kovan測試網路。
2.您需要具有該網路的以太幣才能支付部署的交易費用。
3.合同將從config.json中指定的私鑰進行部署。

部署命令為:

node deploy.js <contract_name>

在我們的例子中,命令將是

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中.

與智慧合約互動

1、在本教程中,我們使用ES6編寫程式碼並將ES6轉換為ES5,我們將使用帶babel loader的webpack。

安裝開發人員依賴項:

npm i webpack webpack-cli @babel/core @babel/plugin-proposal-object-rest-spread @babel/preset-env babel-loader babel-polyfill -D

2、建立一個新檔案app.js並貼上下面的程式碼。此檔案的已轉換ES5程式碼將儲存在dist/bundle.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;
};

首先,我們必須指定我們將要工作的provider/network。

const provider = ethers.getDefaultProvider(config['network']);

為了與智慧合約進行互動,您需要做兩件事:

1.智慧合約地址。
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;

3、在建立合同例項之前,我們必須先建立一個錢包例項,以便每當呼叫setter函式(或必須進行交易)時,都必須有一個私鑰來簽署這些交易。 在ethers.js中,您只需要建立wallet(簽名者),所有setter函式(交易)都將由此wallet簽名。

const wallet = new ethers.Wallet(config['private_key'] , provider);

您也可以在金鑰庫和助記符的幫助下製作錢包。 如果您要與此錢包進行智慧合約互動,則應透過provider。 如果您只想使用私鑰對郵件簽名,則不需要provider。

erc20 = new ethers.Contract( address , abi , wallet );

上面的程式碼建立了一個合約例項,您可以像這樣呼叫合約功能

erc20.functions.function_name_in_smart_contract(parameters);

例如:在ERC20中,我們有一個名為transfer的函式,該函式將地址和金額作為引數。 下面的程式碼呼叫傳遞函式。 電子錢包將簽署此交易並將其釋出到指定的網路。

erc20.functions.transfer(address, "1000000000000000000");

注意:無論何時進行交易,都應該在錢包裡放以太幣,以支付gas。

4、在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"
}
}

現在每當你對智慧合同做任何修改時,你就必須編譯和修改這些更改。編譯和部署都可以在一個命令中完成,即

npm run deploy

這將自動更改配置和構建檔案中的地址和ABI。在互動過程中,您無需更改任何地址或abi,它將自動更新。

如果您對app.js進行了任何更改,則必須透過以下方式對其進行編譯

npm run build

這將生成(或更新)dist / bundle.js檔案。

5、新建一個名為index.html的檔案。 該頁面僅包含一個文字輸入,該文字輸入需要地址並向輸入的地址傳送1個令牌。 交易雜湊值附加在文字輸入下方,並且當前交易的狀態也可以在狀態部分中看到。

<!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>
<h2>Status:</h2>
<p id="status"></p>
<h2>Transactions</h2>
<ol id="transactions">
</ol>
</div>
<script src="dist/bundle.js"></script>
</body>
</html>

現在,您的資料夾結構應如下所示。

執行後建立構建資料夾

node complie.js

執行後建立dist資料夾

npm run build

總結

整個專案的原始碼可以在這裡找到。您可以將此儲存庫用作模板。複製原始碼並做必要的改變。

git clone https://github.com/SauravKanchan/ethers-template.git
cd ethers-template
npm i
npm run deploy
npm run build

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;