Skip to content

Viem Migration - Failed onchain TXs are not detected#7421

Open
lgahdl wants to merge 6 commits into
cowprotocol:developfrom
bleu:luizhatem/cow-920-failed-onchain-txs-are-not-detected
Open

Viem Migration - Failed onchain TXs are not detected#7421
lgahdl wants to merge 6 commits into
cowprotocol:developfrom
bleu:luizhatem/cow-920-failed-onchain-txs-are-not-detected

Conversation

@lgahdl
Copy link
Copy Markdown
Collaborator

@lgahdl lgahdl commented Apr 28, 2026

Summary

Notion Issue: https://www.notion.so/cownation/Failed-onchain-TXs-are-not-detected-34b8da5f04ca808b9bb1e0a30859f213

image

Fix wrap/unwrap transaction errors not showing in the UI

When a wrap or unwrap transaction fails before being broadcast (e.g. viem simulates the contract call internally and detects a revert due to low gas price), the error was thrown but never caught by the button's click handler, resulting in an unhandled promise rejection with no user feedback.

Additionally, for transactions that were broadcast and mined with status: reverted, the Redux state was not marking them as failed (errorMessage was never set), causing the activity panel to show them in an undefined state instead of FAILED.

To Test

  1. Open the Swap page with ETH as input and WETH as output (or vice versa for unwrap)
  • In MetaMask Advanced settings, set Max base fee to a very low value (e.g. 1)
  • Click Wrap (or Unwrap)
  • Verify an error snackbar appears in the top-right corner with the error message
  • Verify no unhandled promise rejection appears in the browser console
  1. Open the Swap page with WETH as input and ETH as output
  • Repeat the same steps for Unwrap
  • Verify the same error snackbar behaviour

Background

wrapUnwrapCallback re-throws non-rejection errors after logging them. The WrapUnwrapFlow button in tradeButtonsMap.tsx was calling props.wrapNativeFlow() without handling the returned promise, so errors silently became unhandled rejections with no UI feedback.

Two fixes were applied:

  1. tradeButtonsMap.tsx: Added .catch() to the wrap/unwrap button click handler that displays the error via useAddSnackbar. Uses viem's shortMessage when available for a cleaner error message.

  2. enhancedTransactions/reducer.ts: When finalizeTransaction is dispatched with a receipt of status: 'reverted', the transaction's errorMessage is now set to 'Transaction failed', ensuring getTxActivityStatus correctly returns ActivityStatus.FAILED for the activity panel.

Summary by CodeRabbit

  • New Features

    • Notify users (alert + sound) when a transaction is determined not to have been broadcast.
  • Bug Fixes

    • Properly mark reverted transactions as failed.
    • More reliable receipt polling with clearer retry behavior.
    • Added a grace period before marking transactions as cancelled.
    • Improve displayed provider error messages for clearer feedback.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 28, 2026

@lgahdl is attempting to deploy a commit to the cow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Warning

Rate limit exceeded

@lgahdl has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 57 minutes and 52 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 360bb5c8-f172-4872-a7a8-f3fefa4faf5f

📥 Commits

Reviewing files that changed from the base of the PR and between 5a0893c and 057034b.

📒 Files selected for processing (6)
  • apps/cowswap-frontend/src/legacy/hooks/useWrapCallback.ts
  • apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/TradeWidgetModals.tsx
  • apps/cowswap-frontend/src/modules/trade/hooks/useWrapNativeFlow.ts
  • apps/cowswap-frontend/src/modules/trade/state/wrapNativeStateAtom.ts
  • apps/cowswap-frontend/src/modules/tradeFormValidation/types.ts
  • libs/common-utils/src/misc.ts

Walkthrough

Adds detection for transactions never broadcast to chain, a new TransactionNotBroadcastError, grace-period-based cancellation that emits a TX_CANCELLED_NOT_BROADCAST event, UI notification handling for that event, plus related receipt-polling, reducer, hook, and minor utility changes. (≤50 words)

Changes

TX Cancellation & Receipt Flow

Layer / File(s) Summary
Data Shape / Events
apps/cowswap-frontend/src/modules/onchainTransactions/onchainTransactionsEvents.ts
Adds OnchainTxEvents.TX_CANCELLED_NOT_BROADCAST and TxCancelledNotBroadcastPayload mapping in the event payload map.
Errors / Polling API
apps/cowswap-frontend/src/common/hooks/useGetReceipt.ts
Replaces single-step waitForTransactionReceipt with two-step getTransactionReceipt then getTransaction flow; exports TransactionNotBroadcastError thrown when transaction hash is not found (distinguishes non-broadcast from retryable RPC errors).
Core Detection Logic
apps/cowswap-frontend/src/modules/onchainTransactions/updaters/FinalizeTxUpdater/services/checkOnChainTransaction.ts
Handles TransactionNotBroadcastError separately: computes chain-dependent grace period (fallback default) from transaction pending time and emits TX_CANCELLED_NOT_BROADCAST and calls replacement handler only after grace period elapsed; suppresses unconditional receipt-fetch failure logging for retryable errors.
State Finalization
apps/cowswap-frontend/src/legacy/state/enhancedTransactions/reducer.ts
finalizeTransaction now marks transactions as failed when receipt.status === 'reverted' by setting tx.errorMessage = 'Transaction failed'.
Context Resilience
apps/cowswap-frontend/src/modules/onchainTransactions/updaters/FinalizeTxUpdater/hooks/usePendingTransactionsContext.ts
Treats getTransactionCount failures by defaulting transactionsCount to 0, allowing receipt checks to continue when nonce fetch fails.
UI Wiring
apps/cowswap-frontend/src/modules/onchainTransactions/updaters/OnchainTransactionEventsUpdater.tsx
Adds effect listening for TX_CANCELLED_NOT_BROADCAST: plays error sound and enqueues localized snackbar with TransactionContentWithLink.
Utilities
libs/common-utils/src/misc.ts
getProviderErrorMessage now prefers error.shortMessage when error is an object, else falls back to error.message or error?.toString().

Sequence Diagram

sequenceDiagram
    participant Hook as useGetReceipt Hook
    participant RPC as RPC/Provider
    participant Checker as checkOnChainTransaction
    participant Emitter as EventEmitter
    participant Updater as OnchainTransactionEventsUpdater
    participant UI as UI/Notification

    Hook->>RPC: getTransactionReceipt(hash)
    RPC-->>Hook: null (no receipt)
    Hook->>RPC: getTransaction(hash)
    alt RPC: returns transaction
        RPC-->>Hook: transaction (in mempool)
        Hook-->>Checker: treat as retryable (no receipt yet)
    else RPC: TransactionNotFoundError
        RPC-->>Hook: TransactionNotFoundError
        Hook-->>Checker: throw TransactionNotBroadcastError
    end

    Checker->>Checker: compute grace = now - tx.addedTime
    alt grace elapsed
        Checker->>Emitter: emit(TX_CANCELLED_NOT_BROADCAST, payload)
        Emitter-->>Updater: event received
        Updater->>Updater: play error sound
        Updater->>UI: enqueue localized snackbar with tx link
        UI-->>UI: show notification
    else not yet elapsed
        Checker->>Checker: retry on next poll
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A hash that wandered, never found on chain,
We wait a little longer—then signal the refrain.
A gentle grace, a final bell, an event for the lost,
A snackbar hops in, alerting at no extra cost.
Hooray — now users know when transactions are off-course!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective: fixing detection of failed onchain transactions in the UI.
Description check ✅ Passed The description includes a summary with issue link, clear explanation of both problems, testing steps with checkboxes, and background information covering the fixes made.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@lgahdl lgahdl changed the title feat: throwing error message for failed onchain transactions Viem Migration - Failed onchain TXs are not detected Apr 28, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
swap-dev Ready Ready Preview May 5, 2026 10:27am

Request Review

Copy link
Copy Markdown
Contributor

@elena-zh elena-zh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @lgahdl , sorry, not fixed :(

Image

https://sepolia.etherscan.io/tx/0xd14d2e16469be51c56a57c8ebd2eaffbe742c8976d12235cad328c1c2e9a6d3f

To reproduce:

  1. connect to mm
  2. specify an onchain TX
  3. send the tx to MM
  4. before signing, pick an 'advanced' option for gas
Image Image
  1. change gaslimit to an extremely low value there
Image
  1. save and sign the TX --> it will fail.

…ap UX;

fix(revert): reverting fix from other commit that didn't work as expected;
@lgahdl
Copy link
Copy Markdown
Collaborator Author

lgahdl commented Apr 29, 2026

Hey @lgahdl , sorry, not fixed :(

Image https://sepolia.etherscan.io/tx/0xd14d2e16469be51c56a57c8ebd2eaffbe742c8976d12235cad328c1c2e9a6d3f

To reproduce:

  1. connect to mm
  2. specify an onchain TX
  3. send the tx to MM
  4. before signing, pick an 'advanced' option for gas

Image Image
5. change gaslimit to an extremely low value there

Image 6. save and sign the TX --> it will fail.

Hey, now it should work

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/cowswap-frontend/src/modules/onchainTransactions/updaters/FinalizeTxUpdater/hooks/usePendingTransactionsContext.ts`:
- Around line 37-39: Don't turn RPC nonce-fetch failures into a valid 0 nonce:
remove the .catch(() => 0) on getTransactionCount so failures produce undefined
(or propagate), type transactionsCount as number | undefined (used by
CheckEthereumTransactions), and update any replacement-detection logic (in
CheckEthereumTransactions and downstream code) to explicitly guard with
transactionsCount !== undefined before treating the nonce as a real count.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cee38738-e2c8-4060-8af4-7a86b9d129c6

📥 Commits

Reviewing files that changed from the base of the PR and between ef37ee5 and 1426f98.

📒 Files selected for processing (5)
  • apps/cowswap-frontend/src/common/hooks/useGetReceipt.ts
  • apps/cowswap-frontend/src/modules/onchainTransactions/onchainTransactionsEvents.ts
  • apps/cowswap-frontend/src/modules/onchainTransactions/updaters/FinalizeTxUpdater/hooks/usePendingTransactionsContext.ts
  • apps/cowswap-frontend/src/modules/onchainTransactions/updaters/FinalizeTxUpdater/services/checkOnChainTransaction.ts
  • apps/cowswap-frontend/src/modules/onchainTransactions/updaters/OnchainTransactionEventsUpdater.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/cowswap-frontend/src/modules/onchainTransactions/onchainTransactionsEvents.ts

Comment on lines +37 to +39
// Fallback to 0 on failure so receipt checking can still run even when the nonce fetch fails
// (e.g. temporary RPC errors). The nonce-based replacement check will simply be skipped.
const transactionsCount = await getTransactionCount(config, { address: account }).catch(() => 0)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t map nonce-fetch failures to 0

Line 39 turns RPC failure into a valid nonce value, which makes the replacement check indistinguishable from a true fresh-account state. That can suppress replacement detection while the RPC is degraded.

Suggested direction
- const transactionsCount = await getTransactionCount(config, { address: account }).catch(() => 0)
+ const transactionsCount = await getTransactionCount(config, { address: account }).catch(() => undefined)

Then type transactionsCount as number | undefined in CheckEthereumTransactions and guard downstream replacement logic with transactionsCount !== undefined.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/cowswap-frontend/src/modules/onchainTransactions/updaters/FinalizeTxUpdater/hooks/usePendingTransactionsContext.ts`
around lines 37 - 39, Don't turn RPC nonce-fetch failures into a valid 0 nonce:
remove the .catch(() => 0) on getTransactionCount so failures produce undefined
(or propagate), type transactionsCount as number | undefined (used by
CheckEthereumTransactions), and update any replacement-detection logic (in
CheckEthereumTransactions and downstream code) to explicitly guard with
transactionsCount !== undefined before treating the nonce as a real count.

Copy link
Copy Markdown
Contributor

@elena-zh elena-zh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @lgahdl , great!
However, during tests I've found one more side issue: could you please shorten this message and display it as on prod?

Image

Happens, when I try placing a ETH-flow order with 21000 gas limit

@lgahdl
Copy link
Copy Markdown
Collaborator Author

lgahdl commented May 5, 2026

Hey @lgahdl , great! However, during tests I've found one more side issue: could you please shorten this message and display it as on prod?

Image Happens, when I try placing a ETH-flow order with 21000 gas limit

Done

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/common-utils/src/misc.ts`:
- Line 130: The current return uses error.message as string without verifying
its runtime type; update the guard to check typeof error.message === 'string'
before returning it and otherwise fall back to a safe string (e.g.,
String(error) or ''), so callers like isRejectRequestProviderError won't crash
when calling string methods; locate the check around the variable error in
libs/common-utils/src/misc.ts and replace the unconditional cast with a typeof
check and safe fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ced9596e-0ed1-465d-bc67-e05e2679b1f5

📥 Commits

Reviewing files that changed from the base of the PR and between 1426f98 and 5a0893c.

📒 Files selected for processing (1)
  • libs/common-utils/src/misc.ts

// Prefer viem's shortMessage (concise, human-readable) over the full message
// which includes verbose request arguments and hex data.
if ('shortMessage' in error && typeof error.shortMessage === 'string') return error.shortMessage
if ('message' in error) return error.message as string
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard message type before returning to avoid downstream crashes

At Line 130, error.message as string can return non-string values at runtime. That can break consumers that call string methods (e.g., toLowerCase() in isRejectRequestProviderError). Please add a runtime typeof check.

Suggested fix
-    if ('message' in error) return error.message as string
+    if ('message' in error && typeof error.message === 'string') return error.message
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ('message' in error) return error.message as string
if ('message' in error && typeof error.message === 'string') return error.message
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/common-utils/src/misc.ts` at line 130, The current return uses
error.message as string without verifying its runtime type; update the guard to
check typeof error.message === 'string' before returning it and otherwise fall
back to a safe string (e.g., String(error) or ''), so callers like
isRejectRequestProviderError won't crash when calling string methods; locate the
check around the variable error in libs/common-utils/src/misc.ts and replace the
unconditional cast with a typeof check and safe fallback.

Copy link
Copy Markdown
Contributor

@elena-zh elena-zh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you

@elena-zh elena-zh requested a review from a team May 5, 2026 10:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants