Skip to content

Conversation

@shoom3301
Copy link
Collaborator

@shoom3301 shoom3301 commented Oct 21, 2025

Summary

Fixes https://www.notion.so/cownation/Safe-partial-amount-is-sent-to-approve-when-the-setting-is-OFF-2878da5f04ca80b184a5d97b9c8c8a18

Added support of partial approvals in Safe, it should work the same as usual swap but with tx bundling.

image

To Test

See above

Summary by CodeRabbit

  • Refactor

    • Improved internal state management for partial approval features to enhance code organization and maintainability.
    • Streamlined approval amount calculation logic and optimized context dependencies.
    • Updated state synchronization mechanisms across approval workflows.
  • Tests

    • Updated test mocks to align with state management improvements.

@vercel
Copy link

vercel bot commented Oct 21, 2025

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

Project Deployment Preview Updated (UTC)
cowfi Ready Ready Preview Oct 24, 2025 2:37pm
explorer-dev Ready Ready Preview Oct 24, 2025 2:37pm
swap-dev Ready Ready Preview Oct 24, 2025 2:37pm
widget-configurator Ready Ready Preview Oct 24, 2025 2:37pm
2 Skipped Deployments
Project Deployment Preview Updated (UTC)
cosmos Ignored Ignored Oct 24, 2025 2:37pm
sdk-tools Ignored Ignored Preview Oct 24, 2025 2:37pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 21, 2025

Walkthrough

Migrates partial approval state management from hook-based to Jotai atom approach. Introduces Erc20ApproveWidget component to synchronize state, refactors SafeBundleFlowContext to pass computed amountToApprove instead of a flag, and updates related hooks and services to consume the new state structure.

Changes

Cohort / File(s) Summary
Jotai Atom Foundation
apps/cowswap-frontend/src/modules/erc20Approve/state/isPartialApproveEnabledAtom.ts
New module defining isPartialApproveEnabledAtom with default value false for centralized partial approval state
ERC20 Approve Widget & Export
apps/cowswap-frontend/src/modules/erc20Approve/containers/Erc20ApproveWidget/index.tsx
apps/cowswap-frontend/src/modules/erc20Approve/containers/index.ts
New Erc20ApproveWidget component that syncs isPartialApprovalEnabled prop to atom via useSetAtom, rendered as null; added to public exports
Hook Migration to Atom
apps/cowswap-frontend/src/modules/erc20Approve/hooks/useGetAmountToSignApprove.tsx
apps/cowswap-frontend/src/modules/erc20Approve/hooks/useGetAmountToSignApprove.test.ts
Replaced hook-based toggle state with useAtomValue to read isPartialApproveEnabledAtom; updated tests to mock atom value instead of hook tuple
Swap Flow Integration
apps/cowswap-frontend/src/modules/swap/updaters/index.tsx
apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts
apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx
Integrated Erc20ApproveWidget with swap settings flag in updaters; removed useSafeMemoObject wrapping in flow context; updated hook return type annotation
Trade Flow Context & Services
apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts
apps/cowswap-frontend/src/modules/tradeFlow/hooks/useHandleSwap.ts
apps/cowswap-frontend/src/modules/tradeFlow/hooks/useSafeBundleFlowContext.ts
apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts
apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts
Replaced isPartialApproveEnabled flag with amountToApprove in SafeBundleFlowContext; updated approval services to use pre-computed amount; added explicit return type to useHandleSwap

Sequence Diagram

sequenceDiagram
    participant SwapUpdaters
    participant Erc20ApproveWidget
    participant Jotai Atom Store
    participant useGetAmountToSignApprove
    participant SafeBundleContext

    SwapUpdaters->>Erc20ApproveWidget: render with isPartialApprovalEnabled prop
    Erc20ApproveWidget->>Jotai Atom Store: useSetAtom updates isPartialApproveEnabledAtom
    
    rect rgb(230, 245, 255)
        Note over useGetAmountToSignApprove,Jotai Atom Store: Atom-driven flow
        useGetAmountToSignApprove->>Jotai Atom Store: useAtomValue reads isPartialApproveEnabledAtom
        Jotai Atom Store-->>useGetAmountToSignApprove: returns boolean value
        useGetAmountToSignApprove->>useGetAmountToSignApprove: compute amountToApprove
    end
    
    useGetAmountToSignApprove-->>SafeBundleContext: amountToApprove provided
    SafeBundleContext->>SafeBundleContext: memoize with amountToApprove included
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

Partial approve

Suggested reviewers

  • elena-zh
  • limitofzero
  • alfetopito

Poem

🐰 Hops of joy through jotai's halls,
Atoms sync when approval calls,
No more hooks, just state so pure,
Safe bundle flows, we now ensure,
Partial approvals, refined and fleet!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ⚠️ Warning The pull request description partially follows the template structure. The Summary section is well-completed with a clear issue link, high-level description of the change (adding partial approval support for Safe with transaction bundling), and a screenshot showing the expected behavior. However, the "To Test" section is largely incomplete—it only contains "See above" without providing the structured testing steps, numbered instructions, or checkbox-style verification items that the template requires. The Background section is entirely absent, though this is marked as optional in the template. The vague testing instructions would leave a QA reviewer uncertain about the specific steps needed to verify the implementation. The "To Test" section needs to be expanded with concrete, reproducible steps. The author should replace "See above" with numbered steps (1, 2, etc.) and include checkbox lists (with [ ] format) that detail what a QA person should verify when testing partial approvals in Safe. For example, steps might include opening specific pages, performing approval actions, and verifying that the correct amounts are approved based on the partial approval setting being on or off.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix(approve): support partial approvals in Safe" is directly and clearly related to the changeset. The PR implements support for partial approvals in Safe through multiple coordinated changes: creating a new Jotai atom for partial approval state management, integrating it into the swap widget and approval flows, and updating the trade flow context to use computed approval amounts instead of boolean flags. The title accurately summarizes the primary objective and is specific enough to convey the main purpose of the changes without unnecessary detail.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/safe-partial-approvals

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.

Copy link
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: 0

🧹 Nitpick comments (2)
apps/cowswap-frontend/src/modules/erc20Approve/containers/Erc20ApproveWidget/index.tsx (1)

10-18: LGTM! State synchronization component works correctly.

The Erc20ApproveWidget component effectively synchronizes the isPartialApprovalEnabled prop with the global atom state. The pattern of rendering null while performing side effects via useEffect is appropriate for this use case.

Minor optimization: The setIsPartialApproveEnabled function returned by useSetAtom is stable across renders, so it's not strictly necessary in the dependency array. However, including it follows React's exhaustive-deps rule and doesn't cause any issues.

If you prefer a minimal dependency array, you can apply this diff:

   useEffect(() => {
     setIsPartialApproveEnabled(isPartialApprovalEnabled)
-  }, [setIsPartialApproveEnabled, isPartialApprovalEnabled])
+  }, [isPartialApprovalEnabled])
apps/cowswap-frontend/src/modules/erc20Approve/hooks/useGetAmountToSignApprove.tsx (1)

34-34: Clarify the triple-condition logic for partial approvals.

The partial approval logic requires three separate conditions to all be true:

  1. isPartialApproveEnabled (feature flag)
  2. isPartialApprovalSelectedByUser (user selection)
  3. isPartialApprovalEnabledInSettings (from atom)

The distinction between conditions 2 and 3 is not immediately clear. Please consider adding a comment explaining what each condition represents and why all three are necessary, or verify if this complexity can be simplified.

Consider adding a JSDoc comment above line 22 or inline comment above line 34 explaining the three-gate logic:

+  // Partial approval requires:
+  // 1. Feature flag enabled globally
+  // 2. User selected partial approval for this transaction
+  // 3. Partial approval enabled in swap settings
   if (isPartialApproveEnabled && isPartialApprovalSelectedByUser && isPartialApprovalEnabledInSettings) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5646eae and 839848e.

📒 Files selected for processing (13)
  • apps/cowswap-frontend/src/modules/erc20Approve/containers/Erc20ApproveWidget/index.tsx (1 hunks)
  • apps/cowswap-frontend/src/modules/erc20Approve/containers/index.ts (1 hunks)
  • apps/cowswap-frontend/src/modules/erc20Approve/hooks/useGetAmountToSignApprove.test.ts (20 hunks)
  • apps/cowswap-frontend/src/modules/erc20Approve/hooks/useGetAmountToSignApprove.tsx (3 hunks)
  • apps/cowswap-frontend/src/modules/erc20Approve/state/isPartialApproveEnabledAtom.ts (1 hunks)
  • apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx (1 hunks)
  • apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts (1 hunks)
  • apps/cowswap-frontend/src/modules/swap/updaters/index.tsx (3 hunks)
  • apps/cowswap-frontend/src/modules/tradeFlow/hooks/useHandleSwap.ts (1 hunks)
  • apps/cowswap-frontend/src/modules/tradeFlow/hooks/useSafeBundleFlowContext.ts (2 hunks)
  • apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts (2 hunks)
  • apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts (2 hunks)
  • apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-10-13T19:41:31.440Z
Learnt from: limitofzero
PR: cowprotocol/cowswap#6351
File: apps/cowswap-frontend/src/modules/erc20Approve/containers/TradeApproveModal/useTradeApproveCallback.ts:87-121
Timestamp: 2025-10-13T19:41:31.440Z
Learning: In apps/cowswap-frontend/src/modules/erc20Approve, useApproveCallback returns Promise<TransactionResponse | undefined> and is distinct from useApproveCurrency, which can return Promise<TransactionReceipt | SafeMultisigTransactionResponse>. When reviewing approval flows, verify which hook is actually being used before flagging Safe wallet concerns.

Applied to files:

  • apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts
  • apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts
  • apps/cowswap-frontend/src/modules/erc20Approve/hooks/useGetAmountToSignApprove.tsx
  • apps/cowswap-frontend/src/modules/tradeFlow/hooks/useSafeBundleFlowContext.ts
  • apps/cowswap-frontend/src/modules/swap/updaters/index.tsx
📚 Learning: 2025-04-02T09:58:29.374Z
Learnt from: shoom3301
PR: cowprotocol/cowswap#5549
File: apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts:152-152
Timestamp: 2025-04-02T09:58:29.374Z
Learning: In the `safeBundleEthFlow` function, `account` is guaranteed to be truthy based on the type system (`PostOrderParams` defines it as a required string) and the context in which the function is called, so additional runtime checks are unnecessary.

Applied to files:

  • apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts
  • apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts
  • apps/cowswap-frontend/src/modules/tradeFlow/hooks/useSafeBundleFlowContext.ts
📚 Learning: 2025-02-20T15:59:33.749Z
Learnt from: shoom3301
PR: cowprotocol/cowswap#5443
File: apps/cowswap-frontend/src/modules/swap/containers/ConfirmSwapModalSetup/index.tsx:71-71
Timestamp: 2025-02-20T15:59:33.749Z
Learning: The swap module in apps/cowswap-frontend/src/modules/swap/ is marked for deletion in PR #5444 as part of the swap widget unification effort.

Applied to files:

  • apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx
📚 Learning: 2025-10-10T20:28:16.565Z
Learnt from: fairlighteth
PR: cowprotocol/cowswap#6347
File: apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx:49-49
Timestamp: 2025-10-10T20:28:16.565Z
Learning: In apps/cowswap-frontend/src/modules/trade, TradeConfirmation follows a two-layer architecture: TradeConfirmationView (pure/stateless) in pure/TradeConfirmation/index.tsx renders the UI, while TradeConfirmation (container) in containers/TradeConfirmation/index.tsx wraps it to freeze props during pending trades (via useStableTradeConfirmationProps), wire in signing state (useSigningStep), and inject trade confirmation state (useTradeConfirmState). Consuming modules should import the container TradeConfirmation from 'modules/trade' to preserve this stateful behavior.
<!-- [add_learning]
When reviewing component refactoring in apps/cowswap-frontend/src/modules/trade, recognize the pattern where a pure view component (e.g., TradeConfirmationView) is separated from a stateful container (e.g., TradeConfirmation) that wraps it. The container adds runtime state management (prop stabilization, signing state, etc.) while the view remains testable and composable. Do not flag usages that import th...

Applied to files:

  • apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx
🧬 Code graph analysis (7)
apps/cowswap-frontend/src/modules/erc20Approve/containers/Erc20ApproveWidget/index.tsx (1)
apps/cowswap-frontend/src/modules/erc20Approve/state/isPartialApproveEnabledAtom.ts (1)
  • isPartialApproveEnabledAtom (3-3)
apps/cowswap-frontend/src/modules/erc20Approve/hooks/useGetAmountToSignApprove.tsx (1)
apps/cowswap-frontend/src/modules/erc20Approve/state/isPartialApproveEnabledAtom.ts (1)
  • isPartialApproveEnabledAtom (3-3)
apps/cowswap-frontend/src/modules/tradeFlow/hooks/useHandleSwap.ts (2)
apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowContext.ts (1)
  • TradeFlowParams (34-36)
apps/cowswap-frontend/src/modules/trade/containers/TradeWidget/types.ts (1)
  • TradeWidgetActions (8-14)
apps/cowswap-frontend/src/modules/tradeFlow/hooks/useSafeBundleFlowContext.ts (5)
apps/cowswap-frontend/src/modules/erc20Approve/hooks/useGetAmountToSignApprove.tsx (1)
  • useGetAmountToSignApprove (22-46)
libs/wallet/src/api/hooks/useSendBatchTransactions.ts (1)
  • useSendBatchTransactions (13-43)
apps/cowswap-frontend/src/common/hooks/useContract.ts (2)
  • useWethContract (102-104)
  • useTokenContract (98-100)
apps/cowswap-frontend/src/common/hooks/useNeedsApproval.ts (1)
  • useNeedsApproval (23-37)
libs/common-utils/src/getCurrencyAddress.ts (1)
  • getCurrencyAddress (6-8)
apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts (3)
apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts (1)
  • TradeFlowContext (26-53)
apps/cowswap-frontend/src/modules/swap/hooks/useSwapSettings.ts (1)
  • useSwapDeadlineState (14-22)
apps/cowswap-frontend/src/modules/tradeFlow/hooks/useTradeFlowContext.ts (1)
  • useTradeFlowContext (41-235)
apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx (1)
apps/cowswap-frontend/src/modules/tradeFlow/hooks/useHandleSwap.ts (1)
  • useHandleSwap (20-104)
apps/cowswap-frontend/src/modules/swap/updaters/index.tsx (2)
apps/cowswap-frontend/src/modules/swap/hooks/useSwapSettings.ts (2)
  • useSwapDeadlineState (14-22)
  • useSwapSettings (10-12)
apps/cowswap-frontend/src/modules/erc20Approve/containers/Erc20ApproveWidget/index.tsx (1)
  • Erc20ApproveWidget (10-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cypress
  • GitHub Check: Setup
🔇 Additional comments (16)
apps/cowswap-frontend/src/modules/swap/containers/SwapWidget/index.tsx (1)

89-89: Code change is correct; no performance issue exists.

The concern about removing useSafeMemoObject is not valid. useTradeFlowContext destructures the deadline value from the params object and only uses that scalar value. The SWR hook's dependency array includes validTo (derived from the deadline value), not the params object reference, so the new object reference on each render has no effect. The deadline value itself is stable since it comes from state.

apps/cowswap-frontend/src/modules/erc20Approve/containers/index.ts (1)

10-10: LGTM!

The export follows the established pattern in this file and properly exposes the new Erc20ApproveWidget component.

apps/cowswap-frontend/src/modules/erc20Approve/state/isPartialApproveEnabledAtom.ts (1)

1-3: LGTM!

The atom definition is straightforward and follows Jotai best practices. The default value of false is appropriate for the partial approval feature.

apps/cowswap-frontend/src/modules/tradeFlow/types/TradeFlowContext.ts (1)

61-61: LGTM! Interface change makes approval amount explicit.

Replacing the optional boolean flag isPartialApproveEnabled? with a required amountToApprove: CurrencyAmount<Currency> is a good design improvement. It makes the approval amount explicit and type-safe, eliminating ambiguity about what amount should be approved.

This is a breaking change to the SafeBundleFlowContext interface, but the refactor appears comprehensive across the codebase.

apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleEthFlow.ts (2)

50-50: LGTM! Consistent with the refactored approval flow.

The destructuring of amountToApprove from safeBundleContext aligns with the interface changes and mirrors the pattern used in safeBundleApprovalFlow.ts.


85-85: LGTM! Direct usage of amountToApprove from context.

The change to use BigInt(amountToApprove.quotient.toString()) directly is correct and consistent with the refactored flow where the amount is pre-computed and provided via context.

apps/cowswap-frontend/src/modules/tradeFlow/hooks/useHandleSwap.ts (1)

20-23: LGTM! Explicit return type improves type safety.

Adding an explicit return type to useHandleSwap is a good practice that enhances type safety and makes the hook's contract clearer to consumers.

apps/cowswap-frontend/src/modules/erc20Approve/hooks/useGetAmountToSignApprove.test.ts (3)

1-1: LGTM! Test setup correctly migrated to Jotai atom mocking.

The test file has been properly updated to mock useAtomValue instead of the previous hook-based partial approval state. The mocking approach aligns with the new atom-based state management.

Also applies to: 16-19, 37-37


60-61: LGTM! Default mock values are appropriate.

The default mock configuration with mockUseAtomValue.mockReturnValue(true) in beforeEach sets up a reasonable baseline for most test cases.


85-90: LGTM! Test cases comprehensively cover partial approval scenarios.

The test cases properly verify behavior across different combinations of:

  • isPartialApproveEnabled (feature flag)
  • isPartialApprovalSelectedByUser (user selection)
  • isPartialApprovalEnabledInSettings (settings from atom)

All scenarios correctly mock useAtomValue to simulate different settings states.

Also applies to: 98-103, 109-114, 120-125

apps/cowswap-frontend/src/modules/tradeFlow/services/safeBundleFlow/safeBundleApprovalFlow.ts (1)

45-62: Upstream amountToApprove computation is correct—no issues found.

Verification confirms that useGetAmountToSignApprove() (called in useSafeBundleFlowContext line 18) correctly computes the amount based on partial approval settings. The hook returns: zero if approval isn't needed; the partial amount if partial approval is enabled, user-selected, and configured in settings; otherwise the max/unlimited amount. All feature flags, user selections, and settings are properly checked and memoized.

apps/cowswap-frontend/src/modules/tradeFlow/hooks/useSafeBundleFlowContext.ts (2)

7-7: LGTM: Clean migration from boolean flag to concrete amount.

The migration from isPartialApproveEnabled to amountToApprove provides consumers with the actual approval amount rather than just a boolean flag, enabling more precise decision-making in the approval flow.

Also applies to: 18-18, 42-42


31-44: Verify the null guard on amountToApprove.

The null guard on line 32 prevents the context from being created when amountToApprove is null. However, safeBundleEthFlow only uses amountToApprove when needsApproval is true (line 81 of safeBundleEthFlow), meaning this flow can execute with needsApproval=false and a null amountToApprove. The current null guard unnecessarily blocks context creation in this scenario.

Consider relaxing the null guard to only require amountToApprove when needsApproval is true, or make its requirement conditional based on which flow will execute.

apps/cowswap-frontend/src/modules/erc20Approve/hooks/useGetAmountToSignApprove.tsx (1)

1-1: LGTM: Clean atom migration.

The migration from hook-based to atom-based state reading for isPartialApprovalEnabledInSettings is straightforward and maintains the existing logic flow.

Also applies to: 13-13, 27-27

apps/cowswap-frontend/src/modules/swap/hooks/useSwapFlowContext.ts (1)

1-9: LGTM: Excellent simplification.

The removal of the useSafeMemoObject wrapper and direct pass-through to useTradeFlowContext improves maintainability. The deadline value comes from useSwapDeadlineState, which provides a stable reference that only changes when the user updates the setting, so the plain object at line 8 is appropriate.

apps/cowswap-frontend/src/modules/swap/updaters/index.tsx (1)

6-6: LGTM: Clean integration of approval state synchronization.

The Erc20ApproveWidget integration properly establishes the uni-directional data flow from swap settings to the global atom. The placement in SwapUpdaters is appropriate for this side-effect component.

Also applies to: 17-17, 24-24, 39-39

Copy link
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 @shoom3301 , great job, thank you!
There is 1 issue related to the ETH-bundled-flow, but I opened another task to address it later

@alfetopito alfetopito merged commit 434fab4 into develop Oct 27, 2025
15 checks passed
@alfetopito alfetopito deleted the feat/safe-partial-approvals branch October 27, 2025 17:21
@github-actions github-actions bot locked and limited conversation to collaborators Oct 27, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants