区块链中文技术社区

BNB / BSC 节点中的 BLS 密钥:原理、作用与架构升级解析

随着 BNB Chain(包含 BNB Beacon Chain 与 BNB Smart Chain,简称 BSC)持续演进,其共识层经历了重要升级:从早期基于 ECDSA 的签名机制,过渡到采用 BLS(Boneh–Lynn–Shacham)聚合签名的高性能 PoSA 共识架构。

在新的节点体系中,每个验证者除了传统的 ECDSA 私钥之外,还必须生成并配置一套 BLS 密钥。本文将系统性介绍:

  • BLS 是什么
  • 为什么 BNB/BSC 要使用 BLS
  • BLS 密钥在共识中的具体作用
  • 与 ECDSA 的对比
  • 架构升级带来的性能与安全变化

一、为什么 BNB/BSC 需要 BLS?

BSC 早期使用的是 PoSA(Proof of Staked Authority)+ ECDSA 签名。
这种方式在功能上可用,但存在两个核心问题:

  1. 签名无法聚合,每个区块需要存储和验证大量签名
  2. 验证者数量和区块大小难以进一步扩展

随着 BNB Chain 融合 Beacon Chain 架构、提升 TPS 的需求,BNB 引入了基于 BLS 的聚合签名技术,使共识开销更低、性能更高。


二、BLS 的核心能力

BLS(Boneh–Lynn–Shacham)最重要的特点包括以下三点。

1. 支持签名聚合(Aggregate Signature)

多个验证者的签名可以数学地合并为一个签名:

  • 100 个签名
    最终只需要 1 个聚合签名

区块头体积极大缩小,使出块更快、网络传输更轻。

2. 原生支持阈值签名(Threshold Signature)

可以实现多数验证者共同生成一个有效签名:

  • 超过 2/3 的验证者签名
    自动生成一个可验证的单一签名

无需额外的多重签名协议。

3. 验证速度快、可扩展性强

在验证区块时:

  • 节点只需验证一个聚合签名
  • 而不需要验证每个验证者的单独签名

这使得共识的可扩展性显著提升。


三、BLS 私钥在 BNB/BSC 中的作用

在新的 BNB Chain 共识架构中,BLS 密钥承担的是“共识身份”和“投票签名”的职责,而不是交易签名。

它主要有以下三个作用。


1. 作为验证者的共识层身份密钥

在共识过程(prevote、precommit、aggregate)中,验证者使用 BLS 私钥对投票进行签名。
链上保存的是验证者的 BLS 公钥,用于:

  • 验证投票来源
  • 验证者轮换
  • 共识身份验证

可以理解为验证者的“共识身份证”。


2. 生成区块的 BLS 聚合签名

每个区块高度都需要验证者投票确认。
使用 BLS 后:

  • 所有验证者的投票可以聚合为一个签名
  • 区块头中只写入一个 BLS 聚合签名
  • 节点验证速度更快,区块体积更小

这是 BNB Chain 性能提升的关键机制。


3. 支持安全的阈值共识机制

PoSA 要求超过三分之二的验证者同意区块才能最终确认。
BLS 天生支持阈值验证,并提供:

  • 更简单的投票验证逻辑
  • 更强的防伪造能力
  • 更安全的验证者切换

让整个共识层更安全可靠。


四、为什么不是使用 ECDSA?(对比)

能力 ECDSA BLS
签名聚合 不支持 支持
阈值签名 不支持,需要外部协议 原生支持
多验证者共识性能 一般 高效
区块头大小
扩展性 显著受限
用途 交易签名 共识投票签名

结论:
交易层仍然使用 ECDSA(钱包、合约交互等)。
共识层使用 BLS 才能获得高性能和更佳的扩展性。


五、BNB/BSC 架构升级后的变化

随着 BLS 的引入,BNB Chain 获得了以下提升。

1. 共识性能显著提升

减少共识消息体积、减少签名验证次数,使:

  • 区块时间更稳定
  • TPS 上限提升
  • 网络带宽消耗更低

2. 验证者数量可扩展

BSC 早期固定 21 个验证者,使用 BLS 后可以支持更多验证者参与,而不用担心过高的签名成本。


3. 更安全的验证者管理

BLS 将验证者的共识身份抽象为统一的公钥,使身份管理更彻底、更可控。


六、BNB/BSC 节点中的两套密钥

BNB/BSC 节点实际上使用两套密钥体系,用途完全不同。

