系列导航

顺序文章定位
1📍当前:Web3 链上转账完整流程(小白版)零代码:建立整体认识 + 智能合约在项目里的意义
2智能合约(contracts)详解MyToken 代币合约 + MiniSwap 兑换合约(AMM / 恒定乘积 / DeFi),以及智能合约实际作用总结
3前端(frontend)详解网页如何连接 MetaMask / TronLink,发起转账和兑换
4后端(backend)详解后端如何监听链上事件、可靠地抄进数据库,并实时推送给页面

本系列围绕一个学习项目(Mini DApp)展开。它做了三件事:自己发行一种叫 MTK 的代币;让用户之间转账(ETH / TRX / USDT / MTK);让用户在链上把一种资产兑换成另一种(MiniSwap 兑换合约)。项目同时跑在三条链上:本地链 anvil、以太坊测试网 Sepolia、波场测试网 Nile。

本篇不含需要读懂的代码,只帮你搭好“整体地图”,并回答一个核心问题:智能合约在这个项目里到底有什么用? 后面三篇再分别钻进合约、前端、后端。

读完本篇你将学会

  • 看懂 Web3 里最常见的几个词:钱包、地址、区块链、智能合约、燃料费
  • 分清“原生币”“稳定币”“代币”,知道 ETH / TRX / USDT / MTK 各属于哪一类
  • 知道一个 Web3 App 由哪三部分组成、各自负责什么
  • 说清一笔链上转账从准备账户到出现在列表里的完整 4 步
  • ★ 讲清智能合约在本项目中的三个作用:发行代币、链上转账、链上兑换

前置知识:无需任何区块链基础。懂一点前端或后端开发会更好理解,但不是必须。


一、先认识 5 个词

这一章解决的问题:看 Web3 文章时满屏陌生词,先把最核心的 5 个弄明白。

词一句话解释生活类比
钱包(MetaMask / TronLink)浏览器插件,保管你的私钥,替你“签字”。以太坊系用 MetaMask,波场用 TronLink你的印章
地址(Address)你的账号。以太坊地址是 0x 开头的一串字符;波场地址是 T 开头的一串字符银行卡号
区块链(Blockchain)一本所有人都能查、没人能偷改的公共账本公开的总账本
智能合约(Smart Contract)部署在链上的一段程序,规定“谁有多少币、怎样才能转、怎样兑换”,本项目用 Solidity 语言编写银行的规章制度 + 金库
燃料费(Gas)在链上做任何“写操作”都要付的手续费办业务的手续费

再补 4 个小词:**私钥(Private Key)**是只有你知道的一串密码,谁拿到它谁就能动你的资产;**签名(Signature)**是用私钥给操作“盖章”,证明是本人发起;**交易(Transaction)**是向区块链提交的一次写操作,比如转账、兑换、部署合约;**区块(Block)**是链把一段时间内的交易打包成的“一页账”,一页页串起来就是“链”。

记住一句话:钱在链上,数据库里只是“抄本”。

二、原生币 vs 代币:ETH、TRX、USDT、MTK 各是哪一种?

这一章解决的问题:BTC、ETH、USDT、MTK 都叫“币”,它们到底有什么区别?

Web3 代币泛指在区块链上发行的数字资产。它不只能当“钱”用,还可以代表投票治理权、某项服务的使用权,或某件独一无二的资产(如 NFT,非同质化代币,每一个都独一无二)。日常交流中,大家把它们统称为“代币”或“加密货币”;但从技术上看,可以细分为三类:

类别是什么余额记在哪里例子
原生币(Coin)区块链“自带”的币,每条链只有一个,用来支付手续费、维护网络安全直接记在账户上BTC、ETH、BNB、SOL、TRX、XRP、ZEC、DOGE、HYPE
代币(Token)通过智能合约在别人的链上发行,自己没有独立的链,“寄生”在已有的链上记在发行它的合约里USDT、USDC、LINK、OKB、本项目的 MTK
测试币测试网的原生币,没有任何真实价值,供开发者免费领取、测试用直接记在账户上Sepolia ETH、Nile TRX

测试网(Testnet):和真实网络(主网,Mainnet)规则一样、但币不值钱的“练习场”,专门给开发者试错用。

主流币种对比:

名称类别所在的链主要用途真实价值
BTC(比特币)原生币Bitcoin区块链的鼻祖,价值存储(“数字黄金”)、点对点支付有
ETH(以太币)原生币Ethereum支付 Gas、运行智能合约,以太坊生态的“硬通货”有
BNB(币安币)原生币BNB Chain支付 Gas;同时是币安交易所的平台币(可抵扣交易手续费)有
SOL(索拉纳币)原生币Solana支付手续费、运行智能合约,以速度快、费用低著称有
TRX(波场币)原生币TRON支付手续费;质押 TRX 可获得“带宽 / 能量”来抵扣手续费有
XRP(瑞波币)原生币XRP Ledger跨境结算与支付有
ZEC(大零币)原生币(隐私币)Zcash支持“隐私交易”:用零知识证明隐藏转账双方和金额有
DOGE(狗狗币)原生币(Meme 币)Dogecoin源于网络玩笑的币,主要用于小额支付、打赏有
HYPE(Hyperliquid 币)原生币HyperliquidHyperliquid 链的原生币:支付 Gas、质押,与链上合约交易所生态绑定有
USDT(泰达币)代币(稳定币)同时发行在 Ethereum(ERC-20)、TRON(TRC-20)等多条链上由 Tether 公司发行,锚定 1 美元,常用于避险和交易计价有(约 1 美元)
USDC(美元币)代币(稳定币)同时发行在 Ethereum、Solana、Base 等多条链上由 Circle 公司发行,锚定 1 美元,定期公布储备审计有(约 1 美元)
LINK(Chainlink 币)代币以 ERC-20 形式发行在 Ethereum 上,也可跨链到其他链Chainlink 预言机网络的代币:支付节点把链外数据(如价格)送上链的费用、质押有
OKB(欧易平台币)代币(平台币)以 ERC-20 形式发行在 Ethereum 上;同时是 OKX 旗下 X Layer 链的 Gas 币OKX 交易所的平台币:抵扣手续费、参与平台活动有
Sepolia ETH(Sepolia 测试币)测试币Ethereum Sepolia 测试网开发者测试;本项目在 Sepolia 上部署合约、转账、兑换都用它付手续费无(0 元)
Nile TRX(Nile 测试币)测试币TRON Nile 测试网本项目在 Nile 上付手续费、参与兑换无(0 元)
测试 USDT(本项目用到的)代币(测试稳定币)Sepolia(Aave 测试 USDT)、Nile(TRON 官方测试 USDT)模拟稳定币;不是我们部署的,是第三方合约无
MTK(MyToken,本项目)代币anvil / Sepolia / Nile学习演示:本项目用智能合约自己发行的代币无

表中几个词:**质押(Staking)**指把币锁定一段时间来换取收益或权益;零知识证明是一种“不泄露内容也能证明它成立”的密码学技术;**预言机(Oracle)**负责把链外数据送上链。看不懂也不影响读本系列。

看懂这张表,就能理解本项目里的几个现象:

  • MTK 和 USDT 是同一类东西:都是用智能合约发行的代币。以太坊上的 USDT 和本项目的 MyToken 一样遵循 ERC-20 标准(以太坊上代币的“统一接口规范”,规定了查余额、转账等功能该叫什么名字、怎么用,钱包因此能认出所有 ERC-20 代币);波场上对应的标准叫 TRC-20,接口和 ERC-20 基本一致。
  • 手续费只能用原生币付:所以在 Sepolia 上转 MTK / USDT 要付 SepoliaETH,在 Nile 上转 MTK / USDT 要付 TRX。
  • 按用途还能再细分:稳定币(USDT、USDC)、隐私币(ZEC)、Meme 币(DOGE)、预言机代币(LINK)、平台币(BNB、OKB)。“原生币 / 代币”说的是技术上怎么发行,“稳定币 / 平台币”说的是拿来做什么,两种分法可以同时成立,比如 USDC 既是代币,也是稳定币。本项目页面上就按“原生币 → 稳定币 → 代币”给资产排序。
  • 同一个币可能有多重身份:BNB 既是 BNB Chain 的原生币,又是币安的平台币;OKB 在以太坊上是 ERC-20 代币,在 X Layer 上又被用作 Gas。判断“原生币还是代币”,要看在哪条链上。
  • 代币要“导入”才看得到:原生币余额在账户上,钱包直接就能显示;代币余额在合约里,钱包得知道合约地址才能去查。
  • 测试币虽然叫 ETH / TRX,但本身没有价值:它在测试网上的作用和主网上完全一样(付 Gas),但不能兑换成真钱。平时优先去水龙头免费领取;水龙头领不到时(不少水龙头要求账户在主网上有余额,或限制每天的领取量),也可以在闲鱼等平台花少量钱买一点应急。只按“够用”的量买,千万不要当成投资,也不要相信任何“测试币会升值”的说法。

小结:原生币是链自带的、记在账户上;代币是合约发行的、记在合约里。USDT 和 MTK 都是代币,转它们要用原生币付手续费。

三、这个 App 由三部分组成

这一章解决的问题:一个能转账、能兑换的 Web3 App,背后到底有哪些“零件”?

部分在哪里运行负责什么详细文档
智能合约区块链上MyToken:记账(谁有多少 MTK)、执行转账;MiniSwap:保管资金池、按公式执行兑换第 2 篇:智能合约
前端页面浏览器让用户查余额、转账、兑换,并交给钱包(MetaMask / TronLink)签名第 3 篇:前端
后端 + 数据库服务器把链上的转账、兑换记录“抄”到数据库,配上昵称给页面展示,并实时推送(SSE)给页面第 4 篇:后端

它们之间的关系可以画成下面这张图:

flowchart LR
  U[用户] --> FE[前端页面<br/>Vue + ethers / tronWeb]
  FE -- 请求签名 --> W[钱包<br/>MetaMask / TronLink]
  W -- 发送交易:转账 / 兑换 --> C[链上智能合约<br/>MyToken / MiniSwap / USDT]
  FE -- 查余额、查报价(免费) --> C
  C -- Transfer / Swap 事件 --> BE[后端 listener]
  BE --> DB[(MySQL 数据库)]
  BE -- SSE 实时推送 --> FE
  FE -- 查记录 /api --> BE
  • 转账 / 兑换(写):页面 → 钱包签名 → 区块链上的合约改账本
  • 看记录(读):区块链 → 后端监听并抄写 → 数据库 → 通过 SSE 推送提醒页面刷新列表
  • 查余额、查报价(读):页面直接问区块链(免费,不用签名)

SSE(Server-Sent Events,服务器推送事件):浏览器和服务器保持一条长连接,服务器有新数据就主动“推”给页面。本项目后端入库后通过 /api/events 推送 tx(新转账)、swap(新兑换)等事件;推送断了,页面会自动退回每 15 秒轮询一次。

和传统 Web 开发最大的不同:账本不在自己的数据库里,而在链上的合约里。后端停了照样能转账、兑换,数据库只是方便查询的副本。

四、三条链:先在本地练,再上两个测试网

这一章解决的问题:开发时在哪条链上跑?为什么要三条?

  • anvil 是在自己电脑上运行的“迷你区块链”,用来开发调试;
  • Sepolia 是以太坊的公共测试网,全世界共享,用来做以太坊生态的最终验证;
  • Nile 是波场(TRON)的公共测试网。波场是 USDT 转账量最大的链之一,它的地址格式、钱包、交易结构都和以太坊不同,接入它能让你看到“同一套业务在两种生态上”的差异。
对比本地链 anvil测试链 Sepolia测试链 Nile(TRON)
chainId31337111551113448148188
在哪里自己的电脑全世界共享的公共测试网全世界共享的公共测试网
用什么钱包MetaMaskMetaMaskTronLink
地址长什么样0x 开头0x 开头T 开头(Base58 格式)
原生币 / 测试币怎么来启动就送,每个账户 10000 ETH去“水龙头”网站免费领或闲鱼购买去 Nile 水龙头领取测试 TRX
谁能看到只有自己任何人,可在 sepolia.etherscan.io 查询任何人,可在 nile.tronscan.org 查询
转账后多久出现在列表几秒约半分钟(要等 2 个区块确认)约 1 分钟(要等 19 个区块确认,3 秒一个块)
用途开发调试,随时可以重来模拟真实以太坊网络,做最终验证模拟真实波场网络,体验 TRC-20 与 TronLink

**水龙头(Faucet)**是免费发放测试币的网站,像“免费试用装”;区块浏览器(如 sepolia.etherscan.io、nile.tronscan.org)是查链上数据的网站,输入地址或交易就能看到详情。

三条链的账本完全独立:同一个地址在不同链上的余额互不相干。

每条链上有哪些资产、能做什么(本项目当前真实状态):

链原生币稳定币(第三方合约)自发代币页面上能转账的资产兑换合约 MiniSwap
本地链 anvilETH无MTK 0xdc64…f6c9MTK(ETH 只用来付 Gas)未部署
Sepolia 测试网ETHUSDT(Aave 测试 USDT,6 位小数)MTK 0xcbee…2447ETH / USDT / MTK已部署 0x6569…eafb
Nile 测试网TRXUSDT(TRON 官方测试 USDT,TRC-20,6 位小数)MTK 0xd666…11b1TRX / USDT / MTK已部署 0xbbfa…16d3

注:TRON 地址在链上其实就是“0x41 前缀 + 20 字节”,再做 Base58 编码就成了 T 开头的样子。本项目的后端和数据库统一存 0x 形式,前端只在调用 TronLink 和展示时转成 T 开头,所以表里 Nile 的合约地址也写成 0x 形式。

五、完整流程:4 步走

这一章解决的问题:一笔 MTK 转账从零开始,到底要经历哪几步?(兑换的流程在第六章讲,它的骨架和转账一模一样。)

第 1 步:准备账户

  • 在 MetaMask(或 TronLink)里创建账户。创建账户不上链、不花钱,只是用助记词(Mnemonic,一组 12 个左右的英文单词,是所有账户私钥的“总钥匙”)算出一把新钥匙。
  • 本项目的账户分工:
用途本地链 anvil测试链 Sepolia测试链 Nile
部署者(拿到全部 MTK)Anvil-A部署者和持币人部署者(TronLink 账户)
收款 / 转账Anvil-B、C、D收款人1、收款人2其他 TronLink 账户
  • 测试网上还要准备一点原生币当手续费:Sepolia 准备 SepoliaETH,Nile 准备测试 TRX。
  • 页面上每条链独立连接账户:在“本地链 / Sepolia”页签下连接 MetaMask,在“Nile”页签下连接 TronLink,互不影响。

第 2 步:发行 MTK(部署合约)

  • 合约代码里写好了:名字 MyToken、符号 MTK、18 位小数、总量 100 万、部署时全部给部署者。
  • 部署(Deploy)就是发一笔特殊交易,把合约代码放到链上。部署完会得到一个合约地址:
    • 本地链:0xdc64…f6c9
    • Sepolia:0xcbee…2447
    • Nile:0xd666…11b1(0x 形式)
  • 合约一旦部署就永远不能修改。想改名字只能重新部署一个新合约。
  • 本项目实测,部署在 Sepolia 上大约花 0.001 SepoliaETH。
  • Nile 的部署方式不同:同一份合约用兼容 TRON 的编译配置(FOUNDRY_PROFILE=tron)编译,再通过 TRON 的 HTTP API 部署(make deploy-nile),因为以太坊的部署工具 forge script 发不了 TRON 格式的交易。

第 3 步:转账

以 Sepolia 上“收款人2 转 500 MTK 给收款人1”为例:

  1. 打开页面,选择 Sepolia,连接 MetaMask,选中收款人2。
  2. 选择资产 MTK,填入收款人1 的地址,点“查询余额”。查询是免费的,不弹钱包。
  3. 填金额 500,点“转账”。
  4. MetaMask 弹窗,显示手续费约 0.0006 SepoliaETH,点确认。
  5. 交易被打包进区块(网络把交易正式写进账本的一页),合约把收款人2 的余额减 500、收款人1 的余额加 500,并发出一条 Transfer 事件(Event,合约在链上留下的一条“公告”:谁转给谁、多少)。到这里转账已经完成。
  6. 页面上的进度条依次亮起:签名并广播 → 打包上链 → 后端已入库。

转 ETH / TRX(原生币)时,第 5 步不经过任何合约,也没有 Transfer 事件;转 USDT 时调用的是别人部署的 USDT 合约。这三种转账的区别,第六章会专门讲。

第 4 步:在列表里看到记录

  • 后端一直在“监听”:代币合约的 Transfer 事件、MiniSwap 的 Swap 事件,以及逐块扫描原生币转账。一有新记录,就把“谁转给谁、多少钱”写进数据库。
  • 写入后,后端通过 SSE 把新记录推送给页面,页面立即刷新,显示成“迷糊的水獭#7322 → 勇敢的鹦鹉#3717,500 MTK”。
  • 昵称是后端随机起的,链上只有地址,没有昵称。

小结:准备账户 → 部署合约 → 签名转账(合约改账本)→ 后端抄进数据库并推送给页面。第 3 步完成时钱就已经到账,第 4 步只是为了方便展示。

六、★ 智能合约在这个项目里的意义

这一章解决的问题:前面一直在说“合约”,它在本项目里到底解决了什么问题?换成一个普通后端 + 数据库不行吗?

先给结论:智能合约让“规则”本身跑在链上——余额怎么记、钱怎么转、价格怎么算,都由一段谁也改不了的公开代码决定,任何人(包括项目方自己)都没法绕过它。在本项目里,这体现为三件事:

┌─────────────────────┐   ┌──────────────────────────┐   ┌──────────────────────────┐
│ ① 自己发行代币       │   │ ② 用户之间链上转账        │   │ ③ 资产之间链上兑换        │
│ MyToken 合约 → MTK   │   │ ETH/TRX:链自带,不用合约 │   │ MiniSwap 合约:资金池     │
│ 名称/符号/精度/总量  │   │ USDT:调第三方合约        │   │ 按公式自动报价、自动成交  │
│ 都由代码定死         │   │ MTK:调我们自己的合约     │   │ Sepolia、Nile 各一个      │
└─────────────────────┘   └──────────────────────────┘   └──────────────────────────┘

6.1 意义一:可以自己发行代币(MTK)

在传统互联网里,“发行积分”要自己建表、自己记账,用户只能相信你不会乱改。在链上,发行一种代币只需要写一个遵循 ERC-20 标准的合约并部署上去——本项目的 contracts/src/MyToken.sol 就是这样一个合约,它继承了 OpenZeppelin(经过审计的开源合约库)的 ERC20 实现,自己只写了十几行。

发行时能自定义的东西:

可以自定义的本项目的取值说明
合约名MyTokenSolidity 里 contract MyToken 的名字,只是代码层面的名字,用户一般看不到
代币名称 name"MyToken"钱包、区块浏览器里显示的全称
代币符号 symbol"MTK"钱包里显示的简称,和 ETH、USDT 并列
精度 decimals18链上只存整数,“1 MTK”在链上存的是 1 × 10¹⁸。USDT 是 6 位、TRX 也是 6 位,ETH 是 18 位
总量1,000,000 MTK部署时一次性全部铸造(mint)给部署者,之后没有增发函数,总量永远不变

小细节:MyToken 的 name / symbol 写成了直接返回常量的 pure 函数,而不是存进合约存储,这样部署时能省下约 4 万 Gas。具体原因见第 2 篇。

那代币图标呢?——链上没有图标。 这是新手最常见的误解。ERC-20 标准里只有 name、symbol、decimals 这类字段,没有“图标”字段,合约里也存不了图片。你在钱包、交易所里看到的 USDT 绿色图标,都是在链下配置的:

图标来源怎么做谁能看到
钱包 wallet_watchAsset(EIP-747)DApp 调用钱包的这个方法,请求“添加代币”,参数里带上图标的 URL该用户自己的钱包
区块浏览器项目方在 Etherscan / TronScan 上提交代币信息(Logo、官网等),审核通过后显示所有访问浏览器的人
Token List把代币信息(地址、符号、精度、logoURI)登记到公开的代币列表(如 Uniswap Token List)使用该列表的钱包 / DApp

本项目目前没有实现 wallet_watchAsset,所以 MetaMask 里要手动“导入代币”(填合约地址)才能看到 MTK,而且没有图标。如果想加,大致是下面这样(示意代码,项目中并不存在):

// 示意:请求 MetaMask 把 MTK 加入资产列表(EIP-747),image 是链下托管的图标地址
await window.ethereum.request({
  method: "wallet_watchAsset",
  params: {
    type: "ERC20",
    options: {
      address: "0xcbee6a188946136b460c833b238eb5665e1f2447", // Sepolia 上的 MTK
      symbol: "MTK",
      decimals: 18,
      image: "https://example.com/mtk.png", // 示意地址,需要自己托管图标
    },
  },
});

6.2 意义二:可以实现链上用户之间的转账

页面上的“转账”按钮看起来都一样,但 ETH / TRX、USDT、MTK 三种资产在链上的实现方式完全不同:

资产交易发给谁(to)交易里带的是什么经过合约吗余额记在哪有 Transfer 事件吗
ETH / TRX(原生币)直接发给收款人地址value(附带的原生币金额),没有调用数据不经过,链本身完成转账账户上没有
USDT(稳定币)第三方的 USDT 合约地址调用数据:transfer(收款人, 金额)经过别人部署的合约USDT 合约的账本里有
MTK(自发代币)我们自己的 MyToken 合约地址调用数据:transfer(收款人, 金额)经过我们自己的合约MyToken 合约的账本里有

几点值得细品:

  • 原生币转账不需要合约:它是链的“出厂功能”。代价是链上不会留下事件,所以后端只能逐个区块扫描交易来发现 ETH / TRX 转账(而且只记录在页面上登记过的地址,否则整条链的转账都会被抄进来)。
  • USDT 和 MTK 的调用方式一模一样:因为它们都遵循 ERC-20 / TRC-20 标准,前端用同一套代码就能转,只是合约地址不同。区别在于“规则归谁管”:USDT 的合约是 Tether 或测试网方部署的,我们无法控制;MTK 的合约是我们写的,规则我们在部署前说了算(部署后连我们也改不了)。
  • 转代币时,交易的 to 是合约地址,不是收款人。“转账给 B”的实际含义是:请合约把账本上 A 名下的一部分记到 B 名下。B 全程不需要签名,也不需要在线。
  • 第三方合约可能“不守规矩”:实测 Nile 上的测试 USDT 调用 transfer 成功时却返回 false。这就是为什么 MiniSwap 不看返回值,而是看“余额差值”来判断钱有没有到账(第 2 篇细讲)。

6.3 意义三:可以实现链上资产之间的兑换

有了多种资产,下一个自然的需求是“拿 ETH 换点 USDT”。在中心化交易所,这要靠交易所的撮合引擎和数据库;在链上,本项目用一个自己写的兑换合约 MiniSwap(contracts/src/MiniSwap.sol,迷你版 Uniswap V2)来完成。

用最简单的比喻理解:MiniSwap 是一台“自动售货机”。

  • 机器里预先放好了几对“货”:比如一格装着 0.5 ETH 和 5000 MTK,这一格叫一个资金池(Pool),放货的过程叫提供流动性(本项目只有部署者 owner 能放货)。
  • 你投进去一种资产,机器按一个固定公式算出该吐给你多少另一种资产,并当场吐出来。没有人工、没有撮合、没有对手方——你的交易对手就是这个池子。
  • 你投进去的越多,池子里你要的那种货就越少,机器就会越“惜售”,给出的单价越差。这个机制叫 AMM(Automated Market Maker,自动做市商),它的公式是“两种资产数量的乘积保持不变(x · y = k)”,每笔收 0.3% 手续费,手续费留在池子里。公式细节、滑点保护、防重入等放在第 2 篇展开,这里只看效果。

每条链可兑换的组合:

链可兑换的资产池子(初始放入的货)初始价格
本地链 anvil—(未部署兑换合约,本地只演示转账)——
Sepolia 测试网ETH / USDT / MTK 两两互换(3 个池子)ETH-USDT:0.5 ETH / 500 USDT;ETH-MTK:0.5 ETH / 5000 MTK;USDT-MTK:500 USDT / 5000 MTK1 ETH = 1000 USDT = 10000 MTK
Nile 测试网TRX / USDT / MTK 两两互换(3 个池子)TRX-USDT:100 TRX / 30 USDT;TRX-MTK:100 TRX / 300 MTK;USDT-MTK:30 USDT / 300 MTK1 TRX = 0.3 USDT = 3 MTK

真实兑换记录(来自页面“我的兑换记录”和后端数据库):

Sepolia:

时间卖出买入
11:17:246 USDT(部署账户)0.005911 ETH
11:27:48500 MTK0.04533 ETH
11:29:121000 MTK83.124895 USDT
11:37:360.01 ETH118.016236 MTK
11:39:000.2 ETH145.491046 USDT
11:44:00100 USDT0.150367 ETH
11:45:4810 USDT140.144514 MTK

Nile:

时间卖出买入
12:10:0910 TRX27.198326 MTK
12:11:4510 TRX2.719832 USDT
12:15:121 USDT3.878394 TRX
12:16:361 USDT9.64932 MTK
12:19:271000 MTK86.367818 TRX
12:20:571000 MTK24.008221 USDT

从这些记录里能直接读出 AMM 的几个特点:

  1. 小单接近标价,大单明显吃亏。Sepolia 上用 500 MTK 换 ETH:按初始价 1 MTK = 0.0001 ETH,理想应得 0.05 ETH,实际得到 0.04533 ETH。差出来的部分一小半是 0.3% 手续费,大半是因为 500 MTK 相对池子(5000 MTK)不算小,把价格“推”动了——页面上显示的价格影响约 9.06%。
  2. 价格随交易实时变化。这笔之后池子变成约 0.4547 ETH / 5500 MTK,MTK 变“多”了、变便宜了;紧接着有人用 0.01 ETH 买 MTK,得到 118.016 MTK,比初始价(0.01 ETH = 100 MTK)更划算。
  3. 浅池 + 大单 = 巨大价格影响。Nile 的 TRX-MTK 池只有 100 TRX / 300 MTK,先有人用 10 TRX 买走约 27.2 MTK,池子变为 110 TRX / 272.8 MTK;之后有人一次卖出 1000 MTK(是池中 MTK 的 3.7 倍),只换到 86.37 TRX,而按初始价应值约 333 TRX,MTK 的价格随之暴跌约 94%。但池子永远不会被买空——公式决定了剩下的 TRX 只会越来越少、却到不了 0。
  4. 手续费留在池子里。Sepolia 的 ETH-MTK 池经过上面两笔交易后,x · y 从 2500 变成约 2500.8,多出来的就是手续费。

真实的 DeFi(去中心化金融)里,多个池子之间价格失衡时,会有“套利者”低买高卖,把价格重新拉回一致。本项目是学习用的测试网,没人来搬砖,所以你能在记录里看到价格偏离得很厉害。

兑换的流程骨架和转账完全一样:

选择“卖出 / 买入”资产和数量 → 页面调用合约 getAmountOut 免费报价(附价格影响、最少收到)
  → (卖代币时)先 approve 授权合约动用你的代币 → 钱包签名 swap 交易
  → 合约收钱、按公式算、转给你、emit Swap 事件 → 后端监听 Swap 事件入库(swaps 表)
  → SSE 推送 → “我的兑换记录”出现这一条
  • 卖代币(USDT / MTK)要先授权(approve):合约不能主动从你账户里拿代币,你得先告诉代币合约“允许 MiniSwap 动用我的 N 个币”。页面把它做成了“授权并兑换”一个按钮,钱包会弹两次确认。
  • 卖原生币(ETH / TRX)不用授权:直接把原生币随交易一起付给合约即可。
  • 滑点保护:交易从签名到上链有时间差,期间别人的交易可能改变价格。页面让你选 0.5% / 1% / 3% 的滑点容忍,合约收到的“最少收到”低于这个值就整笔回滚,防止被坑。

七、数据库里有什么

这一章解决的问题:既然钱在链上,后端数据库到底存了什么?

表存什么例子
users地址和昵称的对应关系0x455b… = 迷糊的水獭#7322
user_networks用户在哪条链上登记过(连接过钱包)勇敢的鹦鹉#3717 在 Sepolia 登记
transactions每一笔转账(从链上抄来的,带 network 和合约地址)收款人2 → 收款人1,500 MTK
swaps每一笔兑换(从 Swap 事件抄来的)卖出 500 MTK,买入 0.04533 ETH
sync_states后端在每条链、每个合约上抄到了第几个区块Sepolia 已抄到第 11786xxx 块

删光数据库也不会丢钱:余额在链上。重启后端,它会从链上把记录重新抄回来(原生币转账例外:原生币扫描首次开启时从当前区块开始,不补历史)。

八、常见疑问

转 MTK 为什么要付 SepoliaETH(或 TRX)? 手续费只能用这条链的原生币付,而且由发起交易的人付。收款的人不用付,也不用提前做任何准备。

“导入代币”是干什么的?为什么 MTK 没有图标? 导入是告诉钱包“去这个合约查一下我有多少 MTK”,它只影响钱包里能不能看到余额,不影响收款。图标不在链上,需要通过 wallet_watchAsset、区块浏览器或 Token List 在链下配置,本项目暂未实现(见 6.1)。

为什么测试网上的转账要等一会儿才出现在列表? 后端要等这笔交易之后再出若干个区块才入库,这叫等待“确认数”:Sepolia 等 2 个块(约 24 秒),Nile 等 19 个块(约 1 分钟)。原因是公共链上偶尔会发生区块重组(Reorg):刚出的几个区块可能被撤销、换成另一个版本,多等几块能避免记录作废。本地链 anvil 不会重组,所以几秒就能看到。

兑换时为什么钱包弹了两次? 卖出的是代币(USDT / MTK)时,第一次是 approve 授权,第二次才是真正的 swap。卖出 ETH / TRX 只弹一次。

兑换价格是谁定的? 没有人定。价格由池子里两种资产的数量按公式自动算出,每笔交易都会改变池子,从而改变下一笔的价格。

MetaMask 里的账户为什么删不掉? 账户是用助记词按顺序算出来的,删了还能再算出来,所以只能隐藏。

MTK 总量会变吗? 不会。转账、兑换都只是在账户(或池子)之间挪数字。所有人的余额加上池子里的 MTK,永远是 100 万 MTK。

九、最该记住的 6 句话

  1. 余额存在链上(原生币在账户上,代币在合约里),不在钱包里,也不在数据库里。
  2. 查余额、查报价免费;转账、兑换要签名、付手续费,手续费由发起方用原生币付。
  3. 合约部署后不能修改,合约地址由“部署者 + 第几笔交易”算出来。
  4. 代币转账真正完成的标志:合约改了账本,并发出一条 Transfer 事件;兑换则是发出 Swap 事件。
  5. 智能合约在本项目里做了三件事:发行 MTK、执行代币转账、按公式自动兑换;原生币转账是链自带的,不需要合约。
  6. 后端只是抄写员:监听事件、抄进数据库、推送给页面展示。

十、技术栈与工具清单(参与项目前必读)

这一章要解决的问题:想亲手把项目跑起来,需要装哪些工具?每个工具在哪一环起作用?

只想了解概念的读者可以先跳过,准备动手运行项目时再回来看。表格里的名词会在第 2~4 篇中逐一讲解。

以下版本号是本项目安装时拉取到的最新版本。

智能合约层

名称版本作用 / 本项目用途Web2 类比链接
Solidity0.8.37编写 MyToken、MiniSwap 合约的语言;编译目标 EVM 版本为 osaka(见 foundry.toml),部署到 TRON 时用 FOUNDRY_PROFILE=tron(paris)Go / Java 语言本身https://github.com/ethereum/solidity
Foundry(forge / cast / anvil)1.8.3forge:编译、测试、部署脚本、Gas 报告;cast:命令行调用链;anvil:本地链go build + go test + curl + 本地数据库https://github.com/foundry-rs/foundry
forge-std1.16.2Foundry 测试标准库(Test、vm.prank、vm.expectEmit)testifyhttps://github.com/foundry-rs/forge-std
OpenZeppelin Contracts5.7.0经过审计的 ERC20 实现(MyToken 继承自它)、ReentrancyGuard 防重入(MiniSwap 使用)官方标准库 / Spring Securityhttps://github.com/OpenZeppelin/openzeppelin-contracts
MiniSwap(本项目自写)—迷你版 Uniswap V2 兑换合约:多个资金池、x·y=k 定价、0.3% 手续费、滑点与过期保护一个“自动报价的撮合服务”contracts/src/MiniSwap.sol

forge-std 和 OpenZeppelin 以 git 子模块的形式放在 contracts/lib/ 下,版本锁定在 contracts/foundry.lock。make install 会依次执行 git submodule update --init --recursive 和 forge install。

后端(Go)

名称版本作用 / 本项目用途链接
Go1.27后端编程语言(1.24 及以上才支持 go tool air 这种 tool 依赖写法)https://github.com/golang/go
Gin1.12.0HTTP 路由,提供 /api/* 接口(含 SSE 推送 /api/events)https://github.com/gin-gonic/gin
gin-contrib/cors1.7.9跨域中间件。开发环境中前端(5173 端口)访问后端(8080 端口)时需要https://github.com/gin-contrib/cors
GORM1.31.2ORM:模型定义、AutoMigrate、一对多关联、Preload、OnConflicthttps://github.com/go-gorm/gorm
GORM MySQL Driver1.6.0GORM 连接 MySQL 的驱动https://github.com/go-gorm/mysql
go-ethereum(geth)1.17.6ethclient 通过 HTTP 查询链上数据(FilterLogs 查询事件、BlockNumber、HeaderByNumber),通过 WebSocket 订阅事件(SubscribeFilterLogs);crypto.Keccak256Hash 计算事件签名。Nile 的 JSON-RPC 兼容以太坊格式,同一套 listener 复用https://github.com/ethereum/go-ethereum
shopspring/decimal1.4.0精确的十进制运算:把最小单位换算成可读金额(amountDisplay),避免 float64 丢精度https://github.com/shopspring/decimal
godotenv1.5.1加载 backend/.envhttps://github.com/joho/godotenv
Air1.67.4热重载,保存 .go 文件后自动编译并重启。已通过 go get -tool 写入 go.mod,用 go tool air 运行https://github.com/air-verse/air
deploy-nile(本项目自写)—backend/cmd/deploy-nile:通过 TRON HTTP API 把 MyToken / MiniSwap 部署到 Nile(forge script 发不了 TRON 格式的交易)—

前端

名称版本作用 / 本项目用途链接
Node.js24运行 pnpm、Vite、vue-tsc 的 JavaScript 运行时(本机实测 v24.14.0;Docker 构建使用 node:24-alpine)https://github.com/nodejs/node
TypeScript6.0.3前端编程语言,所有代码都是 .ts 或 <script setup lang="ts">,没有 .js 文件https://github.com/microsoft/TypeScript
pnpm11.x包管理器,锁文件是 pnpm-lock.yaml(本机实测 11.12.0;Docker 构建中通过 corepack enable 启用)https://github.com/pnpm/pnpm
Vite8.3开发服务器与构建工具;配置了路径别名 /@/ → src/https://github.com/vitejs/vite
@vitejs/plugin-vue6.0.9Vite 的 Vue 插件,让 Vite 能编译 .vue 单文件组件https://github.com/vitejs/vite-plugin-vue
Vue 33.5UI 框架https://github.com/vuejs/core
vue-tsc3.3对 .vue 文件做类型检查,执行 pnpm build 时会先运行它https://github.com/vuejs/language-tools
@vue/tsconfig0.9.1Vue 官方的 TypeScript 基础配置,tsconfig.app.json 继承自它https://github.com/vuejs/tsconfig
@types/node26.6.2Node.js 的类型声明,供 vite.config.ts(用到 node:url)做类型检查https://github.com/DefinitelyTyped/DefinitelyTyped
TDesign Vue Next1.20.8腾讯出品的 Vue 3 组件库:Form、Input、Button、Table、Tag、RadioGroup、Steps、Messagehttps://github.com/Tencent/tdesign-vue-next
Ethers.js6.17与 EVM 链交互:BrowserProvider 连接钱包、Contract 调用合约、parseUnits / formatUnits 换算金额https://github.com/ethers-io/ethers.js
MetaMask—以太坊钱包:保管私钥并签名,通过 EIP-1193 暴露给页面;页面通过 EIP-6963(多钱包发现协议)自动发现已安装的以太坊钱包https://github.com/MetaMask/metamask-extension
TronLink—波场钱包:注入 window.tron(TIP-1193,接口与 EIP-1193 一致),Nile 上的签名都由它完成https://www.tronlink.org
tronWeb—(由 TronLink 注入,不是 npm 依赖)与 TRON 链交互:查 TRX 余额、构造 / 签名 / 广播 TRX 转账、调用 TRC-20 与 MiniSwap 合约https://github.com/tronprotocol/tronweb

TypeScript 固定在 6.x 的原因:TypeScript 7 是用原生代码重写的版本,不再提供 vue-tsc 依赖的 JS 编译器 API,实测 vue-tsc 会直接报错 ERR_PACKAGE_PATH_NOT_EXPORTED。等 vue-tsc 适配 TS 7 后再升级。

链服务

名称作用 / 本项目用途链接
Sepolia + Etherscan以太坊公共测试链;在区块浏览器里可以查看交易和 Event Loghttps://sepolia.etherscan.io
Infura(本项目使用)/ Alchemy托管的 RPC 节点服务。后端用 HTTPS 地址查询(SEPOLIA_RPC_URL),用 WSS 地址订阅事件(SEPOLIA_WS_URL);forge / cast 部署时也用它https://www.infura.io 、https://www.alchemy.com
publicnode 公共 RPC免费公共节点,只在“向 MetaMask 添加 Sepolia 网络”时作为参数传入(前端 VITE_SEPOLIA_PUBLIC_RPC)https://ethereum-sepolia-rpc.publicnode.com
Sepolia 水龙头(Faucet)免费领取 SepoliaETH,用于支付部署、转账、兑换的 Gas。Infura、Alchemy、Google Cloud 等都有提供,不少要求账户在主网上有少量余额https://cloud.google.com/application/web3/faucet/ethereum/sepolia
Aave 测试 USDTSepolia 上没有 Tether 官方的 USDT,本项目使用 Aave 测试网的 USDT(6 位精度),可在 Aave 测试网水龙头领取合约 0xaa8e23fb1079ea71e0a56f48a2aa51851d8433d0
TRON Nile + TronScan波场公共测试链;在 nile.tronscan.org 查看交易、合约https://nile.tronscan.org
TronGrid波场官方节点服务。后端通过它兼容以太坊的 /jsonrpc 端点轮询 Nile(不支持 WebSocket);部署工具走它的 HTTP APIhttps://nile.trongrid.io
TRON 官方测试 USDTNile 上的 TRC-20 USDT(TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf,6 位精度),不是我们部署的https://nile.tronscan.org

数据库、开发工具与部署

名称作用 / 本项目用途链接
MySQL 8+存储 users、user_networks、transactions、swaps、sync_states 五张表(Docker Compose / K8s 中使用 mysql:8.4 镜像)https://dev.mysql.com/doc/
Git管理代码;合约依赖以 git 子模块形式安装https://git-scm.com
jqexport-abi.sh 用它从 JSON 中提取 ABI 和合约地址;Makefile 的 seed-* 用它读取部署者地址https://github.com/jqlang/jq
Make把常用命令统一成 make xxx,make help 可查看全部命令GNU Make
Docker / Docker Compose构建镜像,一键启动 mysql、backend、frontend。用到的基础镜像:golang:1.27(编译后端)、gcr.io/distroless/static-debian12(运行后端,不含 shell,攻击面小)、node:24-alpine(构建前端)、nginx:alpine、mysql:8.4https://docs.docker.com
nginx托管前端静态文件,并把 /api 反向代理到后端(对 /api/events 单独关闭缓冲,保证 SSE 实时推送)https://nginx.org
Kubernetes(kubectl)容器编排,部署清单在 deploy/k8s/https://github.com/kubernetes/kubernetes

一次转账 / 兑换中各工具的出场顺序:

  • 以太坊(anvil / Sepolia):MetaMask 签名 → ethers 编码并发送 → anvil / Infura 节点 → Solidity 合约执行(MyToken / USDT / MiniSwap)→ go-ethereum 通过 WebSocket 收到事件推送、再用 eth_getLogs 查询日志 → GORM 写入 MySQL → Gin 通过 SSE 推送 → decimal 换算金额 → TDesign 表格展示
  • 波场(Nile):TronLink 签名 → tronWeb 构造并广播 → TronGrid 节点 → 合约在 TVM(波场虚拟机,兼容 EVM)中执行 → go-ethereum 通过 TronGrid /jsonrpc 轮询日志 → 之后与以太坊相同

十一、一张表总结:智能合约的实际作用

场景谁来执行规则本项目里的合约链上留下什么没有合约会怎样
发行代币合约代码(部署后谁都改不了)MyToken(MTK)总量、余额、Transfer 事件只能自己在数据库记积分,用户只能相信你
原生币转账链本身不需要合约交易本身(无事件)—(链的出厂功能)
代币转账代币合约的 transfer第三方 USDT / 我们的 MyToken账本变化 + Transfer 事件代币根本不存在
资产兑换兑换合约按 x·y=k 公式自动成交MiniSwap(Sepolia、Nile)池子储备变化 + Swap 事件需要中心化交易所托管资金、撮合订单

一句话:合约 = 跑在链上、公开透明、谁也改不了的业务规则。在本项目里,它负责“发币、记账、兑换”;前端只负责“提请求”,后端只负责“抄记录”。更完整的总结(包括合约能做什么、不能做什么)见第 2 篇的“智能合约实际作用总结”。


下一篇预告

现在你已经有了整张地图。下一篇 智能合约(contracts)详解 会打开两个合约的源码:MyToken 里“余额怎么记、转账怎么校验、Transfer 事件怎么发出”;MiniSwap 里“资金池怎么存、x·y=k 公式怎么算、滑点和重入怎么防”,并用本篇的真实兑换记录逐笔验算,最后给出智能合约实际作用的完整总结。

后续 3 篇分别适合谁读(建议按顺序,只关心某一端也可以直接跳读):

  • 第 2 篇 智能合约:想知道“链上的规则是怎么写出来的”、对 Solidity、AMM / DeFi 感兴趣的读者
  • 第 3 篇 前端:前端开发者,想知道网页如何连接 MetaMask / TronLink,发起一笔真实的链上转账和兑换
  • 第 4 篇 后端:后端开发者,想知道如何把三条链上的转账、兑换记录可靠地同步到自己的数据库,并实时推送给页面