Skip to content

fix(affiliate): show 'code linked' notification on partial fills - #7906

Closed
tenderdeve wants to merge 8 commits into
cowprotocol:developfrom
tenderdeve:fix/7527-affiliate-partial-fill-toast
Closed

tenderdeve wants to merge 8 commits into
cowprotocol:developfrom
tenderdeve:fix/7527-affiliate-partial-fill-toast

Conversation

@tenderdeve

@tenderdeve tenderdeve commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Closes #7527

Following up on my root-cause analysis — going with the direction proposed there.

Problem

The 'code linked' toast (useNotifyAffiliateLinkedCode) is mounted only from FulfilledOrderInfo, i.e. it fires on the ON_FULFILLED_ORDER event. A partially-filled order never reaches FULFILLED. Meanwhile useAffiliateTraderRecoverySideEffect already detects executed local trades with the partial-aware isExecutedNonIntegratorOrder and links the code — but silently, and because it links first it pre-empts the fulfilled-order toast via the isLinked guard. So on a partial fill no toast ever shows.

Fix

Fire the toast from the same partial-aware local-trade linking path:

  • Extracted buildAffiliateLinkedCodeSnackbar(refCode, timeCapDays); the snackbar id is now keyed on the ref code (not the order id). Since addSnackbarAtom dedupes by id, the fulfilled-order path and the recovery path can't double-toast.
  • The recovery hook shows the toast when it links from a local executed trade (partial or full). Cross-device orderbook recovery stays silent (the toast was already shown on the device where the code was linked).

No change for full fills (still one toast, now possibly from either path — deduped). Partial fills now get the toast.

Notes

  • tsc passes. Local jest for this module can't run here (unrelated bridge-provider import crash), so the wiring is CI-verified.
  • @kernelwhisperer — this is the consolidation @elena-zh flagged you on; happy to adjust if you'd prefer a different trigger point.

Summary by CodeRabbit

  • Bug Fixes
    • Improved affiliate code recovery notifications for partially filled local orders.
    • Added consistent, duplicate-free “code linked” notifications across recovery and order-completion flows.
    • Affiliate link notifications now use the appropriate code validity period.
    • Historical local links and orderbook recoveries no longer display unnecessary notifications.
    • Notifications are dismissed when switching accounts and suppressed for codes already present at initial evaluation.

The 'code linked' toast was fired only from the fulfilled-order event
(useNotifyAffiliateLinkedCode is mounted from FulfilledOrderInfo). A
partially-filled order never reaches FULFILLED, and the recovery side effect
already links the code silently from the partial-aware local-trade detection,
pre-empting the fulfilled path via the isLinked guard — so no toast ever showed.

Fire the toast from the same partial-aware local-trade linking path. The
snackbar id is keyed on the ref code so it dedupes with the fulfilled-order path
if both run, and the cross-device orderbook recovery stays silent.
@vercel

vercel Bot commented Jul 26, 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 26, 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: d0e8a38f-1776-4e8e-9d19-80874c9b7616

📥 Commits

Reviewing files that changed from the base of the PR and between c3c7ce6 and 8f90611.

📒 Files selected for processing (2)
  • apps/cowswap-frontend/src/modules/affiliate/hooks/useAffiliateTraderRecoverySideEffect.ts
  • apps/cowswap-frontend/src/modules/affiliate/hooks/useRefCodeFromLocalTrades.ts

Walkthrough

The affiliate linked-code snackbar now uses a reusable referral-code-keyed builder. Local-trade recovery returns creation timestamps, filters historical links, applies affiliate time-cap metadata, and dismisses active notifications when the account changes.

Changes

Affiliate notification recovery

Layer / File(s) Summary
Deduplicated linked-code snackbar
apps/cowswap-frontend/src/modules/affiliate/hooks/useNotifyAffiliateLinkedCode.tsx
Adds buildAffiliateLinkedCodeSnackbar and keys snackbar IDs by refCode instead of order.id.
Account-aware local-trade recovery
apps/cowswap-frontend/src/modules/affiliate/hooks/useRefCodeFromLocalTrades.ts, apps/cowswap-frontend/src/modules/affiliate/hooks/useAffiliateTraderRecoverySideEffect.ts
Returns the recovered code with its order creation time. Live local-code recoveries show a time-capped snackbar. Historical local links and orderbook recoveries remain silent. Account changes dismiss active affiliate snackbars.

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

Possibly related PRs

Suggested reviewers: kernelwhisperer, shoom3301, elena-zh

Poem

I’m a rabbit tracking each code,
With tidy snackbars in a row.
New local links may softly appear,
Old links stay quiet and clear.
Account changes clear the queue.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the affiliate notification fix for partially filled orders.
Description check ✅ Passed The description explains the problem and fix, links issue #7527, and documents testing limitations, although it omits a detailed To Test checklist.
Linked Issues check ✅ Passed The PR adds notifications for locally detected executed trades, including partial fills, as required by [#7527].
Out of Scope Changes check ✅ Passed The changes support the notification fix and related deduplication, historical-link, and account-switch behavior described in the objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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/modules/affiliate/hooks/useAffiliateTraderRecoverySideEffect.ts`:
- Around line 31-33: Update the local-code recovery branch in the effect using
useAffiliateTraderInfo so it waits until the metadata request finishes before
linking or showing the recovery snackbar. Track the hook’s loading state,
include it in the effect dependencies, and preserve
PROGRAM_DEFAULTS.AFFILIATE_TIME_CAP_DAYS as the fallback only after a completed
not-found or failed response.
🪄 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: c658695d-c019-4cd6-a0f7-ffba059e4782

📥 Commits

Reviewing files that changed from the base of the PR and between 37e9cff and bba2656.

📒 Files selected for processing (2)
  • apps/cowswap-frontend/src/modules/affiliate/hooks/useAffiliateTraderRecoverySideEffect.ts
  • apps/cowswap-frontend/src/modules/affiliate/hooks/useNotifyAffiliateLinkedCode.tsx

@kernelwhisperer
kernelwhisperer self-requested a review July 27, 2026 12:03
@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)
storybook Ready Ready Preview Jul 31, 2026 12:57pm
swap-dev Ready Ready Preview Jul 31, 2026 12:57pm

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, your implementaiton works in terms of a partial fill.

However, there are 2 issues:

  1. I have some accounts where I was able to link a code to 'apready traded' account: now, when I open these accounts, I see 'code linked' message. However, I should not see it.
    Example: 0x9FA3c00a92Ec5f96B1Ad2527ab41B3932EFEDa58
Image
  1. If I change an account, 'code linked' message does not disappear. Morevowe, the app may start showing another 'code linked' message for another account.
Image

It would be nice to hide the message once I change an account.

Thanks

…unt switch

The recovery side-effect fired the 'code linked' toast whenever it recovered a
code from local trades, so opening an already-linked account re-showed it, and
the sticky toast lingered when switching accounts. Record a per-account baseline
and only notify for a code that appears after it (a live partial fill), keeping
historical recovery silent like the orderbook path, and remove the toast when
the account changes.
@tenderdeve

Copy link
Copy Markdown
Contributor Author

@elena-zh thanks — both were real. The toast was fired from the recovery path, which runs on every visit and can't tell a code linked right now from one recovered from past trades, so already-linked accounts kept re-showing it, and the sticky toast lingered when switching accounts.

Fixed in df62d3d:

  1. The recovery path now records a per-account baseline and only raises the toast for a code that appears after it — i.e. a partial fill that links a code live this session. A code already present on load is treated as a historical link and stays silent, the same way the orderbook-recovery path already did. So opening 0x9FA3… (linked in the past) won't show it anymore.
  2. On account change the 'code linked' toast is now dismissed, so it can't stay on screen for — or bleed into — a different account.

Could you re-check with those accounts when you have a moment? I can't reproduce a live partial-fill link locally, so I'd like to confirm a genuinely new link still toasts while the historical ones stay quiet.

@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/modules/affiliate/hooks/useAffiliateTraderRecoverySideEffect.ts`:
- Around line 35-53: Update the affiliate account identity handling in
baselineAccountsRef and the account-change useEffect: key baseline entries with
getAddressKey(account), compare prevAccountRef.current and account using
areAddressesEqual, and store the normalized address key in
prevAccountRef.current after changes. Initialize prevAccountRef consistently
with that same normalized address form.
🪄 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: a6f313af-d516-4222-bd29-8b84fe414804

📥 Commits

Reviewing files that changed from the base of the PR and between bba2656 and c3c7ce6.

📒 Files selected for processing (1)
  • apps/cowswap-frontend/src/modules/affiliate/hooks/useAffiliateTraderRecoverySideEffect.ts

Comment on lines +35 to +53
// Accounts whose baseline has been recorded. A code already present on the first evaluation for an
// account is a pre-existing (historical) link and must stay silent; only a code that appears
// afterwards — a partial fill linking a code live in this session — should raise the toast.
const baselineAccountsRef = useRef<Set<string>>(new Set())
// Id of the affiliate toast currently shown, so it can be dismissed when the account changes.
const shownSnackbarIdRef = useRef<string | null>(null)
const prevAccountRef = useRef(account)

// Dismiss a lingering 'code linked' toast when the account changes, so it doesn't stay on screen
// for (or bleed into) a different account (#7527).
useEffect(() => {
if (prevAccountRef.current !== account) {
if (shownSnackbarIdRef.current) {
removeSnackbar(shownSnackbarIdRef.current)
shownSnackbarIdRef.current = null
}
prevAccountRef.current = account
}
}, [account, removeSnackbar])

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 2 'getAddressKey\(|areAddressesEqual\(' apps/cowswap-frontend/src -g '*.ts' -g '*.tsx'

Repository: cowprotocol/cowswap

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='apps/cowswap-frontend/src/modules/affiliate/hooks/useAffiliateTraderRecoverySideEffect.ts'
if [ -f "$target" ]; then
  wc -l "$target"
  cat -n "$target"
else
  echo "missing $target"
  fd -a 'useAffiliateTraderRecoverySideEffect.ts' .
fi

printf '\n--- dependency/import references in target ---\n'
rg -n 'cow-sdk|getAddressKey|areAddressesEqual|removeSnackbar|baselineAccountsRef|prevAccountRef|useAccount|useDisconnectRecoverySideEffect' "$target" || true

printf '\n--- package versions mentioning cow-sdk ---\n'
rg -n '"`@cowprotocol/cow-sdk`"|`@cowprotocol/cow-sdk`' package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null | head -40 || true

Repository: cowprotocol/cowswap

Length of output: 8582


🌐 Web query:

@cowprotocol/cow-sdk getAddressKey areAddressesEqual utility documentation

💡 Result:

In the @cowprotocol/cow-sdk, the functions getAddressKey and areAddressesEqual are utility functions found within the @cowprotocol/sdk-common package [1]. They are designed to simplify address handling and comparison across different blockchain formats. getAddressKey This function generates a normalized address key for any supported blockchain address type [2][3]. Signature: function getAddressKey(address: string): AddressKey Parameters: address: The address string to be converted into a key [2][4]. Returns: A normalized address key based on the detected address type [2][5]. areAddressesEqual This utility compares two addresses to determine if they are identical, specifically handling variations in blockchain address formats [2][3]. Signature: function areAddressesEqual(a: Nullish, b: Nullish): boolean Parameters: a: The first address string to compare (can be null or undefined) [2][4]. b: The second address string to compare (can be null or undefined) [2][5]. Returns: A boolean value indicating whether the two addresses are equal [2][3].

Citations:


🌐 Web query:

@cowprotocol/cow-sdk exports getAddressKey areAddressesEqual

💡 Result:

The @cowprotocol/cow-sdk package includes getAddressKey and areAddressesEqual as utility functions, which are typically available through its common utilities package (often exported from @cowprotocol/sdk-common) [1][2][3]. getAddressKey This function is designed to normalize blockchain addresses [1][2]. It takes an address string as input and returns an AddressKey, which represents a normalized version based on the detected address type (supporting various blockchain formats) [1][2]. function getAddressKey(address: string): AddressKey [1][2] areAddressesEqual This function is used to compare two addresses to determine if they are the same, effectively handling different blockchain address formats [1][2]. It accepts two inputs, which can be nullable strings, and returns a boolean value [1][2]. function areAddressesEqual(a: Nullish, b: Nullish): boolean [1][2]

Citations:


Use SDK address identity helpers for base-line storage and account change detection.

baselineAccountsRef keys and the account-change check both currently use raw address strings. Key the baseline set with getAddressKey(account), compare account changes with areAddressesEqual(prevAccountRef.current, account), and update prevAccountRef.current to the same address form stored in the baseline.

🤖 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/modules/affiliate/hooks/useAffiliateTraderRecoverySideEffect.ts`
around lines 35 - 53, Update the affiliate account identity handling in
baselineAccountsRef and the account-change useEffect: key baseline entries with
getAddressKey(account), compare prevAccountRef.current and account using
areAddressesEqual, and store the normalized address key in
prevAccountRef.current after changes. Initialize prevAccountRef consistently
with that same normalized address form.

Source: Coding guidelines

@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 , the 2nd case is fixed, the 1st one is not.

…dration

The recovery path recorded a per-account baseline on first evaluation and toasted
any code that appeared afterwards. Because redux hydrates persisted orders
asynchronously after mount, a historical code shows up after the baseline is
recorded and was wrongly treated as a live link, so already-linked accounts kept
re-showing the toast on open.

Compare the linking order's own creationTime against a fixed session-start
timestamp instead: only a code carried by an order created after the session
started (a partial fill happening now) raises the toast; historical codes stay
silent regardless of when redux hydrates them.

Addresses @elena-zh's feedback.
@tenderdeve

Copy link
Copy Markdown
Contributor Author

@elena-zh fixed the 1st case in 8f90611fa.

Root cause: the recovery path recorded a per-account baseline on first evaluation and toasted any code that appeared after it. But redux hydrates persisted orders asynchronously after mount, so a historical code shows up right after the baseline is recorded and got mistaken for a live link — that's why already-linked accounts like 0x9FA3… kept re-showing the toast on open.

New rule: compare the linking order's own creationTime against a fixed session-start timestamp. Only a code carried by an order created after the session started (a partial fill happening now) raises the toast; historical codes stay silent no matter when redux hydrates them. This sidesteps the hydration race entirely rather than trying to time it.

Case 2 (dismiss on account change) is unchanged. tsc and eslint pass locally. I still can't drive a live partial fill on my setup, so if you could re-check that 0x9FA3… stays silent while a genuinely new partial-fill link still toasts, that'd confirm it.

@elena-zh

elena-zh commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Hey @tenderdeve , thank you for the contribution.
Just a reminder that one of the expectations is to take responsibility for delivering good quality code. This means not only writing clean and well-formatted code, but also making sure the final implementation is working correctly and properly tested before opening a PR.

If your current setup doesn't allow you to build, review, or test the changes, I'd appreciate it if you could avoid starting work on that issue until you're able to validate the implementation.

I still can't drive a live partial fill on my setup

Since you're not able to validate your changes in your current setup, I have to close this PR for now. Unfortunately, I don't have enough time to go through multiple rounds of back-and-forth to verify and test the implementation myself.

Once you have a setup that allows you to build and test your changes, feel free to work on the issue again and open a new PR.

Thanks.

@elena-zh elena-zh closed this Aug 3, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 3, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Affiliate: no 'code linked' notification when a limit order is partially filled

2 participants