Next.js 服务端动作在 DApp 中的应用:交易构建、签名中继与安全边界
Next.js 服务端动作在 DApp 中的应用交易构建、签名中继与安全边界一、Server Actions 为什么是 DApp 前端的范式转移传统的 DApp 前端架构遵循链上一切原则用户在浏览器中签名交易通过 MetaMask 注入的 provider 直接广播到节点。这套模式简单直接但也暴露了明显的问题——私钥管理分散在客户端签名逻辑暴露在浏览器环境中复杂的交易构建如多步 DeFi 操作在前端完成导致代码臃肿且难以调试。Next.js 的 Server Actions 为 DApp 带来了一个新的选项将交易构建、Gas 估算、签名中继等逻辑移到服务端。服务端持有应用级私钥或通过 MPC/TSS 管理前端只负责用户意图的收集和 EIP-712 签名授权的获取。这种架构转变不是要替代用户签名而是重新划分客户端与服务端的职责边界。二、服务端交易中继的架构设计sequenceDiagram participant Browser as 浏览器端 participant SA as Server Action participant Bundler as 交易打包服务 participant SC as 智能合约 participant AAWallet as AA 钱包/中继器 Browser-SA: 1. 提交交易意图 EIP-712 授权签名 SA-SA: 2. 验证签名来源与权限 SA-SA: 3. 构建交易 calldata Gas 估算 SA-Bundler: 4. 提交 UserOperation Bundler-SC: 5. handleOps 批量执行 SC--Bundler: 6. 执行结果 Bundler--SA: 7. UserOpReceipt SA--Browser: 8. 交易哈希 回执Server Action 在 DApp 中的核心价值在于三个环节交易构建的复杂度隔离。一个涉及 AMM 套利的交易需要先查询多个池子的储备量计算最优路径构造多跳 swap 的 calldata。这些逻辑放在前端不仅增加 bundle 体积还需要暴露 RPC endpoint。迁移到 Server Action 后计算在服务端完成前端只需传入用户的 swap 意图。Gas 策略的集中管理。服务端可以监控各链的 Gas 价格按区块空间需求自动选择最优的 Gas 参数maxFeePerGas、maxPriorityFeePerGas。结合 EIP-1559 的 baseFee 预测可以显著降低用户在 Gas 高峰期的额外支出。签名的责任分离。用户的 EOA 签名只用于授权意图EIP-712 typed data实际的交易由服务端的中继器账户提交。这种分离意味着用户的钱包不需要持有 Gas Token可以由中继器代为支付后从交易中结算。三、实现安全的服务端交易中继器以下实现展示如何在 Next.js Server Action 中构建一个生产级交易中继包含签名验证、nonce 管理和 Gas 估算。// app/actions/relayTransaction.ts use server; import { ethers } from ethers; import { createPublicClient, http, parseEther } from viem; import { base } from viem/chains; import { z } from zod; // 多链 Provider 映射 const chainProviders: Recordnumber, ethers.JsonRpcProvider { 8453: new ethers.JsonRpcProvider(process.env.BASE_RPC_URL), 1: new ethers.JsonRpcProvider(process.env.ETHEREUM_RPC_URL), 42161: new ethers.JsonRpcProvider(process.env.ARBITRUM_RPC_URL), }; /** * 中继器钱包 * 设计决策使用环境变量管理私钥生产环境应迁移到 KMS/HSM 或 MPC 方案 * 中继器钱包需要持有各链 Gas Token余额监控由独立定时任务负责 */ const relayWallet new ethers.Wallet( process.env.RELAYER_PRIVATE_KEY!, chainProviders[8453] ); // 输入验证 Schema const RelayRequestSchema z.object({ chainId: z.number().int().positive(), to: z.string().regex(/^0x[a-fA-F0-9]{40}$/), data: z.string().regex(/^0x[a-fA-F0-9]*$/), value: z.string().regex(/^\d$/).default(0), userAddress: z.string().regex(/^0x[a-fA-F0-9]{40}$/), // 用户对交易意图的 EIP-712 签名 userSignature: z.string().regex(/^0x[a-fA-F0-9]$/), deadline: z.number().int().positive(), }); // EIP-712 域定义 const DOMAIN { name: DAppRelayer, version: 1, chainId: 8453, verifyingContract: process.env.NEXT_PUBLIC_ENTRYPOINT_ADDRESS! as 0x${string}, }; const RELAY_TYPES { RelayRequest: [ { name: chainId, type: uint256 }, { name: to, type: address }, { name: data, type: bytes }, { name: value, type: uint256 }, { name: userAddress, type: address }, { name: deadline, type: uint256 }, ], } as const; // Nonce 管理器 const pendingNonces new Mapstring, number(); export async function relayTransaction(formData: FormData) { const raw Object.fromEntries(formData.entries()); // 严格校验输入防止注入攻击 const parsed RelayRequestSchema.safeParse({ chainId: Number(raw.chainId), to: raw.to, data: raw.data, value: raw.value || 0, userAddress: raw.userAddress, userSignature: raw.userSignature, deadline: Number(raw.deadline), }); if (!parsed.success) { return { error: Invalid input, details: parsed.error.flatten() }; } const { chainId, to, data, value, userAddress, userSignature, deadline } parsed.data; // 检查 deadline 是否已过期 if (Date.now() / 1000 deadline) { return { error: Request expired }; } // 验证 EIP-712 签名 const recoveredAddress ethers.verifyTypedData( DOMAIN, RELAY_TYPES, { chainId, to, data, value, userAddress, deadline }, userSignature ); if (recoveredAddress.toLowerCase() ! userAddress.toLowerCase()) { return { error: Invalid signature }; } // Nonce 管理 const provider chainProviders[chainId]; if (!provider) { return { error: Unsupported chain: ${chainId} }; } const relayAddr relayWallet.address; const currentNonce pendingNonces.get(relayAddr) ?? await provider.getTransactionCount(relayAddr); try { // Gas 估算 const gasLimit await provider.estimateGas({ from: relayAddr, to, data, value: parseEther(value), }); const feeData await provider.getFeeData(); // 发送中继交易 const tx await relayWallet.sendTransaction({ to, data, value: parseEther(value), nonce: currentNonce, gasLimit: (gasLimit * 120n) / 100n, // 20% buffer maxFeePerGas: feeData.maxFeePerGas ?? undefined, maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? undefined, chainId, }); pendingNonces.set(relayAddr, currentNonce 1); return { success: true, txHash: tx.hash, }; } catch (err) { const message err instanceof Error ? err.message : Unknown error; // 如果 nonce 冲突清除缓存让下次请求重新查询 if (message.includes(nonce)) { pendingNonces.delete(relayAddr); } return { error: message }; } }前端调用 Server Action 的方式// components/RelayForm.tsx use client; import { relayTransaction } from /app/actions/relayTransaction; import { useAccount, useSignTypedData } from wagmi; import { DOMAIN, RELAY_TYPES } from /lib/constants; export function SwapRelayForm({ to, data, value }: { to: string; data: string; value: string; }) { const { address } useAccount(); const { signTypedDataAsync } useSignTypedData(); const handleRelay async () { if (!address) return; const deadline Math.floor(Date.now() / 1000) 300; // 5 分钟有效期 // 用户对交易意图进行 EIP-712 签名 const signature await signTypedDataAsync({ domain: DOMAIN, types: RELAY_TYPES, primaryType: RelayRequest, message: { chainId: 8453, to: to as 0x${string}, data: data as 0x${string}, value, userAddress: address, deadline, }, }); // 通过 Server Action 中继 const formData new FormData(); formData.append(chainId, 8453); formData.append(to, to); formData.append(data, data); formData.append(value, value); formData.append(userAddress, address); formData.append(userSignature, signature); formData.append(deadline, String(deadline)); const result await relayTransaction(formData); if (error in result) { console.error(Relay failed:, result.error); } else { console.log(Tx submitted:, result.txHash); } }; return ( button onClick{handleRelay} disabled{!address} 确认并中继交易 /button ); }四、安全边界分析服务端私钥的单点风险。中继器钱包的私钥存储在服务端环境变量中一旦泄露攻击者可以提交任意交易。生产环境必须升级使用 AWS KMS 或 Google Cloud HSM 管理私钥通过 signature service 签名应用层不接触原始私钥材料。更进一步的方案是采用 ERC-4337 的 Bundler 架构将签名中继外包给去中心化的打包者网络。签名重放的防护层次。代码中通过 deadline nonce 双重防护deadline 限制签名的有效时间窗口5 分钟nonce 确保每笔请求只被处理一次。但服务端 nonce 缓存是内存级的进程重启后会丢失。持久化方案需要用 Redis 维护 nonce 状态或者在链上通过 EntryPoint 合约的 nonce 机制统一管理。MEV 攻击面的转移。当交易由服务端中继时交易的广播策略直接影响 MEV 暴露程度。如果中继器使用公共 mempool 广播交易可能被 MEV 搜索者前置。生产环境应连接 Flashbots Protect 或其他私有中继避免交易在 mempool 中暴露。Server Action 的速率限制。Server Action 是公开端点任何人都可以调用。必须在中间件层实现基于 IP 和 userAddress 的速率限制防止攻击者通过大量无效请求耗尽中继器的 Gas Token 或触发 RPC 提供商的速率限制。五、总结Next.js Server Actions 为 DApp 带来了架构层面的重新思考机会。将交易构建和签名中继迁移到服务端核心收益有三点前端代码简化、Gas 策略集中优化、用户签名与交易执行的职责分离。但同时需要正视引入的新风险服务端私钥管理、签名重放防护的持久化、MEV 暴露面的变化。这些不是 Server Actions 本身的缺陷而是服务端中继这一架构模式固有的挑战。在设计具体方案时建议从 ERC-4337 的 Account Abstraction 标准出发尽可能复用现有的 Bundler 基础设施避免从零构建私钥管理和 Gas 支付系统。

相关新闻

164.IEC61131-3 三层架构 ST 工程!通用模拟量缩放 FB + 六状态分拣 FB,带称重超限分级报警

164.IEC61131-3 三层架构 ST 工程!通用模拟量缩放 FB + 六状态分拣 FB,带称重超限分级报警

摘要 可编程逻辑控制器(PLC)是工业自动化系统的核心控制单元。本文从工程师视角出发,以IEC 61131-3标准中的结构化文本(ST)语言为主线,系统讲解PLC的硬件架构、扫描周期原理、数据类型与指令系统。通过一个完整的物料分拣系统案例,提供可直接在CODESYS或TwinCAT环境中运…

2026/7/11 18:59:09 阅读更多 →
CVE-2026-31694 漏洞深度分析:Linux 内核 FUSE 组件存在本地提权风险,普通用户可直取 root 权限

CVE-2026-31694 漏洞深度分析:Linux 内核 FUSE 组件存在本地提权风险,普通用户可直取 root 权限

最近安全圈被一枚 Linux 内核漏洞搅得不太平静。编号 CVE-2026-31694 的这个问题,本质上是 FUSE 文件系统子系统里的一处页面缓存溢出缺陷。研究人员不仅把技术细节抖了个干净,连能直接用的 exploit 代码也一并扔到了 GitHub 上。这意味着什么&#xff1…

