Skip to content

refactor(wallets): migrate chain wallet providers to class + Zustand stores#1999

Open
babkenmes wants to merge 27 commits into
dev-monorepofrom
dev-evm-wallet-class-refactor
Open

refactor(wallets): migrate chain wallet providers to class + Zustand stores#1999
babkenmes wants to merge 27 commits into
dev-monorepofrom
dev-evm-wallet-class-refactor

Conversation

@babkenmes
Copy link
Copy Markdown
Collaborator

Summary

  • Refactor every chain wallet package (EVM, Starknet, SVM, Fuel, Bitcoin, TRON, TON, Paradex, Immutable Passport) from hook-based connections to a class + Zustand store architecture, exposing connections via external vanilla stores
  • Replace the hook-based WalletProvider contract with external stores; integrate all providers in the bridge app through getDefaultProviders
  • Add wallet icon resolution utilities (knownConnectorIcons, resolveWalletIcon) and connect-modal/connector store plumbing in the widget
  • Allow the EVM wallet to accept an externally-provided wagmi Config
  • Prune unused dependencies across wallet packages and the bridge app, slimming the lockfile significantly

Test plan

  • Connect/disconnect wallets for each chain (EVM, Starknet, Solana, Fuel, Bitcoin, TRON, TON, Paradex, Immutable Passport)
  • Verify wallet icons render correctly in the wallet modal and connected-wallets list
  • Confirm Starknet connectors only show when the extension is actually installed
  • Run a withdrawal flow end-to-end (ManualWithdraw)
  • pnpm install resolves cleanly with the pruned lockfile; pnpm build succeeds

Note

The tonconnect-manifest.json in this branch points at a trycloudflare.com tunnel URL for local testing — revert to the production URL before merging.

🤖 Generated with Claude Code

babkenmes and others added 25 commits May 21, 2026 15:11
… store

Eliminates React-hook-based state and provider trees in the EVM wallet
package. State now lives in a module-scoped Zustand store and orchestration
in a class singleton, both of which survive the lazy-load remount cycles
that were forcing hotfix workarounds in the old hook-heavy code.

- Replaces the 561-line useEVMConnection.ts hook with a class
  (EvmConnectionService) plus a thin React adapter (service/useEvmConnection.ts).
- Adds Zustand store (service/evmStore.ts) and vanilla wagmi watchers
  (service/syncWagmi.ts) that mirror connections/account/connectors.
- Hoists the wagmi Config into a module-scoped singleton (service/getEvmConfig.ts)
  with reconnect() called on init, replacing <WagmiProvider reconnectOnMount>.
- Deletes ActiveEvmAccountProvider, WagmiProvider, QueryClientProvider,
  WagmiWrapper, QueryWrapper from the EVM tree. EVMProvider/index.tsx is
  now an EVMHydrator that pushes init params and renders children directly.
- Extracts pure helpers to service/: resolveWallet, resolveSupportedNetworks,
  networkBuckets, connectorsHelpers (dedupePreferInjected, splitRegistryConnectors,
  wagmiDisplayUriSource, attemptGetAccount).
- Converts useEVMTransfer hook into a vanilla createEvmTransfer factory.
- Rewrites EVMRpcHealthCheckProvider to read from the store instead of useAccount.
- Replaces useWalletClient in evmUtils/ethers.ts with getWalletClient over the
  config singleton.
- Migrates paradex useParadexConnection's useConfig() to getEvmConfig() so app
  boot doesn't crash without WagmiProvider in tree. Remaining paradex hooks
  (useAccount, useWalletClient) only fire during withdrawal flow and are a
  documented follow-up.
- Preserves all connector orchestration bug-fix sediment verbatim (synchronous
  display URI subscription, attemptGetAccount retry loop, pending-metadata
  ordering, dedupePreferInjected).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The bridge app now mounts only the EVM provider by default instead of
the full multi-chain bundle from getDefaultProviders().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores parity with the pre-refactor context-fallback behavior so host
apps that already own a wagmi Config (and optionally wrap the widget in
their own <WagmiProvider>) can have the EVM package adopt that config
instead of creating a parallel one.

- getEvmConfig.ts: add provideExternalEvmConfig(cfg) + _external flag.
  Adopting an external config skips the internal createConfig() and
  skips reconnect() (host owns that lifecycle). Re-providing a different
  config after one is already in use warns and no-ops.
- EVMHydrator: accept optional wagmiConfig prop (explicit path) and fall
  back to useContext(WagmiContext) for ambient detection (implicit path).
  Resolution order: existing registered config → external (prop/ambient)
  → internal createConfig() with widget-built connectors.
- createEVMProvider: expose wagmiConfig?: Config on EVMProviderConfig and
  forward it to EVMProviderWrapper.
- Re-export provideExternalEvmConfig and isExternalEvmConfig.

attachWagmiSync works against any wagmi Config, so EvmConnectionService,
useEthersSigner, and rpcHealthCheckProvider need no changes — they still
read through getEvmConfig() / useEvmStore.

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

Mirrors the EVM wallet-provider refactor (ab23f35) for TON: orchestration
moves into TonConnectionService, wallet state is observed once via
attachTonSync and stored in a Zustand store, and useTonConnection becomes a
thin React adapter. Old useTONConnection.ts stays as a re-export shim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d store

Mirrors the EVM/TON refactors for Tron: orchestration moves into
TronConnectionService, wallet state is synced from the @tronweb3/* react
hooks into a Zustand store via a TronSync hydrator, and useTronConnection
becomes a thin React adapter. Old useTronConnection.ts stays as a re-export
shim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tand store

Mirrors the EVM refactor for Bitcoin: orchestration moves into
BitcoinConnectionService, wagmi-style state from @bigmi/client is mirrored
into a Zustand store via attachBitcoinSync, and useBitcoinConnection becomes
a thin React adapter. Old useBitcoinConnection.ts stays as a re-export shim;
unused useAccount/useSyncExternalStoreWithTracked utils are removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d store

Mirrors the EVM/TON/Tron/Bitcoin refactors for Fuel: orchestration moves into
FuelConnectionService, @fuels/react state (connectors + fuel instance) is
mirrored into a local Zustand store from a thin React hook, and the hook
becomes a thin adapter that wires global useWalletStore deps via configure().
Old useFuelConnection.ts stays as a re-export shim.

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

Mirrors the EVM refactor for Solana: orchestration moves into
SvmConnectionService, @solana/wallet-adapter-react state is mirrored into a
local Zustand store via an SvmSync hydrator, and useSvmConnection becomes a
thin React adapter. Old useSVMConnection.tsx stays as a re-export shim.

Also adds @types/react to devDependencies on packages that now use zustand,
so pnpm doesn't pick @types/react@18 transitively (which surfaces a React
Context type mismatch in EVM); and adds an explicit ReactElement return type
to TronProvider for the same reason.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…stand store

Mirrors the EVM refactor for Starknet: orchestration moves into
StarknetConnectionService, @starknet-react/core state (connectors +
disconnectAsync) is mirrored into the existing Zustand store via a
StarknetSync hydrator, and useStarknetConnection becomes a thin React
adapter. resolveStarknetWallet moves into the service. Old
useStarknetConnection.ts and starknetWalletStore.ts stay as re-export shims.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ice class

Mirrors the EVM refactor for Paradex: connect/switch/requestAdditional
orchestration moves into ParadexConnectionService. The hook now wires inner
EVM + Starknet providers and the global useWalletStore paradexAccounts deps
into the class via configure(). Old useParadexConnection.ts stays as a
re-export shim.

Paradex doesn't get its own Zustand store — its persisted state already
lives in the widget's global useWalletStore.paradexAccounts, and inner-
provider connector state lives in the EVM/Starknet stores; the service just
composes them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…stand store

Mirrors the EVM refactor for ImtblPassport. ImtblPassport has no
WalletConnectionProvider hook of its own (connection happens through the EVM
provider via EIP-6963), so the refactor is scoped to the Passport SDK
singleton: ImtblPassportService owns init + connectEvm + loginCallback, and
the ready flag + instance live in a Zustand store. The old module-level
passportInstance export is removed; consumers should use
imtblPassportService.getInstance() or useImtblPassportStore() instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the EVM-only default in WidgetWrapper with getDefaultProviders from
@layerswap/wallets, which bundles EVM, Starknet, Fuel, Paradex, Bitcoin, TON,
SVM, Tron, and (when configured) Immutable Passport. WalletConnect, TON, and
Immutable Passport configs come from the existing NEXT_PUBLIC_* env vars;
the TON manifest URL points at the manifest already shipped under
/public/tonconnect-manifest.json. Callers can still pass walletProviders
explicitly to override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes @walletconnect/universal-provider, @walletconnect/types,
@walletconnect/utils, and bs58 — none have any import in src/. Only
@walletconnect/ethereum-provider is still used (in
connectors/resolveConnectors/walletConnect.ts as a type import).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removed when useAccount/useSyncExternalStoreWithTracked utils were deleted in
the class-based refactor (commit 14db284). No imports remain.

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

None have any import in src/. Likely orphans from earlier Fuel implementations
that no longer apply.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ypto-utils and bignumber.js

No imports in src/. Paradex SDK and starknet handle the crypto/big-number
needs that these were presumably brought in for.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ton/crypto has no imports in src/ — only @ton/core and @ton/ton are used.
The devDep `ton` (legacy v13) was superseded by @ton/ton (v15) and is also
unimported.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
No axios imports in src/. @imtbl/sdk handles its own HTTP needs internally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TronProvider only dynamically imports adapters for MetaMask, TronLink, OKX,
BitKeep, Bybit, and Gate. The foxwallet, imtoken, ledger, tokenpocket, and
trust adapters are declared but never imported anywhere in src/, so they're
just bloating install size.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous commit (46da1af) dropped @ton/crypto because nothing in
wallet-ton/src imports it directly. But @ton/ton declares
@ton/crypto as a peerDep, so a clean install of the bridge app fails
with "Module not found: Can't resolve '@ton/crypto'" once the
transitively-hoisted copy is gone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…act/types

None are imported in apps/bridge source. The other "looks unused" deps
(@bigmi/*, wagmi/@wagmi/*, viem, zustand, @tanstack/react-query) are
peerDeps of the wallet packages and must stay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ternal stores

Reshapes the widget's wallet-provider contract so wallet packages no longer
have to be React hooks. The driver is to make @layerswap/wallet-evm fully
React-free; the contract change cascades to every wallet package.

Widget contract changes
- WalletProvider.walletConnectionProvider (hook) → createConnection: (props)
  => WalletConnectionStore (vanilla subscribe/getSnapshot/updateProps/destroy)
- WalletProvider gains optional `init: (ctx) => () => void` for one-shot
  lifecycle; `wrapper` stays for packages that need an upstream React tree
  (TonConnectUI, StarknetReact, etc.)
- RpcHealthCheckProvider.useRpcHealthCheck (hook) → createStore() external
  store with combined snapshot + imperative ops
- Wallet.icon: (props) => JSX.Element → string | undefined (URL or data URI);
  new WalletIconView falls back to AddressIcon when undefined
- BaseWalletProviderConfig.customHook → customConnection

Widget internals
- New vanilla zustand-style stores: getAdditionalConnectorsStore(ns, projectId)
  and connectModalStore. useAdditionalConnectors / useConnectModal become thin
  useSyncExternalStore adapters
- New createReactHookConnectionAdapter(useFn) helper bridges legacy hooks to
  the external-store contract via a hidden <Hydrator> component
- getKnownConnectorIconBase64(id) / resolveWalletIconString expose string-only
  icon lookup so wallet packages produce Wallet.icon strings without
  importing React

EVM package (now fully React-free)
- Source has zero `from 'react'`, zero JSX, zero .tsx files
- react / react-dom / @types/react removed from peer + dev deps
- useEvmConnection hook → createEvmConnection(props) external store with
  input-memoized snapshots and ref-counted internal subscriptions
- EVMHydrator component → initEvmProvider(opts) one-shot function
- useChainConfigs hook → getEvmChainsConfig(networks) pure function
- useEVMConnectors hook → buildEVMConnectors(...) pure function
- useEthersSigner hook → getEthersSigner({ chainId }) promise
- EVMRpcHealthCheckProvider.useRpcHealthCheck → createStore() vanilla store
- Deprecated EVMProvider constant + useEVMConnection shim removed

Other wallet packages (contract-only migration)
- bitcoin, fuel, starknet, svm, ton, tron, paradex, imtblPassport: each ships
  a thin xxxConnectionAdapter built with createReactHookConnectionAdapter
  whose Hydrator is mounted inside the package's existing wrapper. The
  factories now return createConnection instead of walletConnectionProvider
- Paradex no longer recreates EVM/Starknet provider snapshots itself — reads
  from useWalletProviders() instead
- Deprecated XxxProvider constants removed across all packages

Strict-mode safety
- The EVM connection store ref-counts its upstream subscriptions
  (useEvmStore, additionalConnectorsStore, connectModalStore) so React 18's
  double-mount cleanup doesn't permanently tear down internal listeners

Apps
- widget-playground: useCustomEvm wrapped via createReactHookConnectionAdapter;
  LayerswapWidgetCustomEvm mounts the hydrator inside its WagmiProvider tree
- examples/nextjs-dynamic: same shape for useCustomStarknet
- bridge: getDefaultProviders shape unchanged externally

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…factories across chain packages

Implements the external-store WalletProvider contract from b5ee460 across
bitcoin, fuel, imtblPassport, paradex, starknet, svm, ton, and tron. Drops
React XxxProvider components and useXxxConnection hooks in favor of
createXxxConnection vanilla factories backed by Zustand stores, renames
useXxxTransfer to createXxxTransfer, and updates the widget consumer to
resolve providers through the new registry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Connectors were always marked type:'injected' with extensionNotFound
keyed off installLink presence, so configured wallets showed "Installed"
yet routed to the not-detected screen, and Web Wallet / Controller were
mislabeled as installed. Now gate both on the connector's available()
check plus configured-extension status.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 27, 2026

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

Project Deployment Actions Updated (UTC)
layerswapapp Error Error May 27, 2026 1:23pm

Request Review

@arentant arentant changed the base branch from dev-monorepo-1.3.0 to dev-monorepo May 27, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant