Trust Wallet Core 功能路径总览:从助记词到多链签名

摘要

Trust Wallet Core 可以理解成一个多链钱包底层库:它主要处理本地的助记词、HD 派生、私钥/公钥、地址、交易/消息编码与签名。它不是 web3.jsethers.js 那种面向节点 RPC 与合约交互的网络层,也不负责行情、余额索引、广播重试、风控或账户体系。

更准确的一句话是:

Trust Wallet Core 负责把「本地密钥材料」变成「多链地址」和「可广播/可验证的签名结果」;链上参数查询、交易广播、回执轮询和业务风控由 App、后端或 RPC 层完成。

Read more

Flutter 应用里Dart 侧如何与链打交道:库分工与调用顺序

摘要

在「原生壳 + Flutter 模块」的混合架构中,链上读写、业务网关与钱包连接往往同时存在。本文将 Dart 侧常见能力归纳为 五类:JSON-RPC 客户端、HTTP 传输、链数据编解码与请求封装、WalletConnect 会话,以及签名与跨语言辅助;再用 一张总览流程图多张时序图 说明它们如何串成一条可上线的 Swap / 授权类链路。文末补充 大单、单池深度不足 场景下 路由服务与客户端 的分工,便于读者按自家模块命名做映射。

Read more

npm 包命名格式差异分析

@rainbow-me/rainbowkitwagmiethers 的命名格式差异,主要源于 npm 包的两种不同命名规范:作用域包(Scoped Package)普通包。这种差异背后有明确的设计意图和实践考量,具体分析如下:

1. 命名格式差异的本质

  • @rainbow-me/rainbowkit:使用了 作用域包 格式(@组织名/包名
  • wagmiethers:使用了 普通包 格式(直接包名)

2. 为什么 @rainbow-me/rainbowkit 使用作用域包?

作用域包(以 @ 开头)是 npm 提供的一种命名空间机制,主要用于:

(1)品牌与归属明确化

  • 组织标识@rainbow-me 是 Rainbow Wallet 团队的官方 npm 组织名,明确标识了该包的开发和维护主体
  • 品牌一致性:与 Rainbow Wallet 钱包应用的品牌保持统一,增强用户和开发者的信任感
  • 避免命名冲突:即使其他开发者想创建名为 rainbowkit 的包,也不会与官方包冲突

(2)包管理的规范化

  • 权限控制:只有 Rainbow Wallet 团队的成员才能发布和更新 @rainbow-me 作用域下的包
  • 版本管理:便于团队内部统一管理多个相关包(如可能存在的 @rainbow-me/utils@rainbow-me/hooks 等)
  • 生态系统构建:为未来扩展其他相关工具包预留了命名空间

3. 为什么 wagmi 和 ethers 使用普通包?

普通包(直接包名)通常适用于以下场景:

(1)wagmi 的情况

  • 独立工具库定位:wagmi 是一个通用的以太坊 React Hooks 库,不隶属于特定组织或产品
  • 简洁性优先:直接使用 wagmi 作为包名更简洁易记,符合其作为基础工具库的定位
  • 社区驱动:强调其跨项目、跨生态的通用性,适合所有以太坊前端开发者使用

(2)ethers 的情况

  • 历史原因:ethers 是以太坊生态中较早的核心库之一,在作用域包广泛使用前就已存在
  • 行业标准地位:作为以太坊官方推荐的 JavaScript 库,直接使用 ethers 已成为行业共识
  • 全球知名度:无需通过组织名来增强辨识度,ethers 本身就是以太坊开发的标志性库

4. 命名格式差异的意义

这种命名差异反映了前端 Web3 生态中包管理的不同策略:

命名格式 适用场景 优势 示例
作用域包 组织维护的产品化工具、品牌相关库 品牌归属明确、避免冲突、权限可控 @rainbow-me/rainbowkit
普通包 通用工具库、行业标准库、社区驱动项目 简洁易记、通用性强、全球认知度高 wagmiethers

5. 总结

  • @rainbow-me/rainbowkit 使用作用域包:突出品牌归属,适合作为 Rainbow Wallet 生态的一部分
  • wagmiethers 使用普通包:强调通用性和简洁性,适合作为跨项目的基础工具

这种命名差异并非技术优劣之分,而是根据项目定位、维护主体和使用场景选择的不同策略,体现了 npm 包管理系统的灵活性和适应性。

Provider vs Contract:Balance 查询对比

一句话结论

  • ProvidergetBalance 查的是地址的原生币余额
  • Contract(如 ERC20)里的 balanceOf 查的是地址在该合约中的 Token 余额

1) Provider:getBalance(address)

含义

查询某个地址当前持有的原生代币数量。

典型场景

  • 查询 EOA 钱包地址的 ETH 余额
  • 查询合约地址持有的 ETH(比如某个合约金库里的 ETH)

关键点

  • 不关心该地址是否是 EOA/Contract,统一按“地址的原生币”查询
  • 不能直接查 ERC20(USDT/WETH/DAI)余额

示例

1
2
const eth1 = await provider.getBalance("0xEOA...");
const eth2 = await provider.getBalance("0xContract...");

2) Contract:balanceOf(address)(以 ERC20 为例)

含义

查询某地址在某个 Token 合约账本中的余额。

典型场景

  • 查询用户的 USDT/WETH/DAI 余额
  • 查询某地址在指定 ERC20 里的持仓

关键点

  • 必须先确定“哪一个 token 合约”
  • 同一个地址在不同 token 合约中的余额互不影响

示例

1
2
3
4
5
const usdt = new ethers.Contract(USDT_ADDR, erc20Abi, provider);
const weth = new ethers.Contract(WETH_ADDR, erc20Abi, provider);

const usdtBalance = await usdt.balanceOf("0xUser...");
const wethBalance = await weth.balanceOf("0xUser...");

3) 对比表

对比项 Provider getBalance Contract balanceOf
查询对象 地址的原生币余额 地址在某合约中的 token 余额
资产类型 ETH/BNB/MATIC 等原生币 ERC20/ERC721/ERC1155 等合约资产(方法不同)
是否需要合约 ABI
是否需要合约地址 否(只要目标地址) 是(必须指定 token 合约)
输入地址角色 EOA 或 Contract 都可 EOA 或 Contract 都可
常见误区 以为可直接查 ERC20 忘记切换到正确 token 合约

4) 常见误区澄清

  • 误区:给 provider.getBalance 传 token 合约地址,就能得到该 token 总余额。
    实际:得到的是“这个合约地址持有的原生币余额”,不是 token 余额。
  • 误区provider 不能查合约地址余额。
    实际:可以查,但查的是该合约地址的原生币余额。