2026/7/11 18:57:09 阅读更多 →
本地部署无限画布:基于ComfyUI与ModelScope的批量内容生成实践

本地部署无限画布:基于ComfyUI与ModelScope的批量内容生成实践

1. 先搞清楚这个“无限画布”到底能解决什么问题如果你经常需要批量生成漫画、插画或营销素材,这个本地部署的无限画布工具最值得关注的点是:它把 ComfyUI 的节点化工作流、ModelScope 的免费生成能力和自定义扩展揉在了一起。和常见的在线生成工具不同&…

2026/7/11 18:57:09 阅读更多 →

最新新闻

10分钟掌握Gopeed:让你的下载速度飞起来的全能下载管理器

10分钟掌握Gopeed:让你的下载速度飞起来的全能下载管理器

10分钟掌握Gopeed:让你的下载速度飞起来的全能下载管理器 【免费下载链接】gopeed A fast, modern download manager for HTTP, BitTorrent, Magnet, and ed2k. Cross-platform, built with Golang and Flutter. 项目地址: https://gitcode.com/GitHub_Trending/…

2026/7/11 20:54:29 阅读更多 →
编程助手真实体验测评:6大模型在开发工作流中的手感对比

编程助手真实体验测评:6大模型在开发工作流中的手感对比

1. 项目概述:这不是跑分,是写代码时的真实手感“主观测评一下我用的各大模型的编程能力”——这个标题乍看像极了科技媒体常见的横向评测,但其实它背后藏着一个更真实、更琐碎、也更难量化的日常:一个每天要和代码打交道的人&…

2026/7/11 20:54:29 阅读更多 →
如何用airplay2-win在5分钟内将Windows电脑变成AirPlay接收器

如何用airplay2-win在5分钟内将Windows电脑变成AirPlay接收器

如何用airplay2-win在5分钟内将Windows电脑变成AirPlay接收器 【免费下载链接】airplay2-win Airplay2 for windows 项目地址: https://gitcode.com/gh_mirrors/ai/airplay2-win 想让你的Windows电脑也能像Apple TV一样接收iPhone、iPad或Mac的屏幕镜像吗?ai…

2026/7/11 20:50:27 阅读更多 →
3 款虚拟串口方案对比:com0com vs VSPD vs FabulaTech,功能与兼容性实测

3 款虚拟串口方案对比:com0com vs VSPD vs FabulaTech,功能与兼容性实测

3 款虚拟串口方案深度评测:功能、兼容性与实战选型指南在工业自动化、嵌入式开发和物联网应用场景中,串口通信依然是设备间数据交互的基石。然而现代计算机普遍取消了物理串口接口,这使得虚拟串口技术成为开发者不可或缺的工具。本文将针对三…

2026/7/11 20:48:27 阅读更多 →
终极指南:如何在Docker容器中运行QEMU虚拟机并通过Web控制台管理

终极指南:如何在Docker容器中运行QEMU虚拟机并通过Web控制台管理

终极指南:如何在Docker容器中运行QEMU虚拟机并通过Web控制台管理 【免费下载链接】qemu-docker QEMU in a Docker container. 项目地址: https://gitcode.com/gh_mirrors/qe/qemu-docker 你是否想要在Docker容器中运行虚拟机,并通过浏览器轻松管理…

2026/7/11 20:48:27 阅读更多 →
Python 生产者-消费者模式:在 RAG 文档处理 pipeline 中的并发设计

Python 生产者-消费者模式:在 RAG 文档处理 pipeline 中的并发设计

Python 生产者-消费者模式:在 RAG 文档处理 pipeline 中的并发设计 一、深度引言与场景痛点 RAG 系统的第一步永远是文档处理——把 PDF、Word、网页之类的东西变成向量化的文本块。这听起来是个简单的"读文件、切文本、生成向量"三步走,但当你…

2026/7/11 20:48:27 阅读更多 →

日新闻

5分钟完全掌握:ComfyUI ControlNet预处理器终极使用指南

5分钟完全掌握:ComfyUI ControlNet预处理器终极使用指南

5分钟完全掌握:ComfyUI ControlNet预处理器终极使用指南 【免费下载链接】comfyui_controlnet_aux ComfyUIs ControlNet Auxiliary Preprocessors 项目地址: https://gitcode.com/gh_mirrors/co/comfyui_controlnet_aux 想要让AI图像生成真正听从你的指挥吗&…

2026/7/11 0:02:12 阅读更多 →
PIC18F45K42驱动EPT-14A4005P压电蜂鸣器方案详解

PIC18F45K42驱动EPT-14A4005P压电蜂鸣器方案详解

1. 项目背景与核心需求 在工业控制、安防系统和智能家居等领域,可靠的声音警报系统是不可或缺的基础组件。传统蜂鸣器存在音量不足、音质模糊等问题,而基于压电陶瓷技术的EPT-14A4005P蜂鸣器配合PIC18F45K42微控制器,能够构建一套适应性强、音…

2026/7/11 0:04:12 阅读更多 →
大模型推理中的算子融合优化:LayerNorm + Attention 的 CUDA Kernel 手写与验证

大模型推理中的算子融合优化:LayerNorm + Attention 的 CUDA Kernel 手写与验证

大模型推理中的算子融合优化:LayerNorm Attention 的 CUDA Kernel 手写与验证 一、GPU 利用率 30%:分开的算子吃掉所有带宽 推理服务的 GPU 利用率监测显示一个反直觉的现象:计算核心(SM)利用率不到 30%,但…

2026/7/11 0:04:12 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/10 19:03:29 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/9 13:46:46 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/11 19:55:29 阅读更多 →

月新闻