Repository navigation
chore(release): 2026-11-02 - #8507
Conversation
…ing and Solana balances (#8450)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **Localization** * Russian translations are now available for route-related text, approval steps, and unlimited-permit labels. * French translations have been added across price charts, trade and order controls, TWAP, token lists, bridging, and other interface areas. * Spanish and Brazilian Portuguese catalogs include additional price-percentage shortcuts and take-profit prompts, but these entries remain untranslated or obsolete and do not change the corresponding user-facing interfaces. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary <img width="400" alt="image" src="https://github.com/user-attachments/assets/790e024c-df1f-483d-99f4-0b76eb3277ed" /> <img width="400" alt="image" src="https://github.com/user-attachments/assets/f51a0f4e-503c-46df-90a8-a3dd5e378eea" /> A Solana quote nets its execution costs out of the amounts it returns — the route's network fee and the rent of a buy token account the settlement has to create. We displayed that net amount as the headline and knew nothing about the costs, so the whole deduction showed up as negative price impact next to the fiat value: `-7.44%` on a 0.02 SOL trade, `-15.03%` on 0.01 SOL, and up to `-75%` on small ones, because the rent is a fixed ~0.0015 SOL whatever the trade size. The cost breakdown said **Network costs — Free** while the trader was paying it out of the fill, and "Receive" and "Receive (incl. costs)" were the same number. The backend now reports those costs in the traded token ([cowprotocol/services#5143](cowprotocol/services#5143)). This reads them and puts the amounts back the way EVM shows them: the headline is the amount **before** costs, and the costs are listed — the network fee, and the account rent as its own **Account deposit** row with a tooltip explaining the deposit comes back when the account is closed. Reported by Anxo in [#solana](https://nomevlabs.slack.com/archives/C0980HAC78S/p1792529822665909?thread_ts=1792507938.868119&cid=C0980HAC78S), with the format agreed in the same thread. > [!IMPORTANT] > **Based on `main`** — this goes out as a hotfix. > > **SDK:** pinned to the released packages from [cow-sdk#1067](cowprotocol/cow-sdk#1067) (`cow-sdk 9.2.22`, `sdk-trading-solana 0.16.0`, …), with no preview overrides left. > > **Backend:** [services#5143](cowprotocol/services#5143) is deployed on barn, so `barn.api.cow.fi/solana/api/v1/quote` already returns `quote.costs` (`networkFees`, `buyTokenAtaCosts`, `protocolFee`, `total`) and the new rows show up there. Prod (`api.cow.fi`) still returns the costs as flat fields on `quote` (no `quote.costs`), which the SDK doesn't read. On prod the Account deposit and Network costs rows stay hidden, and the headline stays the net amount, until the backend release reaches it. <details> <summary>Details</summary> **Where the amounts come from.** `applySolanaQuoteCosts` restores the pre-cost stages and nothing else. A sell order's `beforeAllFees.buyAmount` gains the costs back; a buy order's `beforeAllFees.sellAmount` loses them, since the quoted sell amount already carries them. Everything from `afterNetworkCosts` onwards — `afterSlippage`, `amountsToSign`, and the intent the wallet signs — is left exactly as the quote reported it. The balance gate reads `amountsToSign.sellAmount` (`getSwapMaximumSellAmount`), so it is unaffected too. **Why no display change was needed for the headline or the price impact.** Both read `beforeAllFees` — the trade form's output amount comes from `QuoteObserverUpdater`, and the fiat badge compares the USD value of that amount against the input. Correcting the amounts fixes both at once. **The breakdown.** `ReceiveAmountInfo.costs` gains an optional `accountRentFee`, filled only on Solana. `TradeFeesAndCosts` renders it as a new row above network costs, so it shows in the form, the confirmation modal and the rate accordion alike; the receive tooltip gains a matching line, and `getTotalCosts` counts it. **Why the gross-up sums the costs instead of reading `costs.total`.** `total` also carries `costs.protocolFee`, and a protocol fee reaches the same stages through the response's `protocolFeeBps`, which `getQuoteAmountsAndCosts` already adds back. Taking the total would count it twice and still leave it off the breakdown. Identical today — the backend reports `protocolFee` as always `'0'` — see the review block below. **Not changed:** `quotePrice`. It stays the executed rate, which is how EVM sell orders already behave. **Arithmetic, for the two directions.** Sell: `7,105,874,972 − 919,115,681 (rent) − 11,732,551 (network) = 6,175,026,740`, the quoted buy amount the tooltip shows as "To". Buy: `8,492,560 + 1,488,440 + 19,000 = 10,000,000`, the quoted sell amount. </details> <details> <summary>⚠️ <strong>AI Review</strong> (Claude Opus 5): self-review — one release gate, two contract questions, no code defects</summary> **Finding:** [BLOCKING] The SDK dependency is a preview build and must not merge as one - Location: `package.json` (`pnpm.overrides`) - Every `@cowprotocol/*` package is pinned to a `pr-1067-4a23426e` tarball on GitHub Packages. Those URLs stop resolving once the SDK PR's branch is deleted, so merging this as-is breaks `pnpm install` for everyone on `main`. - This one is important: it is the single step easiest to forget between a green CI run and promoting the PR. **Suggested fix** - After cow-sdk#1067 merges and releases, re-run the install against the released version and drop the override block, then push that as its own commit. **Finding:** [QUESTION] Which field will carry a Solana protocol fee — `costs.protocolFee` or `protocolFeeBps`? - Location: `apps/cowswap-frontend/src/modules/trade/utils/applySolanaQuoteCosts.ts` - The pre-cost amounts are restored from `networkFees + buyTokenAtaCosts`, not from `costs.total`, even though the backend defines `buyAmount + costs.total` as the amount before costs. Reason: a protocol fee also reaches these stages via the response's top-level `protocolFeeBps`, which `getQuoteAmountsAndCosts` adds back on its own — using `total` would count it twice and still not list it. - Numerically identical today (`costs.protocolFee` is always `'0'`). If the backend will fill `costs.protocolFee` instead of `protocolFeeBps` on Solana, this should switch to `total` and render a protocol-fee row. **Finding:** [NON-BLOCKING] The buy-ATA rent may be charged twice while `isAtaCreationDisabled` is false - Location: `apps/cowswap-frontend/src/modules/trade/pure/AccountRentRow/index.tsx` - The backend charges the rent when the receiver's token account is missing at quote time. With that flag off, the order bundle creates the account itself and the owner funds the rent in SOL (`planCreateBuyAtaStep`, priced by `useSolanaTradeOverhead`), while the quote has already netted the same rent out of the fill. - The trader would pay it twice and the row names it once. Not addressed here — the fix belongs to whichever side stops charging. Not reachable in the flow this ships for (`isAtaCreationDisabled = true`, where the driver creates the account). <details> <summary>Review scope and related context</summary> - Self-review: I wrote this code, which is weaker evidence than an independent review. - Verified the signed amounts are untouched — `amountsToSign` and `afterSlippage` come straight from the quote, and the balance gate reads `amountsToSign.sellAmount`, so the restored amounts are display-only. - Verified the breakdown balances in both directions (the arithmetic is in the Details accordion). - Checked that the rent row reaches every surface that renders costs: the form's rate accordion, the confirmation modal and the bridge quote panel all go through `TradeFeesAndCosts`. - Automated checks run locally before pushing: `tsc --noEmit` clean against the preview SDK, `eslint` clean on every touched file, 155 jest tests across `modules/trade/utils`, `modules/trade/hooks` and `common/pure/ReceiveAmountInfo`. CI on this head had not finished when this was written. - No manual verification yet: the backend field is not deployed, so there is nothing to observe in a browser beyond the unchanged-behaviour case. </details> *Generated using the `pr-review` skill from the [CoW Protocol skills repo](https://github.com/cowprotocol/skills).* </details> ## To test Run these against **barn**, where the quote already carries `costs`, with `isAtaCreationDisabled` on. 1. Open the preview deployment and connect a Solana wallet. - [ ] An EVM swap on any chain is untouched: the headline, "Receive (incl. costs)", network costs and partner/protocol fee rows all read as before. 2. Place a Solana order and let it settle. - [ ] The order is created, signed and filled; the amounts in the confirmation and in the order details match what was quoted. For steps 3–4, buy a token the wallet has never held: 3. Quote a small Solana sell, e.g. 0.01 wSOL into a token with no existing account. - [ ] The big output number is the amount **before** costs, and the percentage next to the fiat value is an ordinary price impact — not the double-digit negative we show today. - [ ] Opening the cost details shows **Account deposit** and **Network costs (est.)** as separate rows, both non-zero, and no longer "Free". - [ ] "Receive (incl. costs)" is lower than the headline by exactly those two rows, and matches the amount in the confirmation screen. - [ ] Hovering **Account deposit** explains the one-time deposit and that closing the account returns it as SOL. - [ ] The same numbers appear in the confirmation modal as in the form. 4. Quote the same pair again, now that the token account exists. - [ ] The rent row is gone, the network cost row remains, and the headline matches the receive amount more closely. 5. Quote a Solana **buy** order. - [ ] The input amount shown is the sell amount before costs, the cost rows are denominated in the sell token, and the balance check still uses the full amount the wallet will actually be debited. ## Self-checks - [ ] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [ ] I have manually tested changes on Vercel preview deployment - [ ] I have done self-review and (or) AI review - [ ] I have addressed all comments from @coderabbitai - [ ] I have less than three open PRs/Stacks in this repo at the moment of creating this PR <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Solana trade details now show transaction costs and one-time account deposits, with USD estimates where available. * Tooltips explain that account deposits are paid from your SOL balance when signing and may be returned as SOL when an empty account is closed. * Sponsored Solana transactions show covered costs as free. * Solana quote costs are reflected in estimated amounts received or spent. * **Improvements** * Fee details show “Free” when there are no partner or protocol fees, separately from network costs. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Alexandr Kazachenko <shoom3301@gmail.com>
## Summary A connected wallet could vanish on page load and then refuse to reconnect — clicking it in the connect modal did nothing and showed *"This account is already linked, change your account in MetaMask"*. One more fault, found in QA: a wallet connected on Solana alone came back connected on Ethereum. Fixed as `patches/` entries; no application code changes. Fixes https://linear.app/cowswap/issue/FE-891/possibly-stuck-when-connecting-the-wallet <details> <summary>Details</summary> ## What changed - `@reown/appkit-adapter-wagmi` — `syncConnection()` returns `undefined` when there is nothing to restore, instead of checksumming an `undefined` address and throwing. - `@reown/appkit-adapter-wagmi` — `syncConnections()` falls back to the connector the namespace last used when it has no connection records, instead of handing wagmi's `reconnect()` an empty list it reads as *try every connector*, and reconnects nothing when there is no such connector. - `@reown/appkit-scaffold-ui` — the "already linked" refusal is gated on the `multiWallet` remote feature, and the connecting view's snapshot of the active connector id is cleared before connecting so the modal closes on a same-wallet reconnect. - `pnpm-workspace.yaml` gains the `@reown/appkit-scaffold-ui` patch entry; the lockfile follows. ## Why Two independent faults combined into one unrecoverable state, and a third surfaced in QA. **The wallet was dropped on load.** `initialize()` awaits `syncExistingConnection()` *before* `syncAdapterConnections()`, and the second is what actually reconnects wagmi. The first reads `getConnections(wagmiConfig)`, normally non-empty because wagmi's zustand `persist` rehydrates `connections` synchronously. When that persisted entry is missing, `toChecksummedAddress(undefined)` throws; `syncAdapterConnection()` treats the throw as a user disconnect and calls `onDisconnectNamespace()`, which writes `addDisconnectedConnectorId()`. The wagmi reconnect that runs next filters on `!hasDisconnected` — so it skips the wallet it was about to restore. **The wallet could not be reconnected by hand.** `w3m-connecting-external-view` refuses the click whenever a connection record exists for the namespace, and those records outlive a restore that produced no account. "Already linked" only means something while `multiWallet` lets one account hold several wallets; it is disabled here, so there is no flow to link another account and refusing the click only strands the user. The modal also closes only when `activeConnectorIds[ns]` *changes*, so reconnecting the same wallet wrote an identical id and left the user on the connecting screen. **A wallet connected on one namespace came back connected on another.** Reported by @elena-zh on this PR: connect MetaMask to Solana, close the tab, reopen, and Ethereum is connected too. `syncConnections()` narrows the namespace's connectors to the ones AppKit recorded as connected *there*, then passes the result to wagmi's `reconnect()`. wagmi reads an empty `connectors` array as "no preference" and falls back to every configured connector, connecting the first whose `isAuthorized()` is true — and MetaMask approved for Solana alone still answers `eth_accounts` for the origin. `WagmiProvider` already gets `reconnectOnMount={false}` so that wagmi never reconnects on its own; this was the one remaining path to it. Not introduced here: `syncConnections()` is untouched by the other two hunks and behaves the same on `develop`. That blanket fallback is also what restores an EVM wallet when AppKit has no connection record for it — the very state this PR is about — so simply refusing to reconnect on an empty list broke the restore instead. The e2e harness caught it: `seedAutoConnect` seeds only `@appkit/eip155:connected_connector_id`, never a connection record, and the app booted disconnected. Hence the narrower fallback: reconnect the connector the namespace last used, which is that same key. **Not fixed here: a revoked wallet asks to connect on every refresh.** Reported by @elena-zh (disconnect the app from inside MetaMask, refresh, and a connect prompt appears, again after cancelling, and again on the next refresh) and raised as blocking in review. It reproduces on production, so it is not introduced by this branch, and the fix attempted for it here was reverted as out of scope — it needs its own issue. What the investigation did establish: the prompt comes from the **Solana** restore path, not the EVM one. `SolanaAdapter.syncConnection()` is literally `connect()`, and `syncConnections()` calls `connector.connect()` directly for every connector with a stored connection record — a real, prompting connect on every load, twice per load (once from `syncExistingConnection()`, once from `syncAdapterConnections()`), with a cancelled prompt leaving the record in place for the next one. Wallet-standard's own `silent` connect option is the shape of a fix. All of them are upstream bugs, so all of them are patched rather than worked around in app code. ## Risks - Three AppKit patches now. They cannot regress silently — pnpm fails the install when a patch stops applying — but every AppKit bump needs all three re-checked, and none has unit coverage since the behaviour lives in `node_modules`. - The `multiWallet` gate assumes that remote feature is disabled for this project. `/appkit/v1/config` returns 403 outside a browser so it was not confirmed directly; the reported behaviour is single-wallet throughout (a second wallet replaced the first, no "New Wallet Added" snackbar, no wallet list). - Two earlier attempts on this branch cleared the stale records from app code instead. Both were wrong: the adapter re-adds records from storage on every boot, and those same records are what marks a connector eligible for the wagmi reconnect, so deleting them would have broken reconnect outright. History kept rather than squashed for that reason. - `syncConnections()` no longer reconnects a wallet that has neither a connection record nor the namespace's stored connector id. That combination should not occur for a wallet the user actually connected — AppKit writes the connector id on connect and `onDisconnectNamespace()` is what clears it — but if some path does clear it on its own, the wallet now needs one manual click instead of being restored by wagmi's blanket fallback. - Not fixed here, deserves its own issue: AppKit's `ChainController` can hold a `caipAddress` for a wallet wagmi has no connection for. That drift is what made an earlier fix attempt read as connected when it was not. ## Verification - The reconnect half was confirmed by hand on the preview, with the patch verified present in the deployed bundle rather than assumed. - The restore paths were exercised against the e2e mock wallet (a throwaway Playwright spec, not kept), dumping the wallet's RPC log and the AppKit keys from `localStorage` after each reload: - wagmi state lost on tab close, wallet still authorized → **reconnects**, connector id kept. This is the case the PR exists for, and the one that caught the first version of the `syncConnections()` fix refusing to restore anything; - wallet revokes the app while the page is open, then reload → disconnected, no account requests; - wagmi state lost on tab close **and** wallet revoked → disconnected, no account requests. - The mock wallet never reproduced the reported prompt loop in any of the three, which is consistent with it coming from the Solana path rather than the EVM one. - `[CS-102]` and `[NW-01]` pass locally against a throwaway key; the rest of `@smoke` needs `INTEGRATION_TEST_PRIVATE_KEY` and runs in CI. - The open question — *what* clears wagmi's persisted `connections` entry when the tab closes — is largely answered by #8440, now merged into `develop`: the entry is not cleared, it is rehydrated with a connector that has no methods on it (`typeof connection.connector.getProvider !== 'function'`), and that PR revives it with a targeted `reconnect()`. The two fixes sit in the same function and cover different states — #8440 handles a connection that exists but is dead, this one handles no connection for that id at all — so both hunks are kept after the merge. Worth re-running **To test** steps 2–3 on the merged branch, since the restore path now has two guards in it. </details> <details> <summary>✅ <strong>AI Review</strong> (Claude Opus 5): self-review, no blocking findings; two QA items outstanding</summary> Self-review by the author's agent, so weaker evidence than an independent review. **Scope reviewed** — the full branch against `develop` as of `5e415633d6`: three patch hunks across two AppKit packages, the `patchedDependencies` entry, and the lockfile. The later `syncConnections()` hunk, added for @elena-zh's report, is not covered by this pass. No application code changes; the earlier app-level helpers were removed, so every file under `libs/` and `apps/` is back to its `develop` state. **Verification** - CI on `5e415633d6`: CI (lint, test, typecheck), Generated Files, E2E PW smoke and cla all green. Smoke had been red on the two previous heads; the same `[CS-61]` and `[CS-136]` tests also failed on `release/2026-10-16`, which does not contain this branch, so that was pre-existing flake and it now passes here. - Local: `wallet:lint` clean, `wallet:test` 17 suites / 135 tests, `cowswap-frontend:typecheck` and `typecheck:workspaces` both pass. revdiff returned no annotations on the last two rounds. **Finding:** [QUESTION] The restore half is not yet verified on a deployment - `5e415633d6` was pushed after the last manual test, so "close the tab and the wallet is gone" has been reasoned through, not observed fixed. That is the main thing to confirm before this leaves draft — step 2 of **To test**. **Finding:** [NON-BLOCKING] Three AppKit patches, none with unit coverage - Covered in **Risks**. Each AppKit bump needs all three re-checked. <details> <summary>Review scope and related context</summary> No review threads to de-duplicate against — CodeRabbit skips draft PRs and there are no human reviews yet. **Checked and not raised** - The new `syncConnection()` guard only intercepts the path that previously threw; when `connection` has accounts the returned shape is unchanged, so a working restore cannot regress. - `hasConnected` is read from the `@appkit/connections` records — the same records two earlier rounds tried to delete, which would have disabled the wagmi reconnect outright. - `enableReconnect` is sound: `wagmiStorage` is localStorage-backed under key `cowswap-wallet`, so `hasRecentConnector` reads the key wagmi actually writes. - `bundle-size.jsonc` 11.2 → 11.3 came from `cowswap-release-sync[bot]` in `7300299c60`, not a hand-edited budget; Generated Files re-measures it and passed. </details> *Generated using the `pr-review` skill from the [CoW Protocol skills repo](https://github.com/cowprotocol/skills).* </details> ## To test 1. Connect MetaMask and use it, so the session is established. Make sure that the wallet shows as connected in the header. 2. Fully close the tab, then reopen the app from a bookmark or a fresh tab. Make sure that the wallet reconnects on its own and does **not** fall back to "Connect wallet". 3. Repeat step 2 a few times, including after leaving the tab closed for a while. Make sure that it holds every time — this was intermittent before, so one pass does not confirm it. 4. Disconnect from the app, then connect the same wallet again from the connect modal. Make sure that it connects, that the modal closes by itself, and that "This account is already linked, change your account in …" never appears. 5. Repeat steps 1–4 with Phantom, and with an EVM and a Solana wallet connected at the same time. Make sure that switching between network types still works. 6. Clear local storage, connect MetaMask to **Solana only**, close the tab and reopen it. Make sure that Solana reconnects and that **Ethereum does not connect by itself** — the header must not show an EVM account you never connected. Then connect that EVM account by hand and repeat: now it should come back on both. 7. Open the connect modal while already connected and click the wallet you are on. Make sure that it attempts a connect rather than dead-ending, and does not land in an error state. 8. Connect with WalletConnect, reload, and reconnect. Make sure that it behaves as before — this path takes `reconnectWalletConnect()` and is not covered by the change. 9. Regression sweep across the three surfaces, since all of them go through the changed restore path and the first two force `enableReconnect`: **Safe** (as a Safe App, in the Safe iframe), **widget / dapp mode** (embedded in an integrator page or the widget configurator), and **standalone** (the preview URL on its own). Make sure that connect, reload and reconnect behave the same in each. > If anything looks off in any of these steps, compare the same flow against production (https://swap.cow.fi) before filing it — some of this behaviour is long-standing rather than introduced here, and the prod comparison is the quickest way to tell the two apart. <!-- TODO: screenshot — the "already linked" modal and the disconnected header from the original report are worth attaching as "before" images --> ### Preview URLs | Surface | URL | | --- | --- | | swap-dev - branch preview URL | https://swap-dev-git-fix-wallet-stale-connection-blo-b122f0-cowswap-dev.vercel.app | ## Self-checks - [x] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [x] I have manually tested changes on Vercel preview deployment - [x] I have done self-review and (or) AI review - [x] I have addressed all comments from @coderabbitai - [x] I have less than three open PRs/Stacks in this repo at the moment of creating this PR <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **Bug Fixes** * Improved connection recovery when connector information is unavailable, and prevented reconnection attempts when no usable connector is found. * Improved account-change handling when account data has no connector. * Updated connection-linking behavior so the already-linked flow is blocked only when multi-wallet mode is enabled. Failed connection attempts now restore the previous active connector state. * Balance displays now fall back to the raw balance value when formatted balance data is unavailable. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: cowswap-release-sync[bot] <274575433+cowswap-release-sync[bot]@users.noreply.github.com>
# Summary Set the limit price from the chart and show order activity in Simple and Advanced modes. - Add a price picker beside the limit-rate input. A chart click updates the rate and exits picker mode. - Show the current limit price in green and the TWAP worst acceptable price in orange. - Show matching open limit orders across trade pages, with remaining amounts and gray labels. Unfillable orders use red. - Show past buys and sells with amount details on hover. <img width="1217" height="618" alt="image" src="https://github.com/user-attachments/assets/c83856fa-87e0-4e52-a089-14056bffb8bf" /> # To Test Use an account with completed trades and open limit orders for the selected pair. 1. Open Limit and activate the price picker beside the rate input. - [x] A chart click updates the rate and exits picker mode. - [x] Manual rate changes update the green limit line. - [x] Selection works for either token in Simple and Advanced modes. - [x] Market Cap selection uses circulating or total supply, according to Chart settings. 2. Open Swap, Limit, or TWAP with matching open limit orders. - [x] Order lines show the remaining amounts for the selected token. - [x] Partially filled orders show their remaining amounts. Unfillable orders use red. - [x] Filled or cancelled orders disappear from the open-order lines. - [x] Reference lines also appear in Market Cap mode. 3. Open TWAP and change the worst acceptable price. - [x] The orange line updates in both chart types. 4. Verify completed trades show up in the chart (execution markers). - [x] Buy and sell markers show Bought/Sold details on hover. - [x] Token changes update the marker side and amounts. # Background Third and final chart PR, based on [Advanced charts](#8317). # Self-checks - [x] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [x] I have manually tested changes on Vercel preview deployment - [x] I have done self-review and (or) AI review - [ ] I have addressed all comments from @coderabbitai - [x] I have less than three open PRs/Stacks in this repo at the moment of creating this PR <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Added chart-based limit-price selection for regular limit orders, with a mobile-friendly chart picker. * Added reference lines for active prices, open orders, unfillable orders, and worst acceptable prices. * Added execution markers and buy/sell details to price-chart tooltips. * Added chart support for selecting currencies and limit prices. * Added chart-based worst acceptable price visibility for advanced orders. * Improved chart labels and guidance for limit-order workflows. * **Bug Fixes** * Improved handling of token pairs, price conversions, and chart symbol matching. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: cowswap-release-sync[bot] <274575433+cowswap-release-sync[bot]@users.noreply.github.com>
## Summary When an integrator's provider rejects a request from the widget, the widget now shows the integrator's own error message instead of a generic or raw technical error. Approval errors also show a short message instead of the full viem dump. Fixes [FE-944](https://linear.app/cowswap/issue/FE-944/widget-show-the-integrators-own-message-when-their-provider-rejects-a) and [FE-943](https://linear.app/cowswap/issue/FE-943/widget-approval-error-screen-shows-the-raw-viem-error-dump) |before|after| |-|-| |<img width="850" height="1139" alt="image" src="https://github.com/user-attachments/assets/bca7fc53-949b-4e3c-a02e-b86368f94c44" />|<img width="886" height="823" alt="image" src="https://github.com/user-attachments/assets/63d8a181-373f-45de-a276-6c85088498a8" />| <details> <summary>Details</summary> ## What changed - `getProviderErrorMessage` (`libs/common-utils/src/misc.ts`) walks the error's `cause` chain for the widget host's rejection and returns its message, first line only. The host error is matched by shape (`isRpcError` + `reason`), so `common-utils` doesn't depend on `iframe-transport`. - `useHandleApprovalError` uses `getProviderErrorMessage` instead of the raw `error.message`. - Every flow that already used the helper picks this up: order signing (`getSwapErrorMessage`), both Safe bundle flows, wrap/unwrap and order cancellations. ## Why - Lido blocks some requests in their provider and rejects them with their own message. The widget showed either "User rejected approval transaction" (code `4001`, misleading for a blocked request) or the whole viem error ("JSON is not a valid request object. Request Arguments: …"). - The host's message already reached the iframe: `WidgetEthereumProvider` rethrows it as `RpcError(code, reason)`, and viem keeps it at the end of the chain (`ContractFunctionExecutionError → TransactionExecutionError → InvalidRequestRpcError → RpcError`). The widget just never displayed it. - No widget-lib change or new error shape is needed. Integrators reject with any code except `4001` and put the user-facing text in `message`. ## Risks - In widget dapp mode, any error the host's provider returns now shows the host's message (first line) instead of viem's short message. That includes wallet errors the host passes through as is. - On swap.cow.fi, approval errors show viem's short message instead of the full multi-line error. - Code `4001` is unchanged: it still shows the fixed "User rejected …" text. ## Verification - `libs/common-utils/src/misc.test.ts`: new cases for the host message through a viem-shaped chain, first-line trimming of a rethrown viem error, fallback when the host sent no usable message, regular wallet errors unchanged, and `4001` from a host still detected as a rejection. - `useTradeApproveCallback.test.ts`, `getSwapErrorMessage.test.ts` and `safeBundleApprovalFlow.test.ts` pass unchanged. - The cause chain was checked against real viem 2.48 with a custom transport throwing the same `RpcError` shape: the host message is the last cause. </details> ## To test The test page below embeds the widget with Lido's integration params and a provider that can reject the approval with a chosen code and message. Nothing is signed or sent. 1. Open https://test-lido-widget.pages.dev and fill in: - Custom URL: the swap-dev branch preview URL below - Read-only: `0x176f3dab24a159341c0509bb36b833e7fdd0a132` (an EOA holding stETH with no CoW approval) 2. Click "Connect wallet", then pass the Cloudflare check. If the sell token shows "Select a token", click "Recreate widget" 3. Set "Reject approve tx with" to `-32600`, set "…with message" to `Safety of the transaction could not be verified`, and click "Approve and Swap" with 1 stETH 4. Make sure the error screen shows "Safety of the transaction could not be verified", not "JSON is not a valid request object. Request Arguments: …" 5. Set "Reject approve tx with" to `4001` and click "Approve and Swap" again 6. Make sure the error screen shows "User rejected approval transaction" 7. For comparison, clear Custom URL, keep Build on `cow-ipfs.lido.fi` (an older release) and repeat step 3 8. Make sure the old build still shows the raw technical error ### Preview URLs | Surface | URL | | --- | --- | | swap-dev - branch preview URL | https://swap-dev-git-feat-widget-provider-msgs-cowswap-dev.vercel.app | ## Self-checks - [x] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [x] I have manually tested changes on Vercel preview deployment - [x] I have done self-review and (or) AI review - [x] I have addressed all comments from @coderabbitai - [x] I have less than three open PRs/Stacks in this repo at the moment of creating this PR <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **Bug Fixes** * Improved error messages shown when a token approval fails. The app now displays a relevant reason from the connected provider when available, with a fallback message when no provider reason is available. * User-rejected approvals continue to be handled as before. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary Adds a `hideWrapBanner` widget parameter. When it is `true`, the widget no longer shows the "Wrap your ETH and use the classic WETH experience!" block under the Swap button when the user sells the native token. Fixes [FE-979](https://linear.app/cowswap/issue/FE-979/add-a-widget-parameter-to-hide-the-eth-wrapping-banner) <details> <summary>Details</summary> ## What changed - `libs/widget-lib`: new optional `hideWrapBanner?: boolean` on `CowSwapWidgetParams`, next to `hideBridgeInfo`. - `cowswap-frontend`: `EthFlowBanner` returns `null` when the injected widget param is set. This follows the same pattern as `hideBridgeInfo` in `NetworkBridgeBanner`. - `widget-configurator`: new "Show wrap native token banner" toggle in the Behavior section. It is on by default and maps to `hideWrapBanner`. ## Why - Lido's integration doesn't support wrapping, so they asked to hide this block. `@cowprotocol/widget-lib` 4.6.0 has no parameter for it. ## Risks - The param hides the whole block. That includes the "Switch to the classic WETH experience and benefit!" variant, which shows when the user already holds enough WETH. - The default is unchanged. Integrations that don't pass the param still see the banner. ## Rollout notes - The param only takes effect once the widget iframe serves a build with this change. Lido loads the widget from their own IPFS pin, so they'll need to move that pin to this release. - Integrators on widget-lib 4.6.0 types need a cast until a new widget-lib version is published. </details> ## To test 1. Open the widget configurator preview and connect an EOA wallet that holds some ETH, for example on Sepolia. Don't use a Safe or another smart contract wallet, because those get a different flow. 2. On Swap, select ETH as the sell token, pick any buy token and enter an amount 3. Make sure the "Wrap your ETH and use the classic WETH experience!" block shows under the Swap button 4. In the Behavior section, turn off "Show wrap native token banner" 5. Make sure the block disappears and the Swap button still works 6. Make sure the generated code snippet includes `hideWrapBanner: true` 7. Turn the toggle back on and make sure the block shows again ## Self-checks - [x] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [x] I have manually tested changes on Vercel preview deployment - [ ] I have done self-review and (or) AI review - [x] I have addressed all comments from @coderabbitai - [x] I have less than three open PRs/Stacks in this repo at the moment of creating this PR <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Added a widget setting to show or hide the banner suggesting that users wrap a native token before selling. The banner is shown by default. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
# Summary The UI bits for the Wait for cows confirmation screen are hardcoded (they will appear with mocked data when you check the Wait for cows toggle). The changes in the confirmation screen for both regular swaps and twaps should work for real. The animations/transitions for both of them are still not final. Waiting for clarification on what Figma designs we want to go for. Closes https://linear.app/cowswap/issue/FE-535/simplify-the-swap-success-screen <table> <tr> <th>Before</th> <th>After</th> </tr> <tr> <td><img width="320" alt="swap cow fi_" src="https://github.com/user-attachments/assets/60785534-c1db-4ed9-bc8d-15037c770ec6" /> </td> <td><img width="320" alt="localhost_3000_" src="https://github.com/user-attachments/assets/c8746123-ef50-4242-a52d-2110e04607cd" /> </td> </tr> <tr> <td>-</td> <td><img width="320" alt="localhost_3000_ (1)" src="https://github.com/user-attachments/assets/d2484b25-5ca8-4fea-9709-5787903305bb" /> </td> </tr> <tr> <td>-</td> <td><img width="320" alt="localhost_3000_ (1)" src="https://github.com/user-attachments/assets/8baba535-e809-4be6-b427-3bcbb898101d" /> </td> </tr> <tr> <td><img width="320" alt="swap cow fi_ (1)" src="https://github.com/user-attachments/assets/ec5750b3-b8d7-42a9-b780-98e3affd4a46" /> </td> <td><img width="320" alt="localhost_3000_ (2)" src="https://github.com/user-attachments/assets/c7ce811e-9ea3-4442-8d41-0bff43e1b03d" /></td> </tr> </tr> <table> # To Test Try placing regular swap, swap with wait for cows, twap, and verify the changes described above in the confirmation screens. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Swap completion screens now show a clearer success message, executed token amounts, and solver competition details, including the winning bid and other solver bids. * Eligible orders can display a Wait and Save summary comparing the outcome with trading alone. * TWAP success confirmations now present active-order details and relevant navigation links. * **Improvements** * Expandable content and confirmation layouts have improved sizing and accessibility; reduced-motion preferences are respected for animations. * **Bug Fixes** * End-to-end swap checks now recognize the updated completion message. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: cowswap-release-sync[bot] <274575433+cowswap-release-sync[bot]@users.noreply.github.com>
## Summary In the widget, closing an order's progress view (back arrow, "Close" or Escape) now shrinks the widget back to its content height instead of leaving a large empty block below it. Fixes [FE-942](https://linear.app/cowswap/issue/FE-942/widget-empty-space-remains-after-closing-the-order-progress-modal) <details> <summary>Details</summary> ## What changed - The legacy `Modal` (`common/pure/Modal`) resets the global `openModalState` flag in its effect cleanup when it unmounts while still open. ## Why - Opening the progress view sets `openModalState`, and `IframeResizer` then asks the host page for full height (`SET_FULL_HEIGHT` → `--dynamicHeight: 100dvh`). - Back, Close and Escape remove the order from `SurplusModalSetup`, which then returns `null`. The `Modal` unmounts while `isOpen` is still `true`, and nothing ever cleared the flag, so the iframe kept asking for full height. - Once the flag flips back, `IframeResizer` re-runs its effect and posts a fresh `UPDATE_HEIGHT` with the content height. ## Risks - `openModalState` is also read by `NotificationSidebar` in the main app, which skips "click outside to close" while it's set. With the stuck flag that stayed disabled after closing a progress view; it now behaves as intended. - The flag is still one boolean shared by all legacy modals: closing one of two modals open at the same time clears it. That was already the case before. ## Verification - `common/pure/Modal/Modal.test.tsx`: the flag is set while a modal is open and cleared when it closes; unmounting an open modal clears it. The unmount case fails without the fix. - `IframeResizer.test.tsx` passes unchanged. - Measured the widget container on the test page with Lido's params, mocking the order API so an open swap order shows "Show progress": on the branch preview the container went 394 → 900 → 394 px (closed with Escape); on swap.cow.fi it stayed at 900 px after closing. </details> ## To test You need a wallet with a little stETH or wstETH (the test page uses Lido's token lists), because "Show progress" only appears for an open swap order placed in that browser. 1. Open https://test-lido-widget.pages.dev, set Custom URL to the swap-dev branch preview URL below, pick your wallet under "Wallet" and click "Connect wallet" 2. Place a small stETH → ETH swap 3. While the order is open, click "My orders", then "Show progress" on that order 4. Make sure the widget grows to show the progress view 5. Close it with the back arrow, or press Escape 6. Make sure the widget shrinks back to its normal height, with no empty block below the form 7. For comparison, set Custom URL to `https://swap.cow.fi` and repeat steps 2–5 8. Make sure the empty block stays there on the old build ### Preview URLs | Surface | URL | | --- | --- | | swap-dev - branch preview URL | https://swap-dev-git-fix-widget-empty-block-cowswap-dev.vercel.app | ## Self-checks - [ ] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [ ] I have manually tested changes on Vercel preview deployment - [ ] I have done self-review and (or) AI review - [ ] I have addressed all comments from @coderabbitai - [ ] I have less than three open PRs/Stacks in this repo at the moment of creating this PR <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit - **Bug Fixes** - Fixed an issue where the modal’s open-state indicator could remain active after an open modal was unmounted. - **Tests** - Added coverage for the modal’s state while open, after closing, and after unmounting. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **Localization** * Updated the Spanish, French, Brazilian Portuguese, and Russian catalogs with entries for Solana account deposits and transaction costs, solver-auction results, price-chart interactions and markers, limit-price controls, and Wait & Save comparisons. * Many new entries are untranslated, so some text may not appear in those languages yet. * Some existing order-progress and auction entries are now marked obsolete; a TWAP entry’s source reference was updated. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
# Summary Allow integrators to disable Wait & Save. # To Test 1. Load the preview URL in the preview widget configurator in the widget configurator. 2. Disable Wait & save. 3. Verify the UI does't show the Wait & save toggle anymore. 4. Make a swap. 5. Verify the confirmation screen is not the Wait & save version either. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Widget configuration now includes a “Show Wait & Save” option. When hidden, the Wait & Save toggle and related confirmation outcome are concealed. * **Improvements** * Wait & Save controls, confirmation details, and related messaging are shown only when the feature is available and enabled. * Back and right-side controls in modal headers remain clickable when displayed over an overlay. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: cowswap-release-sync[bot] <274575433+cowswap-release-sync[bot]@users.noreply.github.com>
## Summary Adds an `isAlphaNetworksOptionVisible` flag for the "Enable Alpha Networks" setting, so it no longer depends on `isSolanaEnabled`. `isSolanaEnabled` now launches Solana for everyone. While it's off, the new flag lets users opt in from the settings menu. <details> <summary>Details</summary> ## What changed - The "Enable/Disable Alpha Networks" settings item is now shown by `isAlphaNetworksOptionVisible` instead of `isSolanaEnabled`. - Solana now has two separate checks: - Available (`getIsSolanaAvailable`): either flag is on. Controls the Solana wallet connector at startup, Solana links, quotes, the trade button and the explorer's network list. - Visible (`useIsSolanaVisible`): `isSolanaEnabled` is on, or the option is on and the user opted in. Controls the network and token pickers, and whether the connect modal offers Solana wallets. - The widget forces both flags off. ## Why - With a single flag, Solana couldn't go live for everyone: turning `isSolanaEnabled` on only showed the opt-in. - Keeping availability separate from the opt-in preserves #8367. Solana links still open and connect a Solana wallet for users who haven't opted in, and changing the setting doesn't reload the page. ## Rollout notes | `isSolanaEnabled` | `isAlphaNetworksOptionVisible` | Result | | --- | --- | --- | | on | any | Solana listed for everyone; the setting has no effect | | off | on | Setting shown; Solana listed once the user opts in; Solana links work for everyone | | off | off | No setting and no Solana; Solana links fall back to an EVM network | - Both flags are set in LaunchDarkly ahead of the release. ## Risks - Turning `isSolanaEnabled` on is now the public launch. Before this PR, it only showed the opt-in. - The explorer lists Solana when either flag is on. This matches develop, where `isSolanaEnabled` did the same job. - Saved opt-ins (`alphaNetworksEnabled:v0`) carry over, but are ignored while `isAlphaNetworksOptionVisible` is off. ## Verification - `entities/alphaNetworks/useIsSolanaVisible.test.ts`: all five flag and opt-in combinations. - `common/updaters/FeatureFlagsUpdater.test.tsx`: with only `isAlphaNetworksOptionVisible` on, the Solana wallet connector is enabled for the next page load. - `legacy/state/application/hooks.test.ts`: the connect-modal cases from #8367. The modal offers only EVM wallets while Solana is hidden, and all wallets once Solana is visible or the app is on Solana. - `libs/common-hooks/src/useFeatureFlags.test.ts`: the widget forces both flags off. </details> ## To test 1. In LaunchDarkly, turn off `isSolanaEnabled` and `isAlphaNetworksOptionVisible`, then reload the page - Make sure the settings menu has no "Enable Alpha Networks" item - Make sure Solana isn't in the network selector or in the token selector's network list 2. Open the Solana link: https://swap-dev-git-fix-alpha-networks-feature-flag-cowswap-dev.vercel.app/#/1000000001/swap - Make sure the app doesn't open Solana and switches to an EVM network 3. Turn on `isAlphaNetworksOptionVisible`, keep `isSolanaEnabled` off, and reload - Make sure the settings menu shows "Enable Alpha Networks" - Make sure Solana is still not listed - Click "Connect wallet", open "All Wallets" and search for "Solflare" - Make sure no wallet is found 4. Open the Solana link again - Make sure the app opens on Solana - Make sure "Connect wallet" → "All Wallets" finds Solflare 5. Switch back to Ethereum and click "Enable Alpha Networks" - Make sure the page doesn't reload - Make sure Solana is listed and "All Wallets" finds Solflare 6. Click "Disable Alpha Networks" - Make sure Solana is hidden again and Solflare isn't found 7. Open the explorer preview - Make sure Solana is in its network selector 8. Turn on `isSolanaEnabled` and reload, without enabling alpha networks - Make sure Solana is listed and "All Wallets" finds Solflare ### Preview URLs | Surface | URL | | --- | --- | | swap-dev - branch preview URL | https://swap-dev-git-fix-alpha-networks-feature-flag-cowswap-dev.vercel.app | | explorer-dev - branch preview URL | https://explorer-dev-git-fix-alpha-networks-feature-flag-cowswap-dev.vercel.app | ## Self-checks - [x] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [ ] I have manually tested changes on Vercel preview deployment - [x] I have done self-review and (or) AI review - [x] I have addressed all comments from @coderabbitai - [x] I have less than three open PRs/Stacks in this repo at the moment of creating this PR <details> <summary>Summary by CodeRabbit</summary> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Solana availability now reflects its feature flag and the visibility of the Alpha Networks option. * When available, Solana can appear in the network selector and be used for trading and quote requests. * Wallet connections use the appropriate network context for Solana. * **Bug Fixes** * Improved consistency across Solana availability, network selection, trading, and related prompts. * Solana remains unavailable in embedded widget views. <!-- end of auto-generated comment: release notes by coderabbit.ai --> </details>
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **Localization** * Added Russian translations for solver auctions, charts, order progress, trades, and execution statuses. * Updated the Russian wording for the unlimited-approval step and changed the “Recreate” action label. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
# Summary Main => Develop <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Added Solana trade-cost details that distinguish transaction fees covered by CoW Swap from deposits funded by the trader, with amounts shown in USD. * Added st0x token-list support and routing for the RWA experience. * Added a simple price chart and an option to hide the classic ETH flow banner in the widget. * Added support for Aave V3 leverage and V4 flash-loan hooks. * **Bug Fixes** * Improved Solana quote-cost and pre-trade price displays, order fillability handling, and pending balances. * Fixed wallet network synchronization while disconnected and balances refresh behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
# Summary - Closes https://linear.app/cowswap/issue/FE-986/expandable-sometimes-crashing - Replace the Accordion component, which was used as an expandable. - Replace the old Expandable component with BaseUI's one, fixing React's crash. - Also corrects the color of some borders in the dark theme, which were incorrectly using a light border: - Swap form — “My orders” outlined button - Confirm / finished modal — CoW footer under the sold/received pair - Confirm details — divider between “Expected to receive” and slippage/min receive - Solver auction — framed auction card, expand panel frame, legend divider under the dots # To Test 1. Verify the expandable orders details and steps tracker in TWAP for EOA is unchanged. 2. Verify the expandable solver auction for Wait & Save works fine and doesn't crash / show an error ocassionally. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **Updates** * Expandable sections in trade steps, TWAP confirmations, and solver auction results now use a consistent collapsible interaction. * Closed panels retain their content, and sections that aren’t user-expandable remain non-interactive. * **Visual refinements** * Borders across trade details, solver results, and outlined buttons now use the standard border color for a more consistent appearance. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This PR contains an automated merge commit from `main` into `develop`.
## Summary The Solana estimated fill price ignored the costs the quote nets out of its amounts, so it always read back as the market price. It now accounts for them, the way every other chain already does. Fixes [FE-994](https://linear.app/cowswap/issue/FE-994/account-for-network-costs-in-solana-estimated-fill-price) <details> <summary>Details</summary> ## What changed - `getSolanaCostsInSellAtoms` turns a Solana quote's `costs` into the sell-token atoms the execution-price formula expects. - `getQuoteFeeAmountRaw` is now the one accessor for "the quote's fee", used by both `QuoteObserverUpdater` (which feeds `limitRateAtom`, and through it the displayed price) and `useLimitOrdersFormState`. Previously each read `quote.feeAmount` directly. - `getSolanaCostAtoms` holds the cost-atom breakdown — and the invariant that `protocolFee` must be left out of it — in one place, shared with `applySolanaQuoteCosts`. ## Why A Solana quote reports `feeAmount: "0"` and settles its real costs inside the amounts instead, reporting them separately under `quote.costs`. Checked against barn: ``` sell 0.02 wSOL → USDC feeAmount "0", costs in the BUY token: 904 + 149387 (USDC atoms) buy 2 USDC ← wSOL feeAmount "0", costs in the SELL token: 9001 + 1488440 (wSOL atoms) ``` Limit orders read exactly that zero, so `getEstimatedExecutionPrice` ran with no fee and collapsed to `max(limitPrice, marketPrice)` — which is why the estimated partial fill price matched the market price even after the backend started reporting costs. Note the asymmetry in the table above: a sell order's costs come off the buy side, a buy order's off the sell side. Buy orders therefore pass straight through, while sell orders convert at the quote's own rate before costs, `sellAmount / (buyAmount + costs)`. That rate is chosen so `fee / sellAmount == costs / grossBuyAmount`, which is the identity `FEP = B / (A − F)` already assumes — no new price source is introduced, and USD spot and the user's limit price are both deliberately left out of it. ## Risks Three behaviour changes fall out of `feeAmount` no longer being zero on Solana. All three look correct to me, so none is suppressed, but they are wider than the ticket's wording: - **A dust *buy* order can now be blocked.** Buy-order costs are compared raw against the user's limit sell amount, so a buy whose sell side is under the ~0.0015 SOL account rent hits `FeeExceedsFrom`. It could never have filled. Sell orders cannot reach this: the derived fee is always strictly below the sell amount. - **The 10% small-volume warning can now fire** on a small Solana limit order, since the account rent is flat and clears that threshold at small sizes. - **"Current network fees" in the execution-price tooltip is now populated**, where it was previously blank on Solana. Not addressed here: the orders table still feeds a zero fee for a *placed* Solana order, so its row will show a slightly different estimated execution price than the form did. Fixing that needs cost data on the persisted order, which the backend does not provide — it wants its own ticket. ## Verification - New unit coverage: `getSolanaCostsInSellAtoms` (10 cases — both directions, the protocol-fee exclusion, zero and malformed amounts) and `getQuoteFeeAmountRaw` (4 cases — the EVM/Solana branch, and that a zero-cost quote yields `"0"` rather than `undefined`, which would blank the price entirely). - `getQuoteFeeAmountRaw.test.ts` mocks the `modules/trade` and `modules/tradeQuote` barrels. Loading them under jest hits a pre-existing `No available storage method found` crash, reproducible on a clean `develop`, which also stops three `limitOrders` suites and `applyConsumedHookGasReceive.test.ts` from loading at all. Its arithmetic assertion is therefore a delegation check; the real arithmetic is covered in `getSolanaCostsInSellAtoms.test.ts`. </details> <details> <summary>⚠️ <strong>AI Review</strong> (Claude Opus 5, worked ~45m): self-review — three behaviour changes to call out, no code defects</summary> **Finding:** [NON-BLOCKING] `FeeExceedsFrom` becomes reachable for Solana **buy** orders - Location: `apps/cowswap-frontend/src/modules/limitOrders/hooks/useLimitOrdersFormState.ts:35` - A buy order's costs pass through un-converted and are compared against the user's *limit* sell amount, not the quote's. A dust buy order whose sell side is under the ~0.0015 SOL ATA rent now hits `FeeExceedsFrom` and the form stops accepting it. - For **sell** orders this cannot happen: the derived fee is `total * A / (B + total)`, always strictly less than `A`. - I believe blocking is right — the rent alone exceeds the order, so it could never fill — and it matches EVM semantics. Flagging it because it is a new reachable state that the ticket did not ask for. **Finding:** [NON-BLOCKING] A placed Solana limit order's estimated execution price will not match what the form showed - Location: `apps/cowswap-frontend/src/common/updaters/orders/UnfillableOrdersUpdater/updaters/UnfillableOrderUpdater.ts:86` - The orders table feeds `getEstimatedExecutionPrice` the order's own `feeAmount`, which is `0` for Solana. The form now uses a derived fee, so the same order reads differently in the two places. - Not fixed here: it needs cost data on the persisted order, which does not exist today. Worth its own ticket rather than stretching this one. **Finding:** [QUESTION] The 10% small-volume warning now fires on small Solana limit orders — intended? - Location: `apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx:70` - `feeAmount` feeds `HIGH_FEE_WARNING_PERCENTAGE` (10%) as well as the price. The ATA rent is a flat ~0.0015 SOL, so on a small trade it clears that threshold and `SmallVolumeWarningBanner` appears where nothing appeared before. - Same input also now populates "Current network fees" in the execution-price tooltip, which was previously blank on Solana. - I left both unsuppressed because the warning is telling the truth. Confirm that is the product call. <details> <summary>Review scope and related context</summary> **Where the numbers come from.** Verified against live barn rather than inferred — `POST barn.api.cow.fi/solana/api/v1/quote` returns `feeAmount: "0"` with the real costs in `quote.costs`, denominated in the **buy** token for a sell order (`networkFees: 904`, `buyTokenAtaCosts: 149387` on a 0.02 wSOL sell) and in the **sell** token for a buy order (`1497441` wSOL atoms). That asymmetry is why the two directions are handled separately. **Why the quote's own rate.** The sell-side conversion uses `sellAmount / (buyAmount + costs)` rather than the user's limit price or USD spot, so that `fee / sellAmount == costs / grossBuyAmount` — the identity `FEP = B / (A − F)` already assumes. No new price source is introduced. **Checked and deliberately not changed:** `useTradeQuoteFeeFiatAmount` (TWAP only) and `modules/ethFlow` also read `quote.feeAmount`, but neither is reachable on Solana. **Known edge, not fixed:** for a dust sell where costs exceed ~20× the buy amount, `feeWithMargin` (fee + 5%) exceeds the sell amount and `getEstimatedExecutionPrice` returns its degenerate `1/0` price. `FeeExceedsFrom` does not catch it because it compares the fee without the margin. Pre-existing EVM behaviour that Solana can now reach; I expect the backend to reject such a quote first. **Automated checks** (this session, local — CI had not run when this was written): - `nx run cowswap-frontend:lint` — 0 errors; no new warnings on touched files. - `nx run cowswap-frontend:typecheck` — no errors in touched files. The target fails repo-wide on 58 pre-existing errors in `modules/priceChart`, all from `lightweight-charts` / `charting_library` not being installed locally. - Jest — 233 pass across `modules/trade/utils`, `modules/limitOrders/utils` and `legacy/state/orders`, including 10 new cases for `getSolanaCostsInSellAtoms` and 4 for `getQuoteFeeAmountRaw`. - Three `limitOrders` suites and `applyConsumedHookGasReceive.test.ts` cannot load at all, on a pre-existing `No available storage method found` crash I reproduced on clean `develop`. **Test caveat:** `getQuoteFeeAmountRaw.test.ts` mocks the `modules/trade` and `modules/tradeQuote` barrels, because loading them under jest hits that same crash. Its arithmetic assertion is therefore a delegation check; the real arithmetic is covered in `getSolanaCostsInSellAtoms.test.ts`. **No manual verification.** Nothing was exercised in a browser — the ER-vs-AR for the Solana limit form is still owed before this is promoted out of draft. </details> <details> <summary>🤖 Prompt for AI agents</summary> ```text Verify these findings against current code. Fix only if still valid, keep changes minimal, validate with targeted tests. Context: - apps/cowswap-frontend/src/modules/limitOrders/hooks/useLimitOrdersFormState.ts:35 A Solana BUY order's quote costs are compared raw against the user's limit sell amount, so a dust buy order can now enter FeeExceedsFrom. Confirm this is wanted, or gate it. - apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx:70 The same fee now drives the 10% small-volume warning on Solana. Confirm product intent. - Do not attempt to fix the orders-table mismatch here; it needs cost data on the persisted order that the backend does not provide. ``` </details> *Generated using the `pr-review` skill from the [CoW Protocol skills repo](https://github.com/cowprotocol/skills).* </details> ## To test Solana steps need the backend that reports `quote.costs`, i.e. **barn/staging**. 1. Open the Solana limit-order form and quote a small sell, e.g. 0.01 wSOL into a token the wallet has never held. - Make sure the **Est. partial fill price** is no longer identical to the market price shown above it. - Make sure hovering the price shows a non-zero **Current network fees** line in the tooltip. 2. Repeat with the account-rent component gone — quote the same pair again once the buy token account exists. - Make sure the gap between the fill price and the market price shrinks noticeably, since only the network fee remains. 3. Quote a Solana **buy** limit order. - Make sure the estimated fill price accounts for the costs, and that an ordinary-sized order is still placeable. 4. Place a Solana limit order and let it fill. - Make sure it is created, signed and filled, and that the amounts match what was quoted. 5. EVM regression — run one limit order and one swap on mainnet in **standalone**, in **widget / dapp mode**, and as a **Safe App**. - Make sure the estimated fill price, the execution-price tooltip and the small-volume warning all read exactly as they do today. EVM takes a different branch and nothing should move. If something looks off, check the same flow on [production](https://swap.cow.fi) before filing it — a fair amount of this surface has long-standing quirks, and the comparison is the quickest way to tell those apart from a regression in this PR. ## Self-checks - [x] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [ ] I have manually tested changes on Vercel preview deployment - [x] I have done self-review and (or) AI review - [ ] I have addressed all comments from @coderabbitai - [ ] I have less than three open PRs/Stacks in this repo at the moment of creating this PR <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **Bug Fixes** * Improved fee calculation for limit-order quotes, including Solana network and account costs. * Solana costs are now accounted for in the appropriate token units for buy and sell orders, supporting more accurate fee and rate calculations. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedBot user detected. To trigger a single review, invoke the
|
# Summary - Close https://linear.app/cowswap/issue/FE-534/detect-and-show-cow-participation-after-settlement - Close https://linear.app/cowswap/issue/FE-536/show-cow-savings-when-comparison-is-reliable - Close https://linear.app/cowswap/issue/FE-993/solvers-issue-with-a-color-filling-of-the-last-solver - Added a fallback white background for solver icons that are transparent. - Close https://linear.app/cowswap/issue/FE-991/solvers-info-may-appear-with-a-delay - We now wait for the data needed for the confirmation screen while still showing the order progress tracker. - Partially address https://linear.app/cowswap/issue/FE-980/improve-solver-icons by adding a fallback background color. - Close/replace #8464 - This PR has a less strict definition of cow, so we don't need to load individual orders, just the solver competition data. - Close/replace #8465 - We'll keep cow detection in the end with a more relaxed definition. Other changes: - Solver competition item progress bar representing the price difference from each solution now has a dynamically adjusted range, where the worst solution is always displayed as 10% filled, and the winning one as 100% filled. This way, the relative differences are easier to see. The range adjusts depending if only the first 3 solvers or all of them are displayed. Screenshots: <table> <tr> <th>Dynamic progress (collapsed)</th> <th>Dynamic progress (expanded)</th> </tr> <tr> <td><img width="320" alt="localhost_3000_" src="https://github.com/user-attachments/assets/acc6916f-797d-4b8a-aadb-7b4efad2dea1" /> </td> <td><img width="320" alt="localhost_3000_ (1)" src="https://github.com/user-attachments/assets/2ac1a9a4-389d-485d-861a-165055bc2de9" /> </td> </tr> <tr> <th>Tooltip with exact amount</th> <th>New green box</th> </tr> <tr> <td><img width="320" alt="Screenshot 2026-10-28 at 18 26 44" src="https://github.com/user-attachments/assets/b704a180-9777-4592-8aab-5c32628dd652" /> </td> <td></td> </tr> </table> # To Test Place a few orders with Wait & Save enabled: In mainnet, it is more likely that we detect a cow, while in L2s you are more likely to trade alone or with just a few other traders. That will make the different new tooltips defined in https://github.com/cowprotocol/cowswap/pull/8484/changes#diff-fb15aa416135bb0e54dc5d7177c34657efc3358227525a39639c5eb070926236R488 appear. Verify also the "progress" / filled background of each solver competition row is now calculated dynamically so that the relative difference between them is more obvious. # Background Optional: Give background information for changes you've made, that might be difficult to explain via comments # Self-checks - [ ] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [ ] I have manually tested changes on Vercel preview deployment - [ ] I have done self-review and (or) AI review - [ ] I have addressed all comments from @coderabbitai - [ ] I have less than three open PRs/Stacks in this repo at the moment of creating this PR <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Finished orders can show Wait & Save details, including batch participation, companion orders, solver results, and gains compared with trading alone. * Solver bids are easier to compare: leading bids appear first, additional bids can be expanded, and bid amounts and differences include token-value tooltips. * Order confirmations show loading placeholders while execution amounts or comparison details are being prepared. * **Bug Fixes** * Small gains remain visible when their fiat value is below one cent. * Solver comparisons show a fallback participation status when comparison details are unavailable. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: cowswap-release-sync[bot] <274575433+cowswap-release-sync[bot]@users.noreply.github.com> Co-authored-by: Denis Makarov <limitofzero@gmail.com> (cherry picked from commit d3cb6b6)
…8513) ## Summary Fixes the full-page "Something went wrong" crash hit during release testing (Sentry EventID `4aa59ff7903a45418f285a57e3cf2385`) when the Advanced price chart is rebuilt after a network switch. Fixes <issue link> <details> <summary>Details</summary> ## What changed - `priceChartAdvancedWidget.utils.ts`: `removeChartWidget()` / `isChartWidgetRemoved()` record which TradingView instances have been destroyed. - `AdvancedChartCanvas.tsx`: both `widget.remove()` call sites go through `removeChartWidget()`. - `usePriceChartAdvancedPriceSelection.ts`: the teardown skips `widget.unsubscribe('mouse_up', …)` once the instance is gone. - Unit test covering both the live-widget and removed-widget teardown. ## Why The reported stack: ``` TypeError: Cannot read properties of null (reading 'doWhenApiIsReady') at Qe.unsubscribe (charting_library.standalone.js:1:46882) at usePriceChartAdvancedPriceSelection.ts:25:14 ... React ErrorBoundary ... AdvancedChartCanvas.tsx:40:3 ``` A network switch changes `currencies`, which rebuilds `symbols` and re-runs the widget effect in `AdvancedChartCanvas`. That cleanup does two things at different times: - `setReadyWidget(null)` — synchronous; - `widget.remove()` — deferred into `saveLayout().finally(...)`, because the layout has to be saved through the live iframe first. So the widget legitimately outlives the React state pointing at it. When removal wins that race, the teardown of the render that cleared `readyWidget` still holds the old instance and calls `widget.unsubscribe('mouse_up', …)` on it. Widget-level calls route through `_doWhenInnerApiLoaded`, the iframe is gone, and the library throws — the error boundary then replaces the page. `AdvancedChartCanvas` already wraps its own `widget.unsubscribe('onAutoSaveNeeded', …)` in a `try {} catch {}` for exactly this reason; the price-selection hook was the one unguarded caller. Tracking removal on the instance makes that lifetime explicit instead of leaving it to ordering luck. Skipping the call loses nothing: `remove()` drops every subscription with the iframe. ## Risks - Chart-level and subscription-object calls are untouched — only the widget-level `unsubscribe` is gated — so the rest of the teardown behaves exactly as before. - `usePriceChartAdvancedReferenceLines` makes the same kind of post-removal call (`chart.removeEntity`), but it already sits inside a `.catch(() => undefined)`, so it was never reachable as a crash. Left alone to keep the diff scoped. - Targets `release/2026-11-02` rather than `develop`; it returns to `develop` through the usual main → develop sync after the release. ## Verification - `usePriceChartAdvancedPriceSelection.test.tsx` fails without the guard (`Expected number of calls: 0, Received number of calls: 1`) and passes with it, so it pins the regression rather than just the new code path. - The race itself is timing-dependent and cannot be reproduced deterministically in jsdom; the test covers the ordering the crash comes from (widget removed, teardown still pending). </details> <details> <summary>✅ <strong>AI Review</strong> (Claude Opus 5, worked ~10m): self-review of 6aa9da4, no new findings</summary> Second pass over the full branch diff, now at head `6aa9da4`. Self-review by the author, so weaker evidence than an independent one. No blocking findings. **Why only the teardown is guarded** The obvious question on this diff is why `widget.subscribe` at `usePriceChartAdvancedPriceSelection.ts:24` is left unguarded when `unsubscribe` is not. Checked it: every `removeChartWidget` call is preceded by `setReadyWidget(null)` in the same synchronous block (`AdvancedChartCanvas.tsx:127` for the deferred path, and the `cancelled` branch at :107 only ever runs for a widget that was never published to state). So no render can observe a removed widget as non-null, and the effect *body* is unreachable with a dead instance. Only cleanups, which hold the old widget in a closure, can reach one — which is exactly what the guard covers. **Rechecked this pass** - Production diff is byte-identical to the previous head; `6aa9da4` only rewrites the test file (drops a `ReturnType<typeof renderHook>` cast, adds `expect(widget.remove).toHaveBeenCalled()` so the helper's call-through is covered too). - Scanned every widget-level call in `modules/priceChart` again. After removal only two are reachable: the guarded one, and `AdvancedChartCanvas.tsx:129`, already inside a `try {} catch {}`. - `saveLayout()` in the cleanup cannot run against a removed widget: when `chartReady()` has not resolved, `ready` is false and the save is skipped entirely, so `widget.save()` only ever sees a live instance and there is no double-remove path. - Scoping to widget-level calls is backed by the incident, not only by reading the library: `usePriceChartAdvancedReferenceLines` is declared first (`AdvancedChartCanvas.tsx:145-146`), so its teardown ran against the same dead widget in the crashing render and did not throw. - Regression still pinned: reverting the guard fails the test with `Expected number of calls: 0, Received number of calls: 1`. **CI and gaps** - Lint, Typecheck and Agent Harness passed on `b3f565f`, whose production code is identical to this head. Test was still running when the push superseded it; the run for `6aa9da4` is in progress. - CodeRabbit was still processing at the time of writing. - No manual browser verification yet — the `## To test` steps are the ask. The race is timing-dependent and cannot be reproduced deterministically in jsdom, so the unit test covers the resulting ordering (widget removed, teardown still pending) rather than the race itself. <details> <summary>Review scope and related context</summary> Two alternatives considered and rejected, so a reviewer does not have to re-derive them: - **Keep `readyWidget` pointing at the widget until removal finishes.** `setReadyWidget` is async while `widget.remove()` runs on the next line, so the stale-teardown window survives; it would need `flushSync`. - **Remove the widget synchronously in the cleanup.** Not possible — the layout has to be saved through the live iframe first, which is why the deferral exists. Known tradeoff left in place: nothing prevents a future `widget.remove()` call site from bypassing `removeChartWidget`. With two call sites in one file, a lint rule or wrapper type looked like more machinery than the risk warrants on a release-branch fix. </details> *Generated using the `pr-review` skill from the [CoW Protocol skills repo](https://github.com/cowprotocol/skills).* </details> ## To test 1. Open a trade page on Solana, open the price chart and click a price on it to use the limit price picker. 2. Switch the network to BNB Chain and connect a wallet. 3. Open the Limit tab, then the TWAP tab. - Make sure that the page renders normally. Before this change it showed "Something went wrong" with a `doWhenApiIsReady` TypeError in the console. 4. With the chart open, switch networks back and forth a few times, and close and reopen the chart. - Make sure that picking a price on the chart still fills the limit price after each rebuild. If something looks off, compare the same flow on production (https://swap.cow.fi) first — a fair amount of this surface has long-standing quirks, and that is the quickest way to tell a regression from one of them. ### Preview URLs | Surface | URL | | --- | --- | | swap-dev - branch preview | https://swap-dev-git-fix-price-chart-widget-teardown-9d4348-cowswap-dev.vercel.app | ## Self-checks - [x] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [ ] I have manually tested changes on Vercel preview deployment - [x] I have done self-review and (or) AI review - [ ] I have addressed all comments from @coderabbitai - [ ] I have less than three open PRs/Stacks in this repo at the moment of creating this PR
…hain switch, long market price (#8508) # Summary Fixes [FE-978](https://linear.app/cowswap/issue/FE-978/dont-reset-a-percentage-when-invert-a-price), [FE-972](https://linear.app/cowswap/issue/FE-972/chart-disappears-from-the-swap-form-when-switch-a-chain), [FE-1021](https://linear.app/cowswap/issue/FE-1021/ui-edge-case-handle-long-amounts-better) Three small limit order and price chart fixes before launch: - **FE-978: the custom percentage is kept when you invert the price.** `PricePercentageShortcuts` was remounted on every invert (`isInverted` was part of its `key`), so the custom value was lost. The component now remembers which side the value was typed on. After an invert it converts the stored rate back into the matching percentage for the other side, using the new `getRatePercentage` (the inverse of `getPercentageRate`, capped at the opposite maximum). The rate impact tooltip now says "higher than current market price" when the inverted price is above market. Before, it always said "lower". - **FE-972: the chart no longer disappears on a chain switch.** When you switch from the app's network selector with a wallet connected, the trade state briefly has the new chain ID with the old chain's token IDs. Both currencies resolve to `null`, and `PriceChart` used to render nothing. `usePriceChartVisibility` no longer depends on the currencies. When there are none, the chart keeps its frame and shows the existing loader. - **FE-1021: long market prices are clipped.** The `Market:` price button is capped at 110px and cut off with an ellipsis. Hovering it shows the full value. # To Test 1. Limit order, custom percentage + invert (FE-978) - [ ] Type a custom percentage (e.g. `7`), then invert the price. The field shows the matching percentage for the inverted side (e.g. `6.54`) and is still selected. - [ ] Invert back. The original value (`7`) comes back. - [ ] On the inverted side, set a limit price below market. The tooltip says "higher/lower than current market price" to match where the price actually is. 2. Swap with the price chart on (FE-972) - [ ] Turn on the `isPriceChartEnabled` flag and the chart toggle, then connect a wallet (e.g. Rabby). - [ ] Switch chain from the app's network selector. The chart frame stays and shows a loader instead of disappearing, then loads the new chain's chart. 3. Limit order with a long market price (FE-1021) - [ ] Pick a pair with a very long market rate. The `Market:` value is cut off with an ellipsis, and hovering it shows the full value. # Background - Unit tests for `getRatePercentage` cover the round trip, the conversion between sides, the caps and invalid input. - Edge case: if neither token can be resolved (both are unknown), the chart frame now shows the loader indefinitely, where before it was hidden. # Self-checks - [x] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [x] I have manually tested changes on Vercel preview deployment - [x] I have done self-review and (or) AI review - [x] I have addressed all comments from @coderabbitai - [x] I have less than three open PRs/Stacks in this repo at the moment of creating this PR 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Price charts can now remain visible while currency details are loading, with a pending status displayed until they’re available. * Percentage shortcuts preserve custom rates when switching between inverted and non-inverted prices. * **Bug Fixes** * Rate impact messages now show the correct price direction and percentage. * The market-price button displays the rate in its tooltip and truncates long values. * **Documentation** * Added a warning explaining when a limit-order price is above market and how to set it to the current market price. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What changed - The share label and X draft now follow the displayed result: surplus, Wait & save gain, solver auction, or tip. - Auction posts use the displayed bidder count and CMS winner name; one bid or an unknown winner gets generic copy. - Sharing stays hidden while the result is loading. **Solver auction** | Before | After | | --- | --- | | <img width="600" alt="Before: solver auction shares an unrelated tip" src="https://github.com/user-attachments/assets/d7e7637e-f8be-4ab3-89b3-e4251384f285" /> | <img width="600" alt="After: share draft includes the displayed bidder count and winning solver" src="https://github.com/user-attachments/assets/5c5bfec0-460a-46d9-ad58-c820b5d4d924" /> | **Wait & save** | Before | After | | --- | --- | | <img width="600" alt="Before: Wait and save gain shares an unrelated tip" src="https://github.com/user-attachments/assets/50f520d8-2253-47f7-a0c1-fb8b08aed95d" /> | <img width="600" alt="After: share draft includes the displayed 1.46 RAI Wait and save gain" src="https://github.com/user-attachments/assets/9ffc1237-10b3-4ef9-9ac3-a52ebcdad4d7" /> | Screenshots use the actual success components with matching fixture data on base `22a29f054` and PR commit `b2915ee6e`. The adjacent panel decodes the rendered share link; it is not the X composer or a real-wallet swap. ## Why The success screen could show an auction or Wait & save gain while sharing a hidden “Did you know?” tip. Implements the [October 26 proposal](https://app.notion.com/p/4058da5f04ca813f8b6fd8d104379f71), tracked in [FE-1012](https://linear.app/cowswap/issue/FE-1012). ## QA Testing Preview URL QA: - On the [swap preview](https://swap-dev-git-feat-success-screen-sharing-cowswap-dev.vercel.app), complete a swap and open its share action. Confirm the prefilled X draft matches the displayed result; no post needs to be published. Developer verification: - 34 targeted tests passed across `FinishedStep`, `SolverAuctionResult`, `WaitAndSaveOutcome`, and the source catalog. Coverage includes sell/buy surplus, Wait & save priority, bidder counts, one/unknown winner fallbacks, loading, and widget settings. - Matching before/after component fixtures confirmed the generated auction and Wait & save drafts shown above. Reviewer note: - Real-wallet preview execution was not tested. Marketing copy and public winner naming remain open for review; swap-and-bridge sharing is outside this change. ## Preview URLs | Surface | URL | | --- | --- | | swap-dev - branch preview URL | https://swap-dev-git-feat-success-screen-sharing-cowswap-dev.vercel.app | | explorer-dev - branch preview URL | https://explorer-dev-git-feat-success-screen-sharing-cowswap-dev.vercel.app | | widget-configurator - branch preview URL | https://widget-configurator-git-feat-success-screen-sharing-cowswap-dev.vercel.app | | storybook - branch preview URL | https://storybook-git-feat-success-screen-sharing-cowswap-dev.vercel.app | | rwa-dev - branch preview URL | https://rwa-dev-git-feat-success-screen-sharing-cowswap-dev.vercel.app | | cowfi - branch preview URL | https://cowfi-git-feat-success-screen-sharing-cowswap.vercel.app | <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Sharing options now reflect the completed swap outcome, including solver competition details, Wait & Save gains, surplus, and tips. * Share messages use the relevant winner, gain amount, or token details when available, with a general message when solver details are unavailable. * Sharing is hidden while an outcome is pending. Widget settings can suppress non-win sharing while retaining win-sharing options. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary Takes a Solana quote's `feeAmount` at its word when it reports one, instead of always rebuilding the fee from `costs`. Nothing changes until the backend starts sending a non-zero value — this is the half that has to ship first so the cost is not counted twice when it does. Fixes https://linear.app/cowswap/issue/FE-1050/solana-stop-deriving-the-quote-fee-from-costs-read-feeamount <details> <summary>Details</summary> ## What changed - `trade/utils/reportsFeeInSellToken.ts`: new predicate, `feeAmount > 0`. - `trade/utils/applySolanaQuoteCosts.ts`: new `feeReported` argument. It still reports the cost breakdown, but returns before restoring the pre-cost amount stages, which the SDK has already done. - `trade/utils/getSolanaCostsInSellAtoms.ts`: returns `feeAmount` rather than converting `costs.total`. - `modules/limitOrders/utils/getQuoteFeeAmountRaw.ts`: doc only — it delegates to the above and lands on the same number under either shape. ## Why [services#5214](cowprotocol/services#5214) moves the Solana quote to the EVM shape: `feeAmount` carries the execution cost in the sell token, `sellAmount` is net of it. Today the quote answers `feeAmount: "0"` and settles the cost inside the amounts, so this app derives the sell-denominated fee and un-nets the stages by hand. The SDK's `getQuoteAmountsAndCosts` already reads `orderParams.feeAmount`. The moment the backend stops sending zero, it restores those stages itself and our compensation becomes a duplicate — and not in one direction. For a **sell** order it adds the cut back to the buy side (`getQuoteAmountsAndCosts.ts:53`) while `applySolanaQuoteCosts` adds it too. For a **buy** order it adds `feeAmount` back to the sell side (line 82) while `applySolanaQuoteCosts` subtracts `costs.total`. That is the double count #5214's description refers to when it says it waits on this release. Branching on the quote rather than on a release is what keeps the two deploys independent: `feeAmount > 0` means the stages are already restored, `"0"` keeps today's behaviour byte for byte. ## Risks - `getSolanaCostsInSellAtoms` feeds the limit-order fee. Under the new shape it returns `feeAmount` directly, which is the number the order is signed against, so it is more accurate than the conversion it replaces — but it is a different number, by the rounding of that conversion. - The cost **breakdown** still comes from `costs` under both shapes, deliberately. It is exact and denominated in the token the cut actually comes from, while `feeAmount` is a rounded conversion of it (~0.005% apart). Reading the breakdown off `feeAmount` would have been the easier change and the wrong one. - Not covered here: whether `/orders` responses also move to effective amounts. That is a separate piece of work if the backend takes it on. ## Verification - The new-shape cases go through `getReceiveAmountInfo`, so they exercise the SDK's math rather than `applySolanaQuoteCosts` in isolation — the double count only shows up in that combination. - Three of the new tests fail with the guards disabled, so they pin the regression rather than the new path. - Every pre-existing test passes unmodified, which is the evidence that the `feeAmount: "0"` path is untouched. - `modules/trade` + `modules/limitOrders`: 114 suites / 942 tests. eslint clean on both util directories. `nx run cowswap-frontend:typecheck` clean. </details> <details> <summary>⚠️ <strong>AI Review</strong> (Claude Opus 5, worked ~10m): self-review, one gap in what the tests actually prove</summary> Self-review by the author, so weaker evidence than an independent one. **Finding:** [NON-BLOCKING] The new-shape tests pin "we stop compensating", not "the number is right" - Location: `applySolanaQuoteCosts.test.ts`, the `quote already reporting the fee` block - Each case asserts the result equals `buildReceiveAmountInfo(withFee, undefined)` — that is, that this app leaves the SDK's stages alone. It does not assert the stages are *correct*, because the expected value is whatever the SDK produced. - That is enough to prove the double count is gone, which is the point of the change. It is not enough to prove the displayed amounts match what the backend intends. - Ilya's thread has real barn numbers for both kinds under the new shape — `0.1 SOL → USDC` giving `sellAmount ~98425440 / feeAmount ~1574560`, and `buy 10 USDC` giving `sellAmount 96376269 / feeAmount 1496140`. I named them in FE-1050 as fixtures and then did not use them. Pinning the stages against those would close the gap. **Suggested fix** - Add one sell and one buy case built from those figures, asserting the actual stage values rather than equality with an SDK round-trip. - Alternatively, confirm the numbers against a quote from the services#5214 branch before it merges. **What was checked and holds** - **EVM is untouched.** `reportsFeeInSellToken` is evaluated for every chain, but `applySolanaQuoteCosts` returns at `if (!costs) return info` before the new branch is reached, and EVM quotes carry no `solanaQuoteCosts`. `getSolanaCostsInSellAtoms` is only reachable behind `isSolanaQuoteAndPost`. - **Malformed input falls back to the old path.** `positiveIntegerAtom` answers `undefined` for a negative or non-integer `feeAmount`, and `?? 0n` makes the predicate false — the conservative direction. - **The cost breakdown survives the new branch.** The early return sits after `withCosts` is built, so `costs.networkFee.amountInBuyCurrency` and `accountRentFee` are still populated. Returning before that would have blanked the two fee rows; there is a test for it. - **`feeReported` defaults to `false`**, so any caller that does not pass it keeps today's behaviour. `getReceiveAmountInfo` is the only one. - **The old path is byte-identical.** Every pre-existing test passes unmodified, which is the real evidence that shipping this ahead of the backend is a no-op. <details> <summary>Review scope and related context</summary> - The SDK needs no edit. `getQuoteAmountsAndCosts` already reads `orderParams.feeAmount`; it changes behaviour on its own when the value stops being zero. That is *why* this app has to stop compensating, not a reason to touch the SDK. - One genuinely stale thing is left alone on purpose: `SolanaQuoteCosts.total`'s doc in cow-sdk says a buy order's pre-cost sell amount is `sellAmount - total`, which stops being true under the new shape. That is the spec this app was written from. Correcting it is a separate, non-blocking cow-sdk change. - Not attempted: whether `/orders` responses also move to effective amounts. Raised with the backend, still unanswered, and separate work if so. </details> <details> <summary>🤖 Prompt for AI agents</summary> ```text Verify this finding against current code. Fix only if still valid, keep the change minimal, and validate with the targeted tests. Context: - apps/cowswap-frontend/src/modules/trade/utils/applySolanaQuoteCosts.test.ts, describe block 'applySolanaQuoteCosts · quote already reporting the fee'. - The cases assert equality with buildReceiveAmountInfo(withFee, undefined), which proves the app no longer modifies the SDK's stages but not that the stages are correct. - Expected: add a sell and a buy case with explicit expected stage values, built from the backend's own figures (0.1 SOL -> USDC: sellAmount 98425440, feeAmount 1574560; buy 10 USDC: sellAmount 96376269, feeAmount 1496140, buyAmount 10000000). - Run: npx jest --config apps/cowswap-frontend/jest.config.mjs --rootDir apps/cowswap-frontend --testPathPatterns 'applySolanaQuoteCosts' ``` </details> *Generated using the `pr-review` skill from the [CoW Protocol skills repo](https://github.com/cowprotocol/skills).* </details> ## To test This is a regression pass. The backend still answers `feeAmount: "0"` on every network, so this build takes the existing code path and **every number below should be identical to the release branch**. Anything that differs is a bug in this PR, not a new behaviour. In both words - just open develop and this preview branch and compare the fee costs, they should be identical What changes later, once services#5214 deploys: the fee stops being rebuilt from `costs` and comes straight from the quote, which moves the limit-order fee by the rounding of that conversion (~0.005%). Not visible on this build. ### Preview URLs | Surface | URL | | --- | --- | | swap-dev - branch preview | https://swap-dev-git-fix-solana-quote-fee-amount-cowswap-dev.vercel.app | | swap-dev - release branch, for comparison | https://swap-dev-git-release-2026-11-02-cowswap-dev.vercel.app | ## Self-checks - [x] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) - [ ] I have manually tested changes on Vercel preview deployment - [x] I have done self-review and (or) AI review - [ ] I have addressed all comments from @coderabbitai - [ ] I have less than three open PRs/Stacks in this repo at the moment of creating this PR
…back (#8524) ## What changed - The order progress rows, collapsed stack and bridge result now share one small `SolverLogo` component. - Every logo gets a 0.5px inner ring at 24% of the text colour. - A solver with no logo shows its initial on the same white plate, instead of the Gnosis Chain logo. - The stack uses the rows' white plate and `object-fit: contain`. Its logos grow from 16px to 18px, so a stack of three is 2px wider. - The bridge result logo is now round, on the same white plate. - Logos are decorative, because the solver's name is always beside them. The `{solver} logo` string is gone. - `legacy/components/AMMsLogo` and its 14 images are deleted. <img width="1912" height="2308" alt="solver-logos-before-after" src="https://github.com/user-attachments/assets/5eb85688-0ac1-433f-8b3f-a07613d61681" /> ## Why - Even with corrected files, 32 of 46 active solver logos have no clear edge in one stack theme: 14 on white and 18 on navy. - dfusion has no CMS logo, so it shows Gnosis Chain's logo through a fallback labelled `Default unknown AMM`. - `object-fit: cover` crops wide and tall logos in the stack. - The bridge result has no plate or radius, so the replacement files would show as squares there. - `AMM_LOGOS` can't be reached any more. Since c669629, `solver` is an address or a CMS `solverId`. The only active CMS solvers with a matching id, `balancer` and `curve`, have CMS images. ## QA Testing Preview URL QA: - Swap on https://swap-dev-git-fix-solver-logos-cowswap-dev.vercel.app and wait for the order to fill. On the progress screen, check the collapsed stack and expanded rows in light and dark mode: logos are round, uncropped and have an edge. - If dfusion or another solver with no CMS logo bid, it shows its initial, not the Gnosis Chain logo. - Complete a swap-and-bridge order and open its swap details: the `Winning solver` logo is round. Developer verification: - `SolverLogo.test.tsx` covers the CMS image, the initial fallback, a blank display name and a leading emoji. - `SolverCompetitionExpand.test.tsx` covers the stack's initials and that CMS images reach each avatar. - In Cosmos, `OrderProgressBar` > `4-finished` shows `N`, `U` and `O`, and `bridgingFinished` shows `T`, with no Gnosis Chain logo. Reviewer note: - Rendered in Chrome and macOS WebKit at 2x with real CMS logos in light and dark mode. Not yet checked in Firefox, in the Safari app or on a real order. ## Risks - This PR doesn't change any logo files. Until replacements reach the CMS, white-on-transparent logos such as Kipseli and Nexroute stay mostly blank on the white plate. In dark mode, the stack and bridge result show less of these logos than they do today. ## Preview URLs | Surface | URL | | --- | --- | | swap-dev - branch preview URL | https://swap-dev-git-fix-solver-logos-cowswap-dev.vercel.app | | explorer-dev - branch preview URL | https://explorer-dev-git-fix-solver-logos-cowswap-dev.vercel.app | | cowfi - branch preview URL | https://cowfi-git-fix-solver-logos-cowswap.vercel.app | | storybook - branch preview URL | https://storybook-git-fix-solver-logos-cowswap-dev.vercel.app | | widget-configurator - branch preview URL | https://widget-configurator-git-fix-solver-logos-cowswap-dev.vercel.app | | sdk-tools - branch preview URL | https://sdk-tools-git-fix-solver-logos-cowswap-dev.vercel.app |
Release!
Changes
607d14c feat: Wait & save cow detection from solver competition (#8484)
ab73826 fix(charts): remember visibility per trading mode and default Swap to off (#8455)
26e73a8 fix(account): add bottom padding to unsupported network message (#8499)
aecc2da fix(menu): remove "New" badges from Affiliate and My Rewards (#8497)
52b7f57 fix(limit-orders): do not sign orders with an expired custom deadline (#8394)
c50fbf4 fix(solana): account for quote costs in the estimated fill price (#8480)
82139f3 fix(rwa): fix asset data discrepancies and support commodities (#8488)
ab80e73 fix(rwa): adjust content to legal requirements (#8479)
1668531 chore(sync): merge main into develop (#8494)
3026f91 chore(sync): merge main into develop
20ad9ea fix: replace Accordion with Expandable, fix React crash (#8475)
49bfa57 chore(sync): merge main into develop (#8492)
4e7f034 chore: resolve conflicts and sync main to develop
d77b23a chore: new Crowdin updates (#8486)
cf14cdc feat: add isAlphaNetworksOptionVisible flag (#8477)
47d9cd8 feat: Wait & save widget toggle (#8473)
22c3121 feat(rwa): issuer dividend models and share multipliers (#8476)
cd207f8 chore: new Crowdin updates (#8472)
395a0bc fix(widget): close modals on unmount (#8463)
f976764 feat(rwa): trading UI improvements (#8469)
5c482b7 feat(rwa): add Coinbase, bStocks and Reality issuers (#8468)
edb76fa feat: last-mile check for restricted addresses in orders and transactions (#8451)
99b9962 feat(solana): integrate appData api (#8467)
447dc0e feat: Wait for cows UI bits & new confirmation (#8443)
9cf6656 feat(widget): hide eth flow classic banner with hideWrapBanner (#8461)
d582be9 fix(widget): display widget provider error in the UI (#8462)
4acf626 feat: price chart annotations & limit picker (#8358)
7897e8f fix(wallet): patch appkit so a dropped wallet can reconnect (#8430)
68edcf4 fix(solana): show quote costs and the price before them (#8452)
2b34d9f feat(rwa): add order cancellation (#8437)
5acb2ce chore: new Crowdin updates (#8459)
54ad502 fix(performance): wake countdowns once per displayed second (#8448)
e259def fix(performance): remove render hotspots in token search, route matching and Solana balances (#8450)