5) 记忆口诀

  • 原生币 -> provider.getBalance
  • 合约资产 -> contract.xxx(...)(ERC20 用 balanceOf

Flutter 到区块链:分层架构与交互全流程

本文从移动端工程视角,说明 Flutter + 原生 + 密码学库(如 Trust Wallet Core)+ 节点 RPC + 区块链 在用户完成一笔链上交易时的典型分工与数据流。适用于自托管钱包、DEX 集成等场景的架构理解(具体产品实现可能有网关、风控等差异)。

1. 先厘清三件事

  1. 区块链节点不会直接和你的手机 App「长连接握手」完成业务;App 通常通过 HTTPS 上的 JSON-RPC(或 REST)远程节点通信。
  2. Trust Wallet Core 一类库,主体多为 C++,负责 地址派生、交易构造、数字签名、按链协议序列化不负责替你选公共节点或发起 HTTP(由宿主 App 实现)。
  3. Flutter 负责 UI 与业务状态;涉及 私钥、签名 时,常见做法是放到 iOS/Android 原生(Keychain / Keystore + JNI),再通过 MethodChannel 与 Dart 通信。

2. 分层总览:谁做什么

层级 典型技术 职责
表现层 Flutter Widget 表单、确认弹窗、加载态、结果页
业务 / 状态 Bloc / Riverpod / Provider 等 参数校验、组装请求模型、调用 Channel
跨端桥 MethodChannel / Pigeon / FFI Dart ↔ 原生类型与异步传递
原生 Kotlin / Swift 安全存储、调 TWC 绑定、HTTP 客户端、证书策略
密码学 / 链协议 Trust Wallet Core(C++ .so / xcframework) 未签名交易 → 签名 → 原始交易字节 / hex
网络 OkHttp / URLSession / Dio RPC:读 nonce、余额、estimateGas;写 eth_sendRawTransaction
公链节点集群 验证交易、广播、mempool、出块

核心结论:「上链」在工程上 = 库内完成签名 + 网络层把已签名交易发给节点


3. 架构图:从界面到链

flowchart TB
  subgraph UI["表现层"]
    F["Flutter Widget\n按钮、金额、链选择、确认弹窗"]
  end

  subgraph VM["业务层"]
    B["Flutter 业务逻辑\n校验、状态机、组装 DTO"]
  end

  subgraph Bridge["跨端桥"]
    C["MethodChannel / Pigeon"]
  end

  subgraph Native["原生层"]
    N1["Kotlin / Swift\nKeychain / Keystore、权限"]
    N2["Trust Wallet Core\n(C++ 动态库)\n交易体 + 签名"]
  end

  subgraph Net["网络层"]
    H["HTTP 客户端"]
    RPC["节点 RPC\nJSON-RPC / REST"]
  end

  subgraph Chain["区块链"]
    BC["共识与出块"]
  end

  F --> B --> C --> N1 --> N2
  N2 -->|"signed tx"| N1
  N1 --> H --> RPC --> BC

4. 主流程时序图:用户点击「确认」之后

以下为 签名在原生、RPC 也由原生(或统一网络层)发起 的常见形态,便于统一钉证书与审计日志。

sequenceDiagram
  autonumber
  participant U as 用户
  participant W as Flutter UI
  participant B as Flutter 业务层
  participant CH as MethodChannel
  participant N as 原生
  participant TWC as Trust Wallet Core
  participant RPC as 节点 RPC
  participant BC as 区块链

  U->>W: 点击确认交易
  W->>B: 提交 to / amount / chainId 等
  B->>B: 校验与组装参数

  B->>CH: invokeMethod(signAndSend / buildTx, args)
  CH->>N: 传递到原生

  N->>N: 解锁密钥材料(生物识别 / PIN)
  N->>RPC: 读链:nonce、fee、余额等
  RPC-->>N: 返回值

  N->>TWC: 构造未签名交易并签名
  TWC-->>N: 已签名交易 hex / bytes

  N->>RPC: 写链:eth_sendRawTransaction 等
  RPC->>BC: 广播
  BC-->>RPC: 返回 tx hash
  RPC-->>N: tx hash

  N-->>CH: { txHash, error? }
  CH-->>B: Future 完成
  B-->>W: 更新 UI
  W-->>U: 展示哈希 / 区块浏览器

步骤解读(与上图序号对应)

  1. 用户操作:仅在 UI 层。
  2. 业务层:做产品规则校验(限额、格式),不碰私钥明文。
  3. Channel:把「意图」和「非敏感参数」交给原生;不要把私钥字符串在 Dart 里传来传去
  4. 原生:从安全存储取密钥或触发系统认证。
  5. 读链 RPC:例如 EVM 链上需要先拿 nonce、估算 gas;这些请求 与签名无关,走 HTTP 即可。
  6. TWC:输入链类型、账户、目标交易内容,输出 签名后的原始交易
  7. 写链 RPC:将 已签名交易 发给节点;节点再进入 mempool。
  8. :矿工/验证者打包,产生确认数。

5. 变体:原生只签名,Flutter 发 RPC

部分团队会让 Dart 层用 Dio 直接调 Infura / Alchemy / 自建节点,原生 MethodChannel 只返回 signedHex

sequenceDiagram
  participant B as Flutter
  participant CH as MethodChannel
  participant N as 原生
  participant TWC as Trust Wallet Core
  participant RPC as 节点

  B->>CH: signOnly(unsignedTxParams)
  CH->>N: 
  N->>TWC: 签名
  TWC-->>N: signed hex
  N-->>CH: 
  CH-->>B: signed hex

  B->>RPC: eth_sendRawTransaction(signedHex)
  RPC-->>B: tx hash

注意:私钥仍应只在原生 + TWC 边界内使用;Dart 侧只拿 已签名 的十六进制串,不要把助记词传到 Flutter。


6. 数据流简图:字节往哪走

flowchart LR
  subgraph in["输入"]
    A1["用户输入\n地址、金额"]
    A2["链与合约参数\nchainId、ABI、nonce"]
  end

  subgraph twc["Trust Wallet Core"]
    S["编码 + 签名\n→ raw transaction"]
  end

  subgraph out["对外"]
    H["0x… 已签名交易"]
    TX["交易哈希"]
  end

  A1 --> S
  A2 --> S
  S --> H
  H -->|"HTTPS JSON-RPC"| RPC["节点"]
  RPC --> TX

7. Trust Wallet Core 与「和链交互」的关系

  • 开发语言:核心为 C++;Android 为 libTrustWalletCore.so,iOS 为 xcframework;通过 Kotlin / Swift 绑定 调用,而非在 App 内用 JS 实现核心链逻辑。
  • 和链的交互方式
    • 库内部:密码学、交易结构、签名算法、各链差异。
    • 库外部:你的 App 用 RPCsigned transaction只读请求(余额、nonce)发到节点;协议多为 JSON-RPC(如以太坊系)。

因此:「与区块链交互」在实现上 = RPC 上的读写TWC 负责产生可被链接受的那串字节


8. 安全与工程要点

  • 私钥与助记词:优先 Keychain / Keystore / 硬件模块;Dart 内存尽量不出现明文。
  • Channel 传参:传 交易参数、链 ID、签名结果,不传私钥。
  • RPC 端点:生产环境常配合 证书钉扎、代理、自有网关,防止中间人篡改 sendRawTransaction 的返回。
  • 重放与链切换:正确设置 chainId(EIP-155),避免跨链重放。

9. 与「交易所托管」模式的对比(扩展)

若业务是 CEX 托管钱包,时序里往往在 用户确认 之后增加 你们服务端:风控、限额、内部记账,再由服务端或专用签名机与链交互。此时上图中的 RPC 可能变为 先调业务 API,与纯 自托管 + TWC 的图不同。理解分层后,可按实际产品替换「RPC 前」的一跳。


10. 小结

问题 答案
Flutter 怎么「上链」? 多数情况下 不直接上链,而是 调原生签名 + HTTP 调节点 RPC
TWC 做什么? 交易构造与签名(C++ 库)。
链从哪里来? 远程节点返回区块与收据;广播即节点接受你的 sendRawTransaction
JS 是不是核心? 不是;可选 WASM/TS 绑定,核心仍是 C++。

文档为通用架构说明,便于学习与工程对照;具体 App 的网关、风控、多签等需以实际系统为准。

ethers.js vs web3.js vs viem vs wagmi

1. 核心定位与层级关系

四者分属不同层级,服务于以太坊 Web3 前端开发的不同需求:

定位 层级 核心价值
ethers.js 以太坊 JavaScript 库 底层 提供与以太坊交互的核心功能
web3.js 以太坊 JavaScript 库 底层 以太坊基金会官方维护的全功能库
viem 以太坊 JavaScript 库 底层 现代、轻量的以太坊交互库
wagmi React Hooks 工具库 上层 基于底层库封装的前端开发工具

2. 详细功能对比

(1)ethers.js

  • 核心功能
    • 钱包管理(生成、导入、签名交易)
    • 智能合约交互(部署、调用、监听事件)
    • 网络连接(与以太坊节点通信)
    • 以太坊数据解析(地址、交易、区块等)
  • 特点
    • 轻量级(体积小,按需引入)
    • API 现代(使用 Promise,支持 async/await)
    • TypeScript 友好
    • 文档清晰,社区活跃
  • 使用场景
    • 需要直接与以太坊区块链交互的项目
    • 对包体积敏感的应用
    • 追求现代 API 设计的开发者

(2)web3.js

  • 核心功能
    • 与 ethers.js 类似,提供全面的以太坊交互功能
    • 支持更多底层 RPC 方法
    • 内置更多工具函数
  • 特点
    • 功能全面(官方维护,覆盖所有以太坊功能)
    • 相对较重(包体积大)
    • API 设计较传统(早期版本使用回调,新版支持 Promise)
    • 历史悠久,生态成熟
  • 使用场景
    • 需要使用官方全功能实现的项目
    • 传统 Web3 项目迁移
    • 对官方支持有强依赖的场景

(3)viem

  • 核心功能
    • 与 ethers.js 类似,提供以太坊交互的核心功能
    • 支持 EIP-1193 钱包标准
    • 内置链数据和地址工具
  • 特点
    • 极致轻量(体积比 ethers.js 更小)
    • 性能优化(更快的交易签名和网络请求)
    • 现代 API 设计(链式调用,类型安全)
    • 专注于前端使用场景
  • 使用场景
    • 对包体积和性能要求极高的项目
    • 现代前端框架(如 React 18+)
    • 追求最新技术栈的开发者

(4)wagmi

  • 核心功能
    • 基于 React Hooks 的以太坊交互封装
    • 钱包连接与管理(支持多种钱包)
    • 智能合约调用的 Hook 化(如 useContractRead
    • 交易发送与状态管理
    • @tanstack/react-query 集成,提供数据缓存
  • 特点
    • 前端开发友好(Hooks 化 API)
    • 支持多底层库(默认支持 viem,可选 ethers.js)
    • 与现代前端生态集成(React、TypeScript)
    • 配置灵活,扩展性强
  • 使用场景
    • React 前端 Web3 项目
    • 需要快速实现钱包连接、合约交互的场景
    • 追求开发效率和代码简洁的项目

3. 联系与依赖关系

  • 底层库之间的关系
    • ethers.jsweb3.jsviem 均为底层以太坊交互库,功能重叠但设计理念和性能不同
    • viem 是较新的库,针对前端场景优化,体积和性能优于传统库
  • wagmi 与底层库的关系
    • wagmi v1+ 默认使用 viem 作为底层库(替代之前的 ethers.js)
    • 仍支持通过配置使用 ethers.js(兼容性考虑)
    • wagmi 将底层库的复杂逻辑封装为简洁的 React Hooks
  • 功能互补
    • 底层库(ethers.js/web3.js/viem)负责与区块链交互的核心逻辑
    • 上层库(wagmi)简化前端开发,提供 Hooks 化 API 和状态管理
  • 生态组合
    • 现代 Web3 前端开发的主流组合:viem + wagmi + RainbowKit(或其他钱包连接库)
    • 传统组合:ethers.js/web3.js + 自定义钱包连接逻辑

4. 技术演进与选择建议

  • 技术趋势
    • web3.jsethers.js:追求更现代、轻量的 API
    • ethers.jsviem:进一步优化性能和体积
    • 从直接使用底层库到 wagmi:提升前端开发效率
  • 选择建议
    • 现代前端项目:推荐使用 viem + wagmi 组合
      • 优势:极致轻量、性能优异、开发效率高
    • 需要兼容性:选择 ethers.js + wagmi
      • 优势:生态成熟、文档丰富、社区支持广
    • 传统项目:继续使用 web3.js
      • 优势:官方维护、功能全面、历史兼容性好
    • 底层定制需求:直接使用 viemethers.js
      • 优势:灵活性高,可按需实现特定功能

5. 总结

  • 底层库(ethers.js/web3.js/viem):负责与以太坊区块链交互的核心逻辑,选择依据为性能、体积和 API 偏好
  • 上层库(wagmi):简化前端开发,提供 Hooks 化 API 和状态管理,大幅提升开发效率
  • 最佳实践:现代项目优先选择 viem + wagmi 组合,兼顾性能和开发体验

这种分层设计反映了 Web3 前端开发的演进趋势:从复杂的底层操作到简洁的上层抽象,从单一功能库到系统化的生态工具链。

Solidity 入门:变量、函数与回调机制

这篇把 Solidity 初学最容易混的几块放在一起:变量类型、函数可见性、数据位置(memory/storage/calldata)、以及 receive / fallback 回调。

你可以把它当作一份“先跑通认知,再写代码”的速查稿。

一、Solidity 是什么

Solidity 是面向 EVM 的静态编译型高级语言,语法受 C++、JavaScript 影响明显。
如果你有前端或后端基础,上手门槛不高,但它和普通服务端语言的最大区别在于:每一步状态写入都和 Gas 成本直接挂钩


二、合约最小结构

一个最简单的合约通常包含四件事:

  1. 编译器版本声明(pragma solidity
  2. 合约定义(contract
  3. 状态变量(state variable)
  4. 函数(function)
1
2
3
4
5
6
7
8
9
10
11
12
13
pragma solidity ^0.8.0;

contract Counter {
uint public counter;

constructor() {
counter = 0;
}

function count() public {
counter = counter + 1;
}
}

三、函数:可见性与状态可变性

1) 可见性(visibility)

  • public:内外都可调
  • external:通常给外部调用
  • internal:仅当前合约及继承合约可调
  • private:仅当前合约可调

2) 状态可变性(mutability)

  • view:只读状态,不写
  • pure:既不读状态也不写状态
  • payable:允许接收 ETH
1
2
3
4
5
6
7
8
9
10
11
function add(uint i, uint j) public pure returns (uint) {
return i + j;
}

function addTo(uint y) public view returns (uint) {
return counter + y;
}

function deposit() public payable {
deposited += msg.value;
}

四、变量与类型:先记住这一层

1) 状态变量 vs 本地变量

  • 状态变量:存在链上 storage,写入贵
  • 本地变量:函数执行期的临时数据,多在 memory

2) 常量相关

  • constant:编译期常量,写死
  • immutable:部署时赋值,之后不可改

3) 常见类型

  • 值类型:boolint/uintaddressbytes1~bytes32enum
  • 引用类型:arraystructmapping

五、引用类型与数据位置

Solidity 里引用类型的成本差异,核心就看数据位置:

  • storage:链上持久化,最贵
  • memory:函数调用期间存在
  • calldata:外部函数参数区,只读,通常更省 Gas
1
2
3
4
function copy(uint[] calldata arrs) public returns (uint len) {
numbers = arrs; // calldata -> storage(发生拷贝)
return numbers.length;
}

一个常见优化点:参数只读时优先 calldata,别先拷贝到 memory 再用。


六、数组、结构体、映射:三个高频容器

1) 数组(Array)

  • 定长:uint[10]
  • 动态:uint[]
  • 常用成员:lengthpushpop

注意:在链上循环数组,长度不可控时很容易把 Gas 顶上去。

2) 结构体(Struct)

适合把多个相关字段打包成一个业务对象,例如用户资料、订单记录。

3) 映射(Mapping)

1
mapping(address => uint) public balances;

映射读取不存在的 key 会返回默认值(如 0)。
它没有“长度”概念,也不能直接遍历全部 key。


七、地址与转账:address vs address payable

  • address:20 字节地址
  • address payable:可转账地址,可调用 transfer / send
