区块链研究实验室|使用JavaScript进行智能合约的编译、部署和交互

  • 时间:
  • 浏览:56
  • 来源:区块链技术网

如果您已经在以太坊上构建了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 fs-extra@8.1.0 solc@0.5.11 --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">
<h1>Ethers Template</h1>
<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

猜你喜欢

区块链骗局曝光骗局,看看你上过当没有?

区块链骗局曝光骗局,看看你上过当没有? 随着这一波的下跌,很多人怕是过不好这个年了。 其实按道理说,从6万上方跌到5万,和从43000跌到35000跌幅差不多,但为什么大家的感受如此截然不同?

2022-01-24

从零开发区块链应用(一)-golang配置文件管理工具viper

理解 HTTP 构建的网络应用只要关注两个端--客户端(client)和服务端(server),两个端的交互来自 client 的 request,以及 server 端的 response。所谓的 http 服务器,主要在于如何接受 client 的 request,并向 client 返回 response

2022-01-20

从零开发区块链应用(二)--mysql安装及数据库表的安装创建

varchar 类型的长度是可变的,在创建表时指定了最大长度。定义时,其最大值可以取 0~~65525 之间的任意值。指定了 varchar 类型的最大值以后,其长度可以在 o 到最大长度之间。

2022-01-20

从零开发区块链应用(三)--mysql初始化及gorm框架使用

Gorm 是 golang 的一个 orm 框架,它提供了对数据库操作的封装,使用起来相当便利。所以如果对 mysql 使用操作不熟悉,建议也可以使用 gorm 框架操作 mysql 数据库。

2022-01-20

从零开发区块链应用(四)--自定义业务错误信息

在实际业务开发中,一个条错误信息需要包含两部分内容:直接展示给用户的 message 和用于开发人员 debug 的 error 。message 可能会直接展示给用户,error 是用于 debug 的错误信息,可能包含敏感/内部信息,不宜对外展示

2022-01-20