Skip to content

feat(explorer): show network costs and protocol fee breakdown on order details#7588

Open
jmg-duarte wants to merge 17 commits into
developfrom
jmgd/ucp
Open

feat(explorer): show network costs and protocol fee breakdown on order details#7588
jmg-duarte wants to merge 17 commits into
developfrom
jmgd/ucp

Conversation

@jmg-duarte

@jmg-duarte jmg-duarte commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Derive the breakdown client-side from trades[].executedProtocolFees: sum protocol fees per token, then split order.totalFee into network costs vs. protocol fees — subtracting only when both are denominated in the same token, since the protocol fee comes from the surplus token while totalFee lives in executedFeeToken.

Based on #5024

Screenshot 2026-05-28 at 17 54 12

To Test

  1. Open the explorer
  2. Write an order ID
  3. Check the Costs & Fees

Summary by CodeRabbit

  • New Features

    • Per-token protocol-fee breakdowns in order views and a new expandable numbers breakdown component
    • Protocol-fee types surfaced to APIs and UI; order-level protocol-fee aggregation hook
  • Improvements

    • Fee display updated to show individual protocol-fee rows (dash when none)
    • Unified "Show more/less" behavior; order details include protocol fees and execution info
    • Storybook scenarios updated; fee tooltip clarified (network costs excluded)
  • Tests

    • Added tests for protocol-fee extraction, ordering, paging, token handling, and hook error cases
  • Chores

    • Dependency version bumps across packages

…r details

Derive the breakdown client-side from `trades[].executedProtocolFees`: sum
protocol fees per token, then split `order.totalFee` into network costs vs.
protocol fees — subtracting only when both are denominated in the same token,
since the protocol fee comes from the surplus token while `totalFee` lives in
`executedFeeToken`.
@vercel

vercel Bot commented May 28, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cowfi Ready Ready Preview Jun 16, 2026 10:44am
explorer-dev Ready Ready Preview Jun 16, 2026 10:44am
storybook Ready Ready Preview Jun 16, 2026 10:44am
swap-dev Ready Ready Preview Jun 16, 2026 10:44am
widget-configurator Ready Ready Preview Jun 16, 2026 10:44am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
cosmos Ignored Ignored Jun 16, 2026 10:44am
sdk-tools Ignored Ignored Preview Jun 16, 2026 10:44am

Request Review

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

Introduce protocol-fee types and extraction (getProtocolFees), add unit tests, render per-fee protocol-fee rows in GasFeeDisplay (with token resolution), add NumbersBreakdown UI, enrich orders in OrderDetails, adjust a ShowMoreButton usage, and bump many @cowprotocol package versions.

Changes

Protocol fee extraction & display

Layer / File(s) Summary
Protocol fee types
apps/explorer/src/api/operator/types.ts
Add ProtocolFeeType and ProtocolFee; extend Order with optional protocolFees; include executedProtocolFees in Trade picks.
Protocol fee extraction utility
apps/explorer/src/utils/operator.ts
Export getProtocolFees(trades) that collects per-trade executedProtocolFees, normalizes token addresses, classifies policy to ProtocolFeeType, skips malformed entries, and returns ordered ProtocolFee[].
Protocol fee unit tests
apps/explorer/src/test/utils/operator/orderFees.test.ts
Jest tests for getProtocolFees: no-fees, normalization, separate token entries, policy mapping, preserved ordering, and ignoring incomplete entries.
NumbersBreakdown UI & AppData toggle
apps/explorer/src/components/orders/NumbersBreakdown/index.tsx, apps/explorer/src/components/AppDataRowContent/AppDataRowContent.tsx
Add NumbersBreakdown (collapsible details with ShowMoreButton) and switch AppDataRowContent to use shared ShowMoreButton.
GasFeeDisplay rendering
apps/explorer/src/components/orders/GasFeeDisplay/index.tsx, apps/explorer/src/components/orders/GasFeeDisplay/GasFeeDisplay.stories.tsx
Rewrite to render order.protocolFees as a tokenized table, fetch token metadata (useMultipleErc20), resolve token display (prefer sell/buy match and handle wrapped-native), and update Storybook stories to protocol-fee scenarios.
OrderDetails enrichment
apps/explorer/src/components/orders/OrderDetails/index.tsx
Replace getOrderWithTxHash with enrichOrderFromTrades which sets protocolFees from getProtocolFees(trades) and conditionally assigns txHash/executionDate for single-trade non-multi-trade cases.
Tooltips and test-data import
apps/explorer/src/components/orders/DetailsTable/detailsTableTooltips.tsx, apps/explorer/src/test/data/operator.ts
Update fees tooltip text to describe protocol fees per token (excluding network costs); adjust test-data type imports to the .../types path.