1. ECDSA Key(交易密钥)

  • 用于链上交易签名
  • secp256k1 曲线
  • 与以太坊、MetaMask 完全一致

2. BLS Key(共识密钥)

  • 仅验证者节点使用
  • 只用于共识投票
  • 不用于交易
  • 不出现在任何钱包中

这两套密钥互不影响,各司其职。


七、总结

BLS 密钥在 BNB/BSC 中主要用于验证者共识层的投票与聚合签名,通过支持多签名聚合、阈值验证和快速校验,显著增强了区块确认速度、网络扩展性与整体安全性。它不用于交易,只用于共识层,是 BNB Chain 迈向高性能架构的重要基础组件。

BNB BSC 合约关系与方法梳理

1. 合约架构概述

核心合约

  • System.sol / SystemV2.sol:基础合约,定义系统常量、地址和修饰符
  • BSCValidatorSet.sol:验证者集合管理
  • StakeHub.sol:验证者质押管理
  • GovHub.sol:治理参数更新
  • SlashIndicator.sol:验证者行为监控与惩罚
  • SystemReward.sol:系统奖励管理

合约关系图

2. 各合约主要功能与方法

2.1 System.sol / SystemV2.sol

  • 功能:定义系统常量、地址和修饰符
  • 核心常量:各系统合约地址(VALIDATOR_CONTRACT_ADDR、SLASH_CONTRACT_ADDR等)
  • 修饰符:onlyCoinbase、onlyZeroGasPrice、onlyGov等

2.2 BSCValidatorSet.sol

  • 功能:管理验证者集合,处理惩罚和维护
  • 对外方法
    • initializeFromGenesis:从创世块初始化验证者集合
    • updateValidatorSetV2:更新验证者集合
    • deposit:收集交易费用并分配
    • distributeFinalityReward:分配最终性奖励
    • getLivingValidators:获取活跃验证者
    • getMiningValidators:获取挖矿验证者
    • enterMaintenance:进入维护模式
    • exitMaintenance:退出维护模式
  • 被链代码调用的方法
    • updateValidatorSetV2:由共识引擎调用
    • deposit:由区块生产者调用
    • distributeFinalityReward:由区块生产者调用

2.3 StakeHub.sol

  • 功能:验证者质押管理,包括创建验证者、委托等
  • 对外方法
    • createValidator:创建验证者
    • editConsensusAddress:编辑共识地址
    • editCommissionRate:编辑佣金率
    • editDescription:编辑描述
    • editVoteAddress:编辑投票地址
    • unjail:解锁验证者
    • delegate:委托
    • undelegate:取消委托
    • redelegate:重新委托
    • claim:领取取消委托的BNB
  • 被链代码调用的方法
    • distributeReward:由验证者合约调用
    • downtimeSlash:由惩罚合约调用
    • maliciousVoteSlash:由惩罚合约调用
    • doubleSignSlash:由惩罚合约调用

2.4 GovHub.sol

  • 功能:治理参数更新
  • 对外方法
    • updateParam:更新参数,只能由治理时间锁合约调用

2.5 SlashIndicator.sol

  • 功能:监控验证者行为,处理惩罚
  • 对外方法
    • slash:惩罚未出块的验证者
    • clean:清理惩罚记录
    • submitFinalityViolationEvidence:提交最终性违规证据
    • submitDoubleSignEvidence:提交双重签名证据
  • 被链代码调用的方法
    • slash:由区块生产者调用
    • clean:由验证者合约调用

2.6 SystemReward.sol

  • 功能:系统奖励管理
  • 对外方法
    • claimRewards:领取奖励,只能由操作员调用
  • 被链代码调用的方法
    • claimRewards:由轻客户端、激励合约等调用

3. 合约调用关系

3.1 链代码调用合约

  • 共识引擎 → BSCValidatorSet.updateValidatorSetV2
  • 区块生产者 → BSCValidatorSet.deposit
  • 区块生产者 → BSCValidatorSet.distributeFinalityReward
  • 区块生产者 → SlashIndicator.slash

3.2 合约间调用

  • BSCValidatorSet → StakeHub.distributeReward
  • BSCValidatorSet → SlashIndicator.clean
  • SlashIndicator → BSCValidatorSet.felony
  • SlashIndicator → BSCValidatorSet.misdemeanor
  • SlashIndicator → StakeHub.downtimeSlash
  • SlashIndicator → StakeHub.maliciousVoteSlash
  • SlashIndicator → StakeHub.doubleSignSlash
  • GovHub → BSCValidatorSet.updateParam
  • GovHub → StakeHub.updateParam
  • GovHub → SlashIndicator.updateParam
  • GovHub → SystemReward.updateParam