1
2
3
4
5
function testTransfer(address payable to) public {
if (address(this).balance >= 10) {
to.transfer(10);
}
}

transfer/send 都有 2300 gas stipend 的历史限制语境。实际开发中,很多团队更倾向 call + 显式检查返回值,配合重入保护一起做。


八、特殊函数:constructorreceivefallback

1) constructor

部署时执行一次,用于初始化。
链上运行时字节码里不再包含构造逻辑本体,而是构造执行后的结果。

2) receive() external payable

合约接收纯 ETH(空 calldata)时触发。

3) fallback() external [payable]

调用了不存在的函数,或某些不匹配场景时触发。
如果没有 receive,转账时也可能落到 fallback(取决于调用方式与 calldata)。


九、全局变量里最常用的几个

  • block.number:当前区块号
  • block.timestamp:当前区块时间戳
  • msg.sender:当前调用者
  • msg.value:本次调用携带的 wei
  • tx.origin:整条调用链最初发起者(权限判断一般不建议依赖)

十、初学阶段最容易踩的坑

  1. 循环不设边界:数组过大时函数可能直接超 Gas。
  2. 数据位置乱用:能 calldata 的参数别无脑拷贝。
  3. 权限控制粗糙:管理员操作要配合 modifier 和事件。
  4. 回调函数理解不清:收款路径、调用路径没分开。
  5. 版本差异忽略:例如 0.8+ 默认带溢出检查,和 0.8 前行为不同。

十一、练手建议:做一个简版 Bank 合约

可以用下面这组要求自测:

  • 用户可向合约存款(payable
  • 记录每个地址余额(mapping(address => uint)
  • 维护存款 Top N(可先做 Top 3)
  • 仅管理员可提取全部 ETH(onlyOwner

先把功能跑通,再做两件事:

  • 补事件(Deposit / Withdraw
  • 补安全细节(重入防护、错误处理)

如果你刚开始学 Solidity,别急着追“大全”。先把这篇里的函数、类型和回调路径吃透,再进到 token、DEX、治理类合约,速度会快很多。

EIP-1559:为什么需要它,以及 Gas 怎么算

EIP-1559 是以太坊手续费模型的一次关键改造。它没有把 Gas 费“变便宜”,但把原来难以预估的纯竞价模式,换成了更容易理解的“基础费 + 小费”。

这篇就讲三件事:

  1. 为什么会有 EIP-1559
  2. 它具体改了什么
  3. Gas 费用到底怎么计算(含具体数字例子)

一、为什么会有 EIP-1559

在 EIP-1559 之前,以太坊主要用 First-Price Auction(第一价格拍卖)

  • 你给出一个 gasPrice
  • 矿工优先打包 gasPrice 更高的交易
  • 你实际支付的单价就是你自己出的那个价

这个模型在高峰期会很难受:

  • 费用很难预估:用户往往只能“多给一点”,否则交易容易卡住。
  • 过度竞价:很多人同时抬价,结果整体支付高于真实所需。
  • 体验不稳定:同样一笔转账,不同时间价格差异非常大。
  • 钱包策略复杂:钱包要反复估算“现在到底该出多少”。

所以 EIP-1559 不是来承诺“长期低价”的,它主要解决的是定价不透明和体验抖动


二、EIP-1559 具体内容

EIP-1559 引入了几个关键概念。

1) Base Fee(基础费)

  • 每个区块都有一个基础费 baseFeePerGas
  • 这个值由协议自动调整,不是矿工随意定价
  • 基础费会被销毁(burn),不会给打包者

这点很关键:基础费不是“给矿工/验证者”的收入,而是直接销毁。

2) Priority Fee(小费)

  • 也叫 tip,参数通常是 maxPriorityFeePerGas
  • 这部分给打包者(PoW 时期矿工 / PoS 下验证者)
  • 本质上是你给打包者的激励

3) Fee Cap(最高可接受总单价)

  • 参数名是 maxFeePerGas
  • 表示你愿意支付的“每单位 gas 总上限”

交易最终每单位 gas 实际支付单价遵循:

effectiveGasPrice = min(maxFeePerGas, baseFeePerGas + maxPriorityFeePerGas)

并且要满足交易可被执行的基本条件:

maxFeePerGas >= baseFeePerGas

4) 弹性区块(Elastic Block Size)

  • 每个区块有一个“目标 gas 使用量”(target gas)
  • 实际区块可在目标附近弹性波动(上限通常是目标的 2 倍)
  • 如果区块持续高于目标,下一块的 baseFee 上升
  • 如果持续低于目标,下一块的 baseFee 下降