Sequence Diagram

sequenceDiagram
  participant OrderDetails
  participant getProtocolFees
  participant GasFeeDisplay
  participant useMultipleErc20
  OrderDetails->>getProtocolFees: enrichOrderFromTrades(trades)
  getProtocolFees->>OrderDetails: protocolFees[]
  OrderDetails->>GasFeeDisplay: render(order with protocolFees)
  GasFeeDisplay->>useMultipleErc20: request token metadata for fee tokenAddresses
  useMultipleErc20->>GasFeeDisplay: token metadata map
  GasFeeDisplay->>GasFeeDisplay: resolveToken & formatFee -> render rows
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

RELEASE

Suggested reviewers

  • alfetopito
  • elena-zh

Poem

🐰 I counted fees from trades so small,

Protocols, tokens — I noted them all,
NumbersBreakdown shows each line,
Rows of fees, tidy and fine,
A hopping rabbit cheers the call.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 feature: showing network costs and protocol fee breakdown on order details, which aligns with the core changes throughout the PR.
Description check ✅ Passed The description covers the summary, technical approach, and testing steps, but lacks detail on background/context and omits the checklist items from the template.
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
  • Commit unit tests in branch jmgd/ucp

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 28, 2026

Copy link
Copy Markdown

Deploying explorer-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2e8419a
Status: ✅  Deploy successful!
Preview URL: https://9c0c9bc8.explorer-dev-dxz.pages.dev
Branch Preview URL: https://jmgd-ucp.explorer-dev-dxz.pages.dev

View logs

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 28, 2026

Copy link
Copy Markdown

Deploying swap-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2e8419a
Status: ✅  Deploy successful!
Preview URL: https://dfc74df0.swap-dev-5u6.pages.dev
Branch Preview URL: https://jmgd-ucp.swap-dev-5u6.pages.dev

View logs

@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: 3

🧹 Nitpick comments (1)
apps/explorer/src/test/utils/operator/orderFees.test.ts (1)

81-89: ⚡ Quick win

Add a mixed-checksum aggregation regression test.

Current coverage checks case-insensitive comparison, but not aggregation across multiple trades where the same token appears in different casing. Add one test with two trades (FEE_TOKEN and FEE_TOKEN.toLowerCase()) and assert single-token aggregation without warning.

🤖 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/explorer/src/test/utils/operator/orderFees.test.ts` around lines 81 -
89, Add a regression test in orderFees.test.ts that verifies aggregation across
mixed-checksum token strings: create two trades using makeTrade — one with token
FEE_TOKEN and one with FEE_TOKEN.toLowerCase() — pass them to getFees alongside
an order from makeOrder with executedFeeToken set to a different-cased FEE_TOKEN
and totalFee large enough to split protocol/network fees, then assert the
returned protocolFees equals the sum of both trade amounts (as a single token
aggregation) and networkCosts equals the expected remainder; also assert no
warning was emitted (e.g., result.warnings is empty or console.warn was not
called) to ensure the same-token branch aggregates case-insensitively across
multiple trades.
🤖 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/explorer/src/api/operator/types.ts`:
- Around line 104-108: Change the protocolFeeTokenAddress property in the
OperatorTrade type from string to AddressKey (replace protocolFeeTokenAddress?:
string with protocolFeeTokenAddress?: AddressKey) and update all callers; then
replace any address comparisons that use toLowerCase() in the sameDenomination
logic with the address helpers — in apps/explorer/src/utils/operator.ts and
apps/explorer/src/components/orders/GasFeeDisplay/index.tsx use
areAddressesEqual(a,b) or normalize using getAddressKey(addr) before comparing
instead of addr.toLowerCase(), ensuring you import AddressKey, areAddressesEqual
and getAddressKey from the existing address utility module.

In `@apps/explorer/src/components/orders/GasFeeDisplay/index.tsx`:
- Around line 67-69: Replace direct .toLowerCase() comparisons with the cow-sdk
address helper: update the sameDenomination computation in GasFeeDisplay to call
areAddressesEqual(executedFeeToken, protocolFeeTokenAddress) instead of
comparing lowered strings; change formatFee and resolveToken signatures to
accept an AddressKey parameter type and inside each use
areAddressesEqual(tokenAddress, addressKey) (or similar) to determine equality
rather than calling toLowerCase() on addresses, and update any internal logic
that relied on string lowercasing to use the areAddressesEqual helper for all
address comparisons.

In `@apps/explorer/src/utils/operator.ts`:
- Around line 461-468: The fees grouping currently keys feesByToken by raw token
strings causing checksum-casing splits; change feesByToken to Map<AddressKey,
BigNumber> and when iterating executedProtocolFees use getAddressKey(token) as
the key (while still validating token exists) so amounts aggregate under a
normalized AddressKey (refer to feesByToken and the trades.forEach block). Also
replace the string lower-case comparison in sameDenomination with a call to
areAddressesEqual(tokenA, tokenB) per the address-comparison guideline to ensure
correct equality checks across checksum variants.

---

Nitpick comments:
In `@apps/explorer/src/test/utils/operator/orderFees.test.ts`:
- Around line 81-89: Add a regression test in orderFees.test.ts that verifies
aggregation across mixed-checksum token strings: create two trades using
makeTrade — one with token FEE_TOKEN and one with FEE_TOKEN.toLowerCase() — pass
them to getFees alongside an order from makeOrder with executedFeeToken set to a
different-cased FEE_TOKEN and totalFee large enough to split protocol/network
fees, then assert the returned protocolFees equals the sum of both trade amounts
(as a single token aggregation) and networkCosts equals the expected remainder;
also assert no warning was emitted (e.g., result.warnings is empty or
console.warn was not called) to ensure the same-token branch aggregates
case-insensitively across multiple trades.
🪄 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: 834ec6c8-a987-4d6c-a53b-f60aacd95af2

📥 Commits

Reviewing files that changed from the base of the PR and between b6459f0 and 90b75e9.

📒 Files selected for processing (9)
  • apps/explorer/src/api/operator/types.ts
  • apps/explorer/src/components/AppDataRowContent/AppDataRowContent.tsx
  • apps/explorer/src/components/common/ShowMoreButton.tsx
  • apps/explorer/src/components/orders/GasFeeDisplay/GasFeeDisplay.stories.tsx
  • apps/explorer/src/components/orders/GasFeeDisplay/index.tsx
  • apps/explorer/src/components/orders/NumbersBreakdown/index.tsx
  • apps/explorer/src/components/orders/OrderDetails/index.tsx
  • apps/explorer/src/test/utils/operator/orderFees.test.ts
  • apps/explorer/src/utils/operator.ts

Comment thread apps/explorer/src/api/operator/types.ts Outdated
Comment thread apps/explorer/src/components/orders/GasFeeDisplay/index.tsx Outdated
Comment thread apps/explorer/src/utils/operator.ts Outdated
@alfetopito

Copy link
Copy Markdown
Collaborator

@alfetopito

Copy link
Copy Markdown
Collaborator

Additionally, something is weird. Checking a random order, I see this:

image

The network costs token is not being decoded for some reason.
When the token details cannot be fetched, it defaults to showing the full integer amount and the token address.

In this case, the token is WETH.

However, it's also an edge case as it's an ethflow order, so the sell token should actually be displayed as ETH.

@fhenneke

Copy link
Copy Markdown

I was expecting a change towards only displaying protocol fees. We will not be able to reconstruct network fees. (I am assuming that order.totalFee is essentially what we call executedFee in other places. We want to remove that number.)

@fairlighteth fairlighteth 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.

⚠️ AI Review (Codex GPT-5, worked 4m): fee breakdown uses only the current fills page

Finding: Aggregate fee breakdown from all order trades, not the paginated fills table page

  • Location: apps/explorer/src/components/orders/OrderDetails/index.tsx:160
  • enrichOrderFromTrades calls getFees(order, trades), but trades comes from useOrderTrades(order, tableState.pageOffset, tableState.pageSize), which fetches limit + 1 records and then slices back to the current page.
  • For a partially fillable order with more than 10 fills, the overview breakdown only sums the visible page. Page 1 omits later fills; after paging in the Fills tab, returning to Overview can show different fee/network-cost values for the same order.
  • Impact: order-level costs become undercounted and page-dependent.

Suggested fix

  • Use a complete trade set or dedicated aggregate for the order-level fee breakdown, or only render the breakdown when all trades needed for the aggregate have been loaded.
  • Add coverage for an order with more fills than RESULTS_PER_PAGE so protocolFees includes all fills and does not change with pageOffset.
Review scope and related context

This is separate from the existing review comments, which already cover:

  • Address typing/comparison: resolved with AddressKey, getAddressKey, and areAddressesEqual.
  • EthFlow WETH/ETH fee-token formatting: already reported by @alfetopito.
  • Whether network fees should be displayed at all: already raised by @fhenneke.
  • Mixed-checksum aggregation coverage: already suggested by CodeRabbit.
🤖 Prompt for AI agents
Verify this finding against current code. Fix only if still valid, keep the change minimal, and validate with targeted tests.

Context:
- apps/explorer/src/components/orders/OrderDetails/index.tsx:160 calls getFees(order, trades).
- apps/explorer/src/explorer/components/OrderWidget/index.tsx:36 passes useOrderTrades(order, tableState.pageOffset, tableState.pageSize).
- apps/explorer/src/hooks/useOperatorTrades.ts:69 fetches limit + 1 and lines 119-122 slice back to the current page.
- Fee breakdown should be order-level, not dependent on the currently selected fills page.

Surface each volume fee's policy factor as basis points (e.g. "Volume
fee (2 bps)") so an order's protocol vs partner volume fees—identically
labeled by the API, which doesn't expose fee owner—can be told apart.
The factor is already in executedProtocolFees, just unused until now.

Also skip non-positive fees so a policy that charges nothing doesn't add
a "0" row, and stack the breakdown table under the show-more toggle
(via DetailRow stack) instead of beside it, trimming the gap above it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@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/explorer/src/test/hooks/useOrderProtocolFees.test.tsx`:
- Around line 55-77: The test currently generates trades with fees "0".."24" but
getProtocolFees filters non-positive amounts, so update the trade creation in
the test (the Array.from call that calls createRawTradeWithFee) to start amounts
at 1 (e.g. use String(i + 1) instead of String(i)), and adjust the expected
mapped values in the assertion that checks protocolFees.map(...) to match the
new sequence (String(i + 1)); keep the rest of the paging/assertion logic
unchanged.
🪄 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: 99909cd1-bd36-4558-be09-79e316969175

📥 Commits

Reviewing files that changed from the base of the PR and between 1179936 and 55c36b9.

📒 Files selected for processing (10)
  • apps/explorer/src/api/operator/types.ts
  • apps/explorer/src/components/orders/DetailsTable/items/CostAndFeesItem.tsx
  • apps/explorer/src/components/orders/GasFeeDisplay/index.tsx
  • apps/explorer/src/components/orders/NumbersBreakdown/index.tsx
  • apps/explorer/src/components/orders/OrderDetails/index.tsx
  • apps/explorer/src/explorer/components/OrderWidget/index.tsx
  • apps/explorer/src/hooks/useOperatorTrades.ts
  • apps/explorer/src/test/hooks/useOrderProtocolFees.test.tsx
  • apps/explorer/src/test/utils/operator/orderFees.test.ts
  • apps/explorer/src/utils/operator.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/explorer/src/components/orders/DetailsTable/items/CostAndFeesItem.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/explorer/src/utils/operator.ts
  • apps/explorer/src/api/operator/types.ts
  • apps/explorer/src/test/utils/operator/orderFees.test.ts
  • apps/explorer/src/components/orders/NumbersBreakdown/index.tsx

Comment thread apps/explorer/src/test/hooks/useOrderProtocolFees.test.tsx

@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 @jmg-duarte , thank you, however, changes does not lgtm.

  1. Fees and costs total value does not match to the one displayed on CoW swap:
Image

Please, compare it with the develop:

Image
  1. It would be great to show a total amount at the top, then show a collapsed value with fees details. IMO, just 'show more' without a value looks weird.
Image
  1. For an order with multiple fills it would be great to show a total of fees per category for all fills instead of showing an endless table like this one:
Image

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.

5 participants