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

摘要

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


一、为什么要拆成「五类」

链相关需求在客户端至少叠了三层语义:

  1. 与节点或 RPC 网关对话:Ethereum JSON-RPC 等,关注 method、params、错误码与重试策略。
  2. 与自家业务服务对话:REST、鉴权、聚合报价、路由、配置——通常 不是 eth_* 协议本身。
  3. 与「密钥在谁手里」对话:内置签名(多经 Platform Channel 调原生库)或 WalletConnect 把授权交给外部钱包。

若全部混进一个「大网络层」,排查超时、区分网关 4xx 与 JSON-RPC error、做埋点与限流都会变难。按 职责边界 拆五类,比单纯罗列依赖更有利于架构讨论与排障。


二、五类职责与典型业务映射

类型 核心库 / 技术 典型在做什么
web3dart 及项目内对 HTTP Provider 的封装(统一 BaseURL、链 ID、Header 等) eth_calleth_sendRawTransactioneth_getTransactionReceiptJSON-RPC;读余额、allowance、池子;广播已签交易、轮询回执;切换网络时更换 endpoint。
package:httpdio 业务 HTTPS:行情、订单、活动、聚合报价 / 智能路由 REST、运营配置等;dio 适合拦截器、鉴权、重试;http 轻量或作为其它库的底层。
on_chain、业务侧 链核心封装(命名因项目而异) 交易与多链类型的 编解码;统一 JsonRpcRequest 形态、链别枚举;与 ① 配合「先组 params,再发 RPC」。
reown_corejson_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 ② 传输层:httpdio 并存

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_corejson_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、minOutdeadline、建议 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/

作者

Felix Tao

发布于

2025-01-22

更新于

2025-04-02

许可协议