Skip to content

hzeroyuke/blockchain-hw2

Repository files navigation

区块链课程作业2

1. 项目概述

EasyBet 是一个基于以太坊区块链的去中心化竞猜平台,旨在解决传统体育彩票系统缺乏交易功能的问题。通过智能合约技术,本系统实现了公平、透明、可交易的彩票竞猜机制。

核心创新点:

  • 支持彩票 NFT 化(ERC721),使彩票成为可交易资产
  • 链上订单簿系统,实现彩票二级市场交易
  • 双币种支付系统(ETH + ERC20 代币)
  • 自动化奖金分配机制,确保公平性

2. 技术栈

2.1 智能合约层

技术 版本 用途
Solidity ^0.8.20 智能合约编程语言
Hardhat 最新版 开发框架、测试、部署
OpenZeppelin 5.x ERC20/ERC721 标准库
Ethers.js v6 合约交互库

2.2 前端层

技术 版本 用途
React 19.x 前端框架
TypeScript 最新版 类型安全
Ethers.js v6 Web3 连接
MetaMask - 钱包集成

3. 项目启动

3.1 环境准备

3.1.1 安装必要工具

# 安装 Node.js (v18+)
# 访问 https://nodejs.org/ 下载安装

# 安装 Ganache
# 方式1: 下载 Ganache GUI
# 访问 https://trufflesuite.com/ganache/
# 下载并安装桌面应用

# 方式2: 安装 ganache-cli
npm install -g ganache

3.1.2 安装 MetaMask

  1. 访问 https://metamask.io/
  2. 添加浏览器扩展(Chrome/Firefox/Brave)
  3. 创建或导入钱包

3.2 启动本地区块链

cd contracts
npx hardhat node

alt text

3.3 部署智能合约

# 进入合约目录
cd contracts

# 安装依赖
npm install

# 编译合约
npx hardhat compile

px hardhat run scripts/deploy.ts --network localhost

alt text

3.4 配置前端

3.4.1 更新合约地址

cd ../frontend

编辑 src/contracts/addresses.ts,粘贴刚才部署的合约地址

alt text

3.4.2 导出合约 ABI

# 在 contracts 目录下
cd ../contracts

# 复制 ABI 文件到前端
cp artifacts/contracts/EasyBet.sol/EasyBet.json ../frontend/src/contracts/
cp artifacts/contracts/BettingTicket.sol/BettingTicket.json ../frontend/src/contracts/
cp artifacts/contracts/BettingToken.sol/BettingToken.json ../frontend/src/contracts/

3.5 启动前端应用

cd ../frontend

# 安装依赖(首次运行)
npm install

# 启动开发服务器
npm run start

预期输出:

Compiled successfully!

You can now view frontend in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://192.168.1.100:3000

配置MetaMask

  1. 点击 MetaMask 图标
  2. 点击顶部网络下拉菜单
  3. 点击"添加网络" → "手动添加网络"
  4. 输入以下信息:

Hardhat Network 配置:

  • 网络名称: Hardhat Local
  • RPC URL: http://localhost:8545
  • 链 ID: 1337
  • 货币符号: ETH

显示填写好的网络配置信息,包括网络名称、RPC URL、链ID等字段

alt text

4. 项目功能演示

4.1 链接钱包

alt text

随后会显示基本的界面

alt text

4.2 创建活动

. 在"Create New Activity"区域填写表单:

  • Title: 输入活动标题(如"NBA 2025 MVP")
  • Choices: 输入选项,用逗号分隔(如"LeBron James, Stephen Curry, Kevin Durant")
  • Duration: 选择持续时间(默认 3600 秒 = 1 小时)
  • Prize Pool: 输入初始奖池金额(如 0.1 ETH)

alt text

下注区域

alt text

交易区域

alt text

