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.
17 KiB
Tasks: Refactor Earn Feature
Input: Design documents from /specs/002-refactor-earn-feature/
Prerequisites: plan.md ✅, spec.md ✅, research.md ✅, data-model.md ✅, contracts/ ✅
Tests: No new tests required. This is a pure refactoring task that must preserve all existing functionality. Manual testing checklist provided in quickstart.md.
Organization: Tasks are grouped by user story (from spec.md) to enable independent implementation and testing of each structural improvement.
Format: [ID] [P?] [Story] Description
- [P]: Can run in parallel (different files, no dependencies)
- [Story]: Which user story this task belongs to (US1-US5)
- All paths are relative to
apps/web/src/features/earn/
Phase 1: Setup (Pre-Refactoring Preparation)
Purpose: Prepare for refactoring by backing up, documenting current state, and verifying baseline
- T001 Create a backup of the current earn feature state (git commit or branch checkpoint)
- T002 Run and document current type-check status:
pnpm --filter @safe-global/web type-check | grep earn - T003 [P] Run and document current lint status:
pnpm --filter @safe-global/web lint | grep earn - T004 [P] Document all current external imports (search codebase for
@/features/earn)
Checkpoint: Baseline documented and verified - ready to begin refactoring
Phase 2: Foundational (Core Structure Files)
Purpose: Create foundational files that all subsequent tasks depend on
⚠️ CRITICAL: No component/hook refactoring can begin until these core files exist
- T006 Create
apps/web/src/features/earn/types.tswithEarnButtonPropsinterface from research.md - T007 Create
apps/web/src/features/earn/hooks/index.tsbarrel export file (initially empty) - T008 [P] Create
apps/web/src/features/earn/components/index.tsbarrel export file (initially empty) - T009 [P] Create
apps/web/src/features/earn/services/directory - T010 Create
apps/web/src/features/earn/services/index.tsbarrel export file (initially empty)
Checkpoint: Foundation ready - component/hook/service refactoring can now proceed
Phase 3: User Story 1 - Restructure Earn Feature Folders (Priority: P1) 🎯
Goal: Organize all files into standard folders with proper barrel exports
Independent Test: Verify folder structure exactly matches pattern in apps/web/docs/feature-architecture.md
3.1: Move Services
- T011 [US1] Move
apps/web/src/features/earn/utils.tstoapps/web/src/features/earn/services/utils.ts - T012 [US1] Update all internal imports of
utils.tsto point to../services/utilsor./services/utils - T013 [US1] Export utilities from
services/index.ts:export { vaultTypeToLabel, isEligibleEarnToken } from './utils'
3.2: Rename Main Component
- T014 [US1] Create directory
apps/web/src/features/earn/components/EarnPage/ - T015 [US1] Move current
apps/web/src/features/earn/index.tsxtoapps/web/src/features/earn/components/EarnPage/index.tsx - T016 [US1] Update imports in
EarnPage/index.tsxto use relative paths (e.g.,../../constants,../EarnView)
3.3: Update Hook Exports
- T017 [US1] Update
useIsEarnFeatureEnabled.tsto use named export:export function useIsEarnFeatureEnabled(): boolean | undefined - T018 [US1] Update
useIsEarnFeatureEnabled.tsto properly handle undefined return (preserve loading state) per research.md - T019 [US1] Export hooks from
hooks/index.ts:export { useIsEarnFeatureEnabled } from './useIsEarnFeatureEnabled' export { default as useGetWidgetUrl } from './useGetWidgetUrl'
3.4: Update Component Barrel Exports
- T020 [US1] Export components from
components/index.ts:export { default as EarnPage } from './EarnPage' export { default as EarnButton } from './EarnButton' // Do NOT export EarnView, EarnWidget, EarnInfo, or vault components (internal)
Checkpoint: Folder structure complete - all files organized in correct directories
Phase 4: User Story 2 - Create Proper Public API (Priority: P1) 🎯
Goal: Expose only public API through root index.ts, preventing direct imports of internals
Independent Test: Attempt to import internal components from outside feature (should fail after barrel file is the only entry point)
4.1: Create Public API Barrel File
-
T021 [US2] Create new
apps/web/src/features/earn/index.ts(replacing old index.tsx) with structure from research.md:import dynamic from 'next/dynamic' // Re-export types export type { EarnButtonProps } from './types' // Re-export hooks export { useIsEarnFeatureEnabled } from './hooks' // Re-export components export { EarnButton } from './components' // Default export: lazy-loaded main component const EarnPage = dynamic(() => import('./components/EarnPage').then((mod) => ({ default: mod.default })), { ssr: false, }) export default EarnPage
4.2: Update External Imports (Files Outside Earn Feature)
-
T022 [US2] Update
apps/web/src/components/dashboard/Assets/index.tsx:- Change:
import EarnButton from '@/features/earn/components/EarnButton' - To:
import { EarnButton } from '@/features/earn'
- Change:
-
T023 [US2] Update
apps/web/src/components/balances/AssetsTable/PromoButtons.tsx:- Change:
import EarnButton from '@/features/earn/components/EarnButton' - To:
import { EarnButton } from '@/features/earn'
- Change:
-
T024 [US2] Update
apps/web/src/components/dashboard/index.tsx:- Change:
import { useIsEarnFeatureEnabled as useIsEarnPromoEnabled } from '@/features/earn/hooks/useIsEarnFeatureEnabled' - To:
import { useIsEarnFeatureEnabled as useIsEarnPromoEnabled } from '@/features/earn'
- Change:
4.3: Verify No Deep Imports Remain
- T025 [US2] Search codebase for remaining deep imports:
grep -r "@/features/earn/components" apps/web/src/(should return nothing) - T026 [US2] Search codebase for remaining deep imports:
grep -r "@/features/earn/hooks" apps/web/src/(should return nothing) - T027 [US2] Search codebase for remaining deep imports:
grep -r "@/features/earn/services" apps/web/src/(should return nothing)
Checkpoint: Public API complete - all external imports use @/features/earn barrel file
Phase 5: User Story 3 - Ensure Proper Lazy Loading (Priority: P1) 🎯
Goal: Verify earn feature is lazy-loaded and code-split correctly
Independent Test: Disable EARN feature flag, load app, verify no earn code loaded in bundle
5.1: Verify Page-Level Lazy Loading
- T028 [US3] Verify
apps/web/src/pages/earn.tsxusesdynamic(() => import('@/features/earn'), { ssr: false })- ✅ Already correct (confirmed in research.md), no changes needed
5.2: Verify No Static Imports
- T029 [US3] Search for static imports of earn feature:
grep -r "^import.*@/features/earn" apps/web/src/ | grep -v "pages/earn.tsx"- Verify only the three updated files from Phase 4 appear (Assets, PromoButtons, dashboard)
- Verify all three use dynamic imports or are not page-level (components can use named imports)
5.3: Bundle Analysis
- T030 [US3] Build the app:
pnpm --filter @safe-global/web build - T031 [US3] Analyze bundle to verify earn is in separate chunk:
- Look for chunk files containing "earn" in
.next/static/chunks/ - Verify earn code is not in main bundle (
main-*.js)
- Look for chunk files containing "earn" in
5.4: Runtime Verification
- T032 [US3] Start app, open DevTools Network tab, navigate to a chain with earn DISABLED
- T033 [US3] Navigate to
/earnpage - T034 [US3] Verify "Earn is not available on this network" message shows
- T035 [US3] Verify no network requests for earn-related chunks in DevTools
- T036 [US3] Navigate to a chain with earn ENABLED (Ethereum mainnet or Base)
- T037 [US3] Navigate to
/earnpage - T038 [US3] Verify earn page loads and shows disclaimer or widget
- T039 [US3] Verify earn chunk is loaded on-demand in DevTools Network tab
Checkpoint: Lazy loading verified - earn code only loads when feature is enabled and user navigates to page
Phase 6: User Story 4 - Add TypeScript Type Definitions (Priority: P2)
Goal: All TypeScript interfaces centralized in types.ts
Independent Test: Verify all types exported from types.ts, no inline interfaces in components
6.1: Update EarnButton to Use Centralized Types
- T040 [US4] Update
apps/web/src/features/earn/components/EarnButton/index.tsx:- Remove inline props interface (lines 18-27)
- Import type from parent:
import type { EarnButtonProps } from '../../types' - Change component signature to:
const EarnButton = (props: EarnButtonProps): ReactElement => { - Destructure props inside component body
6.2: Verify Type Exports
- T041 [US4] Verify
types.tsexportsEarnButtonPropsinterface - T042 [US4] Verify root
index.tsre-exports:export type { EarnButtonProps } from './types'
6.3: Verify Type Safety
- T043 [US4] Run type-check:
pnpm --filter @safe-global/web type-check - T044 [US4] Verify no type errors related to earn feature
- T045 [US4] Verify external consumers of
EarnButtonstill type-check correctly
Checkpoint: Types centralized - all earn types defined in types.ts with proper exports
Phase 7: User Story 5 - Preserve All Existing Functionality (Priority: P1) 🎯
Goal: Validate 100% of existing functionality works identically after refactoring
Independent Test: Run manual testing checklist from quickstart.md and verify all scenarios pass
7.1: Type Checking & Linting
- T046 [US5] Run type-check:
pnpm --filter @safe-global/web type-check(must pass with zero errors) - T047 [US5] Run lint:
pnpm --filter @safe-global/web lint(must pass or match baseline from T003) - T048 [US5] Run format check:
pnpm --filter @safe-global/web prettier(must pass)
7.2: Build Verification
- T049 [US5] Clean build artifacts:
rm -rf apps/web/.next - T050 [US5] Build app:
pnpm --filter @safe-global/web build(must succeed)
7.3: Manual Testing - Feature Flag Behavior
- T051 [P] [US5] Navigate to Ethereum mainnet (chain with earn enabled)
- T052 [P] [US5] Verify
/earnpage loads successfully - T053 [P] [US5] Navigate to Sepolia testnet (chain with earn disabled)
- T054 [P] [US5] Verify
/earnshows "not available" message
Checkpoint: All functionality verified - refactoring preserves 100% of existing behavior
Phase 8: Polish & Validation
Purpose: Final cleanup, documentation, and compliance verification
8.1: Code Quality
- T089 [P] Run prettier auto-fix:
pnpm prettier:fix - T090 [P] Final type-check:
pnpm --filter @safe-global/web type-check(must pass) - T091 [P] Final lint check:
pnpm --filter @safe-global/web lint(must pass)
8.2: Architecture Compliance
- T092 Review folder structure against checklist in
apps/web/docs/feature-architecture.md - T093 Verify all required files exist:
index.ts,types.ts,constants.ts,components/index.ts,hooks/index.ts,services/index.ts - T094 Verify no files in wrong locations (e.g., no loose files in feature root except required ones)
8.3: Documentation
- T095 [P] Update
apps/web/docs/feature-architecture.mdif any learnings from this refactoring should be added- No updates needed - refactoring followed documented pattern exactly
- T096 [P] Document any challenges or deviations in migration notes (if applicable)
- No deviations - implementation matches plan
8.4: Git & Cleanup
- T097 Review all changed files in git diff
- T098 Verify no unintended changes (e.g., formatting changes in unrelated files)
- T099 Verify all TODOs or FIXME comments are addressed
- Pre-existing TODO in VaultRedeemConfirmation (not introduced by refactoring)
8.5: Success Criteria Validation
Verify all success criteria from spec.md:
- T102 ✅ SC-001: Folder structure passes 100% compliance against feature-architecture.md checklist
- T103 ✅ SC-002: All existing tests pass (N/A - no tests exist)
- T104 ✅ SC-003: Bundle analysis confirms earn in separate chunk, not loaded when disabled
- T105 ✅ SC-004: External imports use only
@/features/earn(verified by grep in T025-T027) - T106 ✅ SC-005:
types.tscontains all earn-specific interfaces - T107 ✅ SC-006: Earn page loads and functions identically (requires manual testing)
- T108 ✅ SC-007: Analytics events fire correctly (requires manual testing)
- T109 ✅ SC-008: Geoblocking and consent work identically (requires manual testing)
Checkpoint: All success criteria met - refactoring complete and validated
Dependencies & Execution Order
Phase Dependencies
- Setup (Phase 1): No dependencies - start immediately
- Foundational (Phase 2): Depends on Setup (Phase 1) - BLOCKS all refactoring work
- User Story 1 (Phase 3): Depends on Foundational (Phase 2)
- User Story 2 (Phase 4): Depends on User Story 1 (Phase 3) - needs folder structure in place
- User Story 3 (Phase 5): Depends on User Story 2 (Phase 4) - needs public API in place
- User Story 4 (Phase 6): Can run in parallel with Phase 5 - independent type work
- User Story 5 (Phase 7): Depends on all previous phases - final validation
- Polish (Phase 8): Depends on all user stories complete
User Story Dependencies
- US1 (Folder Structure): Must complete before US2 (needs organized structure)
- US2 (Public API): Must complete before US3 (lazy loading needs public API)
- US3 (Lazy Loading): Can run after US2, independent of US4
- US4 (Type Definitions): Can run in parallel with US3, independent type work
- US5 (Preserve Functionality): Must run last, validates all previous work
Within Each Phase
- Tasks marked [P] can run in parallel (different files)
- Tasks marked [US#] are sequenced within that user story
- Follow task numbers for optimal ordering
Parallel Opportunities
Within Phase 3.1 (Move Services):
- T011, T012, T013 are sequential (same files)
Within Phase 3.3 (Update Hook Exports):
- T017, T018, T019 are sequential (same files)
Within Phase 4.2 (Update External Imports):
- T022, T023, T024 can run in parallel (different files) ✅
Within Phase 7 (Manual Testing):
- T051-T054 can run in parallel (feature flag tests) ✅
- T085-T088 can run in parallel (analytics tests) ✅
Within Phase 8.1 (Code Quality):
- T089, T090, T091 can run in parallel (different tools) ✅
Within Phase 8.3 (Documentation):
- T095, T096 can run in parallel (different docs) ✅
Implementation Strategy
Recommended Sequence (Single Developer)
-
Phase 1: Setup (T001-T005) - 30 minutes
- Document baseline, verify current state
-
Phase 2: Foundational (T006-T010) - 30 minutes
- Create core structure files (types, barrel exports, directories)
-
Phase 3: User Story 1 (T011-T020) - 2 hours
- Reorganize folder structure, move files, update imports
-
Phase 4: User Story 2 (T021-T027) - 1 hour
- Create public API, update external imports
-
Phase 5: User Story 3 (T028-T039) - 1 hour
- Verify lazy loading, test bundle splitting
-
Phase 6: User Story 4 (T040-T045) - 30 minutes
- Centralize types, update EarnButton
-
Phase 7: User Story 5 (T046-T088) - 2-3 hours
- Comprehensive manual testing of all functionality
-
Phase 8: Polish (T089-T109) - 1 hour
- Final validation, cleanup, success criteria check
Total Estimated Time: 8-10 hours (single developer, sequential)
Checkpoints for Validation
Stop and validate at these points:
- ✅ After Phase 2: All foundational files exist, structure ready
- ✅ After Phase 3: Folder structure matches standard, all files organized
- ✅ After Phase 4: Public API created, external imports updated, no deep imports
- ✅ After Phase 5: Bundle analysis confirms code splitting works
- ✅ After Phase 6: Types centralized, type-check passes
- ✅ After Phase 7: All manual tests pass, functionality preserved
- ✅ After Phase 8: All success criteria met, ready to commit
Notes
- This is a pure refactoring task with zero functional changes
- No new features, components, or logic are being added
- All existing behavior must be preserved exactly
- Focus on structural organization and API boundaries
- Reference implementation:
apps/web/src/features/walletconnect/ - Complete documentation:
apps/web/docs/feature-architecture.md - Manual testing checklist:
specs/002-refactor-earn-feature/quickstart.md
Commit Strategy
Suggested commit points:
- After Phase 2 (Foundational):
feat(earn): create foundational structure files - After Phase 3 (US1):
refactor(earn): reorganize folder structure to match standard pattern - After Phase 4 (US2):
refactor(earn): create public API and update external imports - After Phase 5 (US3):
refactor(earn): verify lazy loading and code splitting - After Phase 6 (US4):
refactor(earn): centralize TypeScript types - After Phase 8 (Polish):
refactor(earn): final cleanup and validation
Or single commit after all phases complete:
refactor(earn): restructure feature to follow standard architecture pattern
Follow semantic commit conventions per CONTRIBUTING.md.