feat(limit-orders): support approval bundling for EIP-7702 accounts - #7902
feat(limit-orders): support approval bundling for EIP-7702 accounts#7902tenderdeve wants to merge 12 commits into
Conversation
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).
|
@tenderdeve is attempting to deploy a commit to the cow-dev Team on Vercel. A member of the Team first needs to authorize it. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
🚧 Files skipped from review as they are similar to previous changes (7)
WalkthroughLimit-order approval bundling now supports EIP-7702 wallets. Orders record EIP-5792 bundle identifiers, poll ChangesEIP-5792 limit-order bundling
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
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
apps/cowswap-frontend/src/common/updaters/orders/PendingOrdersUpdater.tsapps/cowswap-frontend/src/legacy/state/orders/actions.tsapps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersConfirmModal/index.tsxapps/cowswap-frontend/src/modules/limitOrders/hooks/useHandleOrderPlacement.tsapps/cowswap-frontend/src/modules/limitOrders/services/safeBundleFlow/index.tsapps/cowswap-frontend/src/modules/tradeFormValidation/hooks/useTradeFormValidationContext.ts
| /** | ||
| * 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) | ||
| } |
There was a problem hiding this comment.
🩺 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 || trueRepository: 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 || trueRepository: 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:
- 1: https://eips.ethereum.org/EIPS/eip-5792
- 2: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-5792.md
- 3: https://docs.metamask.io/metamask-connect/evm/reference/json-rpc-api/wallet_getCallsStatus/
- 4: `getCallsStatus` indicates failure despite transactions succeeding wevm/wagmi#4755
- 5: https://www.dynamic.xyz/docs/javascript/reference/evm/eip-5792
- 6: https://www.eip5792.xyz/reference/getCallsStatus
- 7: https://eips.ethereum.org/EIPS/eip-7867
- 8: https://portal.thirdweb.com/references/typescript/latest/eip5792/getCallsStatus
- 9: https://viem.sh/docs/actions/wallet/getCallsStatus
- 10: https://cdn.jsdelivr.net/npm/viem@2.41.2/actions/wallet/getCallsStatus.ts
🌐 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:
- 1: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-5792.md
- 2: https://eips.ethereum.org/EIPS/eip-5792
- 3: https://www.eip5792.xyz/reference/getCallsStatus
- 4: https://docs.metamask.io/metamask-connect/evm/reference/json-rpc-api/wallet_getCallsStatus/
- 5: https://www.eip5792.xyz/reference/showCallsStatus
- 6: https://github.com/ethereum/EIPs/blob/269a53fd579737e9e8a2a573f786ce5ab179627a/EIPS/eip-5792.md
- 7: https://cdn.jsdelivr.net/npm/viem@2.53.1/_types/errors/rpc.d.ts
🏁 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 || trueRepository: 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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
elena-zh
left a comment
There was a problem hiding this comment.
Hey @tenderdeve , thank you, very nice!
Some nitpicks:
-
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
-
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
It would be nice to handle this case as well
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.
|
@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? |
|
@tenderdeve , there is a conflict in the branch, could you please resolve it first?
I can tell you how: just create a Base smart account and connect a wallet to it: 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
|
@elena-zh conflict resolved in The conflict was in
|
|
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. |

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:
PRESIGNATURE_PENDING(unchanged for Safe).sendBatchTransactionsreturns an EIP-5792 bundle id. It's stored on the order and flagged withpresignIsEip5792Bundle, so it's tracked withgetCallsStatusinstead of the Safe transaction service (which can't resolve a bundle id).PENDING.PRESIGNATURE_PENDING.The Safe flow is untouched: orders without
presignIsEip5792Bundlekeep polling the Safe tx service exactly as before.Notes
presignGnosisSafeTxHashwith a discriminator flag rather than a new field, to keep the diff contained.tscpasses. Couldn't exercise the wallet lifecycle locally (needs an EIP-7702 wallet) — thegetCallsStatussuccess/failure/pending branches would benefit from a review pass.Summary by CodeRabbit
New Features
Bug Fixes