实战:构建一个 Web3 应用

端到端实战:写一个去中心化图书馆合约,用 TronIDE 部署到 Nile 测试网,再把 TronLink 驱动的前端接到合约。

📘

前置阅读

本文从空白目录起步,最终交付一个可运行的原型应用。示例是一个去中心化图书租赁平台:书主上架书籍并按天定价,借阅方支付 TRX 换取租借时长,合约把每次租借记录到链上。

整条主线由三个环节构成:

  1. 通读图书馆合约的 Solidity 实现
  2. 通过 TronIDE 将合约部署到 Nile 测试网
  3. 借助 TronLink 把 Vue 前端接到已部署的合约

准备工作

Node.js v10+

# node -v
v10.24.1

TronLink

安装 TronLink Chrome 扩展 并新建或导入账户。DApp 通过 TronLink 向页面注入 TronWeb,并由 TronLink 负责交易签名。


最终成品

一个支持三种用户操作的去中心化图书馆:

  • 浏览 —— 列出所有可借书籍
  • 上架 —— 上架一本新书
  • 借阅 —— 支付 TRX 借走一本书一段时间

克隆完整工程:

git clone https://github.com/TRON-Developer-Hub/decentralized-library
cd decentralized-library
npm install

仓库内包含 Solidity 合约和 Vue 前端。接下来按模块拆解协作关系;完整代码以仓库为准。


合约速览

合约源码在上述仓库里。本节梳理链上数据结构与入口函数,便于对照后续前端代码;完整实现、NatSpec 注释、修饰符定义见 Solidity 源文件。

数据结构

合约状态由两个结构体承载:

结构体用途关键字段
Book一本上架书籍namedescriptionvalid(借出时为 false)、price(每天 TRX)、owner
Tracking一条租借记录bookIdstartTimeendTimeborrower

两者都以自增 ID 为键:

uint256 public bookId;
uint256 public trackingId;

mapping(uint256 => Book) public books;
mapping(uint256 => Tracking) public trackings;

公开入口函数

函数职责触发事件
addBook(name, description, price)上架新书,设 owner = msg.sendervalid = trueNewBook(bookId)
borrowBook(bookId, startTime, endTime) payable收取 price × 天数,向书主转 TRX,写入 Tracking,把 valid 置为 falseNewRental(bookId, trackingId)
deleteBook(bookId)下架;仅书主或合约 owner 可调DeleteBook(bookId)

两个辅助函数为 internal,不可通过 ABI 直接调用:_sendTRX(向书主转账 TRX)与 _createTracking(写入租借记录并把 valid 置为 false)。


部署与测试

把 Solidity 合约部署到 TRON 的工具有多种——TronBox(Truffle 风格的 CLI)、TronWeb 程序化部署、面向 JVM 技术栈的 Trident 等。本文选用 TronIDE——一个无需本地工具链即可编译、部署的浏览器 IDE——目的是把注意力放在 DApp 接入而非工具配置上。

部署目标优先选 Nile 测试网——TRON 公共测试网之一,适合上主网前先验证合约。各网络的差异见 TRON 网络

领取测试 TRX 覆盖能量成本

合约部署会消耗能量。能量可以来自质押资源;不足时,网络会燃烧 TRX 支付资源成本。前往领取测试网 Token页面领取测试 TRX,每个地址在每个水龙头每天可领一次。

让 TronIDE 接入 TronLink

TronIDE 会自动识别已登录的 TronLink 会话。务必确认 TronLink 当前网络与部署目标一致(本文为 Nile):

启用 TronIDE 模块

首次使用时,到 TronIDE 插件管理器启用 Solidity CompilerDEPLOYMENT 两个模块:

启用后左侧栏出现两个模块:

编译

新建 Library.sol,粘贴仓库内完整合约:

在编译器版本下拉中选择 0.8.0+commit.7c2e641,执行编译。

部署

fee_limit 设置为 1000000000(单位 sun,即 1,000 TRX)。它限定本次交易可使用的能量上限,覆盖账户可用的质押能量和可通过燃烧 TRX 换算出的能量,不是简单的“最多燃烧多少 TRX”。设置策略见 fee_limit 与能量费用

点击 Deploy,TronLink 弹出确认窗,对部署交易签名:

部署上链后,TronIDE 下方面板渲染合约的可调用方法,可借此核对 ABI 是否正确加载:

复制部署后的合约地址,前端会用到。


接入前端

把上一步的合约地址填进 utils.jslibraryContractAddress 变量。

TronLink 集成

TronLink 把 TronWeb 对象注入页面,使 DApp 获得一条具备签名能力的 TRON 网络连接。下面把它封装成可复用的合约句柄。

dapp-ui/plugins/utils.js 中加入:

export async function setLibraryContract() {
    bookRentContract = await
        window.tronWeb.contract().at(libraryContractAddress);
}

在页面挂载阶段初始化句柄(index.vue),随后加载书单:

async mounted() {
    // 初始化合约对象
    await setLibraryContract();
    // 拉取全部书籍
    const books = await fetchAllBooks();
    this.posts = books;
}

三个用户操作对应合约里的三个公开入口函数。

上架书籍

上架表单(bookForm.vue)收集书名、简介与每日价格。提交时把 TRX 转换成 sun,再调用合约:

postAd() {
    // 价格从 TRX 转 sun
    postBookInfo(this.title, this.description, tronWeb.toSun(this.price));
}

dapp-ui/plugins/utils.jspostBookInfo() 调用合约 addBook

const result = await bookRentContract.addBook(name, description, price).send({
    feeLimit: 100_000_000,
    callValue: 0,
    shouldPollResponse: true
});

浏览可借书籍

fetchAllBooks() 从 0 迭代到当前计数器值,逐条读取条目。被删除的条目会留下空槽,通过过滤 book.name 跳过:

const books = [];

const bookId = await bookRentContract.bookId().call();
// 从 0 迭代到 bookId
for (let i = 0; i < bookId; i++) {
    const book = await bookRentContract.books(i).call();
    if (book.name != "") {       // 过滤掉已删除的书籍
        books.push({
            id: i,
            name: book.name,
            description: book.description,
            price: tronWeb.fromSun(book.price)
        });
    }
}
return books;

index.vue 调用 fetchAllBooks() 完成首页渲染。

借阅

详情弹窗(detailsModal.vue)收集借阅时长。客户端计算总费用,再调用合约:

// 起始日
const startDay = this.getDayOfYear(this.startDate);
// 结束日
const endDay = this.getDayOfYear(this.endDate);
// 总价
const totalPrice = tronWeb.toSun(this.propData.price) * (endDay - startDay);
// 调用合约
borrowBook(this.propData.id, startDay, endDay, totalPrice);

dapp-ui/plugins/utils.jsborrowBook() 把租金作为 callValue 发出去:

const result = await bookRentContract.borrowBook(spaceId, checkInDate, checkOutDate).send({
    feeLimit: 100_000_000,
    callValue: totalPrice,
    shouldPollResponse: true
});

DApp 的完整代码到此为止。


运行

登录 TronLink,启动开发服务器:

npm run dev

浏览器打开 http://localhost:3000

点右上角 Rent Your Books 打开上架表单,填好书名、简介和每日价格:

提交,DApp 调用合约 addBook,TronLink 拦截交易并提示签名:

上链后,新书出现在书单中:

点任意书籍的 View 查看详情并选择借阅时长。Lent Now 触发 borrowBook——TronLink 再次提示签名以广播租借交易:


相关资源