WEB3.0:从零开始到智能合约实战(4.彩票系统实战之Web3.js与智能合约进行交互)

188 阅读4分钟

本次分享如何使用前端页面调用智能合约并且发布合约到测试链

编辑合约代码-彩票系统

首先拆分需求

  • 创建彩票奖金池 彩票单价
  • 创建彩票购买人列表
  • 特定时间或人数达到一定数量开出中级号码
  • 将奖金发送至中奖人地址

代码示列如下:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Lottery {
    // 合约所有者
    address public owner;
    // 彩票单价
    uint public ticketPrice;
    // 最大票数
    uint public maxTickets;
    // 合约结束时间
    uint public endTimestamp;
    // 奖金池
    uint public prizePool;
    // 当前总票数
    uint public totalTickets;
    // 参与者列表
    address[] public participants;

    // 每个地址对应的票数
    mapping(address => uint) public tickets;

    // 购买彩票事件
    event TicketPurchased(address indexed buyer, uint ticketCount);
    // 开奖事件
    event LotteryEnded(address winner, uint prize);

    // 合约构造函数,初始化彩票单价、最大票数和持续时间
    constructor(uint _ticketPrice, uint _maxTickets, uint _durationMinutes) {
        owner = msg.sender;
        ticketPrice = _ticketPrice;
        maxTickets = _maxTickets;
        endTimestamp = block.timestamp + (_durationMinutes * 1 minutes);
    }

    // 仅允许合约所有者调用的方法修饰符
    modifier onlyOwner() {
        require(msg.sender == owner, "Only the owner can perform this action.");
        _;
    }

    // 彩票开放状态修饰符
    modifier lotteryOpen() {
        require(block.timestamp < endTimestamp, "The lottery has ended.");
        require(totalTickets < maxTickets, "All tickets have been sold.");
        _;
    }

    // 购买彩票函数,允许用户购买一定数量的彩票
    function buyTickets(uint ticketCount) public payable lotteryOpen {
        // 确保支付金额足够购买彩票
        require(msg.value >= ticketPrice * ticketCount, "账户余额不足.");

        // 如果用户是第一次购买彩票,记录该用户
        if (tickets[msg.sender] == 0) {
            participants.push(msg.sender);
        }

        // 更新用户的彩票数和总票数
        tickets[msg.sender] += ticketCount;
        totalTickets += ticketCount;
        prizePool += msg.value;

        // 触发购买彩票事件
        emit TicketPurchased(msg.sender, ticketCount);
    }

    // 开奖函数,仅允许合约所有者调用
    function drawWinner() public onlyOwner {
        // 确保当前时间已到或所有彩票已售出
        require(block.timestamp >= endTimestamp || totalTickets >= maxTickets, "The lottery is still ongoing.");
        //确保有购彩人
        require(participants.length > 0, "无人购买彩票.");

        // 生成一个随机索引选出中奖者
        uint randomIndex = getRandomNumber() % participants.length;
        address winner = participants[randomIndex];

        // 将奖金池金额重置并发送给中奖者
        uint prize = prizePool;
        prizePool = 0;

        // 发送奖金
        (bool success, ) = winner.call{value: prize}("");
        require(success, "交易失败.");

        // 触发开奖事件
        emit LotteryEnded(winner, prize);
    }

    // 生成随机数函数
    function getRandomNumber() internal view returns (uint) {
        return uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp, participants)));
    }

    // 获取参与者列表
    function getParticipants() public view returns (address[] memory) {
        return participants;
    }

    // 获取某个参与者的票数
    function getTicketCount(address participant) public view returns (uint) {
        return tickets[participant];
    }

    // 接收以太币
    receive() external payable {}
}

合约调用之前我们需要创建本地区块链环境进行调试开发

ganache 进入此页面下载安装运行此程序

QQ截图20240612114329.png

打开程序之后可以看到此界面

部署合约

前面已讲过remix的一些基本配置 所里这里不再赘述

  1. 将代码复制到remix
  2. 编译合约
  3. 选择部署网络 Dev - Ganache Provider 填写本地Ganache端口(Ganache 客户端页面有写)
  4. 选择账号(记住这个账号 开奖只能合约部署者操作) 很重要
  5. 在DEPLOY属性下填写初始化合约参数(彩票单价、最大票数和持续时间)(1 10 10) 点击transact
  6. 点击部署 复制控制台合约地址(contract address)

准备工作

  • 首先确认浏览器和合约在同一区块链
  • 进入google应用商店,下载MeteMask 插件
  • 进入MeteMask设置->添加网络->手动添加网络

12.png

  • 网络名称 -> Ganache
  • RPC URL -> http://127.0.0.1:7545 (此地址在ganache客户端首页有展示)
  • 链 ID -> 1337
  • 保存网络->在metamask首页左上角切换网络至Ganache

image.png

  • 点击账号下拉选项
  • Add account
  • 导入账户
  • 在Ganache客户端界面打开accounts
  • 点击账后右侧小钥匙图标复制 PRIVATE KEY 粘贴至钱包
  • 确认导入

调用合约

注意 在购买彩票中切换付款账户 开奖账户即是合约部署账户

<html lang="en">
  <head>
    <script src="https://cdn.jsdelivr.net/npm/web3@1.7.0/dist/web3.min.js"></script>
  </head>
  <body>
    <div>
      <h2>购买彩票</h2>
      <label for="ticketCount">购买彩票数量:</label>
      <input type="number" id="ticketCount" value="1" />
      <button onclick="buyTickets()">购买彩票</button>
    </div>

    <div>
      <h2>开奖</h2>
      <button onclick="drawWinner()">开奖</button>
    </div>

    <script>
      // 合约地址和ABI
      const contractAddress = "0xca59D3a64A50702C7DcC5530639d21A799911E3c";
      
      //Application Binary Interface)是用于与以太坊智能合约进行交互的标准接口。
      //它定义了合约中可调用的  函数及其参数和返回值,以及事件。
      //ABI用于生成与合约交互的JavaScript代码,并用于对智能合约调用进行编码和解码。
      //在 remix compiler页面最下方 编译好合约后可一键复制合约ABI后粘贴至此处
      const contractABI = [];

      let web3;
      let lotteryContract;
      let userAccount;

      // 初始化Web3
      window.addEventListener("load", async () => {
        if (window.ethereum) {
          web3 = new Web3(window.ethereum);
          try {
            // 请求用户账户访问权限
            await window.ethereum.request({ method: "eth_requestAccounts" });
            userAccount = (await web3.eth.getAccounts())[0];
            lotteryContract = new web3.eth.Contract(
              contractABI,
              contractAddress
            );
          } catch (error) {
            console.error("用户拒绝了账户访问");
          }
        } else if (window.web3) {
          web3 = new Web3(window.web3.currentProvider);
          userAccount = (await web3.eth.getAccounts())[0];
          lotteryContract = new web3.eth.Contract(contractABI, contractAddress);
        } else {
          console.error("未检测到以太坊浏览器,建议安装MetaMask!");
        }
      });

      // 购买彩票
      async function buyTickets() {
        const ticketCount = document.getElementById("ticketCount").value;
        const ticketPrice = await lotteryContract.methods.ticketPrice().call();
        const totalCost = ticketPrice * ticketCount;

        try {
          await lotteryContract.methods.buyTickets(ticketCount).send({
            from: userAccount,
            value: totalCost,
          });
          alert("购买彩票成功");
        } catch (error) {
          console.error("购买彩票失败", error);
          alert("购买彩票失败");
        }
      }

      // 开奖
      async function drawWinner() {
        try {
          await lotteryContract.methods
            .drawWinner()
            .send({ from: userAccount });
          alert("开奖成功");
        } catch (error) {
          console.error("开奖失败", error);
          alert("开奖失败");
        }
      }
    </script>
  </body>
</html>

相信有一些想法的同学已经开心问了:我写java有spring boot 我写html有vue 那我写合约有没有框架呢 ?

下期预告

使用TRUFFLE编写部署调试合约(从前端到服务器再到合约,一个完整的链上拍卖实战项目)