Skip to content

chore(release): 2026-11-02 - #8507

Merged
limitofzero merged 42 commits into
mainfrom
release/2026-11-02
Nov 5, 2026
Merged

limitofzero merged 42 commits into
mainfrom
release/2026-11-02

Conversation

@cowswap-release-sync

@cowswap-release-sync cowswap-release-sync Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

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)

shoom3301 and others added 30 commits October 26, 2026 13:29
<!-- 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 -->
@cowswap-release-sync cowswap-release-sync Bot added the RELEASE Included in the release that is being closed label Nov 2, 2026
@vercel

vercel Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
cowfi Ready Ready Preview Nov 4, 2026 8:29pm UTC
explorer-dev Ready Ready Preview Nov 4, 2026 8:29pm UTC
rwa-dev Ready Ready Preview Nov 4, 2026 8:29pm UTC
storybook Ready Ready Preview Nov 4, 2026 8:29pm UTC
swap-dev Ready Ready Preview Nov 4, 2026 8:29pm UTC
widget-configurator Ready Ready Preview Nov 4, 2026 8:29pm UTC
2 Skipped Deployments
Project Deployment Actions Updated
cosmos Ignored Ignored Nov 4, 2026 8:29pm UTC
sdk-tools Ignored Ignored Preview Nov 4, 2026 8:29pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4c09d223-3b01-4bdb-8d04-2637069c99d3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@limitofzero
limitofzero marked this pull request as ready for review November 2, 2026 10:32
@elena-zh
elena-zh self-requested a review November 2, 2026 10:50
Danziger and others added 2 commits November 2, 2026 17:12
# 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)

@elena-zh elena-zh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notes are here

limitofzero and others added 4 commits November 2, 2026 23:52
…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 -->
fairlighteth and others added 4 commits November 3, 2026 17:24
## 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 |
@limitofzero
limitofzero merged commit 35129c3 into main Nov 5, 2026
19 checks passed
@limitofzero
limitofzero deleted the release/2026-11-02 branch November 5, 2026 09:41
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

RELEASE Included in the release that is being closed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants