Repository navigation
Conversation
…efetches (JUMEMB-192) The chains response includes token prices, so every chains refetch gave a new chains array and syncWagmiConfig set up all connectors again and reconnected. Connectors whose SDK binds its event handlers to the first connector object (MetaMask Connect, WalletConnect, Coinbase, Base Account) then emitted on an emitter that wagmi no longer reads, so the wagmi chain went stale until the next refetch. syncWagmiConfig now skips setup and reconnect when the chain fields that wagmi and connectors read, and the connectors array, are the same as at the last sync. When they do change, each connector function gets a stable emitter that forwards to the newest connector object. Adds vitest to the package with tests for both parts.
🦋 Changeset detectedLatest commit: ec4ba2e The changes in this PR will be included in the next version bump. This PR includes changesets to release 10 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
📦 Bundle size✅ No significant changes. Every check is within budget. @lifi/widget
@lifi/wallet-management
@lifi/widget-light
Providers
Full integration
How to read this
|
Contributor
✅ E2E Dev Smoke — passing
4 passed · 0 failed · 0 skipped · 19s |
Contributor
E2E Examples — all passedAll examples passed in the latest run. |
Contributor
E2E Playground resultsDetails
📥 Download full HTML report (open the run → Artifacts → |
Open
3 tasks done
…mitter wrapper (JUMEMB-192) - Compare the connectors array item by item, so callers that pass a new array on each render (for example `[]`) still skip a sync. - Store the last sync only after the setup, so a failed setup is tried again. - Key the emitter wrapper per wagmi config, so two configs that share a connector function do not get each other's events. - Build the chains key after the mainnet contracts merge and from the whole rpcUrls, blockExplorers and contracts, plus testnet.
… live chain list (JUMEMB-192) Replace the chains key guard and the emitter forwarding wrapper with a simpler model. Each connector function gets one connector object per wagmi config, so connector SDKs that bind their event handlers once keep reaching wagmi. Connectors read the live wagmi chain list through the setup parameter, so a new chain needs no new setup. A chains refetch only updates the wagmi chains; a change of the connector functions sets up only the new ones and runs reconnect.
…allets too (JUMEMB-192) wagmi sets up the connectors of injected wallets that it finds through EIP-6963 from provider details. They also got a copy of the chain list, so they could not switch to a chain that the API adds after the first sync.
…ncWagmiConfig (JUMEMB-192)
…connects (JUMEMB-192) An independent review found three cases where main's reconnect on each chains refetch hid a gap that the previous commit exposed: - wagmi Hydrate clears the connections on each render when ssr is false, storage is set and reconnectOnMount is false. Reconnect when the current connection is missing. - wagmi drops a reconnect while another one runs, so a connector added during the first reconnect (Jumper mini-app) was never connected. Queue the reconnect calls of the sync. - An EIP-6963 wallet that wagmi set up itself kept a copy of the chain list. Keep one connector with the live chain list for each EIP-6963 uuid, and replace the objects that the sync did not make. The setup parameter is now a copy with property descriptors and a live chains getter instead of a Proxy, so a frozen parameter cannot throw.
…and free old ones (JUMEMB-192) A second review round found two issues in the previous commit: - wagmi reconnect adds its change and disconnect listeners again on each reconnect of the same connector object, so each heal added one more. After each queued reconnect, set the listeners of the connected connectors to exactly one. - The connector cache held old connector functions strongly. Use a WeakMap for connector functions and a Map only for EIP-6963 uuids.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which Linear task is linked to this PR?
JUMEMB-192
SDK side: lifinance/sdk#540. This PR removes the cause in the widget, so wagmi does not keep an old chain.
What was wrong
useSyncWagmiConfigrunssyncWagmiConfigeach time thechainsarray changes. The/v1/chainsresponse contains token prices (nativeToken.priceUSD), so every chains refetch (every 5 minutes) gives a new array.syncWagmiConfigthen made new connector objects withsetupand calledreconnect.setup, they send events to an emitter that wagmi no longer reads.ChainMismatchError(JUMPER-FRONT-CH).chainIdwent to the old chain.Jumper calls the same hook through
@jumperexchange/widget-provider-ethereumand shares the chains query, so it has the same problem. 10 Jumper session replays with 8Z show a/chainsrequest every 300 seconds, and "Got" is the wallet chain at the last/chainsrequest.What this PR changes
syncWagmiConfigsets up each connector function and each EIP-6963 wallet (byinfo.uuid) one time, and keeps the object. A connector SDK that binds its event handlers one time then always sends its events to the object that wagmi reads.setup. The connector now gets a copy of the setup parameter with a livechainsgetter. So a new chain from the API works without a newsetup.reconnectonly when it is necessary. A chains refetch only sets the wagmi chains. The sync runsreconnectin these cases only:ssrisfalse, storage is set andreconnectOnMountisfalse).reconnect. wagmi drops areconnectcall while another one runs. The sync waits for its earlier call, so a connector that is added during the firstreconnect(the Jumper mini-app) is connected too.reconnectadds itschangeanddisconnectlisteners again each time it connects the same connector object. After eachreconnectof the sync, the connected connectors get exactly one of each again. The cache keeps connector functions in aWeakMap, so a connector function that a caller no longer uses can be freed.widget-provider-ethereumgets vitest and 18 tests.Why was it implemented this way?
syncWagmiConfig, so the widget and Jumper both get it.maindesigned the fix from scratch. It reached the same core: one object for each connector, a live chain list, areconnectqueue, and one object for each EIP-6963 wallet. This PR uses its descriptor copy in place of a Proxy.reconnectafter each refetch, which onmainhid gaps in the wagmi state. Parts 3 and 4 above fix them, and the three tests pass now. A second round on the fixed code found two smaller issues, also proved with tests: each healreconnectadded one more wagmi listener to the same connector, and the cache kept old connector functions in memory. Both are fixed (part 5).config._internal.chains.setStatean unsupported workaround. No upstream issue or fix exists for the handlers that are bound to the first object.reconnectafter a refetch. It had the three defects above.mainplus 83 lines.@wagmi/connectorsconnectors, the wagmiinjectedconnector (also for EIP-6963 wallets) and the Farcaster mini-app connector readconfig.chainsin each call.reconnectafter each refetch. Eachreconnectof the same connector object adds one more wagmichangelistener (I measured 1, 2, 3, 4), so the listeners grow in a long session.createConfigstill gets a second connector object at the first sync. The widget and Jumper givecreateConfigno connectors.widget-embeddeddoes, andwidgetLightConnectoralready handles this case.mainthis was the same.main.reconnectcalls of the sync. The widget and Jumper usereconnectOnMount={false}, so no otherreconnectruns at that time.createConfig, so a wagmi change that breaks this makes them fail.main):isNewChainsStale: falseon WalletConnect. Without it, a chain that the API adds ends the WalletConnect session at the nextreconnector page load.io.metamaskconnector stays next tometaMaskSDK.chainsto connectors as a getter, asprovidersalready is. Then the copy of the setup parameter is not necessary.Tests
Unit tests use the real wagmi
createConfigand a connector stub that binds its handlers one time, as MetaMask Connect does:setupand noreconnect. After it, a chain switch by the dApp and a chain switch in the wallet both reach the wagmi state.changelistener.setupand noreconnect.reconnectruns is connected.changeand 1disconnectlistener.reconnect. The playground passes[].On
main, 13 of the 18 tests fail. On the previous version of this PR, the 3 tests for the review defects fail.When I remove each part, only its own tests fail: the queue, the heal, the listener reset, the live chain list, the EIP-6963 reuse, and a
reconnecton each sync.The tests of the adversarial review pass on this code: D1–D3 (also in a React render with
WagmiProviderandStrictMode), the listener test, and a garbage-collection test for the cache. The 74 tests of the blind design pass too.A Node script with the real wagmi
createConfig,switchChainandgetConnectorClient, and the realmetaMask()connector. Only@metamask/connect-evmis a stub. The wallet starts on Polygon. The table shows the chain ofgetConnectorClient({ assertChainId: false }), which the widget uses for the next swap:mainPlayground in Chromium, with the real API and a temporary log, for 11 minutes, two times: with no wallet connector, and with MetaMask as the recent wallet, so the real
metaMask()connector is set up. The chains refetches at 302 and 602 seconds returned new prices. Each time,syncWagmiConfigonly updated the chains: nosetupand noreconnect. ThemetaMaskSDKconnector was set up one time, at the first sync. (This ran on the previous version. The refetch path is the same in this version.)pnpm test(all packages), Oxlint, Oxfmt, knip and the type check pass.Not tested yet: a real wallet. Planned with
release-previewbuilds:After merge
Checklist before requesting a review