使用 Wagmi Vue 发送交易:useSendTransaction 与 useWaitForTransactionReceipt 实战指南
【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi
本文基于 Wagmi 仓库中 Vue 发送交易指南 展开,面向使用 Vue 3 +@wagmi/vue构建 DApp 的开发者。你将学会如何从零搭建一个「转账」表单:接入useSendTransaction发起交易、用useWaitForTransactionReceipt监听上链确认、处理加载状态与用户拒绝/余额不足等错误,并在文章末尾看到源码级的底层调用链解析。读完即可在真实项目中落地一个完整、健壮的 Send Transaction 模块。
前置准备:先让钱包连接起来
发送交易的前提是用户已经通过钱包连接。本指南直接建立在 Connect Wallet 指南 之上,你需要先完成以下三件事:
- 创建 Wagmi 配置:通过
createConfig配置链、连接器(connector)与传输层,例如injected、metaMask、safe、walletConnect; - 注入插件:在应用入口同时注入
WagmiPlugin(提供 Wagmi 响应式状态)与VueQueryPlugin(提供 TanStack Vue Query 的内存缓存与查询状态管理); - 实现连接 UI:使用
useConnect、useConnectors、useConnection等组合式函数让用户选择并连接钱包。
import { VueQueryPlugin } from '@tanstack/vue-query' import { WagmiPlugin } from '@wagmi/vue' import { createApp } from 'vue' import App from './App.vue' import { config } from './wagmi' createApp(App) .use(WagmiPlugin, { config }) .use(VueQueryPlugin, {}) .mount('#app')注意:如果使用 WalletConnect,请务必把
config.ts中的<WALLETCONNECT_PROJECT_ID>替换为你自己的 Project ID。连接完成、useConnection().isConnected变为true之后,再渲染发送交易组件。
第 1 步:创建 SendTransaction 组件骨架
先创建一个SendTransaction.vue,包含一个最简单的转账表单:一个收款地址输入框、一个转账金额输入框和一个提交按钮。
<script setup lang="ts"> </script> <template> <form> <input name="address" placeholder="0xA0Cf…251e" required /> <input name="value" placeholder="0.05" required /> <button type="submit">Send</button> </form> </template>两个输入框都带有name属性(address与value),这是下一步通过FormData读取表单数据的关键。required保证用户在提交前必须填写完整。
第 2 步:添加表单处理器
接下来为表单添加提交事件处理器。当用户点击 "Send" 时,从表单中提取地址与金额,并调用sendTransaction:
<script setup lang="ts"> function submit(event: Event) { const formData = new FormData(event.target as HTMLFormElement) const to = formData.get('address') as `0x${string}` const value = formData.get('value') as string sendTransaction({ to, value: parseEther(value) }) } </script> <template> <form @submit.prevent="submit"> <input name="address" placeholder="0xA0Cf…251e" required /> <input name="value" placeholder="0.05" required /> <button type="submit">Send</button> </form> </template>这里有两个关键细节:
@submit.prevent:阻止表单默认的整页刷新行为;parseEther(value):来自viem的单位换算工具。用户在输入框中填写的是人类可读的0.05(ETH),而链上交易要求以wei为单位的bigint。parseEther('0.05')会返回50000000000000000n。
第 3 步:接入 useSendTransaction 组合式函数
现在把useSendTransaction接入进来,它是整个发送交易流程的核心。从@wagmi/vue导入组合式函数,从viem导入parseEther:
<script setup lang="ts"> import { useSendTransaction } from '@wagmi/vue' import { parseEther } from 'viem' const { data: hash, sendTransaction } = useSendTransaction() function submit(event: Event) { const formData = new FormData(event.target as HTMLFormElement) const to = formData.get('address') as `0x${string}` const value = formData.get('value') as string sendTransaction({ to, value: parseEther(value) }) } </script> <template> <form @submit.prevent="submit"> <input name="address" placeholder="0xA0Cf…251e" required /> <input name="value" placeholder="0.05" required /> <button type="submit">Send</button> <div v-if="hash">Transaction Hash: {{ hash }}</div> </form> </template>useSendTransaction返回的data(解构为hash)就是交易哈希0x${string}。一旦钱包签名并广播交易成功,hash就会填充,我们可以立即把它渲染在页面上,供用户在区块浏览器中查询。
useSendTransaction 的入参与返回值
从 useSendTransaction API 文档 与仓库源码可以看到它的完整形态。其类型定义位于 packages/vue/src/composables/useSendTransaction.ts,本质上是对 TanStack Vue QueryuseMutation的封装:
export function useSendTransaction(parameters = {}) { const config = useConfig(parameters) const options = sendTransactionMutationOptions(config, parameters) const mutation = useMutation(options) return { ...mutation, sendTransaction: mutation.mutate, sendTransactionAsync: mutation.mutateAsync, } }参数(Parameters)
| 参数 | 类型 | 说明 |
|---|---|---|
config | Config \| undefined | 显式指定要使用的 Config,默认从WagmiPlugin注入的配置中获取 |
mutation | MutationParameters | TanStack Query 的 mutation 级选项,如onSuccess、onError、onSettled、retry等 |
提交变量(Variables)
sendTransaction({ to, value })接收的是核心 action sendTransaction 的参数,常见字段包括:
to(必填):0x${string}收款地址;value:bigint转账金额(注意必须用parseEther等工具转成 wei);gas/gasPrice/maxFeePerGas/maxPriorityFeePerGas:Gas 相关覆盖项,不传则自动估算;chainId:config['chains'][number]['id']目标链 ID,不传则使用当前链;connector:指定使用某个连接器签名,不传则使用当前已连接的钱包;data:合约调用的 calldata(本示例为普通转账,无需填写;若调用合约可配合 ABI 编码使用)。
返回值(Return Type)
| 返回值 | 类型 | 说明 |
|---|---|---|
sendTransaction | (variables, options?) => void | 触发交易的函数(即mutate的别名,在源码中标注为 deprecated,推荐直接使用mutate) |
sendTransactionAsync | (variables, options?) => Promise<SendTransactionData> | 异步版本(mutateAsync的别名,同样 deprecated),返回可 await 的 Promise |
data | SendTransactionData \| undefined | 最近一次成功解析的数据,即交易哈希,默认undefined |
error | SendTransactionErrorType \| null | 最近一次 mutation 的错误对象 |
isPending | boolean | mutation 是否正在执行(status === 'pending') |
isError/isSuccess/isIdle | boolean | 由status派生的状态布尔值 |
status | 'idle' \| 'pending' \| 'error' \| 'success' | mutation 的完整状态机 |
reset | () => void | 将 mutation 内部状态重置为初始状态 |
variables | Variables \| undefined | 最近一次传给mutate的变量对象 |
完整的返回值清单可参考仓库中 mutation-result 共享文档,其字段语义与 TanStack Query v5 的useMutation保持一致。
第 4 步:添加加载状态(可选)
用户点击 "Send" 后,钱包会弹出签名确认窗口,这段时间内应禁用按钮并给出反馈,防止重复提交:
<script setup lang="ts"> import { useSendTransaction } from '@wagmi/vue' import { parseEther } from 'viem' const { data: hash, isPending, sendTransaction } = useSendTransaction() function submit(event: Event) { const formData = new FormData(event.target as HTMLFormElement) const to = formData.get('address') as `0x${string}` const value = formData.get('value') as string sendTransaction({ to, value: parseEther(value) }) } </script> <template> <form @submit.prevent="submit"> <input name="address" placeholder="0xA0Cf…251e" required /> <input name="value" placeholder="0.05" required /> <button :disabled="isPending" type="submit"> <span v-if="isPending">Sending...</span> <span v-else>Send</span> </button> <div v-if="hash">Transaction Hash: {{ hash }}</div> </form> </template>isPending在钱包签名、交易广播期间为true,此时按钮禁用并显示 "Sending...",hash一旦产生则切回 "Send"。
第 5 步:等待交易确认(可选)
hash产生只代表交易已被广播,并不代表交易已上链。要拿到确切的交易回执(receipt),需要使用 useWaitForTransactionReceipt:
<script setup lang="ts"> import { useSendTransaction, useWaitForTransactionReceipt, } from '@wagmi/vue' import { parseEther } from 'viem' const { data: hash, isPending, sendTransaction } = useSendTransaction() function submit(event: Event) { const formData = new FormData(event.target as HTMLFormElement) const to = formData.get('address') as `0x${string}` const value = formData.get('value') as string sendTransaction({ to, value: parseEther(value) }) } const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash, }) </script> <template> <form @submit.prevent="submit"> <input name="address" placeholder="0xA0Cf…251e" required /> <input name="value" placeholder="0.05" required /> <button :disabled="isPending" type="submit"> <span v-if="isPending">Sending...</span> <span v-else>Send</span> </button> <div v-if="hash">Transaction Hash: {{ hash }}</div> <div v-if="isConfirming">Waiting for confirmation...</div> <div v-if="isConfirmed">Transaction Confirmed!</div> </form> </template>把上一步拿到的hash直接传入useWaitForTransactionReceipt,组合式函数会持续轮询,直到交易被打包进区块并返回回执:
isLoading(解构为isConfirming):正在等待交易上链;isSuccess(解构为isConfirmed):交易已确认。
useWaitForTransactionReceipt 的常用参数
参考 useWaitForTransactionReceipt API 文档,该组合式函数的关键参数如下:
| 参数 | 类型 | 说明 |
|---|---|---|
hash | `0x${string}` \| undefined | 要等待的交易哈希。当hash为undefined时,查询的enabled自动为false,不会发起轮询——这正是本示例中hash未产生时组件不会空转的原因 |
chainId | config['chains'][number]['id'] \| undefined | 查询目标链 ID,默认取当前链 |
confirmations | number \| undefined | 等待的确认区块数(默认 1),传2即等两个区块后再 resolve |
pollingInterval | number \| undefined | 轮询频率(毫秒),默认取 createConfig 的pollingInterval配置 |
onReplaced | ((replacement) => void) \| undefined | 交易被替换(replaced/repriced/cancelled,例如用户加速或取消交易)时的回调 |
config | Config \| undefined | 显式指定 Config |
从源码 packages/vue/src/composables/useWaitForTransactionReceipt.ts 可以看到,它是一个基于 TanStack Vue QueryuseQuery的封装:参数类型为DeepMaybeRef,内部通过deepUnref把响应式参数解包成普通对象,再交给waitForTransactionReceiptQueryOptions生成查询选项;chainId默认来自useChainId,且支持参数级覆盖。这也意味着它天然支持响应式——把hash这个ref传进去,hash一旦变化查询会自动重新触发。
第 6 步:处理错误(可选)
如果用户在钱包中拒绝了交易,或者账户余额不足以覆盖「转账金额 + Gas 费」,mutation 会进入错误状态。此时应该把错误信息展示给用户:
<script setup lang="ts"> import { useSendTransaction, useWaitForTransactionReceipt, } from '@wagmi/vue' import { parseEther } from 'viem' const { data: hash, error, isPending, sendTransaction } = useSendTransaction() function submit(event: Event) { const formData = new FormData(event.target as HTMLFormElement) const to = formData.get('address') as `0x${string}` const value = formData.get('value') as string sendTransaction({ to, value: parseEther(value) }) } const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash, }) </script> <template> <form @submit.prevent="submit"> <input name="address" placeholder="0xA0Cf…251e" required /> <input name="value" placeholder="0.05" required /> <button :disabled="isPending" type="submit"> <span v-if="isPending">Sending...</span> <span v-else>Send</span> </button> <div v-if="hash">Transaction Hash: {{ hash }}</div> <div v-if="isConfirming">Waiting for confirmation...</div> <div v-if="isConfirmed">Transaction Confirmed!</div> <div v-if="error"> Error: {{ (error as BaseError).shortMessage || error.message }} </div> </form> </template>展示错误时优先取(error as BaseError).shortMessage——BaseError是viem提供的错误基类(从viem导入类型即可),shortMessage是提炼过的简短可读信息,比冗长的原始error.message更适合直接面向用户。
第 7 步:组装到应用入口
最后,把SendTransaction组件挂到应用入口。沿用 Connect Wallet 指南的模式:未连接时显示Connect,已连接时显示Connection与SendTransaction:
<script setup lang="ts"> import { useConnection } from '@wagmi/vue' import Connection from './Connection.vue' import Connect from './Connect.vue' import SendTransaction from './SendTransaction.vue' const { isConnected } = useConnection() </script> <template> <Connection v-if="isConnected" /> <Connect v-else /> <SendTransaction v-if="isConnected" /> </template>完整代码汇总
将上述步骤合并后的完整SendTransaction.vue如下(可直接复制运行):
<script setup lang="ts"> import { useSendTransaction, useWaitForTransactionReceipt, } from '@wagmi/vue' import { parseEther } from 'viem' const { data: hash, error, isPending, sendTransaction } = useSendTransaction() function submit(event: Event) { const formData = new FormData(event.target as HTMLFormElement) const to = formData.get('address') as `0x${string}` const value = formData.get('value') as string sendTransaction({ to, value: parseEther(value) }) } const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash, }) </script> <template> <form @submit.prevent="submit"> <input name="address" placeholder="0xA0Cf…251e" required /> <input name="value" placeholder="0.05" required /> <button :disabled="isPending" type="submit"> <span v-if="isPending">Sending...</span> <span v-else>Send</span> </button> <div v-if="hash">Transaction Hash: {{ hash }}</div> <div v-if="isConfirming">Waiting for confirmation...</div> <div v-if="isConfirmed">Transaction Confirmed!</div> <div v-if="error"> Error: {{ (error as BaseError).shortMessage || error.message }} </div> </form> </template>底层原理:一次点击背后的完整调用链
理解useSendTransaction的封装层级,有助于排查问题和按需定制。从仓库源码可以还原出如下调用链:
- 组合式函数层:packages/vue/src/composables/useSendTransaction.ts 调用
sendTransactionMutationOptions(config, parameters)生成 mutation 选项,再交给 TanStack Vue Query 的useMutation; - Query 选项层:packages/core/src/query/sendTransaction.ts 定义
mutationKey: ['sendTransaction'],并把mutationFn指向核心 action:
return { ...(options.mutation as any), mutationFn(variables) { return sendTransaction(config, variables) }, mutationKey: ['sendTransaction'], }- 核心 action 层:packages/core/src/actions/sendTransaction.ts 通过
getConnectorClient获取当前连接器对应的 viem 客户端(携带已连接账户与签名能力),然后委托给viem/actions的sendTransaction完成「构造交易 → 钱包签名 → 广播」全流程,最终返回交易哈希。其参数类型SendTransactionParameters在 viem 参数基础上移除了chain/gas字段,并叠加了ChainIdParameter与可选的gas覆盖项。
同理,useWaitForTransactionReceipt的封装路径是:Vue 组合式函数(packages/vue/src/composables/useWaitForTransactionReceipt.ts)→waitForTransactionReceiptQueryOptions→ 核心 actionwaitForTransactionReceipt(轮询直到交易上链或回滚)。文档描述其行为为:等待交易被包含进区块后返回回执;若交易 revert 则抛出错误;同时支持替换交易(如加速/取消)的检测。
延伸阅读
- 想读取合约数据(
view/pure函数)?参见 从合约读取数据指南,其中演示了useReadContract以及用query.enabled控制依赖就绪的写法; - 想向合约写入状态?参见 写入合约指南;
- 完整的组合式函数参数与返回值: useSendTransaction API、useWaitForTransactionReceipt API;
- 底层核心 action: sendTransaction 与 waitForTransactionReceipt。
【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考