* chore: migrate tx-builder from safe-apps repo to this monorepo create-react-app is deprecated and I’ve migrated the code to vite. web3 is deprecated and I’ve moved the code to ethersjs the cypress tests were broken and I dropped them original code used mui4 -> here we’ve migrated to mui6 * fix(tx-builder): remove build from tx-builder * fix(tx-builder): vite config * fix(tx-builder): linting * fix(tx-builder): fix typo * fix(tx-builder): don’t mutate array * fix(tx-builder): fix exponential backtracking * fix(tx-builder): git workflows * fix(tx-builder): fix lint setup * fix(tx-builder): align package dependencies * fix(tx-builder): invalid react-dom version * fix(tx-builder): git workflow * fix(tx-builder): drag and drop was not working react-beautiful-dnd was no longer supported and it was not compatible with react 19. Moved to @hello-pangea/dnd * fix(tx-builder): fix pallet issues
11 KiB
Research: Migrate tx-builder to safe-wallet-monorepo
Phase: 0 - Research Date: 2026-01-12
Research Topics
1. MUI v4 to v6 Migration
Decision: Migrate all @material-ui/* imports to @mui/material v6
Rationale:
- Monorepo uses MUI v6 (
@mui/material: ^6.3.0) - Current tx-builder has mixed imports (theme uses v6, components use v4)
- MUI v6 has improved theming, better TypeScript support
Migration Strategy:
- Replace
@material-ui/core→@mui/material - Replace
@material-ui/icons→@mui/icons-material - Replace
@material-ui/lab→@mui/lab - Update component APIs (minimal changes v4→v6 for most components)
- Update theme configuration to use v6
createThemeAPI
Key API Changes:
makeStylesdeprecated → usestyledorsxpropfade()→alpha()- Some prop renames (e.g.,
disableElevation→disableElevationunchanged) - Grid v2 available but v1 syntax still works
Alternatives Considered:
- Keep MUI v4: Rejected - inconsistent with monorepo, v4 is legacy
- Use Tamagui: Rejected - web app pattern is MUI, Tamagui is for mobile
2. React 17 to 19 Migration
Decision: Upgrade to React 19.1.0 (matching monorepo)
Rationale:
- Monorepo uses React 19.1.0
- React 19 has improved performance, concurrent features
react-dom/clientAPI required
Migration Strategy:
- Update
ReactDOM.render()→createRoot().render() - Review useEffect dependencies (stricter in React 18+)
- Update event handler types if needed
- Test Suspense boundaries if any
Breaking Changes:
ReactDOM.renderdeprecated - must usecreateRoot- Automatic batching (usually beneficial, rarely breaking)
- Stricter hydration warnings (not applicable - SPA)
Alternatives Considered:
- React 18: Rejected - monorepo already on 19, no reason to stay behind
3. ethers v5 to v6 Migration
Decision: Upgrade to ethers 6.14.3 (matching monorepo)
Rationale:
- Monorepo uses ethers 6.14.3 (enforced via resolutions)
- ethers v6 has better tree-shaking, improved types
- Safe SDKs require consistent ethers version
Migration Strategy:
ethers.providers.Web3Provider→ethers.BrowserProviderethers.utils.isAddress→ethers.isAddressBigNumberclass removed → use nativebigintContractinstantiation API changes- Update ABI handling
Key API Changes:
// v5
import { ethers } from 'ethers'
const provider = new ethers.providers.Web3Provider(window.ethereum)
const address = ethers.utils.getAddress(addr)
const isValid = ethers.utils.isAddress(addr)
// v6
import { ethers, BrowserProvider, getAddress, isAddress } from 'ethers'
const provider = new BrowserProvider(window.ethereum)
const address = getAddress(addr)
const isValid = isAddress(addr)
Alternatives Considered:
- viem: Rejected - would require rewrite of all web3 code, monorepo uses ethers
4. CRA to Vite Migration
Decision: Use Vite 6.x as bundler
Rationale:
- CRA (react-scripts) is deprecated/unmaintained
- Vite has fast HMR, modern ESM-first approach
- Better build performance
- Simpler configuration
Migration Strategy:
- Create
vite.config.tswith React plugin - Move
public/index.html→index.html(root level) - Rename entry
src/index.tsx→src/main.tsx - Replace
REACT_APP_*env vars →VITE_* - Update import.meta.env usage
- Configure path aliases if needed
- Remove react-scripts, react-app-rewired, config-overrides.js
Vite Configuration:
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
base: '/tx-builder/',
build: {
outDir: 'build',
sourcemap: true,
},
server: {
port: 3000,
},
})
Environment Variables:
REACT_APP_TENDERLY_*→VITE_TENDERLY_*- Access via
import.meta.env.VITE_*instead ofprocess.env.REACT_APP_*
Alternatives Considered:
- Next.js: Rejected - tx-builder is a SPA, Next.js is overkill for iframe app
- Keep CRA: Rejected - deprecated, slow, no active maintenance
5. Safe Apps SDK Compatibility
Decision: Upgrade to @safe-global/safe-apps-sdk ^9.1.0
Rationale:
- Monorepo uses ^9.1.0
- Current tx-builder uses ^8.1.0 (via safe-react-apps root)
- Need consistent SDK version for proper Safe integration
Migration Strategy:
- Update
@safe-global/safe-apps-sdkto ^9.1.0 - Update
@safe-global/safe-apps-react-sdkto compatible version - Review SDK API changes (mostly additive, few breaking)
- Update
SafeProviderusage if needed
API Changes (v8 → v9):
- Mostly additive (new methods)
sdk.txs.send()signature unchanged- New chain support methods
Alternatives Considered:
- Keep v8: Rejected - would create version conflicts with monorepo packages
6. styled-components vs Emotion
Decision: Keep styled-components initially, consider emotion migration later
Rationale:
- Current codebase uses styled-components extensively
- MUI v6 uses emotion internally
- Full migration would be significant effort
- styled-components and emotion can coexist
Migration Strategy:
- Keep styled-components for existing component styling
- Use MUI's
sxprop andstyledfor new components - Consider gradual migration to emotion if maintenance issues arise
Alternatives Considered:
- Migrate all to emotion: Rejected - too much scope for initial migration
- Migrate all to Tailwind: Rejected - inconsistent with monorepo patterns
7. GitHub Actions Workflow Design
Decision: Create independent tx-builder deployment workflow
Rationale:
- tx-builder should release independently from web app
- PR previews needed for testing
- Staging/production deployment via S3
Workflow Design:
# .github/workflows/tx-builder-deploy.yml
name: tx-builder Deploy
on:
pull_request:
paths:
- apps/tx-builder/**
push:
branches: [dev, main]
paths:
- apps/tx-builder/**
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/yarn
- name: Build tx-builder
run: yarn workspace @safe-global/tx-builder build
env:
VITE_TENDERLY_ORG_NAME: ${{ secrets.TENDERLY_ORG_NAME }}
# ... other env vars
deploy-preview:
if: github.event_name == 'pull_request'
needs: build
# Deploy to S3 preview bucket
deploy-staging:
if: github.ref == 'refs/heads/main'
needs: build
# Deploy to staging bucket
deploy-dev:
if: github.ref == 'refs/heads/dev'
needs: build
# Deploy to dev bucket
Alternatives Considered:
- Deploy with web app: Rejected - different release cadence needed
- Use Vercel/Netlify: Rejected - existing S3 infrastructure preferred
8. Testing Infrastructure Migration
Decision: Migrate all unit tests and Cypress E2E tests to the monorepo
Rationale:
- Existing tests provide critical regression coverage
- 17 E2E tests cover all major user flows
- Unit tests validate form logic and utilities
- Tests ensure migration doesn't break functionality
Unit Test Migration Strategy:
-
Copy test files with source files (colocated pattern)
-
Update test-utils.tsx for React 19:
// Before (React 17) import { render } from '@testing-library/react' return render(<Providers>{children}</Providers>) // After (React 19) - no change needed for render, but providers may need updates -
Replace axios mocks with MSW (per constitution):
// Before jest.mock('axios', () => ({ get: jest.fn() })) // After import { setupServer } from 'msw/node' import { http, HttpResponse } from 'msw' const server = setupServer(http.get('/api/*', () => HttpResponse.json({ data: [] }))) -
Update MUI component testing:
- MUI v6 may have different DOM structure
- Use
data-testidattributes where possible - Avoid relying on MUI-specific class names
Cypress E2E Migration Strategy:
-
Copy test files and fixtures to
apps/tx-builder/cypress/ -
Update cypress.config.ts for Vite:
import { defineConfig } from 'cypress' export default defineConfig({ e2e: { baseUrl: 'http://localhost:3000', env: { TX_BUILDER_URL: 'http://localhost:3000/tx-builder', }, }, }) -
Preserve iframe helpers - these are critical for Safe App testing
-
Update environment variables in CI workflow
Test Dependencies to Add:
{
"devDependencies": {
"@testing-library/react": "^16.1.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/user-event": "^14.5.2",
"@faker-js/faker": "^9.0.3",
"msw": "^2.7.3",
"cypress": "^13.15.2",
"cypress-file-upload": "^5.0.8"
}
}
Alternatives Considered:
- Skip E2E tests: Rejected - too much coverage would be lost
- Rewrite tests: Rejected - existing tests are comprehensive and working
Dependency Version Matrix
| Package | Current (safe-react-apps) | Target (monorepo) |
|---|---|---|
| react | 17.0.2 | 19.1.0 |
| react-dom | 17.0.2 | 19.1.0 |
| @mui/material | @material-ui/core 4.12.4 | 6.3.0 |
| @mui/icons-material | @material-ui/icons 4.11.3 | 6.1.6 |
| ethers | 5.7.2 | 6.14.3 |
| typescript | 4.9.4 | 5.9.2 |
| @safe-global/safe-apps-sdk | 8.1.0 | 9.1.0 |
| styled-components | 5.3.6 | 5.3.6 (keep) |
| react-router-dom | 6.4.3 | 6.4.3 (compatible) |
| react-hook-form | 7.39.1 | 7.41.1 (monorepo version) |
Risk Assessment
| Risk | Impact | Likelihood | Mitigation |
|---|---|---|---|
| MUI v4→v6 styling breaks | Medium | High | Incremental migration, visual regression testing |
| React 19 compatibility issues | Medium | Low | Well-documented upgrade path |
| ethers v6 breaks web3 code | High | Medium | Thorough testing of all transaction flows |
| Build performance regression | Low | Low | Vite is faster than CRA |
| Safe Apps SDK incompatibility | High | Low | SDK is backward compatible |
Open Questions Resolved
All research questions have been resolved. No NEEDS CLARIFICATION items remain.