Writing8 Common Mistakes in Web3 Wallet Signing and Approval Flows — Clixo
6 min readweb3, wallet-signing, token-approvals, security, dapp-ux

8 Common Mistakes in Web3 Wallet Signing and Approval Flows

The most common mistakes developers make in Web3 wallet signing and token approval flows, and how to fix each one before they cost your users money or trust.

Signing and approval flows are where most of the security and UX debt in Web3 products lives. They are the steps users trust least, understand least, and are most likely to abandon — and they are also the most common source of exploits when implemented carelessly. Getting them right is not complicated, but it requires deliberate attention that many teams skip under shipping pressure.

Here are the most common mistakes, what they cost, and how to fix each one.

Mistake 1: Requesting Unlimited Token Approvals

The approve function on ERC-20 tokens accepts an amount. The most common default in dApp integrations is uint256.max — the largest possible integer — which grants the approved contract unlimited spending authority over that token balance, now and in the future.

From a UX standpoint, this is a single confirm. From a security standpoint, it means one compromised contract can drain every token in the user's wallet for which they have given unlimited approval.

Fix: Request the exact amount the current transaction requires, or use EIP-2612 permit signatures that expire and are scoped to a single transaction. If unlimited approval is genuinely needed for your product design, explain it explicitly to the user before they sign.

Mistake 2: Using eth_sign for Message Signing

eth_sign signs arbitrary bytes prefixed with \x19Ethereum Signed Message:\n. It is the oldest signing method and the most dangerous — a signed arbitrary-bytes message can potentially be repurposed as a valid transaction on certain networks. Most wallets now show a warning when eth_sign is requested, which causes user anxiety and drop-off.

Fix: Use eth_signTypedData_v4 (EIP-712) for all structured data signing. Typed data shows the user a human-readable breakdown of every field they are signing, including domain, contract, and purpose. It is also cryptographically safer because the domain separator prevents replay across contracts and chains.

Mistake 3: No Replay Protection on Off-Chain Signatures

If your product uses off-chain signatures to authorize on-chain actions — common in meta-transactions, permit flows, and SIWE auth — and those signatures do not include a nonce, chain ID, and verifying contract address, they can be replayed.

A replay attack reuses a valid signature from a past action to authorize a different action. In the worst case, a signature that authorized a small action can be submitted again to authorize the same action repeatedly.

Fix: Every signature payload must include at minimum: a nonce unique to that user + action combination, the chain ID, and the address of the contract that will verify the signature. EIP-712 domain separators enforce this when implemented correctly.

Mistake 4: Showing the Wallet Modal Without Context

Many dApps trigger the wallet's confirmation modal immediately when the user clicks an action button, with no intermediate explanation. The user sees a hex-encoded payload, a gas estimate in gwei, and a countdown timer — and has no idea what they are authorizing.

This is the most common cause of users rejecting valid transactions. They cannot tell if the transaction is legitimate or if something has gone wrong.

Fix: Add an intent confirmation step before triggering any wallet modal. Show in plain language: what action is being taken, what is being authorized, what it costs, and what changes as a result. The wallet modal is a security check, not a UX step — users should arrive at it already understanding what they are confirming.

Mistake 5: Not Handling User Rejection

When a user rejects a signature or transaction in their wallet, the wallet SDK throws an error with a code like 4001 or ACTION_REJECTED. A surprising number of dApps either swallow this error silently (leaving the UI in a loading state) or display a generic unhandled error message.

Fix: Catch rejection errors specifically and treat them as expected user behavior, not exceptions. Show a calm, non-alarming message like "You cancelled the transaction — tap Confirm to try again." Reset the UI to the pre-signing state cleanly.

Mistake 6: Not Validating Chain Before Signing

Token approval on the wrong chain is meaningless but wastes gas. A signature intended for Ethereum mainnet but submitted on a forked testnet with the same contract address can have unintended effects. Many dApps check the chain at connection time but not at transaction time.

Fix: Validate chainId immediately before constructing any transaction or signature payload, not only at the start of the session. If the chain has changed, prompt the user to switch before proceeding.

Mistake 7: Treating Transaction Hash as Transaction Success

When sendTransaction resolves with a hash, many developers immediately show a success screen. A transaction hash means the transaction was received by the node — not that it was included in a block or that it succeeded.

Transactions can fail on-chain for many reasons: insufficient gas, reverted logic, slippage exceeded. Showing success before receipt confirmation creates a poor experience when the user later discovers the action did not complete.

Fix: Wait for the transaction receipt using waitForTransactionReceipt (or equivalent) and check that receipt.status === 1 before triggering any success state. Show a pending state in the interim.

Mistake 8: Exposing the Full Approval Flow to New Users Without Explanation

The ERC-20 approve-then-transact pattern requires two separate wallet confirmations for first-time interactions. Developers who are used to this find it obvious. Users who have never used your product have no mental model for why they are being asked to confirm twice.

Common responses from new users: thinking the first confirmation was the final one, closing the app, or assuming something went wrong after the first transaction succeeded.

Fix: Show a two-step progress indicator that explains both steps before the user begins: "Step 1 of 2: Approve USDC (one-time setup)" and "Step 2 of 2: Complete transaction." Consider migrating to EIP-2612 permit signatures where the token supports it, which collapses two transactions into one signed message plus one transaction.


Signing and approval flows are where trust is earned or lost. If you are building a DeFi product or any application that handles user funds and want these patterns implemented correctly from the start, Start a build with Clixo.