# 【区块链学习笔记】一起入门DAPP智能合约开发

By [Untitled](https://paragraph.com/@0xb23b27077ac999924b460c374e5f9192b4faf16e) · 2021-12-27

---

初学以太坊智能合约

直接打开 [http://trufflesuite.com/tutorial/index.html](http://trufflesuite.com/tutorial/index.html) 有新手指导区，英语好的老板可以直接参考原文，下面是根据教程加上我的体验进行的翻译和实践记录。

这个教程可以让你体验做一个dapp宠物商店（pet shop），通过这个教程可以完整的体验一次合约的开发。

这个教程主要会让我们体验这么几个过程

*   设置开发环境
    
*   使用 Truffle Box 创建 Truffle 项目
    
*   编写智能合约
    
*   编译和迁移智能合约
    
*   测试智能合约
    
*   创建用户界面以与智能合约交互
    
*   在浏览器中与 dapp 交互
    

设置开发环境

需要安装

*   Nodejs
    
*   Git
    

上述两个安装完成之后，npm安装 truffle

    # 安装truffle
    npm install -g truffle
    
    # 确认是否安装完成
    truffle version
    

使用 Truffle Box 创建 Truffle 项目

    # 新建文件夹
    mkdir pet-shop-tutorial
    # 进入文件夹
    cd pet-shop-tutorial
    # 使用truffle 创建项目框架
    truffle unbox pet-shop
    

ps：介于国内的网络原因，这一步会经常失败，老板可以考虑科学上网，或者失败之后多运行几次，总有成功的。等不了的可以直接去clone他的远嘛到该目录 [https://github.com/trufflesuite/pet-shop-tutorial](https://github.com/trufflesuite/pet-shop-tutorial)

成功之后就有如下的目录结构

挑选主要文件讲解一下

    ├── LICENSE
    ├── contracts  #包含我们智能合约的 Solidity 源文件。这里有一个重要的契约叫做Migrations.sol，后续教程会讲到
    ├── migrations #Truffle 使用迁移系统来处理智能合约部署。迁移是一个额外的特殊智能合约，用于跟踪更改。
    ├── src
    ├── test # 包含我们智能合约的 JavaScript 和 Solidity 测试
    └── truffle-config.js # 配置文件
    

开始写第一个智能合约

1.在 contracts文件夹下面创建 Adoption.sol

2.添加如下内容到新建的文件

    # 注明了 solidity的合约最低版本，^ 表示需要更高的版本，即向上兼容
    pragma solidity ^0.5.0;
    
    contract Adoption {
    
    }
    

设置变量

Solidity 是一种静态类型语言，这意味着必须定义字符串、整数和数组等数据类型。 Solidity 有一种独特的类型，称为地址。地址是以太坊地址，存储为 20 字节值。以太坊区块链上的每个账户和智能合约都有一个地址，可以从该地址发送和接收以太币。

    pragma solidity ^0.5.0;
    
    contract Adoption {
        address[16] public adopters;
    }
    

*   我们定义了一个单一的变量：adopters。这是一个以太坊地址的数组。数组包含一种类型，可以有一个固定或可变的长度。在这种情况下，类型是地址，长度是16。
    
*   你还会注意到adopters是公开的。公共变量有自动的getter方法，但在数组的情况下，需要一个key，并且只会返回一个值。稍后，我们将写一个函数来返回整个数组，以便在我们的用户界面中使用。
    

开发第一个方法：收养一只宠物

    // Adopting a pet
    function adopt(uint petId) public returns (uint) {
      require(petId >= 0 && petId <= 15);
    
      adopters[petId] = msg.sender;
    
      return petId;
    }
    

*   在Solidity中，函数参数和输出的类型都必须被指定。在这种情况下，我们将接收一个petId（整数）并返回一个整数。
    
*   我们正在检查以确保petId是在我们的收养者数组的范围内。Solidity中的数组是从0开始索引的，所以ID值需要在0和15之间。我们使用require()语句来确保ID是在范围内。
    
*   如果ID在范围内，我们就把进行呼叫的地址添加到我们的收养者数组中。调用此函数的人或智能合约的地址用msg.sender表示。
    
*   最后，我们返回提供的宠物ID作为确认。
    

开发第二个方法：返回收养数据

    // Retrieving the adopters
    function getAdopters() public view returns (address[16] memory) {
      return adopters;
    }
    

*   由于adopters已经被声明，我们可以简单地返回它。确保指定返回类型（在本例中，adopters的类型）为address\[16\] memory。 memory给出了变量的数据位置。
    
*   函数声明中的view关键字意味着该函数将不会修改合同的状态。关于视图所施加的确切限制的进一步信息，可在这里找到。
    

编译和迁移智能合约

Solidity是一种编译语言，意味着我们需要将我们的Solidity编译成字节码，以便Ethereum虚拟机（EVM）执行。把它看作是把我们的人类可读的Solidity翻译成EVM能理解的东西。 在终端中，确保你在包含dapp的目录的根部，并输入。

    truffle compile
    

迁移到区块链

现在，我们已经成功地编译了我们的合约，是时候将它们迁移到区块链上了！这就是我们的工作。

你会看到在migrations/目录下已经有一个JavaScript文件。1\_initial\_migration.js。这是处理部署Migrations.sol合约以观察后续的智能合约迁移，并确保我们在未来不会重复迁移未改变的合约。 现在我们准备创建我们自己的迁移脚本。 1.在migrations/目录下创建一个名为2\_deploy\_contracts.js的新文件。 2.在2\_deploy\_contracts.js文件中添加以下内容。

    var Adoption = artifacts.require("Adoption");module.exports = function(deployer) {  deployer.deploy(Adoption);};
    

安装Ganache

下载地址：[http://trufflesuite.com/ganache](http://trufflesuite.com/ganache)

Ganache是一个快速启动个人以太坊区块链，您可以使用它来运行测试、执行命令和检查状态，同时控制链的运行方式。如果不用他，直接在真实的网络部署测试的话，是需要花费eth的。安装这个在本地就不需要了。

这个很简单，下载安装就好，然后双击运行，就可以看到本地跑起来了。

本地的端口跑在 [HTTP://127.0.0.1:7545](HTTP://127.0.0.1:7545)

使用 lsof -i:7544 确认已经成功运行

运行迁移

然后你就会得到一堆迁移部署的消息

    Compiling your contracts...===========================> Everything is up to date, there is nothing to compile.Starting migrations...======================> Network name:    'development'> Network id:      5777> Block gas limit: 6721975 (0x6691b7)1_initial_migration.js======================   Replacing 'Migrations'   ----------------------   > transaction hash:    0xf848aad72fb6ab403f201ff2fa1113d959d1ecb7fe331260752c263925ca403c   > Blocks: 0            Seconds: 0   > contract address:    0xC0112c7071c9Fab2f42D438B738D51b367fca38a   > block number:        1   > block timestamp:     1638962361   > account:             0x4035a5D4709fD2Cb87160Bd29ca5eD88C9E5765F   > balance:             99.99616114   > gas used:            191943 (0x2edc7)   > gas price:           20 gwei   > value sent:          0 ETH   > total cost:          0.00383886 ETH   > Saving migration to chain.   > Saving artifacts   -------------------------------------   > Total cost:          0.00383886 ETH2_deploy_contracts.js=====================   Replacing 'Adoption'   --------------------   > transaction hash:    0xabfd61f1d738ab26c5c0f65ee9cc37787d3cc46656cf9ce59c559affeabbb71b   > Blocks: 0            Seconds: 0   > contract address:    0x705b3e834149c2eF5bbE00440474f0aC3327451A   > block number:        3   > block timestamp:     1638962361   > account:             0x4035a5D4709fD2Cb87160Bd29ca5eD88C9E5765F   > balance:             99.99123784   > gas used:            203827 (0x31c33)   > gas price:           20 gwei   > value sent:          0 ETH   > total cost:          0.00407654 ETH   > Saving migration to chain.   > Saving artifacts   -------------------------------------   > Total cost:          0.00407654 ETHSummary=======> Total deployments:   2> Final cost:          0.0079154 ETH
    

最终花费了0.0079154的eth，如果直接在线上的话就需要花钱了。。

创建用户界面以与智能合约交互

实例化web3 在文本编辑器中打开/src/js/app.js 检查该文件。注意，有一个全局的App对象来管理我们的应用程序，在init()中加载宠物数据，然后调用函数initWeb3()。web3的JavaScript库与以太坊区块链进行互动。它可以检索用户账户，发送交易，与智能合约互动，等等。

web3的api可以参考 [https://github.com/ethereum/web3.js/](https://github.com/ethereum/web3.js/)

这段代码本质上就是先获得一个web3的对象，然后通过该web3api与合约进行交互，大部分都是封装好了的api。

最开始我们写了2个合约的方法function adopt function getAdopters() 定义了一个类型为address的变量adopters

1.初始化web3

2.初始化合约。初始化合约的时候载入Adoption.json这个文件，该文件是我们执行truffle compile生成的。这个文件包含了合约的工程文件（artifact file），Artifacts是关于我们合同的信息，比如它的部署地址和应用二进制接口（ABI）。ABI是一个JavaScript对象，定义了如何与合同互动，包括其变量、函数及其参数。（ Artifacts are information about our contract such as its deployed address and Application Binary Interface (ABI). The ABI is a JavaScript object defining how to interact with the contract including its variables, functions and their parameters.）

3.markAdopted判断是否已经被买了，通过adoptionInstance.getAdopters.call();调用所有记录的adopters地址，如果不是初始化的地址，就标记为success，并且disable。返回的合约信息如下图：

4.bindEvents绑定事件，如果可以购买，则触发领养 App.contracts.Adoption.deployed()，然后刷新UI即可。

    // app.jsApp = {  web3Provider: null,  contracts: {},  init: async function () {    // Load pets.    $.getJSON("../pets.json", function (data) {      var petsRow = $("#petsRow");      var petTemplate = $("#petTemplate");      for (i = 0; i < data.length; i++) {        petTemplate.find(".panel-title").text(data[i].name);        petTemplate.find("img").attr("src", data[i].picture);        petTemplate.find(".pet-breed").text(data[i].breed);        petTemplate.find(".pet-age").text(data[i].age);        petTemplate.find(".pet-location").text(data[i].location);        petTemplate.find(".btn-adopt").attr("data-id", data[i].id);        petsRow.append(petTemplate.html());      }    });    return await App.initWeb3();  },  initWeb3: async function () {    // Modern dapp browsers...    if (window.ethereum) {      App.web3Provider = window.ethereum;      try {        // Request account access        await window.ethereum.request({ method: "eth_requestAccounts" });      } catch (error) {        // User denied account access...        console.error("User denied account access");      }    }    // Legacy dapp browsers...    else if (window.web3) {      App.web3Provider = window.web3.currentProvider;    }    // If no injected web3 instance is detected, fall back to Ganache    else {      App.web3Provider = new Web3.providers.HttpProvider(        "http://localhost:7545"      );    }    web3 = new Web3(App.web3Provider);    return App.initContract();  },  initContract: function () {    $.getJSON("Adoption.json", function (data) {      // Get the necessary contract artifact file and instantiate it with @truffle/contract      var AdoptionArtifact = data;      App.contracts.Adoption = TruffleContract(AdoptionArtifact);      // Set the provider for our contract      App.contracts.Adoption.setProvider(App.web3Provider);      // Use our contract to retrieve and mark the adopted pets      return App.markAdopted();    });    return App.bindEvents();  },  bindEvents: function () {    $(document).on("click", ".btn-adopt", App.handleAdopt);  },  markAdopted: function () {    var adoptionInstance;    App.contracts.Adoption.deployed()      .then(function (instance) {        adoptionInstance = instance;        return adoptionInstance.getAdopters.call();      })      .then(function (adopters) {        for (i = 0; i < adopters.length; i++) {          if (adopters[i] !== "0x0000000000000000000000000000000000000000") {            $(".panel-pet")              .eq(i)              .find("button")              .text("Success")              .attr("disabled", true);          }        }      })      .catch(function (err) {        console.log(err.message);      });  },  handleAdopt: function (event) {    event.preventDefault();    var petId = parseInt($(event.target).data("id"));    var adoptionInstance;    web3.eth.getAccounts(function (error, accounts) {      if (error) {        console.log(error);      }      var account = accounts[0];      App.contracts.Adoption.deployed()        .then(function (instance) {          adoptionInstance = instance;          // Execute adopt as a transaction by sending account          return adoptionInstance.adopt(petId, { from: account });        })        .then(function (result) {          return App.markAdopted();        })        .catch(function (err) {          console.log(err.message);        });    });  },};$(function () {  $(window).load(function () {    App.init();  });});
    

安装metamask进行测试

先安装metamask插件，可以在chrome插件商城安装。

如果有了直接点击添加网络

按照如下资料进行填写

这里的[http://127.0.0.1:7545就是你本地启动Gnanche的时候启动的](http://127.0.0.1:7545%E5%B0%B1%E6%98%AF%E4%BD%A0%E6%9C%AC%E5%9C%B0%E5%90%AF%E5%8A%A8Gnanche%E7%9A%84%E6%97%B6%E5%80%99%E5%90%AF%E5%8A%A8%E7%9A%84)

填写完成之后，选择网络，把网络切换到本地测试网络

点击小狐狸插件头像，切换网络即可

切换到本地网络之后，可以直接倒入本地的账号

点击钥匙图标，查看本地的私钥，然后选择小狐狸直接倒入。

倒入成功之后，你账号就有了99.99eth，其他账号没有消耗的话应该都是100ETH

运行你的首个DAPP

回到开发目录下

    npm run dev
    

首个DAPP就跑起来了。点击adopt就可以唤起小狐狸进行领养和gas支付了。

那么至此，基本上就了解了一个DAPP的运行逻辑了。

回顾

梳理一下，就是我们编写了一个solidity的合约，需要先编译成机器码，然后再部署到ETH的网络，部署的时候需要消耗GAS。

部署完成之后，我们就可以通过用户UI去进行交互，调用metamask去与底层web3API进行交流。比如这个demo就是领养宠物，在游戏打金领域就是氪金挖矿了。当然我们可以直接跳过这些，直接用web3去跟合约交流了。

更深入的了解需要学习一下solidity的语法，然后根据游戏去挖掘相应合约的方法调用即可。

可以继续参考老板的未公开合约的游戏怎么制作脚本 思路和方法已经讲明白了，剩下的就靠自己多领悟和学习了

举一反三：多多实践

批量创建账号

既然本地已经有了eth的网络环境了，那么我是不是可以通过web3 api直接批量创建钱包地址呢？

之前一直想看老板批量创建metamask账号的的代码，可惜权限不够，这回理解了应该可以自己动手了～

[https://web3js.readthedocs.io/en/v1.2.11/web3-eth-accounts.html#example](https://web3js.readthedocs.io/en/v1.2.11/web3-eth-accounts.html#example)

查看API，有一个account的接口。通过以下代码即可创建一个账号，后续再通过循环就可以创建和存储了。

    //通过web3 api 创建账号const accepts = require('accepts');var Web3 = require('web3');web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:7545"));let account = web3.eth.accounts.create('test');let keystore = account.encrypt('test');console.log(account, keystore);/***{  address: '0x3E12932eF648bE841e03db1CE46Faec4aFc9AAe1',  privateKey: '0x4291e1585205b5a0217ecea837f2070ea2100a401f753806c15a93dfb84c3661',  signTransaction: [Function: signTransaction],  sign: [Function: sign],  encrypt: [Function: encrypt]} {  version: 3,  id: '8a2ded2a-8469-43d4-88a0-3e4169012d54',  address: '3e12932ef648be841e03db1ce46faec4afc9aae1',  crypto: {    ciphertext: 'e7492608eb06ee703909b6348054f8b98bc30a79a5d4628fad72e012c4a50075',    cipherparams: { iv: 'a2b68822d9b03e788ad8df3a57c95cec' },    cipher: 'aes-128-ctr',    kdf: 'scrypt',    kdfparams: {      dklen: 32,      salt: 'f513ee04af1662d1adab37e8962423d9d4ff65218aadce7b88063eb6e6d1ece0',      n: 8192,      r: 8,      p: 1    },    mac: 'eadc2bb27d5fe6c8889caee2f897eabde4cd9254005af9bdb11f5c7989fe6837'  }}***/
    

尝试转账

假设我已经创建了一个 0x3E12932eF648bE841e03db1CE46Faec4aFc9AAe1的新账号，

然后我本地导入metamask的账号已经有了99.99eth，我想给新账号转账。

直接通过metamask转账即可。

转账成功，可以用代码查询新建账号的余额，查询成功～

    var Web3 = require('web3');//创建 rpc 连接字符串var rpcstring = 'http://127.0.0.1:7545'//创建ws连接字符串var wstring = 'wss://bsc-ws-node.nariox.org:443';// var wscweb3 = new Web3(new Web3.providers.WebsocketProvider(wstring ));var rpcweb3 = new Web3(new Web3.providers.HttpProvider(rpcstring ));//设置web3 使用rpcweb3模式web3 = rpcweb3;const getBNBBalance = async (address) =>{    let result = await web3.eth.getBalance(address)    //由于使用的是大数模式，小数点有18位，所以获得的balance 要除以10^18次方才是正确的数据    //或者使用自带的转换工具    let balance = web3.utils.fromWei(result.toString(10), getweiname());    //打印结果    console.log("地址:" + address +"有" + balance +"个ETH");    return balance;}//通过小数点多少位，转换对应的数据function getweiname(tokendecimals = 18) {  weiname = 'ether';  switch (tokendecimals) {      case 3:          weiname = "Kwei";          break;      case 6:          weiname = 'mwei';          break;      case 9:          weiname = 'gwei';          break;      case 12:          weiname = 'microether ';          break;      case 15:          weiname = 'milliether';          break;      case 18:          weiname = 'ether';          break;  }  return weiname;}let banlance = getBNBBalance('0x3E12932eF648bE841e03db1CE46Faec4aFc9AAe1');console.log('banlance:' + banlance);//banlance:[object Promise]//地址:0x3E12932eF648bE841e03db1CE46Faec4aFc9AAe1有88个ETH
    

后续

可以再试试通过web3api实现批量转账等测试，等有时间再试试。作为小白，通过这个教程大致梳理了一下整个DAPP的开发流程，如果有纰漏也请各位老板指正。感谢感谢～

---

*Originally published on [Untitled](https://paragraph.com/@0xb23b27077ac999924b460c374e5f9192b4faf16e/dapp)*
