Web3 热门面试题
使用说明
本篇共 60 道题。
Web3 面试要把链上不可逆状态、钱包信任边界和前端体验结合起来。回答重点是交易生命周期、签名语义和安全,而不是只会调用 SDK。
Q1: 区块链解决什么问题?
答案:
它让多个互不完全信任的参与者通过共识维护可验证、按规则演进的共享状态,不依赖单一数据库管理员。
代价是吞吐、延迟、成本、隐私和治理复杂度。已有可信中心的普通业务不一定需要上链。
- 区块链通过共识和密码学让互不信任的参与者共同维护可验证状态,适合数字资产、跨组织结算和无需单方控制的规则执行。
- 代价是吞吐、延迟、费用、隐私和不可逆性。普通中心化 CRUD 若已有可信运营方,数据库通常更便宜、简单且可治理。
Q2: 以太坊账户有哪些类型?
答案:
传统模型中有外部拥有账户(EOA)和合约账户。EOA 由私钥签名发起交易;合约账户由代码控制,执行由交易或其他合约调用触发。
账户抽象进一步让智能账户具备更灵活的验证、批处理和恢复能力,但依赖具体标准和基础设施。
- EOA 由私钥控制,可主动签名并发起交易;合约账户由代码控制,只有收到调用时执行逻辑。传统合约账户本身没有私钥。
- 账户抽象把验证、批处理、代付和恢复能力放进智能账户,但仍要通过入口合约或协议规则触发执行。
Q3: 钱包连接时前端实际获得了什么?
答案:
通常获得用户授权暴露的账户和当前链,并可通过 Provider 请求签名或发送交易;前端不会也不应获得私钥。
连接不等于登录,也不等于用户授权任意操作。账户、链和权限变化都要监听并更新 UI。
- 连接钱包通常只请求用户授权公开地址和当前链信息,DApp 不会也不应拿到私钥。后续签名或交易都由钱包弹窗让用户确认。
- 地址不是永久登录态;要监听账户、链和断开事件,并通过签名挑战建立服务端会话,不能把“已连接”直接等同于“已认证”。
Q4: EIP-1193 Provider 的作用是什么?
答案:
它定义 DApp 与钱包 Provider 的通用请求和事件接口,例如 request、账户变化和链变化,让应用不直接依赖某个钱包实现。
调用仍可能被用户拒绝、链不支持或 Provider 断开,必须处理错误和重试边界。
- EIP-1193 把钱包能力统一成
request({ method, params }),并定义 accountsChanged、chainChanged、connect、disconnect 等事件。 - Provider 负责 RPC 与事件,不等同于 Signer 或完整客户端。前端要处理用户拒绝、未授权、链不支持和多个钱包 Provider 竞争。
Q5: 一笔以太坊交易的生命周期是什么?
答案:
DApp 构造交易,钱包展示并签名,节点接收后进入交易池,被区块打包后获得确认,后续仍可能因链重组变化。
前端应展示待签名、已提交、确认中、成功/失败和被替换状态,不能发送后立即当成最终成功。
- 前端构造并模拟交易,钱包估算费用和让用户签名;交易广播到节点后进入 mempool,被验证者打包进区块,再等待若干确认。
- 期间可能被拒签、替换、丢弃、revert 或链重组。UI 应使用状态机展示“等待签名、已广播、已确认、失败”,并用 hash 支持恢复查询。
Q6: Gas 和 EIP-1559 费用怎么理解?
答案:
Gas 衡量执行和存储消耗,实际费用由使用量与每单位价格决定。EIP-1559 引入基础费和优先费,用户设置最大愿付费用,未用完的上限不会简单全部收走。
估算可能因链上状态和合约分支变化失败,前端要展示估算、余额和失败原因。
- Gas 衡量 EVM 执行和存储资源;用户支付的费用约为
gasUsed × effectiveGasPrice。EIP-1559 下费用由会销毁的 base fee 与给验证者的 priority fee 组成。 maxFeePerGas是用户上限,不代表一定花满;估算前应先模拟,并区分执行失败消耗的 Gas 与钱包余额不足。
Q7: Nonce 有什么作用?
答案:
Nonce 是每个地址的交易序号计数器,从 0 开始,每发送一笔交易递增 1。它有两个核心作用:
- 防止重放攻击:同一个签名的交易不能被重复执行
- 保证交易顺序:节点按 nonce 顺序处理交易
如果 nonce 不连续(比如跳过了 nonce=5 直接发 nonce=6),后面的交易会在 mempool 中等待,直到 nonce=5 的交易被执行或超时。
Q8: 普通消息签名和交易签名有什么区别?
答案:
交易签名授权链上状态变更并可能花费资产;消息签名证明账户控制权或表达某项链下意图,不直接发送交易。
消息同样可能授权登录、订单或资产操作,必须展示域名、链、nonce、过期和清晰语义,不能让用户盲签乱码。
- 交易签名包含链 ID、Nonce、目标、金额、Gas 和 calldata,广播后会改变链上状态;消息签名只证明地址对某段字节签过名。
- 消息签名也可能授权登录、订单或 Permit,不能当作无害操作。UI 要展示域、用途、过期时间和 Nonce,避免盲签。
Q9: EIP-712 解决什么问题?
答案:
它定义结构化数据签名,把域和字段编码成可验证摘要,让钱包更可能向用户展示可读内容,并通过 domain separator 限定应用、链和合约上下文。
它改善可读性和重放边界,但业务仍要校验 nonce、deadline 和具体字段。
- EIP-712 对 domain 和结构化字段做类型化哈希,钱包能展示可读数据,并用 chainId、verifyingContract 等域信息防止跨应用重放。
- 前后端必须使用完全一致的类型顺序和值编码;还要包含业务 Nonce 和 deadline,服务端验证签名者、域和是否已使用。
Q10: SIWE 登录流程是什么?
答案:
服务端生成一次性 nonce,前端构造带域名、URI、链和时间的登录消息,用户签名,服务端恢复地址并校验所有字段后建立普通会话。
Nonce 必须一次性并过期,防止重放;登录后的授权仍由服务端会话和权限系统完成。
- 服务端生成包含域名、URI、chainId、Nonce、签发/过期时间的 SIWE 消息,钱包签名后服务端恢复地址并核对所有字段,再创建普通会话。
- Nonce 必须一次性且短期有效,验证成功后立即消费;登录后的权限仍由服务端控制,不能只信客户端传来的地址。
Q11: ABI 在合约交互中有什么作用?
答案:
ABI 描述函数、事件和参数类型,SDK 根据它编码 calldata、解码返回值和日志。
ABI 必须与目标合约和代理实现兼容。前端不能因为类型生成成功就假设调用一定成功,仍要模拟、处理 revert 和链错误。
- ABI 描述函数、事件、参数和错误类型,客户端据此编码 calldata、解码返回值与日志。合约地址相同但 ABI 错误会导致调用失败或错误解释数据。
- 代理合约还涉及实现升级和 ABI 版本。前端应把地址、链 ID、ABI 与发布版本绑定,并对自定义 error 做可读映射。
Q12: approve 和 allowance 有什么安全风险?
答案:
ERC-20 approve 授予 spender 在额度内转走代币的权力,无限授权会扩大合约被攻击或地址配置错误的损失。
前端应显示资产、spender 和额度,优先最小授权并提供管理入口;不要把“授权”文案伪装成普通登录。
- ERC-20 的
approve允许 spender 在额度内转走资产,无限授权会把未来余额也暴露给合约;授权从非零改非零还存在竞态风险。 - 优先精确额度、先模拟并展示 spender,支持撤销;可用 Permit 减少交易,但签名本身仍是授权,必须校验域、Nonce 和截止时间。
Q13: 为什么交易前要模拟?
答案:
模拟在当前链状态下执行调用,可提前发现 revert、估算结果和部分资产变化,减少用户支付失败 Gas 或签署危险交易。
模拟结果不是未来保证,因为状态会变化,也不能覆盖所有恶意合约行为。仍需地址、参数和风险提示。
eth_call或专用模拟服务在指定区块状态上执行交易但不落链,可提前发现 revert、余额不足和滑点,并解码返回与事件。- 模拟结果不是最终保证,因为从模拟到打包期间状态会变化。关键参数仍要设 minOut、deadline 等链上保护,UI 明确模拟区块与限制。
Q14: 事件日志如何用于前端数据查询?
答案:
合约通过 event 写入可索引日志,前端或索引服务按区块和 topic 查询,再构建业务视图。
日志不是合约可直接读取的存储,且会受重组影响。索引器需要确认数、去重、回滚和断点续扫。
- 合约用 event 把可索引主题和数据写入日志,前端可按地址、topic 和区块范围查询,再用 ABI 解码。日志适合历史事件,不等于当前状态数据库。
- 大范围 RPC 查询容易限流,应使用 Indexer/Subgraph,并按 blockNumber、transactionIndex、logIndex 唯一排序;链重组时还要回滚未最终确认日志。
Q15: 为什么链上数据需要确认数?
答案:
新区块可能因共识重组被替换,确认越多,交易被回滚的概率通常越低。不同链和业务金额需要不同最终性标准。
UI 可以先展示“已上链/确认中”,高价值业务等待足够确认后再发放不可逆权益。
- 新区块可能因分叉被替换,交易进入一个区块只代表暂时被包含。等待更多后续区块会降低被重组掉的概率。
- 所需确认数取决于链的最终性和业务价值;UI 可以先显示“已上链”,达到阈值后再标记“最终确认”,同时监听 replaced/reorg。
Q16: Viem/Wagmi 相比直接调用 Provider 有什么价值?
答案:
Viem 提供类型化的链和合约客户端;Wagmi 在 React 中管理连接、查询、缓存和交易状态,减少重复边界代码。
框架不能替代对链、签名和错误的理解。配置链、Transport、缓存键和账户变化仍需正确处理。
- Viem 提供类型安全的 RPC、ABI 推导和客户端分层;Wagmi 在其上封装 React hooks、钱包连接、缓存和状态同步。
- 相比直接调用注入 Provider,它们统一多钱包、多链、错误与请求生命周期。但关键交易参数和状态机仍需业务显式设计,不能完全依赖 hooks 默认行为。
Q17: ERC-20、ERC-721 和 ERC-1155 的区别是什么?
答案:
| 对比维度 | ERC-721 | ERC-1155 |
|---|---|---|
| 每个合约 | 只能管理一种 NFT 集合 | 可以管理多种代币(FT + NFT 混合) |
| 批量转账 | 不支持,每次只能转一个 NFT | 原生支持 safeBatchTransferFrom |
| Gas 消耗 | 转 10 个 NFT 需要 10 笔交易 | 批量转 10 个只需 1 笔交易 |
| 元数据 | 每个 token 独立的 tokenURI | 通过 URI 模板 {id} 替换统一管理 |
| 适用场景 | 每个代币有独立身份的场景(艺术品、PFP) | 游戏道具、门票等需要混合管理的场景 |
Q18: 什么是账户抽象?
答案:
账户抽象让账户验证逻辑可编程,可支持多签、社交恢复、批量操作、会话密钥和代付 Gas,改善钱包体验。
它引入 Bundler、Paymaster、EntryPoint/智能账户等组件及新的信任边界,前端要解释失败和费用来源。
- 账户抽象让智能账户自定义签名验证、批量调用、社交恢复、Session Key 和 Gas 代付,改善助记词与原生币门槛。
- 实现上通常涉及 UserOperation、Bundler、Paymaster 和 EntryPoint。前端要展示代付边界、部署状态和失败原因,并防止 Session Key 权限过大。
Q19: 多链 DApp 的主要难点是什么?
答案:
同一资产/合约在不同链地址和最终性不同,RPC、Gas 代币、区块浏览器和桥接风险也不同。
配置要按 chain ID 隔离,交易前强校验目标链;不要只靠名称判断资产。跨链是异步多阶段流程,需要状态跟踪和恢复。
- 要管理 chainId、地址、RPC、区块时间、确认策略、原生币和合约版本;同一资产在不同链也可能是桥接表示。
- 切链前校验钱包支持,所有缓存键带 chainId,交易链接按链生成。跨链流程是多阶段异步状态机,还要处理桥接最终性和目标链失败。
Q20: DApp 前端最重要的安全原则有哪些?
答案:
不接触私钥,签名前展示真实域、链、合约、方法、资产和额度;合约地址来自可信配置;防 XSS、依赖投毒和钓鱼替换。
交易模拟、允许列表和风险提示是辅助,不能承诺绝对安全。高风险操作支持硬件钱包、撤销和异常监控。
- 永远不接触私钥或助记词;签名和交易前把合约、方法、资产、额度、链和预期结果清楚展示,并默认最小授权。
- 依赖与前端部署要防供应链和域名劫持,关键配置签名或校验;所有输入不可信,交易先模拟,异常链/地址和高价值操作增加二次确认。
Q21: Web2 和 Web3 的核心区别是什么?
答案:
Web2 是平台拥有数据的读写互联网,用户创造内容但平台掌握数据所有权和分发权(如微信文章、抖音视频)。Web3 是用户拥有数据和资产的去中心化互联网,通过区块链技术实现:
- 身份:Web2 用邮箱/手机号登录(依赖平台),Web3 用钱包签名(自主身份)
- 数据:Web2 数据存在中心化数据库,Web3 数据存在区块链/IPFS 上
- 资产:Web2 中你的"资产"(如游戏装备)实际上归平台所有,Web3 中链上资产真正属于你
- 信任:Web2 依赖平台信誉,Web3 依赖代码和密码学("Code is Law")
Q22: 区块链为什么不可篡改?
答案:
区块链的不可篡改性源于哈希链接结构和共识机制:
- 哈希链接:每个区块头包含上一个区块头的哈希(
parentHash)。篡改任一区块的数据会导致其哈希变化,从而使后续所有区块的parentHash失效,需要重新计算整条链 - 共识机制:在 PoS 中,攻击者需要控制全网 2/3 以上的质押 ETH 才能篡改已最终确认的区块,这在经济上几乎不可行
- 分布式验证:全球数千个节点同时存储和验证数据,单点篡改会被其他节点拒绝
Q23: 钱包的本质是什么?它实际存储了什么?
答案:
钱包本质是一个密钥管理器,它存储的是私钥(或助记词),而不是加密货币或 Token。资产始终记录在区块链上,钱包只是用私钥证明"我有权操作这个地址上的资产"。类比:钱包是银行保险箱的钥匙,不是保险箱本身。
Q24: Ethers.js 中 Provider 和 Signer 的区别是什么?
答案:
- Provider 是区块链的只读连接,不关联任何账户,用于查询数据(余额、区块、合约 view 方法等)
- Signer 关联一个以太坊账户(拥有私钥),可以签名消息和发送交易
- 创建合约实例时传入 Provider 只能调用
view/pure方法;传入 Signer 才能调用状态修改方法 BrowserProvider.getSigner()从钱包获取 Signer,JsonRpcProvider本身就是 Provider
Q25: DApp 如何维护钱包连接状态的一致性?
答案:
钱包状态至少包括账号、链、连接器和授权状态,不能只在首次连接时读取一次。前端要订阅 Provider 的 accountsChanged、chainChanged 和连接器生命周期事件,并在页面恢复、网络切换后重新校验。
实践要点:
- 区分“曾经选择过连接器”和“当前钱包仍授权”,自动重连失败时回到可操作 UI。
- 请求发送前再次确认账号与 Chain ID。
- 切链、签名和交易要处理用户拒绝、钱包锁定和请求并发。
- 多标签页可共享提示状态,但最终以 Provider 返回结果为准。
- 清理旧 Provider 监听,避免切换连接器后重复响应。
Wagmi 等库能封装状态机,但业务仍要设计加载、错误和链不匹配状态。
Q26: 合约函数选择器和 Calldata 是怎样编码的?
答案:
外部函数调用的 Calldata 通常由 4 字节函数选择器和 ABI 编码参数组成。选择器是规范化函数签名(如 transfer(address,uint256))的 Keccak-256 哈希前 4 字节。
静态类型按 32 字节槽编码;字符串、bytes 和动态数组通过偏移指向后续动态区域。前端一般交给 Viem/Ethers 等库编码,不应手工拼十六进制。
选择器只有 4 字节,理论上可能碰撞;合约审计和代理路由要考虑冲突。调试交易失败时,先用 ABI 解码 Calldata,确认地址、单位、链和函数重载。
Q27: EIP-1559 交易的实际 Gas 费如何计算?
答案:
实际每单位 Gas 的价格为:
effectiveGasPrice = min(maxFeePerGas, baseFee + maxPriorityFeePerGas)
其中 baseFee 由协议根据上一个区块的 Gas 使用率自动调整,maxPriorityFeePerGas 是给验证者的小费,maxFeePerGas 是用户设定的上限。总费用 = effectiveGasPrice × gasUsed,多付的部分自动退还。
Q28: Permit 相比 approve 有什么价值和边界?
答案:
Permit 让持币人通过链下签名授权额度,第三方可把签名提交到链上,常用于减少一次独立 approve 交易和改善 Gas/交互体验。
它并没有消除授权风险:
- 签名必须绑定 owner、spender、value、nonce、deadline、chainId 和验证合约等域信息。
- 不同 Token 的 Permit 标准和实现可能不同,前端不能假设全部支持。
- 无限额度和长期 deadline 仍然危险。
- 用户看到的是签名而非转账交易,更需要清晰展示授权对象、金额和过期时间。
- 服务端/Relayer 要防重放并处理签名已使用、过期和链切换。
Q29: 什么是盲签?为什么它是 DApp 安全的核心问题?
答案:
盲签是指用户在不理解签名内容的情况下确认签名操作。在早期钱包中,签名请求通常只显示一串十六进制哈希,用户无法判断自己在授权什么操作。
盲签是 DApp 安全的核心问题,因为:
- 不可逆性:链上操作一旦执行无法撤回
- 资产直接暴露:签名可能直接导致资产被转移
- 用户认知不足:大多数用户不具备解读原始签名数据的能力
EIP-712 通过结构化、人类可读的签名格式来缓解盲签问题,钱包可以清晰展示签名的目标合约、链 ID、操作类型和具体参数。
Q30: 什么是 Chain ID?为什么它很重要?
答案:
Chain ID 是区块链网络的唯一数字标识符(如以太坊主网是 1,Polygon 是 137)。它被写入交易签名中(EIP-155),用于防止跨链重放攻击。如果没有 Chain ID,在以太坊主网上签署的交易可以被拿到另一条 EVM 兼容链上重放执行。前端在发送交易前必须校验当前 Chain ID 是否与目标链一致。
Q31: MPC 钱包和传统 HD 钱包有什么区别?
答案:
传统 HD(Hierarchical Deterministic)钱包基于一套助记词派生所有密钥,私钥完整存在于用户设备。MPC 钱包使用分布式密钥生成(DKG),私钥从始至终不以完整形式存在于任何单一设备。核心区别在于:
- 密钥存在形式:HD 钱包有完整私钥;MPC 只有分片
- 安全模型:HD 是单点风险(私钥/助记词泄露即完);MPC 是阈值安全(需攻破多方)
- 恢复机制:HD 依赖助记词;MPC 支持社交恢复或云备份分片
Q32: 前端查询链上数据有哪些方式?
答案:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| RPC | 实时性最高 | 查询能力有限 | 余额、allowance |
| The Graph | 复杂查询、分页排序 | 有索引延迟 | 历史数据、统计 |
| Etherscan API | 开箱即用 | 中心化、速率限制 | 交易历史、合约 ABI |
| Alchemy / Moralis | API 丰富 | 付费、厂商锁定 | NFT、Token 数据 |
Q33: ENS 的正向解析和反向解析有什么区别?
答案:
- 正向解析:将 ENS 名称(如
vitalik.eth)解析为以太坊地址。用于转账场景,用户输入域名代替地址 - 反向解析:将以太坊地址解析为 ENS 名称。用于显示场景,让界面显示人类可读的名称而非长串地址
关键区别在于:正向解析只要域名所有者设置了地址记录即可生效;而反向解析需要地址所有者主动设置 Primary Name,且设置的名称必须正向解析回同一个地址(防止冒充)。
Q34: 前端如何与智能合约交互?
答案:
前端通过 JSON-RPC 协议与区块链节点通信,通常借助 ethers.js 或 viem 等库封装底层调用。核心流程:
- 通过
window.ethereum(钱包注入的 Provider)或 RPC URL 创建 Provider - 用合约地址 + ABI 创建合约实例
- 调用合约方法:读操作直接返回结果,写操作返回交易对象,需要等待确认
Q35: CREATE 和 CREATE2 生成合约地址的方式有什么区别?
答案:
CREATE 的地址主要由部署者地址和部署 nonce 推导;CREATE2 由部署者、salt 和初始化代码 hash 推导,因此可以在部署前计算地址,适合确定性部署和部分反事实账户场景。
使用 CREATE2 要注意:
- 初始化代码或构造参数变化会改变地址。
- 同一部署者、salt 和 init code 组合不能在已有代码的地址上再次部署。
- 地址可预测不代表该地址上的代码可信,前端仍要校验链、部署工厂和字节码。
- 代理地址稳定也不代表实现逻辑不变,还要展示升级与权限风险。
前端应使用经过验证的 ABI 编码和 hash 工具,不要手写字符串拼接。
Q36: accountsChanged 和 chainChanged 事件应该怎样处理?
答案:
accountsChanged 表示钱包向当前站点暴露的账号列表变化;数组为空通常意味着授权断开或钱包锁定。chainChanged 表示当前链变化,Chain ID 通常是十六进制字符串,需要规范化后比较。
处理原则:
- 取消或作废依赖旧账号、旧链的未完成请求和缓存。
- 重新读取余额、授权、合约地址和功能开关,不只修改页面文本。
- 新链不受支持时给出切链或添加网络操作,但不无限弹钱包请求。
- 监听器只注册一次,并在连接器更换或卸载时移除。
- 发交易前仍再次读取实际链和账号,事件不是唯一安全校验。
Q37: Solidity Custom Error 相比字符串 revert 有什么优势?前端怎样解析?
答案:
Custom Error 用错误选择器和 ABI 编码参数表达失败,比长字符串通常更节省部署与执行 Gas,还能携带结构化上下文:
error InsufficientBalance(uint256 available, uint256 required);
前端需要包含对应 Error 定义的 ABI,才能把 revert data 解码为错误名和参数。RPC、钱包或合约包装层可能把数据嵌套在不同错误字段中,因此解析要做兼容和回退。
对用户展示时把技术错误映射成可行动文案,未知错误保留交易哈希、链 ID 和原始错误用于排查,不把内部堆栈或敏感信息直接暴露。
Q38: Wagmi 的 Config 包含哪些核心配置?
答案:
createConfig 的三个核心配置:
- chains:应用支持的区块链列表(如 mainnet、polygon)
- transports:每条链的 RPC 传输方式(
http()、webSocket()) - connectors:钱包连接器(
injected、walletConnect、coinbaseWallet)
此外还支持 ssr(服务端渲染)、multiInjectedProviderDiscovery(自动发现注入式钱包)等可选配置。
Q39: 合约的 call 和 transaction 有什么区别?
答案:
| 特性 | Call(读) | Transaction(写) |
|---|---|---|
| Gas 消耗 | 无 | 有 |
| 钱包签名 | 不需要 | 需要 |
| 改变链上状态 | 不能 | 能 |
| 返回值 | 直接返回函数结果 | 返回交易哈希(tx.hash) |
| 等待时间 | 即时 | 需等区块确认 |
在 ethers.js 中,调用 view/pure 函数会自动使用 call,调用其他函数会自动发送 transaction。也可以用 contract.transfer.staticCall(...) 强制以 call 模式模拟写操作,用于预检交易是否会成功。
Q40: Base Fee 是如何调整的?
答案:
以太坊每个区块有一个目标 Gas 用量(Gas limit 的 50%)。如果实际用量超过 50%,Base Fee 上涨(最多 +12.5%);低于 50% 则下降(最多 -12.5%)。Base Fee 不归验证者所有,而是被直接销毁(burn),这也是 ETH 通缩的来源之一。
Q41: ERC-165 怎样用于检测合约支持的接口?
答案:
ERC-165 通过 supportsInterface(bytes4 interfaceId) 让合约声明是否支持某个接口。接口 ID 通常由接口中函数选择器异或得到,NFT 应用可用它检测 ERC-721、ERC-1155 及扩展接口。
前端调用前仍要处理:
- 目标地址可能不是合约。
- 老合约或非标准实现可能不支持 ERC-165。
- 恶意合约可以虚假声明,接口检测不能替代交易模拟和业务校验。
- RPC 调用可能失败,应提供未知状态而不是直接判定“不支持”。
Q42: eth_sign、personal_sign 和 eth_signTypedData_v4 有什么区别?
答案:
eth_sign:对任意哈希签名,攻击者可构造任何交易哈希让用户签名,极度危险。MetaMask 已默认禁用personal_sign:签名前自动添加"\x19Ethereum Signed Message:\n"前缀,防止签名被冒充为交易。适用于登录验证等场景eth_signTypedData_v4:EIP-712 结构化签名,包含 domain 信息和类型化数据,钱包可解析并展示人类可读内容。是最安全的签名方式
推荐:所有涉及链上操作的签名都使用 eth_signTypedData_v4,消息验证(如登录)使用 personal_sign,完全禁用 eth_sign。
Q43: wallet_switchEthereumChain 和 wallet_addEthereumChain 的区别是什么?
答案:
wallet_switchEthereumChain:请求钱包切换到已存在的链。只需传入chainId参数。如果钱包中没有该链配置,会抛出错误码 4902。wallet_addEthereumChain:请求钱包添加一条新链(并切换到该链)。需要传入完整的链配置:chainId、chainName、rpcUrls、nativeCurrency、blockExplorerUrls。
标准做法是先尝试 switch,捕获到 4902 错误后再调用 add。
Q44: 什么是 ERC-4337?它解决了什么问题?
答案:
ERC-4337 是以太坊的账户抽象标准,将用户账户从 EOA 升级为智能合约钱包。它引入了一套链下 + 链上的基础设施(UserOperation → Bundler → EntryPoint → Smart Account),在不修改以太坊协议的前提下实现了:
- Gas 代付:Paymaster 代付 Gas,用户无需持有 ETH
- 批量交易:一次签名执行多笔操作
- 自定义签名验证:支持多签、社交恢复、生物识别等
- 会话密钥:临时授权,减少签名确认次数
Q45: 什么是 Multicall?解决了什么问题?
答案:
Multicall 是一个部署在链上的聚合合约,它允许在一次 RPC 请求中批量执行多个合约的只读调用。核心解决的问题是 N 次 RPC 请求合并为 1 次,减少网络往返和 RPC 配额消耗。典型场景包括批量查询多个地址的 Token 余额、加载仪表盘数据等。viem 默认内置了 Multicall 支持,调用多个 readContract 时会自动聚合。
Q46: ENS 头像支持哪些格式?如何解析?
答案:
ENS 头像支持三种格式:
- HTTPS URL:直接可用,如
https://example.com/avatar.png - IPFS URI:需要通过 IPFS 网关转换,如
ipfs://QmHash->https://gateway.pinata.cloud/ipfs/QmHash - NFT URI:如
eip155:1/erc721:0xBC4C.../1234,需要调用 NFT 合约的tokenURI方法获取元数据,再从元数据中提取image字段
实际开发中,ethers.js 和 viem 内置了完整的头像解析逻辑,建议直接使用 resolver.getAvatar() 或 getEnsAvatar()。
Q47: 交易回执中的 status、logs 和区块确认分别表示什么?
答案:
回执 status 表示交易执行成功还是回滚;即使失败,交易也可能已经上链并消耗 Gas。logs 是成功执行路径产生的事件日志,前端可按 ABI 解析,但不能把“得到交易 hash”当成已经得到日志。
区块确认表示包含该交易的区块后又追加了多少区块,用于降低链重组的不确定性。确认数取决于链、金额和业务风险,不应写死通用数字。
前端状态通常分为签名中、已广播、待打包、已上链待确认、成功或失败。超时只表示暂时没观察到结果,不能直接判定链上失败;应允许按 hash 恢复查询。
Q48: 为什么 Gas 估算会失败或与实际消耗不同?
答案:
eth_estimateGas 会在当前节点状态和指定交易参数下模拟执行。余额不足、授权缺失、条件会 revert、from/value/chain 错误或节点状态不同,都可能让估算失败。
估算值也不是最终费用保证:交易上链前状态可能变化,动态存储写入、外部调用和区块环境会影响实际 Gas。钱包通常会在估算上增加安全余量,但不能用无限 Gas 掩盖逻辑错误。
前端应先模拟并解析 revert,明确展示 Max Fee 与可能实际费用;合约逻辑本身要设置业务上限,防止不可控循环或外部调用放大成本。
Q49: 签名如何防止跨链、跨合约和重复使用?
答案:
优先使用 EIP-712 结构化签名,并把验证域和消息约束完整:
- Domain 包含 chainId、verifyingContract、name/version。
- Message 包含用户、动作、业务对象、nonce、deadline 和必要金额。
- 合约或服务端原子消费 nonce,拒绝重复签名。
- 过期时间限制泄漏后的利用窗口。
- UI 展示人类可理解的真实授权内容。
personal_sign 只有消息前缀,业务域需要自行编码且更难让钱包清晰展示。签名本身不说明调用方有权执行所有操作,验证端仍要检查当前状态和权限。
Q50: Ethers.js 和 Viem 该如何选择?
答案:
| 场景 | 推荐 |
|---|---|
| 新项目,使用 wagmi | Viem(wagmi 底层就是 Viem) |
| 注重 TypeScript 类型安全 | Viem(ABI 类型自动推断) |
| 已有项目使用 Ethers.js | Ethers.js(迁移成本高) |
| 快速原型、学习阶段 | Ethers.js(文档更丰富、社区示例更多) |
| 关注包体积 | Viem(~35KB vs ~120KB) |
Q51: useReadContract 和 useWriteContract 的区别是什么?
答案:
| 特性 | useReadContract | useWriteContract |
|---|---|---|
| 调用类型 | view / pure 函数 | 状态修改函数 |
| Gas 消耗 | 不消耗 | 消耗 Gas |
| 钱包签名 | 不需要 | 需要用户签名确认 |
| 返回值 | 合约函数的返回值 | 交易哈希 |
| 底层 | TanStack Query | Mutation |
| 缓存 | 自动缓存 | 无缓存(每次是新交易) |
Q52: 解释 ERC-20 的 approve + transferFrom 流程,为什么不直接用 transfer?
答案:
transfer 只能由 Token 持有者自己调用。但在 DEX 场景中,Swap 合约需要代替用户转移 Token,这就需要两步授权机制:
- 用户调用
approve(spenderAddress, amount):授权 DEX 合约可以从自己账户转移最多amount数量的 Token - DEX 合约内部调用
transferFrom(userAddress, dexAddress, amount):DEX 代替用户完成转移
这种设计将"授权"和"执行"分离,用户始终保持对 Token 的控制权,只有被授权的合约才能转移用户的 Token。
Q53: 以太坊中如何替换或取消一笔 Pending 交易?
答案:
同一账户在同一链上,可以发送一笔使用相同 nonce、费用更有竞争力的新交易。节点和打包者接受替代后,最终只会有一笔该 nonce 的交易被确认。
“加速”通常保留原目标和数据但提高费用;“取消”通常向自己发送 0 ETH 的空交易并使用相同 nonce。取消不是协议级撤回保证:原交易若已经被打包、替代费用不足或私有交易通道不同,都可能失败。
前端要从 Pending 队列选择正确 nonce,按 EIP-1559 调整费用,并在任一交易确认后把同 nonce 的其他候选标为被替代。
Q54: 前端处理 ERC-20 代币余额时,为什么不能直接用 Number 类型?
答案:
两个原因:
- 精度丢失:JavaScript 的
Number是 IEEE 754 双精度浮点数,最大安全整数是2^53 - 1(约 9000 万亿)。而 ERC-20 余额的最小单位是uint256,18 位小数的代币中 1 个代币就是10^18,远超Number.MAX_SAFE_INTEGER - 浮点精度问题:
0.1 + 0.2 !== 0.3,在金额计算中不可接受
正确做法是始终使用 BigInt 处理链上原始值,只在展示时使用 ethers.formatUnits 转为字符串。
Q55: Permit 钓鱼是如何实现的?为什么特别难防范?
答案:
Permit(ERC-2612)允许用户通过离线签名授权代币转移,无需链上 approve 交易。攻击者利用这一点:
- 创建钓鱼页面诱导用户签署 Permit 签名
- 获取用户签名后,攻击者自行提交到链上
- 调用
transferFrom转走用户代币
特别难防范的原因:
- Permit 是离线操作,不产生链上交易,区块浏览器无记录
- revoke.cash 等工具无法检测已签署但未提交的 Permit
- 攻击者可以选择最佳时机提交签名
- 签名本身看起来可能像正常操作
防护手段:仔细检查 EIP-712 签名中的 spender 地址、value 金额和 deadline 时间。
Q56: 如何在前端管理多链的合约地址?
答案:
推荐使用合约地址映射表:以 Chain ID 为 key,合约名称为二级 key,地址为值。配合一个 getContractAddress(chainId, name) 工具函数,在运行时根据当前链动态获取合约地址。对于使用 Wagmi 的项目,也可以利用 Viem 的 multicall 和 Wagmi 的 useReadContract 直接传入对应链的地址。所有地址应集中管理在一个配置文件中,避免硬编码分散在各组件里。
Q57: Bundler 在 ERC-4337 中扮演什么角色?
答案:
Bundler 是 ERC-4337 架构中的链下角色,类似于传统以太坊的矿工/验证者。它的职责包括:
- 收集 UserOperation:维护一个 UserOp 内存池(类似 Mempool)
- 打包交易:将多个 UserOp 打包为一笔真实的以太坊交易
- 提交上链:调用 EntryPoint 合约的
handleOps方法执行所有 UserOp - Gas 估算:模拟执行 UserOp 以估算 Gas
Bundler 的经济激励来源于 UserOp 中的 Gas 费差价。
Q58: The Graph 的 Subgraph 是如何工作的?
答案:
开发者在 subgraph.yaml 中声明监听的合约和事件,在 schema.graphql 中定义实体模型,在 mapping.ts(AssemblyScript)中编写事件到实体的映射逻辑。部署后,Graph Node 持续监听链上新区块,执行 Mapping 处理器将数据写入 PostgreSQL,前端通过 GraphQL API 查询,支持分页、排序、过滤等复杂操作。
Q59: DApp 中如何优雅展示用户地址?
答案:
遵循优先级策略:
- 优先显示 ENS 名称(如
vitalik.eth) - 无 ENS 时截断地址(如
0xd8dA...6045),保留前后各 4 位 - 头像使用 ENS Avatar,无头像时使用 Blockie 或 Jazzicon 生成确定性头像
- 鼠标悬停时显示完整地址(tooltip)
- 点击地址可复制到剪贴板
Q60: 智能合约钱包相比 EOA 能提供哪些能力?
答案:
智能合约钱包把授权规则写进合约,可支持多签、社交恢复、批量调用、限额、会话密钥、Gas 代付和可编程验证;EOA 主要由单个私钥直接控制并签署协议交易。
代价是合约漏洞、升级与治理风险、部署/执行 Gas、链上兼容和恢复流程复杂。账户抽象可以改善 User Operation、Bundler 与 Paymaster 体验,但最终安全仍取决于钱包实现和验证策略。
前端必须识别签名与交易能力差异,不能假设所有账户都支持同一种 personal_sign、nonce 或 Gas 支付流程。