Subchapter 4.28
references/migrations/onchainkit/transaction.mdMarkdown17 KBView on GitHub
Replace OnchainKit’s Transaction, TransactionButton, TransactionStatus, TransactionSponsor, and related components with a standalone component built on wagmi hooks.
TransactionFormOnchainKit provides a composable transaction system:
<Transaction /> – container that manages the full transaction lifecycle, accepts calls, chainId, onStatus<TransactionButton /> – submits the transaction, shows status-dependent text (Transact/Confirm/Try again/View transaction)<TransactionStatus /> – displays current transaction state with label and action<TransactionStatusLabel /> – text label (“Confirm in wallet”, “Transaction in progress”, “Successful”, error message)<TransactionStatusAction /> – link to block explorer or call status viewer<TransactionSponsor /> – shows “Zero transaction fee” when paymaster is configuredLifecycleStatus type – status object with statusName and statusDataInternally, OnchainKit uses two submission paths:
useSendCalls (EIP-5792) for wallets with atomicBatch capabilityuseSendTransaction with encodeFunctionData for standard walletsThe replacement component uses useWriteContract which handles both EOA and smart wallet scenarios for single contract calls.
wagmi-config.tsOnchainKit’s Transaction accepts a chainId prop and handles chain switching. The replacement does too, BUT the target chain must exist in the wagmi config’s chains array and transports object.
For example, if transactions target Base Sepolia (84532):
import { base, baseSepolia } from "wagmi/chains";
export const wagmiConfig = createConfig({
chains: [base, baseSepolia],
transports: {
[base.id]: http(),
[baseSepolia.id]: http(),
},
// ...rest
});Create app/components/TransactionForm.tsx (or wherever components live in the project):
"use client";
import { useCallback, useEffect, useState } from "react";
import {
useAccount,
useWriteContract,
useWaitForTransactionReceipt,
useSwitchChain,
} from "wagmi";
import type { Abi, Address } from "viem";
type ContractCall = {
address: Address;
abi: Abi;
functionName
Look at the chainId prop on the existing <Transaction /> component. If it references a chain not in the wagmi config, add it:
// Common: Base Sepolia for testnet
import { base, baseSepolia } from "wagmi/chains";
// Add to wagmi config chains array and transportsCopy the TransactionForm component code above into the project’s components directory.
Before (OnchainKit):
import {
Transaction,
TransactionButton,
TransactionSponsor,
TransactionStatus,
TransactionStatusAction,
TransactionStatusLabel,
} from '@coinbase/onchainkit/transaction';
import type { LifecycleStatus } from '@coinbase/onchainkit/transaction';
const calls = [
{
address: '0x67c97D1FB8184F038592b2109F854dfb09C77C75',
abi: clickContractAbi,
functionName: 'click',
args: [],
}
];
<Transaction
chainId={84532}
calls={calls}
onStatus={handleOnStatus}
>
<TransactionButton />
<TransactionSponsor />
<TransactionStatus>
<TransactionStatusLabel />
<TransactionStatusAction />
</TransactionStatus>
</Transaction>After (wagmi):
import { TransactionForm } from "./components/TransactionForm";
import type { Address } from "viem";
const clickContractAddress: Address = '0x67c97D1FB8184F038592b2109F854dfb09C77C75';
const clickContractAbi = [
{
type: 'function' as const,
name: 'click',
inputs: [],
outputs: [],
stateMutability: 'nonpayable' as const,
},
] as const;
const calls = [
{
address: clickContractAddress,
abi: clickContractAbi,
functionName: 'click',
args: [],
},
];
<TransactionForm
calls={calls}
chainId={84532}
buttonText="Click"
onStatus={handleOnStatus}
/>The OnchainKit LifecycleStatus type has these states: init, transactionIdle, buildingTransaction, transactionPending, transactionLegacyExecuted, success, error, reset.
The replacement uses a simplified set: init, pending, confirmed, success, error.
Mapping:
| OnchainKit Status | Replacement Status |
|---|---|
init / transactionIdle | init |
buildingTransaction / transactionPending | pending |
transactionLegacyExecuted | confirmed |
success | success |
error | error |
If the existing onStatus callback checks specific OnchainKit status names, update the checks to use the new names.
Run npm run build and confirm no errors.
OnchainKit’s TransactionSponsor uses a paymaster URL to sponsor gas fees. This requires a paymaster service (e.g., Coinbase Developer Platform Paymaster). The replacement component does not include paymaster support. To add it, you would need to use wagmi’s useSendCalls with the paymaster capability.
OnchainKit’s Transaction supports batching multiple calls into a single transaction for smart wallets. The replacement uses useWriteContract which handles one call at a time. For batched calls, use wagmi’s useSendCalls hook directly.
OnchainKit’s TransactionToast provides toast-style notifications. The replacement shows inline status instead. Add a toast library if toast notifications are needed.
This is the most common bug. The transaction hash appears, the tx confirms on-chain, but the UI stays stuck on “Transaction in progress…” forever.
Cause: useWaitForTransactionReceipt needs an RPC to poll for the receipt. If the transaction’s chain is not in the wagmi config’s chains + transports, wagmi has no RPC endpoint to poll, so isSuccess never becomes true.
Fix (two parts):
wagmi-config.ts:import { base, baseSepolia } from "wagmi/chains";
export const wagmiConfig = createConfig({
chains: [base, baseSepolia], // Must include every chain the app transacts on
transports: {
[base.id]: http(),
[baseSepolia.id]: http(), // Must have a transport for each chain
},
// ...rest
});chainId to useWaitForTransactionReceipt:const { data: receipt } = useWaitForTransactionReceipt({
hash: txHash,
chainId, // Ensures polling uses the correct chain's transport
});Next.js only allows specific named exports from page files (default, metadata, generateMetadata, generateStaticParams, etc.). If you export contract call arrays, ABI constants, or other non-page values from a page file, the build will fail with an error like: "calls" is not a valid Page export field.
Fix: Move contract call arrays, ABIs, and addresses to a separate module (e.g., contracts.ts) or make them non-exported const declarations within the page file.
The wagmi error types don’t include UserRejectedRequestError as a direct name match. Instead, check error.message for “User rejected” or “User denied” strings.
The component auto-switches chains via useSwitchChain. But the target chain must exist in the wagmi config. If you get a chain error, add the chain to wagmi-config.ts.
Same as wallet: ensure the component is inside the WagmiProvider tree.
When defining the ABI inline, use as const on the array to get proper type inference:
const abi = [
{
type: 'function' as const,
name: 'click',
inputs: [],
outputs: [],
stateMutability: 'nonpayable' as const,
},
] as const;