Upstream merge brings 52 commits from safe-global/safe-wallet-monorepo into the luxfi/safe-wallet@dev fork. Conflicts resolved: - yarn.lock: deleted (we are on pnpm) - apps/web/package.json: kept pnpm version + brand build scripts (build:safe / build:lux / build:hanzo) plus upstream's new generate-version.mjs step - apps/mobile/.../Signer/appKit.ts: took upstream's import pattern for shared/projectId, shared/metadata, shared/mmkvStorageAdapter Yarn sweep applied to 42 files (docs, github-actions, workflows, AGENTS, specs, codemods, scripts, READMEs). Net result: zero yarn-* command references anywhere outside node_modules. ❯ pnpm is the law in this fork. No yarn.
8.7 KiB
Feature Specification: Bridge Feature Refactor
Feature Branch: 002-bridge-refactor
Created: 2026-01-15
Status: Draft
Input: User description: "in 001-feature-architecture we created a feature pattern and refactored walletconnect to use it. I want to continue with the refactoring and refactor the bridge feature next."
User Scenarios & Testing (mandatory)
User Story 1 - Migrate Bridge Feature to Standard Architecture (Priority: P1)
The bridge feature is refactored to fully comply with the feature architecture standard established in 001-feature-architecture. This includes creating missing barrel files, extracting types, adding a proper public API, and ensuring the feature is properly lazy-loaded and feature-flagged.
Why this priority: This is the core deliverable—bringing the bridge feature into full compliance with the documented standard.
Independent Test: Can be fully tested by comparing the refactored feature structure against the documented standard checklist, verifying all required files exist, and confirming existing tests continue to pass.
Acceptance Scenarios:
- Given the current bridge feature structure, When refactored to the standard, Then the feature has all required files:
index.ts,types.ts,constants.ts,components/index.ts,hooks/index.ts - Given the refactored bridge feature, When imported from outside the feature, Then only the public API (
index.ts) can be used (ESLint enforces this) - Given the refactored bridge feature, When the BRIDGE feature flag is disabled, Then no bridge code is loaded or executed
- Given the refactored bridge feature, When all existing tests are run, Then they pass without modification
User Story 2 - Preserve Geoblocking Integration (Priority: P2)
The bridge feature has a useIsGeoblockedFeatureEnabled hook that combines feature flag checks with geoblocking checks. This pattern must be preserved and properly integrated into the new architecture.
Why this priority: Geoblocking is a compliance requirement. Breaking this integration would expose the feature to users in restricted regions.
Independent Test: Can be fully tested by verifying that when geoblocking context indicates a blocked region, the feature flag hook returns false/undefined regardless of the chain's feature flag setting.
Acceptance Scenarios:
- Given a user in a geoblocked region, When they access the bridge feature, Then the
useIsBridgeFeatureEnabledhook returnsfalse - Given a user in an allowed region on a chain with BRIDGE enabled, When they access the bridge feature, Then the
useIsBridgeFeatureEnabledhook returnstrue - Given the
useIsGeoblockedFeatureEnabledhook, When exported from the feature, Then it can be reused by other features (swap, staking) that need geoblocking
User Story 3 - Ensure Proper Lazy Loading (Priority: P3)
The bridge feature must be lazy-loaded using Next.js dynamic() imports to ensure code splitting. The feature's code should not be included in the initial bundle.
Why this priority: Code splitting is essential for performance. Including bridge code in the initial bundle increases load time for all users, even those who never use bridging.
Independent Test: Can be fully tested by building the application and verifying that a separate chunk exists for the bridge feature code.
Acceptance Scenarios:
- Given the bridge feature, When the application is built, Then the bridge code is in a separate chunk file
- Given a user who never navigates to the bridge page, When they use other parts of the app, Then zero bridge code is loaded
- Given the bridge feature, When lazy-loaded with
{ ssr: false }, Then it does not cause hydration mismatches
Edge Cases
- What happens when
useIsGeoblockedFeatureEnabledis called before the geoblocking context is initialized? The hook returnsundefinedduring loading state, and the component renders nothing. - How does the system handle the existing
SanctionWrapperandDisclaimerWrapperpatterns? These wrappers remain in theBridgecomponent as they are UI-level concerns, not architecture concerns. - What happens to the
BRIDGE_WIDGET_URLconstant? It is moved toconstants.tsand exported from the public API if needed externally. - How are the existing test files handled? Test files (
index.test.tsx) remain colocated with their components but are not exported from barrel files.
Requirements (mandatory)
Functional Requirements
Directory Structure
- FR-001: Bridge feature MUST have an
index.tsbarrel file atsrc/features/bridge/index.tsthat exports the public API - FR-002: Bridge feature MUST have a
types.tsfile containing all TypeScript interfaces used by the feature - FR-003: Bridge feature MUST have a
constants.tsfile containingBRIDGE_WIDGET_URLandLOCAL_STORAGE_CONSENT_KEY - FR-004: Bridge feature MUST have a
components/index.tsbarrel file re-exporting public components - FR-005: Bridge feature MUST have a
hooks/index.tsbarrel file re-exporting public hooks
Public API
- FR-006: The
index.tsbarrel MUST exportuseIsBridgeFeatureEnabledhook - FR-007: The
index.tsbarrel MUST exportuseIsGeoblockedFeatureEnabledhook (for reuse by other features) - FR-008: The
index.tsbarrel MUST have a default export of the lazy-loadedBridgecomponent - FR-009: The
index.tsbarrel MUST export types fromtypes.tsusingexport type { ... } - FR-010: The
index.tsbarrel MUST NOT export internal components (BridgeWidget) directly
Feature Flag Integration
- FR-011: The
useIsBridgeFeatureEnabledhook MUST check both the BRIDGE feature flag and geoblocking status - FR-012: The main
Bridgecomponent MUST render nothing whenuseIsBridgeFeatureEnabledreturnsfalseorundefined - FR-013: No bridge API calls or side effects MUST occur when the feature is disabled
Lazy Loading
- FR-014: The
Bridgecomponent MUST be lazy-loaded using Next.jsdynamic()with{ ssr: false } - FR-015: The
BridgeWidgetcomponent MUST continue to be dynamically imported within theBridgecomponent - FR-016: External consumers MUST import the feature via
import Bridge from '@/features/bridge'
Type Definitions
- FR-017: The
types.tsfile MUST define interfaces for any feature-specific data structures - FR-018: Types MUST be exported from the public API for external consumers if needed
Backward Compatibility
- FR-019: All existing bridge functionality MUST continue to work after refactoring
- FR-020: All existing tests MUST pass without modification (or with minimal adaptation to new import paths)
- FR-021: The
FeatureWrapper,SanctionWrapper, andDisclaimerWrapperpatterns MUST be preserved
Key Entities
- Bridge Feature: The self-contained domain module at
src/features/bridge/providing cross-chain asset bridging via LI.FI integration - BridgeWidget: Internal component that renders the LI.FI iframe embed within an
AppFrame - Bridge Component: Public entry point that wraps the widget with feature flag, sanction, and disclaimer checks
- Geoblocking Hook: A reusable hook pattern that combines feature flag checks with geographic restriction checks
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: The bridge feature passes 100% of the structural compliance checklist from the feature architecture documentation
- SC-002: All existing bridge tests pass without modification
- SC-003: ESLint produces no warnings for bridge feature imports from the rest of the codebase
- SC-004: After
pnpm build, a separate chunk file exists for bridge feature code (verified via filename inspection) - SC-005: When BRIDGE feature flag is disabled, network tab shows zero requests to bridge-related endpoints
- SC-006: The refactoring introduces zero regressions (existing E2E tests pass, if any exist for bridge)
- SC-007: A developer can import the bridge feature using only
import Bridge, { useIsBridgeFeatureEnabled } from '@/features/bridge'
Assumptions
- The existing
useIsGeoblockedFeatureEnabledhook pattern is correct and should be preserved - The
FeatureWrapper,SanctionWrapper, andDisclaimerWrappercomponents are shared infrastructure, not bridge-specific code - The
FEATURES.BRIDGEenum value already exists and is correctly configured in the CGW API - No new feature flag is needed; the existing BRIDGE flag is sufficient
- The
BridgeWidgettest file (index.test.tsx) remains valid after the refactoring - The bridge feature has no Redux store requirements (no state management needed beyond what exists)