4. 接口定义

4.1 0.6.x 接口

  • IBSCValidatorSet:验证者集合接口
  • ISlashIndicator:惩罚接口
  • IStakeHub:质押管理接口
  • IParamSubscriber:参数订阅接口
  • ISystemReward:系统奖励接口

4.2 0.8.x 接口

  • IBSCValidatorSet:验证者集合接口
  • IGovToken:治理代币接口
  • IStakeCredit:质押信用接口
  • IStakeHub:质押管理接口

5. 系统流程

5.1 验证者创建流程

  1. 用户调用 StakeHub.createValidator 创建验证者
  2. StakeHub 部署 StakeCredit 代理合约
  3. StakeHub 记录验证者信息
  4. 用户可以调用 delegate 进行委托

5.2 验证者惩罚流程

  1. 区块生产者调用 SlashIndicator.slash 惩罚未出块的验证者
  2. SlashIndicator 检查惩罚阈值
  3. 如果达到 misdemeanor 阈值,调用 BSCValidatorSet.misdemeanor
  4. 如果达到 felony 阈值,调用 BSCValidatorSet.felony 和 StakeHub.downtimeSlash

5.3 参数更新流程

  1. 治理提案通过后,GovernorTimelock 调用 GovHub.updateParam
  2. GovHub 调用目标合约的 updateParam 方法
  3. 目标合约更新参数并触发事件

6. 总结

该合约系统是一个完整的区块链验证者管理和治理系统,主要包括:

  • 验证者管理:BSCValidatorSet 负责验证者集合的更新和维护
  • 质押管理:StakeHub 负责验证者的质押、委托等操作
  • 惩罚机制:SlashIndicator 负责监控验证者行为并处理惩罚
  • 治理系统:GovHub 负责参数更新
  • 奖励管理:SystemReward 负责系统奖励的分配

合约之间通过明确的接口进行交互,形成了一个完整的生态系统,确保区块链网络的安全和稳定运行。

dApp开发入门教程:从零开始构建链上留言板

1. 什么是dApp?

dApp(去中心化应用)是基于区块链技术构建的应用程序,与传统应用的主要区别在于:

  • 去中心化:数据存储在区块链上,不依赖单一服务器
  • 透明公开:智能合约代码和数据对所有人可见
  • 用户控制:用户完全控制自己的数据和资产
  • 无需信任:通过智能合约自动执行,无需第三方中介

本教程将指导你从零开始构建一个完整的dApp——链上留言板,让你掌握dApp开发的核心流程。

2. 开发环境搭建

2.1 安装Node.js

dApp开发需要Node.js环境,推荐使用LTS版本:

  1. 访问 Node.js官网
  2. 下载并安装适合你操作系统的LTS版本
  3. 验证安装:
    node -v
    npm -v

2.2 安装MetaMask

MetaMask是连接dApp和区块链的桥梁:

  1. 访问 MetaMask官网
  2. 下载并安装浏览器插件
  3. 创建钱包并保存助记词
  4. 添加Trustivon测试网络(参考MetaMask网络设置

2.3 获取测试代币

在Trustivon测试网上开发需要测试代币:

  1. 访问 Trustivon水龙头
  2. 输入你的MetaMask地址
  3. 领取测试代币

3. 智能合约开发

3.1 初始化Hardhat项目

Hardhat是以太坊智能合约开发的流行框架:

  1. 创建项目目录:

    mkdir doomsday-dapp
    cd doomsday-dapp
  2. 初始化npm项目:

    npm init -y
  3. 安装Hardhat:

    npm install --save-dev hardhat
  4. 初始化Hardhat项目:

    npx hardhat init
    • 选择"Create a TypeScript project"
    • 按默认选项完成初始化
  5. 安装依赖:

    npm install --save-dev @nomicfoundation/hardhat-toolbox
    npm install dotenv

3.2 编写智能合约

创建一个简单的链上留言板合约:

  1. 创建合约文件:

    mkdir -p contracts
    touch contracts/MessageBoard.sol
  2. 编写合约代码:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.19;
    
    contract MessageBoard {
        struct Message {
            string content;
            address sender;
            uint256 bidAmount;
            uint256 timestamp;
            bytes32 messageId;
        }
    
        Message[] public messages;
        uint256 public constant MAX_MESSAGES = 100;
        uint256 public constant DECAY_INTERVAL = 24 hours; // 衰减间隔:24小时
        uint256 public constant DECAY_RATE = 50; // 衰减率:50%
    
        uint256 public lastMessageTimestamp; // 最后一条留言的时间戳
    
        event MessageAdded(
            bytes32 messageId,
            string content,
            address sender,
            uint256 bidAmount,
            uint256 timestamp
        );
    
        /**
         * @dev 获取当前能进入前100名的最低竞价
         * @return 最低竞价金额,如果留言数量不足100,则返回0
         */
        function getMinimumBidForTop100() public view returns (uint256) {
            if (messages.length < MAX_MESSAGES) {
                // 如果留言数量不足100,任何大于0的竞价都能进入前100
                return 0;
            }
    
            // 获取第100条留言的原始竞价
            uint256 originalBid = messages[MAX_MESSAGES - 1].bidAmount;
    
            // 如果最后一条留言时间为0,说明还没有留言,返回0
            if (lastMessageTimestamp == 0) {
                return originalBid;
            }
    
            // 计算自最后一条留言以来经过的时间
            uint256 timeElapsed = block.timestamp - lastMessageTimestamp;
    
            // 如果经过的时间小于衰减间隔,返回原始竞价
            if (timeElapsed < DECAY_INTERVAL) {
                return originalBid;
            }
    
            // 计算经过了多少个衰减周期
            uint256 decayPeriods = timeElapsed / DECAY_INTERVAL;
    
            // 计算衰减后的竞价
            uint256 decayedBid = originalBid;
            for (uint256 i = 0; i < decayPeriods; i++) {
                // 每次衰减50%
                decayedBid = decayedBid * (100 - DECAY_RATE) / 100;
    
                // 防止衰减到0以下
                if (decayedBid == 0) {
                    break;
                }
            }
    
            return decayedBid;
        }
    
        function addMessage(string calldata _content, bytes32 _messageId) external payable {
            require(msg.value > 0, "Bid amount must be greater than 0");
    
            // 检查当前竞价是否大于等于进入前100名的最低竞价
            uint256 minimumBid = getMinimumBidForTop100();
            require(msg.value > minimumBid, "Bid amount must be greater than the current minimum bid for top 100");
    
            Message memory newMessage = Message({
                content: _content,
                sender: msg.sender,
                bidAmount: msg.value,
                timestamp: block.timestamp,
                messageId: _messageId
            });
    
            // 插入排序,按竞价金额降序
            uint256 i = messages.length;
            messages.push(newMessage);
    
            while (i > 0 && messages[i - 1].bidAmount < newMessage.bidAmount) {
                messages[i] = messages[i - 1];
                i--;
            }
    
            if (i != messages.length - 1) {
                messages[i] = newMessage;
            }
    
            // 只保留前100条留言
            if (messages.length > MAX_MESSAGES) {
                messages.pop();
            }
    
            // 更新最后一条留言的时间戳
            lastMessageTimestamp = block.timestamp;
    
            emit MessageAdded(
                _messageId,
                _content,
                msg.sender,
                msg.value,
                block.timestamp
            );
        }
    
        function getMessages() external view returns (Message[] memory) {
            return messages;
        }
    
        /**
         * @dev 分页获取留言
         * @param _page 页码,从1开始
         * @param _pageSize 每页数量
         * @return 分页后的留言数组
         */
        function getMessagesPaginated(uint256 _page, uint256 _pageSize) external view returns (Message[] memory) {
            require(_page > 0, "Page must be greater than 0");
            require(_pageSize > 0, "Page size must be greater than 0");
    
            uint256 totalMessages = messages.length;
            uint256 startIndex = (_page - 1) * _pageSize;
    
            // 如果起始索引大于等于总留言数,返回空数组
            if (startIndex >= totalMessages) {
                return new Message[](0);
            }
    
            // 计算结束索引
            uint256 endIndex = startIndex + _pageSize;
            if (endIndex > totalMessages) {
                endIndex = totalMessages;
            }
    
            // 创建结果数组
            uint256 resultSize = endIndex - startIndex;
            Message[] memory result = new Message[](resultSize);
    
            // 填充结果数组
            for (uint256 i = 0; i < resultSize; i++) {
                result[i] = messages[startIndex + i];
            }
    
            return result;
        }
    
        function getMessageCount() external view returns (uint256) {
            return messages.length;
        }
    
        // 允许合约接收ETH
        receive() external payable {}
    
        // 允许合约接收ETH(当调用不存在的函数时)
        fallback() external payable {}
    }

3.3 编译和测试合约

  1. 编译合约:

    npx hardhat compile
  2. 编写测试文件:

    mkdir -p test
    touch test/MessageBoard.test.js
  3. 编写测试代码:

    const { expect } = require("chai");
    const { ethers } = require("hardhat");
    
    describe("MessageBoard", function () {
      let MessageBoard;
      let messageBoard;
      let owner;
      let addr1;
    
      beforeEach(async function () {
        [owner, addr1] = await ethers.getSigners();
        MessageBoard = await ethers.getContractFactory("MessageBoard");
        messageBoard = await MessageBoard.deploy();
        await messageBoard.deployed();
      });
    
      it("Should add a message", async function () {
        const content = "Hello, Trustivon!";
        const messageId = ethers.utils.formatBytes32String("test-1");
        const bidAmount = ethers.utils.parseEther("0.1");
    
        await expect(
          messageBoard.addMessage(content, messageId, { value: bidAmount })
        )
          .to.emit(messageBoard, "MessageAdded")
          .withArgs(messageId, content, owner.address, bidAmount, expect.any(BigInt));
    
        const messages = await messageBoard.getMessages();
        expect(messages.length).to.equal(1);
        expect(messages[0].content).to.equal(content);
        expect(messages[0].sender).to.equal(owner.address);
      });
    });
  4. 运行测试:

    npx hardhat test

3.4 部署合约

  1. 创建部署脚本:

    mkdir -p scripts
    touch scripts/deploy.js
  2. 编写部署代码:

    const { ethers } = require("hardhat");
    
    async function main() {
      const [deployer] = await ethers.getSigners();
    
      console.log("Deploying contracts with the account:", deployer.address);
      console.log("Account balance:", (await deployer.getBalance()).toString());
    
      const MessageBoard = await ethers.getContractFactory("MessageBoard");
      const messageBoard = await MessageBoard.deploy();
    
      await messageBoard.deployed();
    
      console.log("MessageBoard contract deployed to:", messageBoard.address);
    }
    
    main()
      .then(() => process.exit(0))
      .catch((error) => {
        console.error(error);
        process.exit(1);
      });
  3. 配置Trustivon网络:

    • 创建.env文件:
      touch .env
    • 添加配置:
      PRIVATE_KEY=your-private-key
      TRUSTIVON_RPC_URL=https://rpc.trustivon.com
    • 修改hardhat.config.js,添加Trustivon网络配置
  4. 部署合约到Trustivon测试网:

    npx hardhat run scripts/deploy.js --network trustivon

4. 前端开发

4.1 初始化React项目

  1. 创建前端目录:

    npx create-react-app frontend
    cd frontend
    npm install web3 @web3-react/core @web3-react/injected-connector
  2. 创建合约ABI目录:

    mkdir -p src/contracts
  3. 复制合约ABI:

    cp ../artifacts/contracts/MessageBoard.sol/MessageBoard.json src/contracts/

4.2 编写前端代码

  1. 创建Web3连接组件:

    mkdir -p src/components
    touch src/components/Web3Provider.js
  2. 编写Web3连接代码:

    import React, { createContext, useContext, useEffect, useState } from 'react';
    import { InjectedConnector } from '@web3-react/injected-connector';
    import Web3 from 'web3';
    
    const Web3Context = createContext();
    
    export const useWeb3 = () => useContext(Web3Context);
    
    export const Web3Provider = ({ children }) => {
      const [web3, setWeb3] = useState(null);
      const [account, setAccount] = useState(null);
      const [networkId, setNetworkId] = useState(null);
      const [loading, setLoading] = useState(true);
    
      const connector = new InjectedConnector({
        supportedChainIds: [19478], // Trustivon测试网链ID
      });
    
      const connectWallet = async () => {
        try {
          const accounts = await connector.activate();
          setAccount(accounts[0]);
        } catch (error) {
          console.error('Failed to connect wallet:', error);
        }
      };
    
      useEffect(() => {
        const initWeb3 = async () => {
          try {
            if (window.ethereum) {
              const web3Instance = new Web3(window.ethereum);
              setWeb3(web3Instance);
    
              const network = await web3Instance.eth.net.getId();
              setNetworkId(network);
    
              const accounts = await web3Instance.eth.getAccounts();
              if (accounts.length > 0) {
                setAccount(accounts[0]);
              }
            }
          } catch (error) {
            console.error('Failed to initialize Web3:', error);
          } finally {
            setLoading(false);
          }
        };
    
        initWeb3();
    
        // 监听账户变化
        window.ethereum?.on('accountsChanged', (accounts) => {
          setAccount(accounts[0]);
        });
    
        // 监听网络变化
        window.ethereum?.on('chainChanged', (chainId) => {
          setNetworkId(parseInt(chainId, 16));
        });
      }, []);
    
      const value = {
        web3,
        account,
        networkId,
        loading,
        connectWallet,
      };
    
      return <Web3Context.Provider value={value}>{children}</Web3Context.Provider>;
    };
  3. 创建留言板组件:

    touch src/components/MessageBoard.js
  4. 编写留言板代码:

    import React, { useState, useEffect } from 'react';
    import { useWeb3 } from './Web3Provider';
    import contractABI from '../contracts/MessageBoard.json';
    
    const CONTRACT_ADDRESS = 'your-contract-address'; // 替换为你的合约地址
    
    const MessageBoard = () => {
      const { web3, account, connectWallet } = useWeb3();
      const [messages, setMessages] = useState([]);
      const [content, setContent] = useState('');
      const [bidAmount, setBidAmount] = useState('0.1');
      const [loading, setLoading] = useState(false);
    
      const contract = web3 && new web3.eth.Contract(contractABI.abi, CONTRACT_ADDRESS);
    
      const fetchMessages = async () => {
        if (!contract) return;
        try {
          const result = await contract.methods.getMessages().call();
          setMessages(result);
        } catch (error) {
          console.error('Failed to fetch messages:', error);
        }
      };
    
      useEffect(() => {
        fetchMessages();
      }, [contract]);
    
      const addMessage = async (e) => {
        e.preventDefault();
        if (!contract || !account) return;
    
        setLoading(true);
        try {
          const messageId = web3.utils.sha3(Date.now().toString());
          const value = web3.utils.toWei(bidAmount, 'ether');
    
          await contract.methods
            .addMessage(content, messageId)
            .send({ from: account, value });
    
          setContent('');
          setBidAmount('0.1');
          fetchMessages();
        } catch (error) {
          console.error('Failed to add message:', error);
        } finally {
          setLoading(false);
        }
      };
    
      if (!account) {
        return (
          <div className="flex justify-center items-center h-screen">
            <button
              onClick={connectWallet}
              className="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
            >
              Connect Wallet
            </button>
          </div>
        );
      }
    
      return (
        <div className="container mx-auto p-4">
          <h1 className="text-3xl font-bold mb-6 text-center">链上留言板</h1>
    
          <form onSubmit={addMessage} className="mb-8">
            <div className="mb-4">
              <label className="block text-sm font-medium mb-2">留言内容</label>
              <textarea
                value={content}
                onChange={(e) => setContent(e.target.value)}
                className="w-full p-2 border border-gray-300 rounded"
                rows="3"
                required
              />
            </div>
            <div className="mb-4">
              <label className="block text-sm font-medium mb-2">竞价金额 (TC)</label>
              <input
                type="number"
                value={bidAmount}
                onChange={(e) => setBidAmount(e.target.value)}
                className="w-full p-2 border border-gray-300 rounded"
                step="0.1"
                min="0.1"
                required
              />
            </div>
            <button
              type="submit"
              className="w-full py-2 bg-green-500 text-white rounded hover:bg-green-600"
              disabled={loading}
            >
              {loading ? '提交中...' : '提交留言'}
            </button>
          </form>
    
          <div className="space-y-4">
            <h2 className="text-2xl font-bold mb-4">留言列表</h2>
            {messages.length === 0 ? (
              <p className="text-center text-gray-500">暂无留言</p>
            ) : (
              messages.map((msg, index) => (
                <div key={index} className="border border-gray-300 rounded p-4">
                  <div className="flex justify-between items-center mb-2">
                    <span className="font-bold">{msg.sender}</span>
                    <span className="text-sm text-gray-500">
                      {new Date(msg.timestamp * 1000).toLocaleString()}
                    </span>
                  </div>
                  <p className="mb-2">{msg.content}</p>
                  <div className="text-right text-sm text-blue-600">
                    竞价: {web3.utils.fromWei(msg.bidAmount, 'ether')} TC
                  </div>
                </div>
              ))
            )}
          </div>
        </div>
      );
    };
    
    export default MessageBoard;
  5. 更新App.js:

    import React from 'react';
    import './App.css';
    import { Web3Provider } from './components/Web3Provider';
    import MessageBoard from './components/MessageBoard';
    
    function App() {
      return (
        <Web3Provider>
          <div className="App">
            <MessageBoard />
          </div>
        </Web3Provider>
      );
    }
    
    export default App;

4.3 运行前端应用

  1. 启动前端开发服务器:

    npm start
  2. 在浏览器中访问 http://localhost:3000

  3. 连接MetaMask钱包

  4. 测试留言功能:

    • 输入留言内容
    • 设置竞价金额
    • 提交留言
    • 查看留言列表

5. 部署和上线

5.1 构建前端应用

  1. 构建生产版本:

    npm run build
  2. 部署到静态网站托管服务(如Vercel、Netlify等)

5.2 验证和测试

  1. 在浏览器中访问部署后的网站
  2. 测试所有功能
  3. 确保与MetaMask正常交互
  4. 检查交易是否正确上链

6. 总结和扩展

6.1 开发总结

通过本教程,你已经学会了:

  • 搭建dApp开发环境
  • 编写和部署智能合约
  • 开发React前端应用
  • 连接Web3和MetaMask
  • 与智能合约交互

6.2 扩展建议

你可以进一步扩展这个dApp:

  • 添加用户认证和个人中心
  • 实现留言编辑和删除功能
  • 添加留言点赞和评论功能
  • 实现留言搜索和筛选
  • 添加链上身份验证
  • 优化前端UI/UX设计

6.3 学习资源

7. 常见问题

7.1 无法连接MetaMask

  • 确保MetaMask已安装并解锁
  • 确保已添加Trustivon测试网络
  • 刷新页面后重试

7.2 交易失败

  • 确保钱包中有足够的测试代币
  • 检查Gas费用设置
  • 查看MetaMask交易记录中的错误信息

7.3 留言不显示

  • 检查合约地址是否正确
  • 确保网络连接正常
  • 刷新页面后重试

8. 社区支持

恭喜你完成了第一个dApp的开发!继续学习和探索,你将能够构建更复杂和强大的去中心化应用。

线上预览:https://eternal.trustivon.com/
GitHub开源:https://github.com/Trustivon/eternal-message-dapp

如何使用 Trustivon 数字钱包(Vault)管理您的数字资产

概述

本教程将指导您如何使用 Trustivon 数字钱包(Vault)管理您的数字资产,包括创建钱包、存储资产和发起交易。

先决条件

操作步骤

1. 打开 Trustivon 数字钱包

浏览器访问地址:https://vault.trustivon.com

img

2. 登录和注册

选项一:电子邮件注册和登录

img

  • 点击“登录”按钮
  • 输入邮箱和密码
  • 点击“登录”按钮
  • 登录您填写的邮箱,查看激活邮件中的链接,然后点击激活。
  • 返回登录界面,输入邮箱和密码完成登录。

选项二:使用 MetaMask 钱包登录

  • 点击“使用以太坊登录”按钮
  • 在弹出的 MetaMask 对话框中,单击确认按钮
  • 自动注册和登录完成

3. 管理资产

查看仪表盘

img

1. 存款地址

登录后,您将自动获得一个分配的 MPC 钱包存款地址,用于接收链上资产。

当此存款地址收到支持的链上资产时,资产将自动记入您的登录账户余额。

Trustivon 测试网目前支持的存款代币如下:

代币符号 令牌地址 标记小数
0x985B6b5B751A0FE61498A36B29F9E73Fe5d733f4 18
美国财政部 0x50fC77e9115CF0CD317A2c341023E454870dd011 18
美元 0x69d0E122E8e207250EB0E84f1eAB7e429A4a27dA 18
比特币 0xF6c3ED439B8534D3A93C0750a00FF8ff71f523cd 18
TC Chain Native Token 18

2. 账户余额

功能介绍:将支持的资产存入指定充值地址后,您的链下账户余额将会更新。您无需任何区块链相关经验,即可通过 Web2 方式使用中心化资产。其充值和提现功能与传统交易所的功能相同。

提现:支持将余额提现到区块链。点击“提现”按钮,打开提现对话框,选择提现资产,输入提现金额和链上接收地址,即可完成链上提现。

img

3. 内部调拨

img

请输入相关参数以完成内部交易转账。

查看交易记录

img

  • 存款记录:链上存款记录
  • 转账记录:内部交易记录
  • 提现记录:链上提现记录

相关链接

新手完全指南:从安装 MetaMask 到完成你在 Trustivon 的第一笔链上转账

这篇文章面向完全没有区块链基础的小白用户。
如果你从未接触过区块链、不会使用钱包,甚至不知道“链”“转账”是什么意思,本教程将带你从零开始学会:

完成阅读后,你将能完整理解一笔区块链交易的运作方式。


一、MetaMask 是什么?为什么要安装它?

MetaMask 是目前最常用的区块链钱包,作用包括:

  • 用于管理你的区块链账户
  • 登录区块链应用(类似 Web3 的“微信登录”)
  • 发起链上转账
  • 添加新的区块链网络
  • 查看资产余额

你可以将 MetaMask 理解为进入区块链世界的入口工具。
下面开始正式安装。


二、安装 MetaMask(以电脑 Chrome 浏览器为例)

步骤 1:访问 MetaMask 官方下载网址

为了安全,请务必访问官方地址:
https://metamask.io/download/

步骤 2:选择浏览器版本(以 Chrome 为例)

点击 “Install MetaMask for Chrome”,跳转至扩展商店。

步骤 3:安装扩展插件

在 Chrome 扩展页面点击:
“Add to Chrome” → “Add extension”

安装完成后 MetaMask 会自动打开。

步骤 4:创建新钱包

  • 点击 “Create a new wallet”
  • 设置钱包密码(这个密码只用于本地浏览器)
  • 按提示备份助记词
    • 不要截图
    • 不要发送给任何人
    • 不要存到微信、QQ
    • 助记词丢失后,资产无法找回

恭喜,你已经成功安装了区块链钱包。


三、通过 Trustivon ChainList 添加 Trustivon 测试网

ChainList 是一个快速添加区块链网络的工具。Trustivon 提供了独立的链配置入口。

步骤 1:打开 Trustivon ChainList

访问:
https://chainlist.trustivon.com/

页面中会看到 Trustivon Testnet 网络的配置项。

步骤 2:连接钱包

点击右上角 “Connect Wallet”
MetaMask 弹出提示 → 选择账户 → 点击连接。

步骤 3:将 Trustivon 测试网添加到 MetaMask

点击 “Add to MetaMask”
MetaMask 会弹出请求
→ 点击 “Approve”
→ 再点击 “Switch Network”

此时你的钱包已成功切换到 Trustivon Testnet 网络。


四、通过 Trustivon Faucet 领取 TC 测试币

测试网的转账不需要真币,TC 是免费的测试代币。

步骤 1:访问水龙头网站

打开:
https://faucet.trustivon.com/

步骤 2:复制你的钱包地址

打开 MetaMask
点击你的地址进行复制
地址格式一般以 “0x” 开头。

步骤 3:粘贴地址并领取测试币

将地址粘贴到输入框
点击“领取”或“Claim”

稍等几秒,你的钱包将收到 TC 测试币。


五、在 MetaMask 中发起第一笔链上转账

这是区块链最核心的操作。

步骤 1:打开 MetaMask,确认当前网络为 Trustivon Testnet

如果不是,请手动切换。

步骤 2:点击 “Send”

步骤 3:填写接收地址

你可以准备第二个地址,或使用朋友的地址。

步骤 4:输入转账金额

例如输入:
0.1 TC

步骤 5:确认 Gas Fee(矿工费)

测试网费用很低。
点击 “Confirm” 发送交易。

交易会进入网络打包,这需要几秒时间。


六、在 Trustivon Scan 查看交易状态

区块链浏览器是用于查询所有链上数据的工具。

步骤 1:访问 Trustivon Scan

打开:
https://scan.trustivon.com/

步骤 2:查看交易信息

你可以通过以下方式查看交易:

  • 搜索交易哈希(MetaMask 中会显示)
  • 搜索你的钱包地址
  • 查看最新区块列表

在浏览器中可以看到:

  • 交易是否成功
  • 发起时间
  • 发送地址/接收地址
  • Gas 消耗
  • 区块高度

这表明你的转账已成功记录在链上。


七、你已完成整个链上流程

到这里,你已经独立完成了:

  • 安装 MetaMask
  • 添加 Trustivon 自定义网络
  • 领取测试币
  • 发起链上转账
  • 在区块链浏览器查询交易

这意味着你已经具备操作任何 EVM 系列区块链的能力,例如 Ethereum、BNB Chain、Polygon、Arbitrum 等。

Trustivon 提供的工具让新用户能够快速、低门槛地体验区块链操作流程。