Flutter 应用里Dart 侧如何与链打交道:库分工与调用顺序
摘要
在「原生壳 + Flutter 模块」的混合架构中,链上读写、业务网关与钱包连接往往同时存在。本文将 Dart 侧常见能力归纳为 五类:JSON-RPC 客户端、HTTP 传输、链数据编解码与请求封装、WalletConnect 会话,以及签名与跨语言辅助;再用 一张总览流程图 与 多张时序图 说明它们如何串成一条可上线的 Swap / 授权类链路。文末补充 大单、单池深度不足 场景下 路由服务与客户端 的分工,便于读者按自家模块命名做映射。
一、为什么要拆成「五类」
链相关需求在客户端至少叠了三层语义:
- 与节点或 RPC 网关对话:Ethereum JSON-RPC 等,关注 method、params、错误码与重试策略。
- 与自家业务服务对话:REST、鉴权、聚合报价、路由、配置——通常 不是
eth_*协议本身。 - 与「密钥在谁手里」对话:内置签名(多经 Platform Channel 调原生库)或 WalletConnect 把授权交给外部钱包。
若全部混进一个「大网络层」,排查超时、区分网关 4xx 与 JSON-RPC error、做埋点与限流都会变难。按 职责边界 拆五类,比单纯罗列依赖更有利于架构讨论与排障。
二、五类职责与典型业务映射
| 类型 | 核心库 / 技术 | 典型在做什么 |
|---|---|---|
| ① | web3dart 及项目内对 HTTP Provider 的封装(统一 BaseURL、链 ID、Header 等) |
eth_call、eth_sendRawTransaction、eth_getTransactionReceipt 等 JSON-RPC;读余额、allowance、池子;广播已签交易、轮询回执;切换网络时更换 endpoint。 |
| ② | package:http、dio |
业务 HTTPS:行情、订单、活动、聚合报价 / 智能路由 REST、运营配置等;dio 适合拦截器、鉴权、重试;http 轻量或作为其它库的底层。 |
| ③ | on_chain、业务侧 链核心封装(命名因项目而异) |
交易与多链类型的 编解码;统一 JsonRpcRequest 形态、链别枚举;与 ① 配合「先组 params,再发 RPC」。 |
| ④ | reown_core、json_rpc_2、Relay WebSocket |
WalletConnect:应用与 外部钱包 的会话;链上意图以 JSON-RPC 2.0 经中继送达对端,由用户在钱包 App 内确认。 |
| ⑤ | eth_sig_util、GraphQL、flutter_rust_bridge + Rust 等 |
EIP-712 / TypedData 哈希与展示;GraphQL 承载非 JSON-RPC 的数据查询;Rust 承载重计算或链工具;原生签名经 MethodChannel 与 Dart 协同。⑤与原生属于 横切 能力,常与 ① 串联。 |
常见组合(内置钱包主路径):② 拉报价 → ① 读链校验 → ③ 组交易 →(⑤ 视需求)→ 原生签名 → ① 广播与轮询。
外接钱包:④ 完成连接与签名决策后,读链与广播仍常由 ① 承担,具体以产品设计为准。
三、端到端顺序:一张流程图
下图以 主 App 内 Web3 Swap / 授权 为贯穿例子,可按产品改名。①~⑤ 为 Dart 侧分类编号;原生签名模块 不属于 Dart 五类,但与链上闭环强相关(常见实现为嵌入式钱包 SDK,经 Platform Channel 调用)。
%%{ init: { "flowchart": { "nodeSpacing": 28, "rankSpacing": 50, "padding": 12 } } }%%
flowchart TB
subgraph SC["典型场景:主 App 内 Web3 Swap / 授权"]
direction TB
S0(["开始:选币对、数量、滑点"])
S2["② http / dio
聚合报价、智能路由等 REST"]
S1a["① web3dart + 自定义 Provider
eth_call:余额、allowance、池子等只读"]
S3["③ 链核心封装 + on_chain
calldata / nonce / 链别与交易体"]
S5["⑤ 可选:eth_sig_util / Rust FRB / GraphQL"]
Q{签名在哪完成?}
S4["④ reown + json_rpc_2 + Relay
WalletConnect → 外部钱包"]
ST["原生签名模块
Platform Channel 签名 raw tx"]
S1b["① web3dart
sendRawTransaction + receipt 轮询"]
S9(["结束:结果页 / 状态更新"])
S0 --> S2
S2 --> S1a
S1a --> S3
S3 -.->|若需要| S5
S5 -.-> Q
S3 --> Q
Q -->|外接钱包| S4
Q -->|内置自托管| ST
S4 --> S1b
ST --> S1b
S1b --> S9
end
读法简述
- 内置钱包:
② → ①(读) → ③ → (⑤) → 原生签 → ①(写)。 - 外接钱包:
… → ③ → (⑤) → ④ → ①(写);会话建立后,只读仍多走 ①。
四、分层时序图
参与者均为抽象角色,便于替换为自家模块名。各时序图开头已含 %%{ init: … }%%(theme: "base" 与 themeVariables),若预览中 alt 分区 对比度仍不理想,可自行微调其中的颜色或 diagramMarginY / diagramMarginX。
4.1 ① JSON-RPC 主干:web3dart 与自定义 Provider
%%{
init: {
"theme": "base",
"themeVariables": {
"background": "#94a3b8",
"primaryColor": "#64748b",
"primaryTextColor": "#f8fafc",
"secondaryColor": "#475569",
"secondaryTextColor": "#f8fafc",
"tertiaryColor": "#334155",
"tertiaryTextColor": "#f8fafc",
"mainBkg": "#cbd5e1",
"actorBkg": "#cbd5e1",
"actorBorder": "#475569",
"actorTextColor": "#0f172a",
"labelBoxBkgColor": "#1e293b",
"labelTextColor": "#f8fafc",
"labelBoxBorderColor": "#0f172a",
"loopTextColor": "#f8fafc",
"signalColor": "#1e293b",
"signalTextColor": "#0f172a",
"textColor": "#0f172a",
"lineColor": "#1e293b",
"noteBkgColor": "#f1f5f9",
"noteTextColor": "#0f172a",
"activationBkgColor": "#64748b",
"activationBorderColor": "#334155"
},
"sequence": { "diagramMarginY": 80, "diagramMarginX": 36, "mirrorActors": false, "showSequenceNumbers": false }
}
}%%
sequenceDiagram
participant UI as Flutter UI
participant VM as ViewModel / 状态层
participant W3 as web3dart Web3Client
participant JR as web3dart JsonRPC
participant Pvd as 自定义 HTTP Provider
participant HTTP as http.Client / 封装
UI->>VM: 读链 / 广播 / 查回执
VM->>W3: 高层 API(如 sendTransaction、getTransactionReceipt)
W3->>JR: 组装 JSON-RPC method + params
JR->>Pvd: 经 Provider(BaseURL、链 ID、Header 等)
Pvd->>HTTP: POST JSON-RPC body
HTTP-->>Pvd: HTTP 200 + JSON-RPC result / error
Pvd-->>JR: 解析 body
JR-->>W3: Dart 对象或异常
W3-->>VM: Future 完成
VM-->>UI: 刷新 UI
4.2 ② 传输层:http 与 dio 并存
web3dart 底层常用 package:http;业务侧大量 REST 更常见 dio(拦截器、证书、超时、下载)。二者对应 不同域名的 HTTP 语义,不必二选一。
%%{
init: {
"theme": "base",
"themeVariables": {
"background": "#94a3b8",
"primaryColor": "#64748b",
"primaryTextColor": "#f8fafc",
"secondaryColor": "#475569",
"secondaryTextColor": "#f8fafc",
"tertiaryColor": "#334155",
"tertiaryTextColor": "#f8fafc",
"mainBkg": "#cbd5e1",
"actorBkg": "#cbd5e1",
"actorBorder": "#475569",
"actorTextColor": "#0f172a",
"labelBoxBkgColor": "#1e293b",
"labelTextColor": "#f8fafc",
"labelBoxBorderColor": "#0f172a",
"loopTextColor": "#f8fafc",
"signalColor": "#1e293b",
"signalTextColor": "#0f172a",
"textColor": "#0f172a",
"lineColor": "#1e293b",
"noteBkgColor": "#f1f5f9",
"noteTextColor": "#0f172a",
"activationBkgColor": "#64748b",
"activationBorderColor": "#334155"
},
"sequence": { "diagramMarginY": 80, "diagramMarginX": 36, "mirrorActors": false, "showSequenceNumbers": false }
}
}%%
sequenceDiagram
participant VM as Dart 业务层
participant D as dio
participant H as package:http
participant N as 链 RPC / 网关
participant B as 业务 REST 域
Note over VM,B: 链 JSON-RPC 多由 web3dart 经 http 发出;本图强调「业务 REST」与「裸 HTTP」分流
VM->>D: 行情、订单、Web3 中台等
D->>B: HTTPS + 拦截器(Token、签名、埋点)
B-->>D: JSON
D-->>VM: Model
VM->>H: Provider 内 POST 等
H->>N: JSON-RPC 或其它 HTTP
N-->>H: 响应体
H-->>VM: 交由 JsonRPC 解析
4.3 ③ 编解码与请求封装:on_chain 与链核心模块
%%{
init: {
"theme": "base",
"themeVariables": {
"background": "#94a3b8",
"primaryColor": "#64748b",
"primaryTextColor": "#f8fafc",
"secondaryColor": "#475569",
"secondaryTextColor": "#f8fafc",
"tertiaryColor": "#334155",
"tertiaryTextColor": "#f8fafc",
"mainBkg": "#cbd5e1",
"actorBkg": "#cbd5e1",
"actorBorder": "#475569",
"actorTextColor": "#0f172a",
"labelBoxBkgColor": "#1e293b",
"labelTextColor": "#f8fafc",
"labelBoxBorderColor": "#0f172a",
"loopTextColor": "#f8fafc",
"signalColor": "#1e293b",
"signalTextColor": "#0f172a",
"textColor": "#0f172a",
"lineColor": "#1e293b",
"noteBkgColor": "#f1f5f9",
"noteTextColor": "#0f172a",
"activationBkgColor": "#64748b",
"activationBorderColor": "#334155"
},
"sequence": { "diagramMarginY": 80, "diagramMarginX": 36, "mirrorActors": false, "showSequenceNumbers": false }
}
}%%
sequenceDiagram
participant VM as ViewModel
participant Core as 链核心封装(项目内模块)
participant OC as on_chain
participant W3 as web3dart Client
participant RPC as RPC 网关 / 节点
VM->>Core: 构造 JsonRpcRequest(method、params、链元数据)
Core->>OC: 编码交易、地址、AccessList 等
OC-->>Core: Uint8List / Map / 强类型对象
Core-->>VM: 完整 params
VM->>W3: 携带 params 调用
W3->>RPC: HTTP JSON-RPC
RPC-->>W3: result / error
W3-->>VM: Dart 类型
VM->>OC: 可选:解码 logs / bytes
OC-->>VM: 结构化结果
4.4 ④ WalletConnect:reown_core 与 json_rpc_2
与节点侧的 eth_* 不同:这里是 应用 ↔ 中继 ↔ 对端钱包 的 JSON-RPC 2.0 会话,密钥与确认 UI 在 外部钱包进程。
%%{
init: {
"theme": "base",
"themeVariables": {
"background": "#94a3b8",
"primaryColor": "#64748b",
"primaryTextColor": "#f8fafc",
"secondaryColor": "#475569",
"secondaryTextColor": "#f8fafc",
"tertiaryColor": "#334155",
"tertiaryTextColor": "#f8fafc",
"mainBkg": "#cbd5e1",
"actorBkg": "#cbd5e1",
"actorBorder": "#475569",
"actorTextColor": "#0f172a",
"labelBoxBkgColor": "#1e293b",
"labelTextColor": "#f8fafc",
"labelBoxBorderColor": "#0f172a",
"loopTextColor": "#f8fafc",
"signalColor": "#1e293b",
"signalTextColor": "#0f172a",
"textColor": "#0f172a",
"lineColor": "#1e293b",
"noteBkgColor": "#f1f5f9",
"noteTextColor": "#0f172a",
"activationBkgColor": "#64748b",
"activationBorderColor": "#334155"
},
"sequence": { "diagramMarginY": 80, "diagramMarginX": 36, "mirrorActors": false, "showSequenceNumbers": false }
}
}%%
sequenceDiagram
participant UI as Flutter 页面
participant RW as reown_core(会话 / Pairing)
participant JR as json_rpc_2 Client
participant Relay as WalletConnect Relay(WSS)
participant Wal as 对端钱包
UI->>RW: 建立连接 / session proposal
RW->>JR: 注册 peer、发送 JSON-RPC 请求
JR->>Relay: WebSocket(topic 订阅)
Relay->>Wal: 转发
Wal-->>Relay: response / event
Relay-->>JR: 推送
JR-->>RW: 模型或错误
RW-->>UI: 更新连接与签名状态
何时会走这条链路
当 连接、签名或发交易 必须由 外部钱包 完成时启用:例如尚未启用内置自托管,或用户主动选择 MetaMask、Rabby 等。它与「是否登录主站账号」无必然关系——只要密钥决策在端外,就会用到 ④。
与 ① 的边界:① 面向 节点 JSON-RPC;④ 面向 钱包会话 JSON-RPC 2.0。读链、广播已签 raw tx 仍多在 ①;④ 负责把 待签名意图 送达对端并由用户确认。
4.5 ⑤ 横切能力:签名数据、GraphQL、Rust、原生
%%{
init: {
"theme": "base",
"themeVariables": {
"background": "#94a3b8",
"primaryColor": "#64748b",
"primaryTextColor": "#f8fafc",
"secondaryColor": "#475569",
"secondaryTextColor": "#f8fafc",
"tertiaryColor": "#334155",
"tertiaryTextColor": "#f8fafc",
"mainBkg": "#cbd5e1",
"actorBkg": "#cbd5e1",
"actorBorder": "#475569",
"actorTextColor": "#0f172a",
"labelBoxBkgColor": "#1e293b",
"labelTextColor": "#f8fafc",
"labelBoxBorderColor": "#0f172a",
"loopTextColor": "#f8fafc",
"signalColor": "#1e293b",
"signalTextColor": "#0f172a",
"textColor": "#0f172a",
"lineColor": "#1e293b",
"noteBkgColor": "#f1f5f9",
"noteTextColor": "#0f172a",
"activationBkgColor": "#64748b",
"activationBorderColor": "#334155"
},
"sequence": { "diagramMarginY": 80, "diagramMarginX": 36, "mirrorActors": false, "showSequenceNumbers": false }
}
}%%
sequenceDiagram
participant UI as Flutter UI
participant VM as ViewModel
participant ES as eth_sig_util
participant RS as Rust(flutter_rust_bridge)
participant GQ as graphql_client
participant W3 as web3dart
participant N as 原生签名(可选)
UI->>VM: 上链前准备
opt EIP-712 / TypedData
VM->>ES: digest / 编码
ES-->>VM: 哈希或结构化数据
end
opt 重计算走 Rust
VM->>RS: FRB 调用
RS-->>VM: 计算结果
end
opt 配置类 GraphQL
VM->>GQ: query / mutation
GQ-->>VM: 数据
end
opt 私钥在原生
VM->>N: Platform Channel 请求签名
N-->>VM: signature / raw tx
end
VM->>W3: eth_sendRawTransaction / eth_call 等
W3-->>VM: 链上结果
VM-->>UI: 汇总展示
五、大单与单池不足:路由、拆单与客户端分工
当 Swap 体量相对 单池深度 过大时,单一路径易出现 滑点恶化 或 无法成交。常见做法是:由 聚合 / 智能路由服务 在服务端给出 多跳、多 DEX、拆单 的可执行方案,并返回 报价包(子路径、每跳 calldata、spender、minOut、deadline、建议 gas 上限等)。客户端侧重 展示拆单与多跳、校验 授权是否覆盖聚合合约或多 spender、在 报价过期 时重拉并重建交易,以及维护 链上结果与异常 的状态机;路径最优化本身仍属服务端领域。
%%{
init: {
"theme": "base",
"themeVariables": {
"background": "#94a3b8",
"primaryColor": "#64748b",
"primaryTextColor": "#f8fafc",
"secondaryColor": "#475569",
"secondaryTextColor": "#f8fafc",
"tertiaryColor": "#334155",
"tertiaryTextColor": "#f8fafc",
"mainBkg": "#cbd5e1",
"actorBkg": "#cbd5e1",
"actorBorder": "#475569",
"actorTextColor": "#0f172a",
"labelBoxBkgColor": "#1e293b",
"labelTextColor": "#f8fafc",
"labelBoxBorderColor": "#0f172a",
"loopTextColor": "#f8fafc",
"signalColor": "#1e293b",
"signalTextColor": "#0f172a",
"textColor": "#0f172a",
"lineColor": "#1e293b",
"noteBkgColor": "#f1f5f9",
"noteTextColor": "#0f172a",
"noteBorderColor": "#475569",
"activationBkgColor": "#64748b",
"activationBorderColor": "#334155"
},
"sequence": {
"diagramMarginY": 88,
"diagramMarginX": 40,
"actorMargin": 50,
"messageMargin": 26,
"mirrorActors": false,
"showSequenceNumbers": false
}
}
}%%
sequenceDiagram
participant U as 用户
participant F as Flutter(DEX / Swap)
participant R as 智能路由 / 聚合报价服务
participant N as 原生签名模块
participant C as 链 JSON-RPC
U->>F: 大额 Swap 意图
F->>R: 报价请求(金额、滑点、链、币对、地址等)
alt 单池或单路径不足
R-->>F: 多跳与/或拆单方案(子单、calldata、spender、minOut、deadline、gas 建议)
F->>U: 展示拆单 / 多跳与风险
else 不可成交
R-->>F: 错误 / 建议减量 / 不可路由
F->>U: 降级提示(改额、换对、或其它兑换形态)
end
U->>F: 确认
F->>F: 校验 minOut、deadline、allowance(含多 spender)
alt 授权不足
F->>N: 构造并签名 approve(可能多笔)
N-->>F: raw tx
F->>C: sendRaw + receipt 轮询
C-->>F: 授权完成
end
F->>N: 按路由包构造 swap(单笔多跳或多笔顺序,依合约设计)
N-->>F: 已签名 raw(可多笔)
F->>C: sendRaw + receipt 轮询
alt 成功
C-->>F: receipt 成功
F->>U: 结果页:总到账、各跳摘要、浏览器链接
else 失败或异常
C-->>F: revert / 超时等
F->>U: 原因与重试 / 改量 / 刷新报价
end
与上文五类的对应:本节中 ② 对应 F→R;①③ 体现在读授权、组交易、广播与轮询;④⑤ 在大单主路径上多为可选项(外接签、EIP-712、额外计算等)。
六、小结
- ① 解决 与链(或 RPC 网关)的 JSON-RPC 问题。
- ② 解决 与自家业务域的 HTTP 问题。
- ③ 解决 链上结构化数据与请求拼装 问题。
- ④ 解决 与外部钱包的会话与签名授权 问题。
- ⑤ 与 原生 解决 签名数据、重计算、跨语言、私钥域 问题。
职责划清后,排障顺序也更自然:先看超时落在 ② 的域名 还是 ① 的 RPC URL;再看错误体属于 HTTP 还是 JSON-RPC;最后再查 ③ 的编码 或 ④ 的会话状态。
Flutter 应用里Dart 侧如何与链打交道:库分工与调用顺序
https://bitgarden.cn/2025/01/22/Cross-Platform/Flutter-Web3-Dart-RPC-技术梳理_hexo/