4.3 玩家下注

  1. 在活动列表中找到想要下注的活动
  2. 在"Place Bet"区域选择:
    • Activity ID: 选择活动编号(如 0)
    • Choice: 选择竞猜选项(如 "Stephen Curry",索引为 1)
    • Amount: 输入下注金额(如 0.05 ETH)
    • Payment: 确保"Use ETH"选中(默认)

alt text

下注两次之后的池子

alt text

4.4 ERC20代币下注

提:** 需要先领取 BTK 代币(参见 7.5 节)

  1. 在下注表单中选择"Use BTK Token"

Payment区域显示两个单选框,"Use ETH"未选中,"Use BTK Token"选中(蓝色圆点),Amount输入框填写"50"(代表50 BTK)

alt text

4.5 区块链交易历史

可以从MetaMask中查看

alt text

也可以从HardHat终端查看

alt text

4.6 Ticket交易

选择出售Ticket

alt text

alt text

另一个账户的视角

alt text

购买ticket

alt text

交易完成,获得ticket

alt text

4.7 庄家结算

只有庄家有权利选择result是哪个选项

alt text

结算完成之后,游戏马上结束

alt text

随后大家会获得相应的分数

5. 项目代码实现

3.1 整体架构图

┌─────────────────────────────────────────────────────────┐
│                    前端应用 (React)                       │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │ 活动管理界面  │  │  下注界面    │  │  交易市场    │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
│                                                          │
│  ┌──────────────────────────────────────────────────┐  │
│  │         Web3Context (Ethers.js v6)                │  │
│  └──────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
                          ↕ MetaMask
┌─────────────────────────────────────────────────────────┐
│              以太坊区块链 (Ganache/Hardhat)              │
│                                                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │  EasyBet     │  │ BettingTicket│  │ BettingToken │  │
│  │  (主合约)    │  │  (ERC721)    │  │  (ERC20)     │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
│        │                  │                  │          │
│        └──────────────────┴──────────────────┘          │
│                   合约间调用关系                          │
└─────────────────────────────────────────────────────────┘

3.2 合约关系

  • EasyBet: 核心业务逻辑合约,部署时自动创建 BettingTicket 和 BettingToken
  • BettingTicket: 由 EasyBet 拥有,只有 EasyBet 可以铸造新彩票
  • BettingToken: 独立的 ERC20 代币,用户可以领取并用于下注

功能实现分析

4.1 智能合约实现

4.1.1 EasyBet.sol (主合约)

核心数据结构:

struct Activity {
    address creator;           // 活动创建者
    string title;              // 活动标题
    string[] choices;          // 竞猜选项
    uint256 totalPrizePool;    // 总奖池
    uint256 endTimestamp;      // 结束时间
    ActivityStatus status;     // 活动状态 (Active/Finished/Cancelled)
    uint256 winningChoice;     // 获胜选项
    bool resultSet;            // 是否已设置结果
    mapping(uint256 => uint256) choiceBetAmounts;  // 每个选项的下注总额
    mapping(uint256 => uint256[]) choiceTickets;   // 每个选项的彩票列表
}

struct OrderBookEntry {
    uint256 tokenId;           // 彩票 ID
    address seller;            // 卖家地址
    uint256 price;             // 出售价格
    bool active;               // 订单是否有效
}

关键功能实现:

  1. 创建活动 (createActivity)

    • 输入:标题、选项数组、持续时间
    • 要求:至少 2 个选项,最短 1 小时,必须附带 ETH 作为初始奖池
    • 存储:自增活动 ID,记录创建者和配置信息
  2. 下注功能

    • placeBet(): 使用 ETH 下注
    • placeBetWithTokens(): 使用 ERC20 代币下注
    • 流程:验证活动状态 → 增加奖池 → 铸造 ERC721 彩票 → 记录到对应选项
  3. 结果设置 (setResult)

    • 权限:仅活动创建者或合约所有者
    • 流程:标记活动为 Finished → 记录获胜选项 → 触发奖金分配
  4. 奖金分配 (_distributePrizes)

    • 扣除 5% 平台手续费
    • 按照下注金额比例分配剩余奖池给获胜者
    • 奖金暂存在用户余额中,需手动提取
  5. 订单簿系统 (Bonus +3分)

    • placeOrder(): 卖家挂单出售彩票
    • fillOrder(): 买家购买挂单彩票
    • getActiveOrders(): 查询活跃订单

安全特性:

  • 使用 OpenZeppelin 的 Ownable 实现权限控制
  • 防止活动结束后下注
  • 防止自己购买自己的订单
  • 余额提取前清零,防止重入攻击

4.1.2 BettingTicket.sol (ERC721 彩票凭证)

核心数据结构:

struct TicketInfo {
    uint256 activityId;        // 所属活动 ID
    uint256 choiceIndex;       // 选择的选项
    uint256 betAmount;         // 下注金额
    uint256 timestamp;         // 购买时间
}

关键功能:

  • mintTicket(): 仅合约所有者(EasyBet)可调用,铸造新彩票
  • listForSale(): 彩票持有者挂单出售
  • buyTicket(): 购买挂单彩票(直接交易,不走订单簿)
  • cancelSale(): 取消挂单

特点:

  • 每张彩票是唯一的 NFT,包含完整的下注信息
  • 支持标准 ERC721 转账和授权
  • 提供按活动查询彩票的功能

4.1.3 BettingToken.sol (ERC20 积分代币)

核心参数:

  • 代币符号: BTK (BettingToken)
  • 初始供应: 1,000,000 BTK (部署时铸造给所有者)
  • 水龙头金额: 1,000 BTK
  • 冷却时间: 24 小时

关键功能:

  • faucet(): 用户每 24 小时可领取 1000 BTK
  • mint(): 所有者可铸造新代币
  • 标准 ERC20 转账和授权功能

实现细节:

  • 使用 lastFaucetTime mapping 记录每个地址的上次领取时间
  • 自动检查冷却时间,防止频繁领取

4.2 前端实现

4.2.1 Web3Context (上下文管理)

职责:

  • 管理 Web3 连接状态(Provider, Signer, Account)
  • 初始化三个智能合约实例
  • 处理钱包连接和账户切换
  • 验证合约部署状态

关键代码逻辑:

// 连接钱包
const connectWallet = async () => {
  const provider = new ethers.BrowserProvider(window.ethereum);
  const accounts = await provider.send('eth_requestAccounts', []);
  const signer = await provider.getSigner();

  // 验证合约是否部署
  const code = await provider.getCode(CONTRACT_ADDRESSES.EasyBet);
  if (code === '0x') {
    alert('Contract not deployed!');
    return;
  }

  // 初始化合约实例
  const easyBet = new ethers.Contract(
    CONTRACT_ADDRESSES.EasyBet,
    EasyBetABI.abi,
    signer
  );

  // 测试合约调用
  const result = await easyBet.helloworld();
  console.log('Contract test:', result);
}

全局状态:

  • provider: BrowserProvider 实例
  • signer: 当前账户的签名者
  • account: 当前连接的账户地址
  • easyBetContract, ticketContract, tokenContract: 合约实例

4.2.2 BettingApp (主应用组件)

状态管理:

interface Activity {
  id: number;
  creator: string;
  title: string;
  choices: string[];
  totalPrizePool: string;
  endTimestamp: number;
  status: number;           // 0:Active, 1:Finished, 2:Cancelled
  winningChoice: number;
  resultSet: boolean;
}

