手摸手用Truffle开发自己的第一个DApp

简介: 手摸手用Truffle开发自己的第一个DApp

前言


简单写个杂货铺的DApp, 每个人可以把自己不用的物品挂在上面, 以获得领取别人物品的机会, 简单来说就是共享自己无用的物品;


效果图, 请忽略样式, 毕竟我是个后端



20201203192037919.png

项目源码

环境需求


  1. MetaMask
  2. node
  3. yarn
  4. Ganache
  5. truffle (npm install -g truffle)
  6. lite-server (yarn add lite-server )


后端

初始化项目

truffle init 
npm init


目录介绍


|-- Dapp
    |-- build                 // 合约编译后自动创建
    |-- contracts             // 放置合约文件
    |-- migrations            // 放置部署文件
    |-- test                  // 放置测试文件


编写合约


contracts/Grocery.sol

pragma solidity ^0.5.0;
pragma experimental ABIEncoderV2;
contract Grocery {
    struct Goods {
        string name;
        string img;
        bool isClaim;
    }
    mapping(uint => address) goodsMap;  // 商品的归属
    Goods[] public goodsList;  // 所有商品列表
    mapping(address => uint[]) claimList;  // 领取列表
    mapping(address => int) claimNum; // 领取统计
    int maxClaim = 10;  // 最大领取数量, 可以用过添加商品来得到更多的机会
    // @notice 添加商品
    function AddGoods(string memory _name, string memory _img) public returns (uint){
        uint id = goodsList.push(Goods(_name, _img, false)) - 1;
        goodsMap[id] = msg.sender;
        claimNum[msg.sender] -= 1;
        return id;
    }
    // @notice 领取商品
    function Claim(uint _id) public {
        require(claimNum[msg.sender] <= maxClaim, "您已经领取的足够多了");
        require(!goodsList[_id].isClaim, "该商品已经被认领");
        goodsMap[_id] = msg.sender;
        goodsList[_id].isClaim = true;
        claimList[msg.sender].push(_id);
        claimNum[msg.sender] += 1;
    }
    // @notice 获取所有的商品
    function GetAllGoods() public view returns (Goods[] memory){
        return goodsList;
    }
    // @notice 获取商品归属地址
    function GoodsOf(uint _id) public view returns (address) {
        return goodsMap[_id];
    }
}

代码逻辑也比较简单, 每个人都可以添加商品, 并且获得领取次数, 已经被领取的商品不允许被再次领取;


编写部署脚本


migrations/2_initial_grocery.js


const Grocery = artifacts.require("Grocery");
module.exports = function (deployer) {
    deployer.deploy(Grocery);
};


配置网络


打开ganache, 启动区块链, 默认会生成10个有100个ETH的账户


20201203142317707.png

查看配置端口, 默认都是7545, 当然也可以修改


20201203142346485.png


修改项目truffle-config.js 里面的配置端口, 连通测试区块

development: {
     host: "127.0.0.1",     // Localhost (default: none)
     port: 7545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)
 }


部署合约并进行交互


进入truffle控制台


truffle console


可以看到 truffle(development) 连接上了development环境

编译合约

compile


后有如下输出, 并且项目会多个build目录, 里面有编译好的json合约文件


> Compiled successfully using:
   - solc: 0.5.16+commit.9c3226ce.Emscripten.clang


部署合约

migrate
migrate --reset    // 如果合约修改了需要重新部署, 则需要添加reset参数


成功后有如下输出, 创建了两个合约

Summary
=======
> Total deployments:   2
> Final cost:           0.02087428 ETH


可以在打开的 Ganache 中看到区块变化, 默认花费第一个帐户的ETH


20201203160413414.png

测试


成功部署后当然要测试一下合约的准确了, 这里可以控制台直接连接测试, 也可以编写js/sol测试文件来测试


控制台测试

let accounts = await web3.eth.getAccounts()     // 获取当前所有的账户, 也就是初始化的是个地址
accounts                                       // 打印地址
let instance = await Grocery.deployed()        // 获取合约实列

用第二个账户去添加商品添加商品


instance.AddGoods("zombie", "https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/i


成功后返会交易详情

{
  tx: '0x3433af229a978e5ae61ad42845041e30136bb381e2ca759415bc05e602ffe042',
  receipt: {
    transactionHash: '0x3433af229a978e5ae61ad42845041e30136bb381e2ca759415bc05e602ffe042',
    transactionIndex: 0,
    blockHash: '0xeb67216667d37474859c60ce33c13bcb8654af40b50c14ceed38bd763d673fa1',
    blockNumber: 5,
    from: '0xd1f614d6577df534f5d17d5c9aba14331515436f',
    to: '0xa916c6f1439c77942d26e9317ef401b164d96978',
    gasUsed: 171053,
    cumulativeGasUsed: 171053,
    contractAddress: null,
    logs: [],
    status: true,
    logsBloom: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000',
    rawLogs: []
  },
  logs: []
}

用第三个账户取认领


instance.Claim(0, {from: accounts[2]})


成功后返会交易详情

{
  tx: '0x1b264ac1497f03c33a7db0b7f89212fe4537b53801cc34a72afda4a744c02cee',
  receipt: {
    transactionHash: '0x1b264ac1497f03c33a7db0b7f89212fe4537b53801cc34a72afda4a744c02cee',
    transactionIndex: 0,
    blockHash: '0x28c3404ce1007f9b7aba3c06038216697af2afbd6c6a4035cbb59df99f97a248',
    blockNumber: 6,
    from: '0x2e097441b0828c69245dfec4ae970945acba7206',
    to: '0xa916c6f1439c77942d26e9317ef401b164d96978',
    gasUsed: 95538,
    cumulativeGasUsed: 95538,
    contractAddress: null,
    logs: [],
    status: true,
    logsBloom: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000',
    rawLogs: []
  },
  logs: []
}

获取商品归属

instance.GoodsOf(0)

成功后返回地址

'0x2E097441B0828c69245DFec4ae970945acbA7206'

获取所有商品列表

 instance.GetAllGoods()

返回

[
  [
    'zombie',
    'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3994931373,529771369&fm=26&gp=0.jpg',
    true,
    name: 'zombie',
    img: 'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3994931373,529771369&fm=26&gp=0.jpg',
    isClaim: true
  ]
]

编写测试文件

test/TestGrocery.sol

pragma solidity ^0.5.0;
import "truffle/Assert.sol";              // 引入的断言
import "truffle/DeployedAddresses.sol";  // 用来获取被测试合约的地址
import "../contracts/Grocery.sol";       // 被测试合约
contract TestGrocery {
    Grocery grocery = Grocery(DeployedAddresses.Grocery());
    //  发布商品测试
    function testAddGoods() public {
        uint id = grocery.AddGoods("GoLang", "https://ucc.alicdn.com/images/user-upload-01/20190629154954578.png?x-oss-process=image/resize,m_fixed,h_64,w_64");
        uint expected = 0;
        Assert.equal(id, expected, "Goods id is not 0");
    }
    // 领取商品测试
    function testClaim() public {
        // 期望领取者的地址就是本合约地址,因为交易是由测试合约发起交易,
        address expected = address(this);
        grocery.Claim(0);
        address claimAddr = grocery.GoodsOf(0);
        Assert.equal(claimAddr, expected, "Owner of goods id 1 should be recorded.");
    }
}

在truffle控制台调用test命令, 返回如下


TestGrocery
    √ testAddGoods (379ms)
    √ testClaim (598ms)
  2 passing (14s)


测试成功

前端

目录介绍

|-- Dapp
    |-- src             //项目根目录下创建src目录,存放前端文件
        -- index.html
        |-- js
        |-- fonts
        |-- css
    -- bs-config.json    // lite-server的配置文件

配置 lite-server

安装

yarn add lite-server


bs-config.json 指定 lite-server 的工作目录

{
  "server": {
    "baseDir": ["./src", "./build/contracts"]
  }
}

配置启动脚本

package.json

"scripts": {
  "dev": "lite-server"   // 添加这行, 启动 yarn run dev 
}

代码


大家直接看我的源码吧, 前端属实好几年没写过了, 这里是根据参考博客的前端代码改的, 然后js里面的truffle-contract.js 和web3.js 是从node_modules里面copy出来的, 因为合约里面有一个返回数组的, 所以得用较新版本的包; 大家主要看看

app.js里面的代码知道怎么初始化调用合约就可以了;


启动

yarn run dev


MetaMask 添加本地测试网

点击MetaMask右上角网络选择自定义Rpc加入本地测试网

20201204112948247.png


然后点击导入账户, 在Ganache中找个有ETH的私钥导进去就行

20201204113242414.png

20201204113419397.png


这样就可以用MetaMash进行添加和认领物品的确认操作了

20201204113735290.png


部署到Ropsten测试网络


安装


yarn add truffle-hdwallet-provider


注册https://infura.io 获取节点连接


注册项目获取PROJECT ID

20201204115354437.png

获取MetaMask账户助记词, 要在Ropsten网络有ETH

账户设置的安全与隐私里面可以获取到助记词

20201204115713278.png


增加Ropsten网络配置

truffle-config.js 实际项目生成都有写好的, 打开注释就可以了

const HDWalletProvider = require('@truffle/hdwallet-provider');
const infuraKey = "infura获取的projectId";
const mnemonic = "助记词";
module.exports = {
    ropsten: {
    provider: () => new HDWalletProvider(mnemonic, "https://ropsten.infura.io/"+infuraKey),
    network_id: 3,       // Ropsten's id
    gas: 5500000,        // Ropsten has a lower block limit than mainnet
    confirmations: 2,    // # of confs to wait between deployments. (default: 0)
    timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
    skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
    },
}


部署合约


truffle migrate --network ropsten --reset --compile-all


成功返回

Summary
=======
> Total deployments:   2
> Final cost:          0.0211683 ETH

启动


切换MetaMash的网络环境为Ropsten测试网络, 然后F5刷新页面就可以了, app.js里判断了如果有web网络注入会优先选取, 注意! 测试网络的区块打包会慢一些, 不会像本地区块一样添加领取秒成功, 需要等区块确认后刷新页面才能显示


扩展


当然有兴趣的朋友也可以在此之上多添加一些功能, 比如:


  1. 新增商品和领取通知(event);
  2. 显示当前账户可领取次数;


参考文档

https://learnblockchain.cn/docs/truffle/index.html
https://learnblockchain.cn/2018/01/12/first-dapp/#%E5%88%9B%E5%BB%BA%E7%94%A8%E6%88%B7%E6%8E%A5%E5%8F%A3%E5%92%8C%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6%E4%BA%A4%E4%BA%92
https://www.jianshu.com/p/b7be51dd8e84
https://cloud.tencent.com/developer/article/1347300
目录
相关文章
|
4月前
|
前端开发 JavaScript C++
【绝技大公开】Webpack VS Rollup:一场前端工程化领域的巅峰对决,谁能笑到最后?——揭秘两大构建神器背后的秘密与奇迹!
【8月更文挑战第12天】随着前端技术的发展,模块化与自动化构建成为标准实践。Webpack与Rollup作为主流构建工具,各具特色。Webpack是一款全能型打包器,能处理多种静态资源,配置灵活,适合复杂项目;Rollup专注于ES6模块打包,利用Tree Shaking技术减少冗余,生成更精简的代码。Rollup构建速度快,配置简洁,而Webpack则拥有更丰富的插件生态系统。选择合适的工具需根据项目需求和个人偏好决定。两者都能有效提升前端工程化水平,助力高质量应用开发。
50 1
|
6月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的本科生交流培养管理平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的本科生交流培养管理平台附带文章源码部署视频讲解等
27 0
|
7月前
|
JavaScript Java 测试技术
基于小程序的社区互助养老+springboot+vue.js附带文章和源代码设计说明文档ppt
基于小程序的社区互助养老+springboot+vue.js附带文章和源代码设计说明文档ppt
49 0
|
7月前
|
前端开发 IDE 编译器
《Solidity 简易速速上手小册》第7章:智能合约的部署与交互(2024 最新版)(上)
《Solidity 简易速速上手小册》第7章:智能合约的部署与交互(2024 最新版)
148 0
|
7月前
|
监控 安全 前端开发
《Solidity 简易速速上手小册》第7章:智能合约的部署与交互(2024 最新版)(下)
《Solidity 简易速速上手小册》第7章:智能合约的部署与交互(2024 最新版)(下)
99 0
|
前端开发
前端学习笔记202303学习笔记第五天-了解vite项目的运行流程1
前端学习笔记202303学习笔记第五天-了解vite项目的运行流程1
73 0
|
前端开发
前端学习笔记202303学习笔记第五天-了解vite项目的运行流程2
前端学习笔记202303学习笔记第五天-了解vite项目的运行流程2
70 0
|
缓存 NoSQL API
国王小组:搭建交易所开发搭建中Rails 2和3、Sinatra框架中使用
秒合约交易所开发详细丨秒合约交易所系统开发详细及规则丨秒合约交易所系统源码部署 数字货币交易所开发源码丨数字货币交易所系统开发(详细及逻辑) 交易所开发正式版丨区块链交易所系统开发实现技术功能及源码 交易所开发案例丨交易所系统开发(详细及流程)丨交易所成熟及源码系统 交易所开发(稳定版)丨交易所系统开发(方案及逻辑)丨 交易所系统源码功能 什么是去中心化交易所系统开发丨浅谈uniswap丨justswap 交易所源码(整体架构演示) 交易所搭建,交易所源码是怎么开发的? 区块链交易所怎么搭建? 区块链交易所平台中常见的开发模式有哪些? 区块链交易所如何开发(介绍区块链应用开发的流程) 区块链
|
前端开发 算法 数据处理
前端基础向~从项目出手封装工具函数
前端基础向~从项目出手封装工具函数
170 0
|
区块链
区块链开发(七)truffle使用入门汇总
区块链开发(七)truffle使用入门汇总
176 0
区块链开发(七)truffle使用入门汇总

热门文章

最新文章