这样做的效果是:短期拥堵不会把手续费一下子拉爆,而是分摊到后续几个区块里。


三、Gas 计算:先看总公式

先看一条最常用的公式:

交易总花费 = gasUsed * effectiveGasPrice

其中(单位常用 gwei):

  • effectiveGasPrice = min(maxFeePerGas, baseFeePerGas + maxPriorityFeePerGas)
  • 用户支付会拆成两部分:
    • 销毁gasUsed * baseFeePerGas
    • 小费给验证者gasUsed * priorityFeePaid

这里:

priorityFeePaid = effectiveGasPrice - baseFeePerGas

你可以把它理解成“愿意付到这里为止”,不是“按这个值强制扣费”。


四、具体算例(带数字)

下面都用 gwei 做单价单位,1 ETH = 1,000,000,000 gwei

例子 1:普通转账

假设:

  • gasUsed = 21,000
  • baseFee = 30 gwei
  • maxPriorityFee = 2 gwei
  • maxFee = 100 gwei

步骤:

  1. 先算 baseFee + priority = 32 gwei
  2. effectiveGasPrice = min(100, 32) = 32 gwei
  3. 总费用:
    21,000 * 32 = 672,000 gwei = 0.000672 ETH

拆分:

  • 销毁:21,000 * 30 = 630,000 gwei = 0.00063 ETH
  • 小费:21,000 * 2 = 42,000 gwei = 0.000042 ETH

例子 2:你把 maxFee 设得很高,但不会按高价全扣

假设:

  • gasUsed = 100,000
  • baseFee = 40 gwei
  • maxPriorityFee = 3 gwei
  • maxFee = 200 gwei

计算:

  • baseFee + priority = 43 gwei
  • effectiveGasPrice = min(200, 43) = 43 gwei
  • 总费用:100,000 * 43 = 4,300,000 gwei = 0.0043 ETH

这个例子想说明一件事:maxFee 设高是为了防止交易卡住,不等于你会按高价成交。

例子 3:maxFee 过低导致交易不可打包

假设:

  • baseFee = 55 gwei
  • 你设置 maxFee = 50 gwei

因为 maxFee < baseFee,交易直接不满足基本条件,节点通常会拒绝或一直 pending。

例子 4:合约调用(更大 gasUsed)

假设一次 DEX 交互:

  • gasUsed = 180,000
  • baseFee = 25 gwei
  • maxPriorityFee = 1.5 gwei
  • maxFee = 35 gwei

先算:

  • baseFee + priority = 26.5 gwei
  • effectiveGasPrice = min(35, 26.5) = 26.5 gwei

总费用:

  • 180,000 * 26.5 = 4,770,000 gwei = 0.00477 ETH

拆分:

  • 销毁:180,000 * 25 = 4,500,000 gwei = 0.0045 ETH
  • 小费:180,000 * 1.5 = 270,000 gwei = 0.00027 ETH

五、设置建议(钱包/脚本)

如果你自己写交易脚本(ethers.js/web3.js),可以按这个思路:

  • maxPriorityFeePerGas:给一个适中小费(例如 1-3 gwei,拥堵时上调)
  • maxFeePerGas:至少覆盖“当前 base fee + 小费”,并留一定缓冲

常见经验做法是:

maxFeePerGas ≈ 2 * 当前baseFee + priority

这样做的好处是,后面 1-2 个区块就算变贵,交易也不至于马上失效。


六、常见误区

误区 1:EIP-1559 之后手续费就会越来越低

不一定。它主要改善的是定价机制和可预测性,不是保证低价。

误区 2:maxFeePerGas 就是实际支付单价

不是。它只是上限,实际支付看当时的 baseFee + priority

误区 3:全部手续费都给验证者

不是。基础费被销毁,验证者拿的是小费部分。


七、小结

EIP-1559 可以记成三句话:

  • 把“纯拍卖”变成“协议定基础价 + 用户给小费”
  • 让费用估算更稳定,减少盲目竞价
  • 通过 burn 机制把基础费从流通里移除

只要把 baseFee / maxPriorityFee / maxFee 这三个参数分清楚,Gas 计算基本就通了。更重要的是根据拥堵情况动态调整,而不是记一套固定数字。

主流公链与测试链对比

主流公链对比(生产环境)

类型 EVM 兼容 Gas 成本 速度/吞吐(体感) 生态成熟度 典型场景
Ethereum Mainnet L1 非常高 高价值 DeFi、核心资产结算
BNB Smart Chain (BSC) L1 交易频繁、成本敏感应用
Polygon PoS 侧链/L2风格网络 游戏、社交、低费应用
Arbitrum One Ethereum L2 (Optimistic) 低-中 很高 DeFi、交易类 dApp
Optimism Ethereum L2 (Optimistic) 低-中 公共品生态、通用 dApp
Base Ethereum L2 (OP Stack) 高(增长快) 消费级应用、社交应用
Avalanche C-Chain L1 子网架构 低-中 中-高 DeFi、企业/子网场景
zkSync Era Ethereum L2 (ZK) 是(有差异) 中-高 低费支付、ZK 叙事应用
Linea Ethereum L2 (ZK) EVM 迁移、低费交互
Solana L1 否(非 EVM) 很低 很快 高频交易、消费级应用

主流测试链对比(开发/联调)

主网 推荐测试链 是否常用水龙头 与主网一致性(开发体感) 备注
Ethereum Sepolia 当前最常用以太坊测试网
BSC BSC Testnet 中-高 需注意节点稳定性差异
Polygon PoS Polygon Amoy 中-高 Mumbai 已逐步被替代
Arbitrum One Arbitrum Sepolia L2 手续费模型更接近主网
Optimism OP Sepolia OP Stack 生态通用性好
Base Base Sepolia Base 开发首选测试网
Avalanche C-Chain Fuji 中-高 AVAX 测试币相对容易获取
zkSync Era zkSync Sepolia 注意部分工具链兼容细节
Linea Linea Sepolia 新生态,文档更新较快
Solana Devnet / Testnet Solana 习惯用 Devnet 做开发

选链建议(简版)

  • 想要最稳和最大生态:Ethereum + Arbitrum/Optimism/Base
  • 想要低费和高频交互:BSC / Polygon / Solana
  • 想做 EVM 且方便迁移:优先 Arbitrum / Base / OP
  • 想提前布局 ZK:zkSync / Linea