Systems

supporting system

@qubitronlabs/crypto-wallet SDK

Published npm Package - Drop-in Wallet Widget with Shadow DOM CSS Isolation

This React and Next.js wallet SDK handles deposits, swaps, withdrawals, and tips inside another application. Shadow DOM keeps the widget's Tailwind styles separate from host styles such as Bootstrap, WordPress themes, or a React app's own CSS. It took three architecture iterations and nearly 32 published versions to reach the npm release.

scale
Supports Ethereum, Polygon, BSC, and Arbitrum, with separate network state for each chain.
latency
Caches token conversion rates for two minutes to reduce repeat RPC requests while the widget is open.
reliability
The Shadow DOM boundary prevents host CSS from cascading into the widget or widget CSS from leaking out. The browser keeps up to 50 recent transactions and clears stale records every hour.
automation
Host applications provide API settings at runtime, so the same build can run in development, staging, and production. If the backend is unavailable during login, the SDK creates a temporary local profile with limited functionality.
Crypto Wallet SDK npm packagefirst proof
Published on npm at @qubitronlabs/crypto-wallet
Context

Problem had shape before code did.

The widget can be embedded in another Next.js application. It lets users connect a wallet, make 1inch swaps, withdraw through smart contracts, deposit, and tip. Its Shadow DOM boundary keeps Bootstrap, Tailwind, and other host styles from changing the widget, while keeping the widget's own styles out of the host page.

  • Packaged the generated Tailwind CSS with the JavaScript bundle and inject it into the shadow root when the widget mounts. The host application and the widget keep separate style boundaries.
  • Split token swaps into an orchestration hook and smaller hooks for UI state, gas calculations, and blockchain transactions so each part could be tested on its own.
  • Added fuzzy matching for the case where a WebSocket transaction update arrives before the API has returned its first block hash.
  • Used synchronizer components to bring wagmi network changes and Socket.IO messages into Zustand through the React lifecycle.
CSS Isolation
Open Shadow DOM with Tailwind bundled into JavaScript at build time through tsup.
Architecture
Four layers: components, hooks, services, and store. UI components do not call the API directly.
Config
Runtime configuration only, with no build-time environment variables.
Storage
Network settings stay in localStorage, transactions use a 24-hour TTL, and token balances remain in memory only.
State
WebSocket state lives in Zustand rather than React Context, avoiding app-wide rerenders for each message.
Bundle
Native fetch only, with custom JSON parsing and JWT handling instead of Axios.
Compatibility
Supports Ethereum, Polygon, BSC, and Arbitrum at the same time.
Build

Structure had to survive job.

  • The SDK is organised into components, hooks, services, and a store. Components do not call the API directly.
  • A mapping layer reduces 36-field API responses to the user objects React state actually needs.
  • API keys and settings come from the host at runtime rather than build-time environment variables. One package can therefore run in development, staging, and production.
  • The SDK uses an open Shadow DOM for style isolation. `tsup` packages Tailwind CSS into JavaScript, then the widget injects it into the shadow root on mount.
The goal was a wallet widget that could be added to any Next.js app without disturbing its CSS. The first version required the host to install Tailwind, which conflicted with Bootstrap. The second bundled CSS, but the cascade could still cross the boundary. Shadow DOM solved that, and moving Radix portals into the shadow root completed the setup. It took 32 published versions to refine and is now used in production.
Working stack

TypeScript / React / Next.js / Zustand / Immer / viem / wagmi / Socket.IO / Dynamic SDK / tsup / Tailwind CSS v4 / shadcn/ui

Evidence

Browser becomes part of argument.

Evidence viewerKeyboard arrows change proof

01 / 05Runtime configuration supplied by the host application.

Decisions

Small cuts make system trustworthy.

  • Kept Tailwind after Bootstrap conflicts and studied the Dynamic.xyz auth modal to understand its Shadow DOM style boundary.
  • Pointed Radix UI portals at a container inside the shadow root so dialogs and tooltips remain inside the widget's styling context.
  • Added a limited fallback profile for login attempts made while the backend is unavailable.
  • Added exponential backoff when token requests hit RPC rate limits or receive incomplete backend data.
  • Used native fetch instead of Axios to keep the package smaller, with custom JSON parsing and JWT handling in return.
Pressure

What resisted, broke, stayed expensive.

Constraints in motion

  • Bootstrap in host applications could cascade into the SDK's Tailwind classes. Solving that took three architecture iterations.
  • Radix UI tooltips and dialogs initially mounted on `document.body`, outside the SDK's shadow root and its styles.
  • A WebSocket update could arrive before the API reported that the transaction had started.
  • Removing Tailwind would have meant discarding the component work already in place.

Failure modes

  • The first version asked host applications to install Tailwind, which conflicted with Bootstrap projects.
  • The second version bundled CSS, but ordinary cascading still let host classes change widget styles.
  • Radix UI portals mounted at the document root and bypassed the Shadow DOM, leaving them without the widget's styles.
  • WebSocket updates could arrive before the API returned the first transaction block hash. Fuzzy matching now covers that timing gap.

Trade

  • Synchronizer components replace standard JavaScript event listeners. They add invisible DOM nodes, but keep state updates aligned with React's lifecycle.
  • The SDK uses an open Shadow DOM. Host applications can inspect it, but CSS still cannot cross the boundary by accident.
  • Native fetch keeps the bundle smaller than Axios, but means the SDK owns JSON parsing and JWT handling.
Result

What held. What carries forward.

architecture

Shadow DOM keeps the widget's styles separate from the host

The build packages Tailwind CSS with JavaScript through `tsup`, then injects it into the shadow root on mount. Host CSS, including Bootstrap, cannot cascade into the widget.

behavior

Fuzzy WebSocket matching handles race conditions

A live transaction update can arrive before the API returns a block hash. Fuzzy matching connects that event to the pending operation through transaction-hash fragments.

architecture

Runtime configuration keeps one package usable across environments

The SDK has no build-time environment variables. It reads API keys and settings from host-provided configuration at runtime, so the same package works in development, staging, and production.

engineering

Radix UI portals stay inside the shadow root

Radix portal components target a specific container inside the shadow root rather than `document.body`.

If rebuilt

  • Use closed Shadow DOM mode where stricter host isolation is supported.
  • Give Socket.IO clients independent backoff offsets to avoid reconnect storms.
  • Add WalletConnect v2 deep-link support on mobile.
結論

Shadow DOM proved to be the practical answer to third-party CSS overriding utility classes. The path there took three architecture iterations and nearly 32 releases. Studying Dynamic.xyz made the key point clear: the boundary must keep styles from crossing in both directions.

Make it hold together

Complex frontend system need steady hand?

Architecture, interface, production constraints. Together.