核心功能模块:

  1. 活动创建表单

    const handleCreateActivity = async () => {
      const choices = newChoices.split(',').map(c => c.trim());
      const tx = await easyBetContract.createActivity(
        newTitle,
        choices,
        parseInt(newDuration),
        { value: ethers.parseEther(newPrize) }
      );
      await tx.wait();
      loadActivities();
    }
  2. 下注功能

    const handlePlaceBet = async () => {
      if (useToken) {
        // 使用 ERC20 代币下注
        const amount = ethers.parseEther(betAmount);
        // 先授权
        await tokenContract.approve(easyBetContract.target, amount);
        // 再下注
        await easyBetContract.placeBetWithTokens(selectedActivity, selectedChoice, amount);
      } else {
        // 使用 ETH 下注
        await easyBetContract.placeBet(selectedActivity, selectedChoice, {
          value: ethers.parseEther(betAmount)
        });
      }
    }
  3. 结果设置(仅创建者可见)

    const handleSetResult = async (activityId: number, choice: number) => {
      await easyBetContract.setResult(activityId, choice);
      loadActivities();
    }
  4. 余额管理

    • 显示 ETH 奖金余额(从 userBalances 读取)
    • 显示 BTK 代币余额(从 tokenContract.balanceOf 读取)
    • 提现功能:easyBetContract.withdraw()
  5. 水龙头功能

    const handleFaucet = async () => {
      await tokenContract.faucet();
      loadBalances();
    }

UI 组件结构:

  • 顶部导航:钱包地址、余额显示、连接按钮
  • 活动创建区:表单输入(标题、选项、时长、奖池)
  • 活动列表区:卡片展示所有活动,包含状态和操作按钮
  • 下注区:选择活动、选项、金额和支付方式
  • 管理区:仅创建者可见的结果设置界面

Bonus功能说明

5.1 ERC20 代币系统 (+2分)

实现内容:

  1. 代币合约 (BettingToken.sol)

    • 符合 ERC20 标准
    • 代币名称: BettingToken (BTK)
    • 初始供应: 1,000,000 BTK
  2. 水龙头功能

    • 每个地址每 24 小时可领取 1000 BTK
    • 使用 lastFaucetTime mapping 跟踪冷却时间
    • 前端提供一键领取按钮
  3. 完整支付流程

    • 前端支持切换支付方式(ETH / BTK)
    • 使用 BTK 下注时先调用 approve(),再调用 placeBetWithTokens()
    • 按 1:1 比例转换 BTK 到 ETH 等值(简化处理)

技术亮点:

  • 冷却机制防止恶意频繁领取
  • 自动转换代币到 ETH 等值用于奖池计算
  • 前端实时显示 BTK 余额

5.2 链上订单簿系统 (+3分)

实现内容:

  1. 订单簿数据结构

    struct OrderBookEntry {
        uint256 tokenId;    // 彩票 NFT ID
        address seller;     // 卖家地址
        uint256 price;      // 出售价格 (ETH)
        bool active;        // 订单状态
    }
    mapping(uint256 => OrderBookEntry[]) public orderBooks; // activityId => orders
  2. 挂单功能 (placeOrder)

    • 卖家选择要出售的彩票和价格
    • 自动授权合约转移彩票
    • 订单添加到对应活动的订单簿
  3. 购买功能 (fillOrder)

    • 买家选择订单索引并支付 ETH
    • 合约转移彩票 NFT 给买家
    • 转移支付金额给卖家
    • 标记订单为不活跃
  4. 查询功能 (getActiveOrders)

    • 返回指定活动的所有活跃订单
    • 包含 tokenId、卖家地址、价格列表
    • 前端可据此显示订单簿界面

技术亮点:

  • 完全在链上存储订单,无需中心化服务器
  • 支持多个订单同时挂单
  • 自动处理 NFT 转移和支付
  • 支持退还多余支付金额

订单簿显示示例:

活动 #1 订单簿
┌────────┬──────────────────┬─────────┐
│ Ticket │ Seller           │ Price   │
├────────┼──────────────────┼─────────┤
│ #001   │ 0x1234...5678    │ 0.05 ETH│
│ #003   │ 0xabcd...ef00    │ 0.03 ETH│
│ #007   │ 0x9876...5432    │ 0.08 ETH│
└────────┴──────────────────┴─────────┘

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •