Skip to content

feat(limit-orders): support approval bundling for EIP-7702 accounts - #7902

Open
tenderdeve wants to merge 12 commits into
cowprotocol:developfrom
tenderdeve:feat/7895-eip7702-limit-bundling
Open

feat(limit-orders): support approval bundling for EIP-7702 accounts#7902
tenderdeve wants to merge 12 commits into
cowprotocol:developfrom
tenderdeve:feat/7895-eip7702-limit-bundling

Conversation

@tenderdeve

@tenderdeve tenderdeve commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Closes #7895

Limit-order approval bundling was gated to Safe wallets (cd8af4c) because EIP-7702 accounts had no EIP-5792 lifecycle tracking — bundled orders showed as unfillable while approval + presign were still pending (see #7844 (comment), point 2).

What changed

Remove the Safe-only gate so any atomic-batch wallet can bundle approval + presign for limit orders (useHandleOrderPlacement, LimitOrdersConfirmModal, useTradeFormValidationContext). This reverts the temporary restriction.

Track EIP-7702 bundles via EIP-5792:

  • Bundled orders are still placed as PRESIGNATURE_PENDING (unchanged for Safe).
  • For non-Safe atomic wallets sendBatchTransactions returns an EIP-5792 bundle id. It's stored on the order and flagged with presignIsEip5792Bundle, so it's tracked with getCallsStatus instead of the Safe transaction service (which can't resolve a bundle id).
  • Once the bundle is mined the backend reports the order as presigned and the existing poll promotes it to PENDING.
  • Failed bundles are invalidated so they don't hang in PRESIGNATURE_PENDING.

The Safe flow is untouched: orders without presignIsEip5792Bundle keep polling the Safe tx service exactly as before.

Notes

  • Scoped to limit orders (swaps were never gated).
  • Bundle id stored in presignGnosisSafeTxHash with a discriminator flag rather than a new field, to keep the diff contained.
  • tsc passes. Couldn't exercise the wallet lifecycle locally (needs an EIP-7702 wallet) — the getCallsStatus success/failure/pending branches would benefit from a review pass.

Summary by CodeRabbit

  • New Features

    • Added approval and signature bundle support for EIP-7702 wallets when placing limit orders.
    • Expanded bundled approval flows beyond Safe wallets when permit-based approval is unavailable.
    • Bundled orders now proceed directly to the Signing tab after placement.
  • Bug Fixes

    • Improved tracking of pending bundled transactions.
    • Failed bundles now invalidate associated orders instead of remaining stuck in “signature pending.”
    • Pre-signed orders now use the appropriate on-chain cancellation flow.

Limit-order approval bundling was restricted to Safe wallets while EIP-7702
accounts lacked EIP-5792 lifecycle tracking, which showed bundled orders as
unfillable while approval and presign were still pending.

Remove the Safe-only gate so any atomic-batch wallet can bundle approval and
presign, and track EIP-7702 bundles through their EIP-5792 lifecycle:

- Bundled orders are placed as PRESIGNATURE_PENDING (unchanged for Safe).
- For non-Safe atomic wallets the returned value is an EIP-5792 bundle id,
  flagged on the order via presignIsEip5792Bundle and tracked with
  getCallsStatus instead of the Safe transaction service.
- Once the bundle is mined the backend reports the order as presigned and the
  existing poll promotes it to PENDING; failed bundles are invalidated so they
  don't hang in PRESIGNATURE_PENDING.

Reverts the temporary Safe-only restriction (cd8af4c).
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

@tenderdeve is attempting to deploy a commit to the cow-dev Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4658aa51-61a5-44fb-b63d-32e3de4d380f

📥 Commits

Reviewing files that changed from the base of the PR and between 883eae1 and 594d57f.

📒 Files selected for processing (7)
  • apps/cowswap-frontend/src/common/hooks/useCancelOrder/index.ts
  • apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts
  • apps/cowswap-frontend/src/legacy/state/orders/actions.ts
  • apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersConfirmModal/index.tsx
  • apps/cowswap-frontend/src/modules/limitOrders/hooks/useHandleOrderPlacement.ts
  • apps/cowswap-frontend/src/modules/limitOrders/services/safeBundleFlow/index.ts
  • apps/cowswap-frontend/src/modules/tradeFormValidation/hooks/useTradeFormValidationContext.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersConfirmModal/index.tsx
  • apps/cowswap-frontend/src/common/hooks/useCancelOrder/index.ts
  • apps/cowswap-frontend/src/modules/tradeFormValidation/hooks/useTradeFormValidationContext.ts
  • apps/cowswap-frontend/src/modules/limitOrders/hooks/useHandleOrderPlacement.ts
  • apps/cowswap-frontend/src/modules/limitOrders/services/safeBundleFlow/index.ts
  • apps/cowswap-frontend/src/legacy/state/orders/actions.ts
  • apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts

Walkthrough

Limit-order approval bundling now supports EIP-7702 wallets. Orders record EIP-5792 bundle identifiers, poll getCallsStatus, invalidate failed bundles, and skip Safe transaction polling.

Changes

EIP-5792 limit-order bundling

Layer / File(s) Summary
Enable bundling beyond Safe wallets
apps/cowswap-frontend/src/modules/tradeFormValidation/..., apps/cowswap-frontend/src/modules/limitOrders/...
Bundling and approval selection no longer require Safe-wallet status when permits cannot be used. Successful bundle placement navigates to the Signing tab.
Record EIP-5792 bundle identifiers
apps/cowswap-frontend/src/legacy/state/orders/actions.ts, apps/cowswap-frontend/src/modules/limitOrders/services/safeBundleFlow/index.ts, apps/cowswap-frontend/src/common/hooks/useCancelOrder/index.ts
Orders record whether their presign reference is an EIP-5792 bundle identifier. Pre-signed orders use the on-chain cancellation path.
Poll EIP-5792 bundle status
apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts
Pending bundle identifiers are polled with getCallsStatus. Failed bundles are invalidated and excluded from Safe transaction polling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PendingOrdersUpdater
  participant getCallsStatus
  participant invalidateOrdersBatch
  PendingOrdersUpdater->>getCallsStatus: Poll EIP-5792 bundle status
  getCallsStatus-->>PendingOrdersUpdater: Return status
  PendingOrdersUpdater->>invalidateOrdersBatch: Invalidate failed bundle orders
Loading

Possibly related PRs

Suggested reviewers: kernelwhisperer, danziger, elena-zh

Poem

A rabbit bundles hops with care,
EIP-5792 moves through air.
Failed calls leave the pending line,
Safe polling skips the bundle sign.
Orders now resolve their fate. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes support for approval bundling with EIP-7702 accounts.
Description check ✅ Passed The description includes the linked issue, change summary, implementation details, scope, and testing status, but omits the template's formal test and self-check sections.
Linked Issues check ✅ Passed The changes remove the Safe-only restriction, track EIP-5792 bundles, handle lifecycle outcomes, and preserve Safe polling behavior as required by issue #7895.
Out of Scope Changes check ✅ Passed All changes support approval bundling, bundle lifecycle tracking, navigation, or cancellation behavior described in issue #7895.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 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.

@coderabbitai coderabbitai Bot left a comment

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.

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 `@apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts`:
- Around line 347-389: The catch block in _updateEip5792BundleStatus should
distinguish terminal EIP-5792 unknown-bundle RPC errors (code 5730) from
transient wallet-indexing failures. Add bounded retry/expiry tracking for status
checks, invalidate the associated order when error 5730 occurs or the retry
limit expires, and retain polling retries for transient errors within the bound.
🪄 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 Plus

Run ID: 220d851b-815c-4a30-8970-395b4d5d1e2e

📥 Commits

Reviewing files that changed from the base of the PR and between 3362a47 and c9a8a76.

📒 Files selected for processing (6)
  • apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts
  • apps/cowswap-frontend/src/legacy/state/orders/actions.ts
  • apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersConfirmModal/index.tsx
  • apps/cowswap-frontend/src/modules/limitOrders/hooks/useHandleOrderPlacement.ts
  • apps/cowswap-frontend/src/modules/limitOrders/services/safeBundleFlow/index.ts
  • apps/cowswap-frontend/src/modules/tradeFormValidation/hooks/useTradeFormValidationContext.ts

Comment on lines +347 to +389
/**
* Track EIP-5792 approval+presign bundles submitted by EIP-7702 wallets.
*
* These orders store an EIP-5792 bundle id in `presignGnosisSafeTxHash` (flagged by
* `presignIsEip5792Bundle`) and cannot be resolved through the Safe transaction service. We poll the
* wallet with `getCallsStatus` instead: while the bundle is pending the order stays in
* PRESIGNATURE_PENDING, and once mined the backend reports the order as presigned so the regular poll
* promotes it to PENDING. If the bundle fails, we invalidate the order so it doesn't hang forever.
*/
async function _updateEip5792BundleStatus(
chainId: ChainId,
allPendingOrders: Order[],
config: Config,
invalidateOrdersBatch: InvalidateOrdersBatchCallback,
isSafeWallet: boolean,
): Promise<void> {
const promises = allPendingOrders
.filter(
(order) =>
order.presignIsEip5792Bundle &&
order.presignGnosisSafeTxHash &&
order.status === OrderStatus.PRESIGNATURE_PENDING,
)
.map((order): Promise<void> => {
const bundleId = order.presignGnosisSafeTxHash as string

return getCallsStatus(config, { id: bundleId })
.then((result) => {
// 'pending' -> keep waiting; 'success' -> backend will report the order presigned and the
// regular poll moves it to PENDING, so nothing to do here.
if (result.status === 'failure') {
console.warn('[PendingOrdersUpdater] EIP-5792 bundle failed, invalidating order:', order.id, bundleId)
invalidateOrdersBatch({ ids: [order.id], chainId, isSafeWallet })
}
})
.catch((error) => {
// The wallet may not have indexed the bundle yet; keep polling on the next tick.
console.debug('[PendingOrdersUpdater] Failed to fetch EIP-5792 bundle status:', bundleId, error)
})
})

await Promise.all(promises)
}

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.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the relevant file and nearby update flow without running repo code.
if [ -f apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts ]; then
  echo "=== file exists ==="
  wc -l apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts
  echo "=== outline ==="
  ast-grep outline apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts || true
  echo "=== relevant lines 300-410 ==="
  sed -n '300,410p' apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts | nl -ba - v300
  echo "=== getCallsStatus references ==="
  rg -n "getCallsStatus|PRESIGNATURE_PENDING|_updateEip5792BundleStatus|presignIsEip5792Bundle|presignGnosisSafeTxHash" apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts -C 3
else
  echo "missing file"
fi

echo "=== broader getCallsStatus definitions/usages ==="
rg -n "getCallsStatus|type .*Call|call|eip-5792|EIP-5792|rpc" apps/cowswap-frontend/src -S || true

Repository: cowprotocol/cowswap

Length of output: 1430


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== relevant lines 340-405 ==="
sed -n '340,405p' apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts

echo "=== call status polling / retry constants around updater ==="
sed -n '1,180p' apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts
sed -n '180,340p' apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts

echo "=== broader getCallsStatus references ==="
rg -n "getCallsStatus|PRESIGNATURE_PENDING|_updateEip5792BundleStatus|presignIsEip5792Bundle|presignGnosisSafeTxHash" apps/cowswap-frontend/src -S || true

Repository: cowprotocol/cowswap

Length of output: 23700


🌐 Web query:

EIP-5792 getCallsStatus unknown batchId error pending response

💡 Result:

The "unknown batchId" error in the context of EIP-5792 is formally represented by RPC error code 5730, which explicitly indicates that the provided bundle ID is unknown or has not been submitted via wallet_sendCalls [1][2]. If you are encountering this error, it typically means one of the following: 1. Invalid or Expired ID: The batchId (or id) provided to wallet_getCallsStatus does not match any active or recent batch known to the wallet [1][2]. Per the EIP-5792 specification, wallets are recommended to maintain status information for at least 24 hours after the corresponding wallet_sendCalls call [1][2]. If you are attempting to poll for a status after this window, or if the wallet has cleared its cache, the ID will be unknown [1]. 2. Incorrect ID format: Ensure that the id being passed to wallet_getCallsStatus is the exact string returned by the initial wallet_sendCalls call [1][3]. 3. Wallet Implementation Issues: Some wallets may not persist batch IDs reliably across sessions or may have bugs in their implementation of the wallet_getCallsStatus method [4]. Regarding "pending" responses: The wallet_getCallsStatus method is designed to be polled [5]. When a batch has been submitted but not yet fully processed/included on-chain, the wallet should return a status code of 100 (Pending) [6][7]. If you are implementing a client, you should continue to poll the getCallsStatus endpoint until the status transitions to a terminal state, such as 200 (Success) or various failure codes (e.g., 300-700 range) [6][8]. If you are using libraries like Viem or Wagmi, they often abstract these RPC calls [9][4]. If you see inconsistent results (e.g., "failure" status while transactions actually succeeded), it may be due to the wallet's specific implementation of status reporting or a divergence in how the client interprets those codes [4][10]. In such cases, verifying the transaction hashes directly via eth_getTransactionReceipt is a standard fallback strategy to determine the true state of the transactions [10].

Citations:


🌐 Web query:

EIP-5792 rpcErrorCode 5730 unknownCallsBatchId wallet sendCalls getCallsStatus

💡 Result:

In the context of EIP-5792, rpcErrorCode 5730 represents an Unknown Bundle ID error [1][2]. This error occurs when a wallet receives a request containing a call bundle identifier that it does not recognize or that has not been previously submitted via wallet_sendCalls [1][2]. This error code is specifically associated with the following EIP-5792 RPC methods [1][2]: 1. wallet_getCallsStatus: Used by applications to query the status of a previously submitted call batch. If the provided ID is unknown to the wallet, it must return error 5730 [3][4]. 2. wallet_showCallsStatus: Used to request that a wallet display information about a specific call bundle. Similarly, if the ID is not recognized, the wallet returns error 5730 [5][6]. In technical implementations, such as those using the Viem library, this is categorized as an UnknownBundleIdError [7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== package references for viem/wagmi versions ==="
for f in package.json apps/cowswap-frontend/package.json yarn.lock pnpm-lock.yaml package-lock.json; do
  if [ -f "$f" ]; then
    echo "---- $f ----"
    rg -n '"viem"|viem@|wagmi@|wagmi"' "$f" | head -50 || true
  fi
done

echo "=== deterministic EIP-5792 behavior from source if docs are vendored ==="
rg -n "unknownCallsBatchId|5730|UnknownBundleIdError|Unknown Calls Batch|getCallsStatus|Pending.*status" -S \
  node_modules \
  "$PWD" 2>/dev/null | head -80 || true

Repository: cowprotocol/cowswap

Length of output: 37965


Do not retry permanent bundle-status errors forever.

The catch block currently treats every getCallsStatus error as temporary. EIP-5792 uses RPC error 5730 for unknown bundle IDs, which should be treated as terminal; a permanently unavailable/unknown bundle ID can otherwise keep the order in PRESIGNATURE_PENDING until the next regular flow update. Add bounded retry/expiry handling and invalidate the order on terminal unknown-bundle errors while still allowing retries for transient wallet indexing failures.

🤖 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 `@apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.ts`
around lines 347 - 389, The catch block in _updateEip5792BundleStatus should
distinguish terminal EIP-5792 unknown-bundle RPC errors (code 5730) from
transient wallet-indexing failures. Add bounded retry/expiry tracking for status
checks, invalidate the associated order when error 5730 occurs or the retry
limit expires, and retain polling retries for transient errors within the bound.

@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
swap-dev Ready Ready Preview Aug 3, 2026 9:24am

Request Review

@elena-zh elena-zh left a comment

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.

Hey @tenderdeve , thank you, very nice!

Some nitpicks:

  1. I see that you've added 'signing' tab to the limit orders table for 7702 accounts. It appears when orders work with bundling. However, you did not apply a correct navigation which is working with SC wallets: a user must be navigated to the signing tab after placing an order. When the order is signed and executed, it should be navigated to the Open orders tab

  2. This isue also happens on develop with market orders, but since you're working with limit orders, I'll report it to you as well: it is impossible to cancel limit orders placed using bundling offchain: cancellation request fails

Image

It would be nice to handle this case as well

elena-zh and others added 4 commits July 28, 2026 12:47
Bundled orders go through the approve-and-presign flow, so they land in the
Signing tab until mined. EIP-7702 accounts are EOAs and so aren't caught by
isSmartContractWallet, which left them on the Open tab after placement. Route
to Signing whenever the bundle flow is used, matching smart-contract wallets.
Off-chain cancellation was offered whenever the wallet supports off-chain
signing, ignoring the order's own signing scheme. Pre-signed orders (e.g. an
approve+presign bundle, as used by EIP-7702 accounts) are signed on-chain, and
the orderbook rejects an off-chain cancellation for them, so the request failed.
Treat pre-signed orders as on-chain-cancellable only. Also covers pre-signed
market orders on develop.
@tenderdeve

Copy link
Copy Markdown
Contributor Author

@elena-zh thanks — both handled.

1. Signing-tab navigation (fae44f2): the post-placement nav only sent smart-contract wallets to the Signing tab, but EIP-7702 accounts are EOAs so they fell through to Open. Bundled orders go through the approve-and-presign flow (they're on-chain pre-signed → they sit in Signing until mined), so I now route to Signing whenever that bundle flow is used — matching SC wallets. Once the order is signed and executed it leaves the pre-signature state and shows up under Open as before.

2. Cancellation failing (f3b151e): root cause — off-chain cancellation was offered based purely on whether the wallet supports off-chain signing, ignoring the order's signing scheme. A pre-signed order (the bundle case) is signed on-chain, and the orderbook rejects an off-chain cancellation for it, so the request failed. I now treat pre-signed orders as on-chain-cancellable only, so the modal drives the on-chain path. This also covers the pre-signed market orders you saw failing on develop.

Both are hard for me to exercise without a 7702 setup — could you re-test placement→Signing→Open and cancellation on a bundled order when you have a chance?

@elena-zh

Copy link
Copy Markdown
Contributor

@tenderdeve , there is a conflict in the branch, could you please resolve it first?

Both are hard for me to exercise without a 7702 setup

I can tell you how: just create a Base smart account and connect a wallet to it:
image

Fund it and place the 1st order --> you will have 7702 account.

…2-limit-bundling

# Conflicts:
#	apps/cowswap-frontend/src/common/hooks/useCancelOrder/index.ts
@tenderdeve

Copy link
Copy Markdown
Contributor Author

@elena-zh conflict resolved in 46adf6436 (merged latest develop).

The conflict was in useCancelOrder: develop meanwhile extracted the cancellability check into isOrderOffChainCancellable, which requires the EIP-712 scheme. Since pre-signed (bundled) orders use PRESIGN, they're now excluded from off-chain cancellation there and fall back to the on-chain path — so my earlier inline guard for point 2 was redundant and I dropped it in favour of the upstream helper (it's covered by its rejects PRESIGN orders unit test). The signing-tab navigation fix for point 1 is unchanged.

tsc and eslint pass locally, and isOrderOffChainCancellable tests are green. Ready for another look — and if you get a chance, the 7702 re-test of placement→Signing→Open plus a bundled-order cancellation would be great.

@elena-zh
elena-zh self-requested a review August 3, 2026 15:08

@elena-zh elena-zh left a comment

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.

Looks good now

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(limit-orders): support approval bundling for EIP-7702 accounts

2 participants