Skip to content

Commit 4797726

Browse files
adnxyronickgtommasiniandrepimentaclaude
authored
feat: React Native Upgrade / 0.81.5 (MetaMask#29195)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** Upgrades the core mobile stack from React Native 0.76.9 to 0.81.5, React 18.3 to 19.1, and Expo SDK 52 to 54. Includes all necessary migration fixes for breaking changes in the new architecture, test infrastructure updates, and dependency alignment. <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** ### Core stack - **React Native** 0.76.9 → 0.81.5 - **React** 18.3.1 → 19.1.0 (and `react-test-renderer` to match) - **Expo SDK** 52 → 54 (every `expo-*` package bumped to its SDK 54 line) - **Hermes** updated alongside RN 0.81 - **`@react-native-community/cli` (+ `cli-platform-android`/`-ios`)** 15.0.1 → 20.0.0 - **`@react-native/metro-config`** 0.76.9 → 0.81.5 - **`@types/react`** ^18.2.6 → ^19.1.0 - **iOS app entry point migrated from Objective-C (`main.m`) to Swift (`AppDelegate.swift`)** as required by the RN 0.81 iOS template ### React Native ecosystem libraries - **`react-native-screens`** 3.37.0 → 4.16.0 (major) - **`react-native-safe-area-context`** ^5.4.0 → ~5.6.0 - **`react-native-reanimated`** ^3.17.2 → 3.19.0 - **`react-native-gesture-handler`** ^2.25.0 → ~2.28.0 - **`react-native-mmkv`** ^3.2.0 → ^4.1.2 (major; resolves to 4.3.1) - **`react-native-pager-view`** ^6.7.0 → 6.9.1 - **`react-native-nitro-modules`** ^0.29.6 → 0.35.5 - **`react-native-svg`** ^15.11.1 → 15.12.1 - **`react-native-video`** ^6.10.1 → ^6.19.0 - **`react-native-vision-camera`** ^4.6.4 → ^4.7.3 - **`react-native-view-shot`** ^3.1.2 → 4.0.3 (major; legacy `patch-package` patch dropped) - **`react-native-qrcode-svg`** 5.1.2 → 6.3.21 (major; legacy patch dropped) - **`react-native-performance`** ^5.1.2 → ^6.0.0 (major; legacy patch dropped) - **`react-native-release-profiler`** ^0.4.0 → ^0.4.4 - **`@react-native-community/slider`** ^4.4.3 → 5.0.1 (major) - **`@react-native-async-storage/async-storage`** ^1.23.1 → 2.2.0 (major) - **`@react-native-community/viewpager`** new direct dep (patched) to keep legacy paged-view consumers building under RN 0.81 - **`lottie-react-native`** 6.7.2 → ~7.3.1 (major) - **`rive-react-native`** 9.3.4 → ^9.8.0 (legacy patch dropped) ### Native / platform integrations - **`@sentry/react-native`** ~6.15.0 → ~7.2.0 - **`@segment/analytics-react-native`** ^2.20.3 → ^2.21.4 - **`@solana-mobile/mobile-wallet-adapter-protocol`** ^2.2.5 added (replaces the removed Solana shim path) ### Tooling / test infra - **`detox`** ^20.35.0 → ^20.43.0 - **`@expo/fingerprint`** ^0.15.0 → ~0.15.4 - **`pretty-format/react-is`** pinned to ^19.0.0 (resolution; required for React 19 test compat) - New global mock: `tests/module-mocking/sentry/react-native.ts` (E2E-only Sentry shim selected by `metro.config.js` when `IS_TEST=true`) - `tsconfig.json` path aliases for selected `@metamask/*` dist subpaths ## Breaking Changes Addressed ### React 19 - Removed `defaultProps` from all function components (deprecated in React 18.3, removed in 19) - `useRef` now requires an explicit initial value — every `useRef<T>()` updated to `useRef<T>(null)` - `RefObject<T>` typing tightened — call sites switched to `RefObject<T | null>` where they were assuming non-null - `React.cloneElement` generics tightened — added explicit type parameters where inference no longer narrows - `ReactChild` removed — switched to `ReactNode` - Migrated hook tests from `@testing-library/react-hooks` (unmaintained on React 19) to `@testing-library/react-native` `renderHook` - Updated mocked ref parameters from `{}` to `undefined` to match the new `forwardRef` signature - Updated test `rerender()` call sites to the new return shape ### React Native 0.81 - Added a `BackHandler.removeEventListener` shim (the static method was removed; `addEventListener` now returns a subscription) - Migrated iOS entry point to **Swift `AppDelegate.swift`** + new `LaunchScreen.xib` (deleted `main.m`) - `MMKV` is now a type-only export and `.remove()` was renamed to `.delete()` (`react-native-mmkv` v4) - `NavigationContainerRef` requires a generic parameter (`@react-navigation/native` v7) - `BlurEvent` / `FocusEvent` typing changes on `TextInput` callbacks - `refreshControl` prop typing on virtualized lists - New `MessageEvent` (private) usage in `WebSocket` is internal — Snaps continue to boot via the existing `patches/@MetaMask+post-message-stream+10.0.0.patch` (no shim needed) - Patched `@metamask/react-native-webview`, `@metamask/react-native-button`, `@metamask/react-native-payments`, `@metamask/react-native-acm` for RN 0.81 / React 19 compatibility (see TODO list below) - Fixed `expo-modules-core` `EventEmitter` mock, `Linking` mock path (now `.default.openURL`), safe-area / screens snapshots ### Snaps - `AbstractExecutionService` renamed to `ExecutionService` in `@metamask/snaps-controllers` - Allowed `XMLSerializer` and `DOMParser` inside the snap iframe via the new yarn patch on `@metamask/snaps-execution-environments` (replaces the runtime HTML mutation we previously carried in `SnapsExecutionWebView.tsx`) - Restored upstream `pump` usage in snap/background bridges (rolled back local stream-cleanup defensive code in `MobilePortStream.js` and `execution-service-init.ts`) ### Test infrastructure - Fixed wallet adapter tests (dynamic import race with `@expensify/react-native-wallet`) - Restored the `isClosingRef` guard in `BottomSheetDialog` to prevent double `goBack` (MUSD-406) and rolled back a faulty `lodash.debounce` attempt - Fixed `PolymarketProvider` fetch mocks (missing `response.text()`) - Fixed `useTokenSearch` throttle timing, `EarnLendingWithdrawal` mock ordering - Updated `initial-background-state.json` fixture, `useTailwind` mock, `reanimatedLogger` config - E2E: fixed URL editor dismissal and snap error assertions across platforms; raised default Detox timeout ### Build / CI - iOS `Podfile.lock` regenerated for RN 0.81's new Pod graph - iOS `PrivacyInfo.xcprivacy` updated for the SDK 54 surface - Android: `CMAKE_VERSION` pinned for RN 0.81's NDK build - E2E CI workflows append `${{ github.run_id }}` to native-build cache keys (Android APKs/Gradle, iOS DerivedData) to force fresh builds during the upgrade rollout - `.depcheckrc.yml` ignores extended for short-name Babel plugins ## Dependencies Patched (`.yarn/patches/`) **MetaMask-owned (see TODO below — should be upstreamed and the patches dropped):** - `@metamask/react-native-acm@1.2.0` - `@metamask/react-native-button@3.0.0` - `@metamask/react-native-payments@2.0.2` - `@metamask/react-native-webview@14.6.0` - `@metamask/snaps-execution-environments@11.0.2` **Third-party (need upstream PRs or version bumps as they ship RN 0.81-compatible releases):** - `react-native@0.81.5` (RN itself — small follow-up patch on top of 0.81.5) - `@react-native-community/viewpager@3.3.0` - `@braze/react-native-sdk@19.1.0` (ReactModuleInfo signature change) - `react-native-aes-crypto-forked` (we maintain the fork; can be folded into the fork repo) - `react-native-fast-crypto@2.2.0` - `react-native-gzip@1.1.0` - `react-native-i18n@2.0.15` (deprecated upstream — long-term replace) - `react-native-os@1.2.6` - `react-native-sensors@5.3.0` - `reactotron-core-client@2.9.7` - `expo-web-browser@14.0.2` **Legacy `patches/` (patch-package) entries removed in this PR** because the upstream packages or our own forks now cover them, or because the patched version is no longer installed: `@metamask+react-native-button+3.0.0`, `@metamask+react-native-payments+2.0.0`, `react-native+0.76.9`, `react-native-aes-crypto-forked+1.2.1`, `react-native-fast-crypto+2.2.0`, `react-native-performance+5.1.2`, `react-native-qrcode-svg+5.1.2`, `react-native-view-shot+3.8.0`, `expo-updates-npm-0.27.4`. Net result: no orphan patches carried forward. ## TODO — follow-ups after this PR is merged Several Yarn patches added by this upgrade target packages we own under `@metamask/*`. They unblock RN 0.81 today, but we should publish fixed releases and drop the local patches in follow-up PRs: - **`@metamask/react-native-acm@1.2.0`** — RN 0.81's Kotlin rewrite of `ReactContextBaseJavaModule` removed the `currentActivity` synthetic accessor (now `getCurrentActivity()`) and made `onNewIntent(intent: Intent)` non-nullable. Patch updates the 4 affected sites in `GoogleAcmModule.kt`. → Publish a 1.x compatible release that compiles against RN 0.81. - **`@metamask/react-native-button@3.0.0`** — `TouchableOpacity.propTypes`, `Text.propTypes` and `ViewPropTypes.style` no longer exist on RN 0.81 / React 19. Patch falls back to `PropTypes.any`/`PropTypes.bool` defensively. → Replace `propTypes` with TypeScript types or PropTypes.shape literals upstream. - **`@metamask/react-native-payments@2.0.2`** — Bumps Android `compileSdk`/`buildTools`/`targetSdk` from 28 → 36 and drops the removed `ReactBridge` import. → Publish a release with the SDK bump (and optionally migrate the module to Kotlin / TurboModules while we're there). - **`@metamask/react-native-webview@14.6.0`** — Adds `codegenConfig.ios.componentProvider`/`modulesProvider` entries needed by RN 0.81's Fabric codegen so `RNCWebView` and `RNCWebViewModule` register correctly. → Cherry-pick into the next published release; this is purely a `package.json` metadata fix. - **`@metamask/snaps-execution-environments@11.0.2`** — Adds `XMLSerializer` and `DOMParser` to the LavaMoat `scuttleGlobalThis.exceptions` list in `dist/webpack/webview/index.html` so snaps can run inside the iframe after lockdown. (This replaces the runtime HTML mutation we previously carried in `SnapsExecutionWebView.tsx`.) → Land the equivalent change in `snaps` upstream so the scuttle exceptions are baked into the published HTML. Other notable patches added here target third-party packages we don't own (see the "Third-party" list above); they'll need separate upstream PRs or version bumps as those projects publish RN 0.81-compatible releases. <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Manual testing steps** Because this PR moves the entire native floor (RN 0.81 / React 19 / Expo 54), the surfaces most likely to regress are the ones that cross the JS↔native bridge or rely on WebViews, animations, and platform integrations. Run the scenarios below on **both iOS and Android** (release-style build preferred so LavaMoat lockdown and Hermes kick in). ```gherkin Feature: RN 0.81 / React 19 / Expo 54 upgrade smoke Scenario: In-place upgrade from the latest production build Given a device with the latest store version of MetaMask Mobile installed And a wallet that has been used (multiple accounts, custom networks, contacts, NFTs, recent txs, push enabled, biometrics enabled) When the user installs this build over the existing one (without uninstalling) Then the app launches without crashing on cold start And redux-persist migrations run cleanly (no migration errors in logs) And accounts, custom networks, contacts, hidden tokens, NFT collections, transaction history, and address book entries are preserved And MMKV-backed storage opens (no "Failed to open MMKV" or version mismatch errors) And biometric unlock still works without re-prompting for a password reset And push-notification token registration still resolves And no data needs to be re-imported Scenario: In-place upgrade from an older minor (n-3 versions back) Given a device with a 3-versions-old build of MetaMask Mobile and a used wallet When the user installs this build over it Then the chained migrations execute in order with no errors And the same continuity assertions as above hold Scenario: Onboarding — create new wallet Given a fresh install When the user creates a new wallet, sets a password, and skips backup Then onboarding completes and the wallet screen renders without animation glitches And the BackHandler shim does not trigger duplicate goBacks on Android Scenario: Onboarding — import via SRP Given a fresh install When the user imports an existing 12-word SRP via "Import using Secret Recovery Phrase" Then accounts populate and the home screen loads with balances Scenario: Manual backup flow (Steps 1, 2, 3) Given a wallet that has not yet completed manual backup When the user walks through ManualBackupStep1 → 2 → 3 Then the SRP is shown, verification succeeds, and Step 3 navigates to HomeNav without re-registering the Android back handler twice Scenario: Send ETH (legacy + EIP-1559) Given a funded EOA on Sepolia and Mainnet When the user sends ETH to another address using both legacy and 1559 gas Then the confirmation sheet renders, the user signs, and the tx appears in Activity → confirmed Scenario: Token send + ERC-20 approval Given a funded EOA with USDC on Mainnet When the user sends USDC and (separately) approves a spender via dApp Then both flows render their confirmation sheets correctly and the txs land Scenario: Swap (single-chain) + Bridge (cross-chain) Given a funded EOA When the user performs a Swap and a Bridge from the wallet UI Then quotes load, approvals (if needed) sign, the user can switch routes, and the txs land Scenario: Smart transactions (STX) on Mainnet Given an account with STX enabled When the user submits a swap eligible for STX Then the STX status sheet renders, polls, and resolves to confirmed without flicker Scenario: Transaction batching (EIP-7702 / 5792) Given a smart account that supports batched txs When the user signs a batch confirmation Then the batch appears in Activity with the correct sub-txs Scenario: Hardware wallet — Ledger BLE Given a Ledger device paired via BLE When the user signs an EVM transaction using the Ledger account Then the BLE connection succeeds, the user approves on-device, and the tx is broadcast Scenario: WalletConnect / SDK dApp connection Given a sample dApp (e.g. Uniswap testnet) on desktop When the user scans the QR with the in-app camera and connects via WalletConnect Then the connection request renders, methods (eth_signTypedData_v4, personal_sign, eth_sendTransaction) work end-to-end, and the dApp shows the wallet address Scenario: Deeplinks — universal links + custom scheme + cold/warm start Given the app is killed (cold) and separately, in the background (warm) When the user taps: | metamask://send?recipient=0x... | from another app | | metamask://buy?address=0x...&chainId=0x1 | from another app | | metamask://swap?from=ETH&to=USDC | from another app | | metamask://wc?uri=wc:abcd... | from a dApp QR | | https://metamask.app.link/wallet | universal link | | https://metamask.app.link/buy?... | universal link | Then each link routes to the correct screen on both cold and warm starts And in-app `Linking.openURL(...)` (e.g. ramp Buy widget, support links) opens the external browser correctly Scenario: In-browser dApp via the built-in Browser tab Given the wallet is unlocked When the user opens the in-app Browser, navigates to a dApp, and signs a tx Then the WebView renders, the provider injects, and signing flows work # exercises the @metamask/react-native-webview Fabric codegen patch Scenario: Snaps — install + invoke Given a Flask build with snaps enabled When the user installs a permissioned snap and invokes a method that opens a snap UI dialog Then the snap iframe loads under LavaMoat lockdown without "XMLSerializer is not a function" or scuttle errors and the dialog renders # exercises the @metamask/snaps-execution-environments LavaMoat patch Scenario: Buy/Sell ramps (in-app + external browser) Given a wallet unlocked and a quote available When the user purchases via Transak (in-app widget) and via an external-browser provider Then both routes complete; on the external path Linking.openURL or InAppBrowser is invoked correctly and Activity reflects the precreated order Scenario: Apple Pay / Google ACM Given the user has an Apple Pay card (iOS) or a Google account on device (Android) When the user triggers a flow that hits @metamask/react-native-payments (iOS) and Google ACM sign-in (Android) Then both native sheets present, complete, and return values to JS without crashing # exercises the @metamask/react-native-payments and @metamask/react-native-acm patches Scenario: Biometrics / Keychain unlock Given a wallet protected by biometrics When the user backgrounds the app, returns, and unlocks Then Face ID / Touch ID / Android biometric prompts present and unlock works on cold start, warm resume, and after switching networks Scenario: Push notifications (transaction notifications) Given push notifications enabled When an inbound tx confirms Then a notification arrives, deeplink tap-through navigates to the right activity row, and Sentry breadcrumbs do NOT show "[E2E Sentry Mock]" (verifies dev/prod env separation) Scenario: Token detail chart (TradingView WebView) Given the user opens a token detail (e.g. ETH) with MM_CHARTING_LIBRARY_URL set When the chart loads Then the TradingView library loads from CDN, the OHLC data renders, the crosshair pills work, and there is no "Failed to load TradingView library" error Scenario: BottomSheets, modals, and gestures Given any flow with a bottom sheet (account selector, network picker, confirmation, settings) When the user opens a sheet and rapidly taps the overlay 3-5 times Then the sheet closes once, the screen behind is unchanged (no double goBack — MUSD-406 regression check), and the swipe-to-dismiss gesture also works Scenario: Animations and Reanimated surfaces Given the user navigates onboarding → wallet → token → bridge → settings When the fox loader, onboarding success animation, carousel cards, and Rive-driven loaders play Then all animations render at 60fps with no warning spam and no crash on iOS 18 / Android 14+ Scenario: Background restoration after long suspend Given the app is backgrounded for >15 minutes When the user resumes Then accounts, balances, and websocket connections (price feed, notifications) reconnect without leaking timers or duplicate listeners Scenario: SDK / WebSocket reconnection Given an active dApp connection and an active price-feed websocket When the user toggles airplane mode off→on→off Then both connections recover and txs/quotes flow again Scenario: Settings → Reveal SRP / Private Key Given an unlocked wallet When the user reveals the SRP and a private key from settings Then biometrics gate works, the secrets render correctly, and the "Hold to reveal" + scroll-on-iPhone-SE behaviors are unchanged Scenario: Power-user wallet (many accounts / tokens / NFTs) Given a power-user SRP imported (see internal SRPs) When the user navigates Wallet, Activity, NFTs, and switches networks Then list rendering, scrolling, and network switching remain smooth (FlashList 2 / RN 0.81 perf check) ``` The `[E2E Sentry Mock]` log line should NOT appear in any of the dev/release builds above; if it does, `IS_TEST=true` is leaking into the bundler env. <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > High risk because it updates core React Native/Android build toolchain and introduces multiple Yarn patches to native dependencies, which can affect app startup, native compilation, and E2E reliability across both platforms. > > **Overview** > Upgrades the RN stack to `react-native@0.81.5` and aligns native build tooling around it: Android moves to `compileSdk/targetSdk 36`, Kotlin `2.1.20`, NDK `27.1`, Gradle `8.14.3`, updates app initialization to `loadReactNative(this)`, and adds an MLKit dependency override plus Detox flavor handling. > > Stabilizes CI/E2E builds during the upgrade by forcing fresh native caches (cache keys include `${{ github.run_id }}`), pinning CMake for Android E2E builds, bumping iOS cache versions, increasing E2E timeout, and restoring iOS app/framework execute permissions after artifact download. > > Introduces/updates multiple `.yarn/patches/*` to keep third-party and MetaMask native modules building under RN 0.81 (Gradle/SDK bumps, Kotlin/ObjC signature fixes, codegen metadata, safer JS snippets), removes obsolete patches, and adjusts JS/TS code for React 19 typing/runtime changes (BackHandler subscription cleanup, stricter `RefObject<T | null>`/`useRef` init, safer prop spreads/cloneElement generics, default props moved to parameters, and test expectation updates). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit c3c6e6e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: BubbleTrouble14 <ronald.goedeke@outlook.com> Co-authored-by: tommasini <tommasini15@gmail.com> Co-authored-by: tommasini <46944231+tommasini@users.noreply.github.com> Co-authored-by: Andre Pimenta <andrepimenta7@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: ieow <4881057+ieow@users.noreply.github.com> Co-authored-by: Frederik Bolding <frederik.bolding@gmail.com> Co-authored-by: metamaskbot <metamaskbot@users.noreply.github.com>
1 parent 7fd945d commit 4797726

357 files changed

Lines changed: 7263 additions & 5861 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.depcheckrc.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,18 @@ ignores:
139139

140140
# Used in Yarn plugin for preview builds
141141
- '@yarnpkg/core'
142+
143+
# Babel plugins referenced in babel.config.js by their short name (without
144+
# the `babel-plugin-` prefix). Babel resolves them correctly at build time
145+
# but depcheck doesn't recognize the indirect reference. See babel.config.js
146+
# line 65 comment for details.
147+
- 'babel-plugin-react-compiler' # used as 'react-compiler' (line 68)
148+
- 'babel-plugin-transform-inline-environment-variables' # used as 'transform-inline-environment-variables' (line 81)
149+
- 'babel-plugin-transform-remove-console' # used as 'transform-remove-console' (line 166, production env)
150+
151+
# Listed as a direct dep in package.json but no longer referenced in
152+
# babel.config.js (we use babel-preset-expo) or anywhere in our source.
153+
# It's still available transitively via babel-preset-expo + Metro, so
154+
# removing the direct declaration should be a no-op — left for a follow-up
155+
# cleanup once the RN 0.81 upgrade has settled to avoid surprises.
156+
- '@react-native/babel-preset'

.github/workflows/build-android-e2e.yml

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ jobs:
117117
exit 1
118118
fi
119119
120+
# TEMPORARY: `${{ github.run_id }}` makes every key unique per workflow
121+
# run so we always get a fresh build during the RN 0.81 upgrade — the
122+
# `yarn fingerprint:generate` heuristic doesn't track every native input
123+
# being changed (yarn patches, MainApplication, Podfile shims, etc.) so
124+
# the branch cache can serve a stale .apk and only the JS gets repacked.
125+
# Remove the trailing `-${{ github.run_id }}` from each `key:` below
126+
# once the upgrade is settled and fingerprint covers the touched paths.
120127
- name: Restore APKs matching fingerprint from branch cache
121128
if: ${{ inputs.runner_provider != 'namespace' }}
122129
id: apk-cache-restore
@@ -132,7 +139,7 @@ jobs:
132139
# - "Restore APKs matching fingerprint from main cache"
133140
# - "Restore Gradle dependencies from branch cache"
134141
# - "Restore Gradle dependencies from main cache"
135-
key: android-apk-${{ github.ref_name }}-${{ inputs.build_type }}-${{ env.CACHE_GENERATION }}-${{ steps.generate-fingerprint.outputs.fingerprint }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
142+
key: android-apk-${{ github.ref_name }}-${{ inputs.build_type }}-${{ env.CACHE_GENERATION }}-${{ steps.generate-fingerprint.outputs.fingerprint }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}-${{ github.run_id }}
136143

137144
- name: Restore APKs matching fingerprint from main cache
138145
if: ${{ inputs.runner_provider != 'namespace' && steps.apk-cache-restore.outputs.cache-hit != 'true' && github.ref_name != 'main' }}
@@ -149,7 +156,7 @@ jobs:
149156
# - "Restore APKs matching fingerprint from main cache"
150157
# - "Restore Gradle dependencies from branch cache"
151158
# - "Restore Gradle dependencies from main cache"
152-
key: android-apk-main-${{ inputs.build_type }}-${{ env.CACHE_GENERATION }}-${{ steps.generate-fingerprint.outputs.fingerprint }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
159+
key: android-apk-main-${{ inputs.build_type }}-${{ env.CACHE_GENERATION }}-${{ steps.generate-fingerprint.outputs.fingerprint }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}-${{ github.run_id }}
153160

154161
- name: Restore Gradle dependencies from branch cache
155162
id: gradle-cache-restore
@@ -168,7 +175,7 @@ jobs:
168175
# - "Restore APKs matching fingerprint from main cache"
169176
# - "Restore Gradle dependencies from branch cache"
170177
# - "Restore Gradle dependencies from main cache"
171-
key: gradle-${{ github.ref_name }}-${{ env.GRADLE_CACHE_VERSION }}-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
178+
key: gradle-${{ github.ref_name }}-${{ env.GRADLE_CACHE_VERSION }}-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}-${{ github.run_id }}
172179

173180
- name: Restore Gradle dependencies from main cache
174181
# This will only restore the cache, not update it
@@ -186,7 +193,7 @@ jobs:
186193
# - "Restore APKs matching fingerprint from main cache"
187194
# - "Restore Gradle dependencies from branch cache"
188195
# - "Restore Gradle dependencies from main cache"
189-
key: gradle-main-${{ env.GRADLE_CACHE_VERSION }}-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
196+
key: gradle-main-${{ env.GRADLE_CACHE_VERSION }}-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}-${{ github.run_id }}
190197

191198
- name: Build Android E2E APKs
192199
if: ${{ inputs.runner_provider == 'namespace' || (steps.apk-cache-restore.outputs.cache-hit != 'true' && steps.apk-cache-restore-main.outputs.cache-hit != 'true') }}
@@ -208,6 +215,12 @@ jobs:
208215
NODE_OPTIONS: '--max-old-space-size=4096'
209216
# Limit Metro workers to prevent OOM (each worker uses ~3GB)
210217
METRO_MAX_WORKERS: '4'
218+
# React Native 0.81's ReactAndroid/build.gradle.kts requests CMake 3.30.5
219+
# via `System.getenv("CMAKE_VERSION") ?: "3.30.5"`. The self-hosted runner
220+
# only ships CMake 3.22.1 in /opt/android-sdk/cmake/ and AGP cannot auto-
221+
# download missing components, causing CXX1300. RN's CMakeLists.txt files
222+
# only require >= 3.13, so 3.22.1 is fully sufficient.
223+
CMAKE_VERSION: '3.22.1'
211224
BRIDGE_USE_DEV_APIS: 'true'
212225
RAMP_INTERNAL_BUILD: 'true'
213226
SEEDLESS_ONBOARDING_ENABLED: 'true'

.github/workflows/build-ios-e2e.yml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ jobs:
3737
sourcemap-uploaded: ${{ steps.upload-sourcemap.outcome == 'success' }}
3838
env:
3939
# Bump these to bust the respective caches and force a full rebuild
40-
XCODE_CACHE_VERSION: 1
41-
IOS_APP_CACHE_VERSION: 2
40+
XCODE_CACHE_VERSION: 4
41+
IOS_APP_CACHE_VERSION: 5
4242
RCT_NO_LAUNCH_PACKAGER: 1
4343
XCODE_BUILD_SETTINGS: 'COMPILER_INDEX_STORE_ENABLE=NO'
4444
GITHUB_CI: 'true' # This ensures it's available during pod install
@@ -75,6 +75,13 @@ jobs:
7575
- name: Checkout repo
7676
uses: actions/checkout@v6
7777

78+
# TEMPORARY: `${{ github.run_id }}` makes every key unique per workflow
79+
# run so we always get a fresh build during the RN 0.81 upgrade — the
80+
# `yarn fingerprint:generate` heuristic doesn't track every native input
81+
# being changed (yarn patches, AppDelegate, Podfile shims, etc.) so the
82+
# branch cache can serve a stale .app and only the JS gets repacked.
83+
# Remove the trailing `-${{ github.run_id }}` from each `key:` below
84+
# once the upgrade is settled and fingerprint covers the touched paths.
7885
- name: Restore Xcode derived data from branch cache
7986
id: xcode-restore-cache
8087
# This action automatically updates the cache at the end of the workflow
@@ -83,7 +90,7 @@ jobs:
8390
path: |
8491
~/Library/Developer/Xcode/DerivedData
8592
ios/build
86-
key: ${{ runner.os }}-xcode-${{ github.ref_name }}-${{ env.XCODE_CACHE_VERSION }}-${{ hashFiles('ios/**/*.{h,m,mm,swift}', 'ios/**/Podfile.lock', 'yarn.lock') }}
93+
key: ${{ runner.os }}-xcode-${{ github.ref_name }}-${{ env.XCODE_CACHE_VERSION }}-${{ hashFiles('ios/**/*.{h,m,mm,swift}', 'ios/**/Podfile.lock', 'yarn.lock') }}-${{ github.run_id }}
8794

8895
- name: Restore Xcode derived data from main cache
8996
if: ${{ steps.xcode-restore-cache.outputs.cache-hit != 'true' && github.ref_name != 'main' }}
@@ -94,7 +101,7 @@ jobs:
94101
path: |
95102
~/Library/Developer/Xcode/DerivedData
96103
ios/build
97-
key: ${{ runner.os }}-xcode-main-${{ env.XCODE_CACHE_VERSION }}-${{ hashFiles('ios/**/*.{h,m,mm,swift}', 'ios/**/Podfile.lock', 'yarn.lock') }}
104+
key: ${{ runner.os }}-xcode-main-${{ env.XCODE_CACHE_VERSION }}-${{ hashFiles('ios/**/*.{h,m,mm,swift}', 'ios/**/Podfile.lock', 'yarn.lock') }}-${{ github.run_id }}
98105

99106
- name: Restore .metamask folder (Foundry download cache for install:foundryup)
100107
uses: actions/cache@v4
@@ -154,7 +161,7 @@ jobs:
154161
with:
155162
path: |
156163
ios/build/Build/Products/Release-iphonesimulator/MetaMask.app
157-
key: ios-app-${{ github.ref_name }}-v${{ env.IOS_APP_CACHE_VERSION }}-${{ steps.generate-fingerprint.outputs.fingerprint }}
164+
key: ios-app-${{ github.ref_name }}-v${{ env.IOS_APP_CACHE_VERSION }}-${{ steps.generate-fingerprint.outputs.fingerprint }}-${{ github.run_id }}
158165

159166
- name: Restore iOS app matching fingerprint from main cache
160167
if: ${{ steps.cache-restore.outputs.cache-hit != 'true' && github.ref_name != 'main' }}
@@ -164,7 +171,7 @@ jobs:
164171
with:
165172
path: |
166173
ios/build/Build/Products/Release-iphonesimulator/MetaMask.app
167-
key: ios-app-main-v${{ env.IOS_APP_CACHE_VERSION }}-${{ steps.generate-fingerprint.outputs.fingerprint }}
174+
key: ios-app-main-v${{ env.IOS_APP_CACHE_VERSION }}-${{ steps.generate-fingerprint.outputs.fingerprint }}-${{ github.run_id }}
168175

169176
# Build the iOS E2E app for simulator
170177
- name: Build iOS E2E App

.github/workflows/run-e2e-workflow.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ on:
2929
type: number
3030
default: 1
3131
test-timeout-minutes:
32+
# Hard cap on the `Run E2E tests` step — when it elapses, GitHub kills the whole suite.
3233
description: 'The timeout in minutes for the test command'
3334
required: false
3435
type: number
35-
default: 30
36+
default: 40
3637
build_type:
3738
description: 'The type of build to perform'
3839
required: false
@@ -210,6 +211,21 @@ jobs:
210211
fi
211212
212213
chmod +x "$APP_PATH/$BUNDLE_EXEC"
214+
215+
# actions/upload-artifact strips execute permissions from ALL files, not just the
216+
# main binary. Framework dylibs and binaries inside Frameworks/ also need +x or
217+
# SpringBoard (SBMainWorkspace) will refuse to launch the app with:
218+
# FBSOpenApplicationServiceErrorDomain code=1: denied by service delegate
219+
if [ -d "$APP_PATH/Frameworks" ]; then
220+
find "$APP_PATH/Frameworks" -type d -name "*.framework" | while IFS= read -r fw; do
221+
binary="$fw/$(basename "$fw" .framework)"
222+
if [ -f "$binary" ]; then
223+
chmod +x "$binary"
224+
fi
225+
done
226+
find "$APP_PATH/Frameworks" -type f -name "*.dylib" -exec chmod +x {} \;
227+
fi
228+
echo "✅ Restored execute permissions on main binary and all framework binaries"
213229
shell: bash
214230

215231
# On re-run (run_attempt > 1), download previous test results to identify failed tests

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
# claude code worktrees (agent-local, not shipped)
55
.claude/worktrees/
6+
# claude code per-user settings (agent-local, not shipped)
7+
.claude/settings.local.json
68

79
# osx
810
.DS_Store
@@ -46,6 +48,7 @@ android/app/gradle*
4648
android/app/_build*
4749
android/libs
4850
.cxx/
51+
.kotlin/
4952

5053
# if we ever want to add google services
5154
android/app/google-services.json
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# @metamask/react-native-acm@1.2.0 was authored against an older RN where:
2+
# - ReactContextBaseJavaModule.getCurrentActivity() was a Java method, so Kotlin
3+
# auto-exposed it as the synthetic property `currentActivity`.
4+
# - ActivityEventListener.onNewIntent took a nullable `Intent?` parameter.
5+
#
6+
# In React Native 0.81 both classes were rewritten in Kotlin source. Because
7+
# `getCurrentActivity()` is now declared as `fun getCurrentActivity()` (not as a
8+
# property), Kotlin does NOT expose `currentActivity` as a synthetic accessor —
9+
# callers must use the explicit method form. Additionally, `onNewIntent` is now
10+
# `fun onNewIntent(intent: Intent)` (non-nullable). Without this patch the
11+
# package fails to compile on RN 0.81 with:
12+
# - "Unresolved reference 'currentActivity'." (3 sites)
13+
# - "'onNewIntent' overrides nothing. Potential signatures for overriding:
14+
# fun onNewIntent(intent: Intent): Unit"
15+
#
16+
# When @metamask/react-native-acm publishes an RN 0.81-compatible release, this
17+
# patch (and the resolution in package.json) can be removed.
18+
diff --git a/android/src/main/java/com/googleacm/GoogleAcmModule.kt b/android/src/main/java/com/googleacm/GoogleAcmModule.kt
19+
index 395a46ebdcc45bc3afd596903e7926525ebe5874..0495944fb891b0999624b50801c11a313e8adee1 100644
20+
--- a/android/src/main/java/com/googleacm/GoogleAcmModule.kt
21+
+++ b/android/src/main/java/com/googleacm/GoogleAcmModule.kt
22+
@@ -65,7 +65,7 @@ class GoogleAcmModule(reactContext: ReactApplicationContext) :
23+
requestObject: ReadableMap,
24+
promise: Promise
25+
) {
26+
- val activity: Activity? = currentActivity
27+
+ val activity: Activity? = getCurrentActivity()
28+
if (activity == null) {
29+
promise.reject("E_NO_ACTIVITY", "Current activity is null, cannot launch UI.")
30+
return
31+
@@ -133,7 +133,7 @@ class GoogleAcmModule(reactContext: ReactApplicationContext) :
32+
}
33+
34+
private suspend fun tryLegacySignIn(serverClientId: String): ReadableMap? {
35+
- val activity = currentActivity
36+
+ val activity = getCurrentActivity()
37+
?: throw Exception("No activity available for legacy sign-in")
38+
39+
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
40+
@@ -188,7 +188,7 @@ class GoogleAcmModule(reactContext: ReactApplicationContext) :
41+
}
42+
}
43+
44+
- override fun onNewIntent(intent: Intent?) { }
45+
+ override fun onNewIntent(intent: Intent) { }
46+
47+
private fun handleLegacySignInResult(resultCode: Int, data: Intent?): ReadableMap? {
48+
if (resultCode != Activity.RESULT_OK) {
49+
@@ -278,7 +278,7 @@ class GoogleAcmModule(reactContext: ReactApplicationContext) :
50+
}
51+
52+
suspend fun handleSignOut() {
53+
- val activity: Activity? = currentActivity
54+
+ val activity: Activity? = getCurrentActivity()
55+
if (activity == null) {
56+
throw Exception("Current activity is null, cannot sign out.")
57+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
diff --git a/Button.js b/Button.js
2+
index 46dc1e06fa389a3660f6ce67c0d2eaead3e2782d..7f92ea9421bd90ba858ad9f81c7f4c1008a2553e 100644
3+
--- a/Button.js
4+
+++ b/Button.js
5+
@@ -11,18 +11,23 @@ import {
6+
import coalesceNonElementChildren from './coalesceNonElementChildren';
7+
8+
const systemButtonOpacity = 0.2;
9+
+const touchableOpacityPropTypes = TouchableOpacity.propTypes || {};
10+
+const textPropTypes = Text.propTypes || {};
11+
+const viewStylePropType = (ViewPropTypes && ViewPropTypes.style) || PropTypes.any;
12+
+const textStylePropType = textPropTypes.style || PropTypes.any;
13+
+const allowFontScalingPropType = textPropTypes.allowFontScaling || PropTypes.bool;
14+
15+
export default class Button extends Component {
16+
static propTypes = {
17+
- ...TouchableOpacity.propTypes,
18+
+ ...touchableOpacityPropTypes,
19+
accessibilityLabel: PropTypes.string,
20+
- allowFontScaling: Text.propTypes.allowFontScaling,
21+
- containerStyle: ViewPropTypes.style,
22+
- disabledContainerStyle: ViewPropTypes.style,
23+
+ allowFontScaling: allowFontScalingPropType,
24+
+ containerStyle: viewStylePropType,
25+
+ disabledContainerStyle: viewStylePropType,
26+
disabled: PropTypes.bool,
27+
- style: Text.propTypes.style,
28+
- styleDisabled: Text.propTypes.style,
29+
- childGroupStyle: ViewPropTypes.style,
30+
+ style: textStylePropType,
31+
+ styleDisabled: textStylePropType,
32+
+ childGroupStyle: viewStylePropType,
33+
};
34+
35+
render() {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
diff --git a/android/build.gradle b/android/build.gradle
2+
index 0d066bd15543931e4029146f627a5902f244d4ee..db6528a8db0b21634c3eb0ca7aabc2405b89818c 100644
3+
--- a/android/build.gradle
4+
+++ b/android/build.gradle
5+
@@ -1,12 +1,12 @@
6+
apply plugin: 'com.android.library'
7+
8+
android {
9+
- compileSdkVersion 28
10+
- buildToolsVersion "28.0.3"
11+
+ compileSdkVersion 36
12+
+ buildToolsVersion "36.0.0"
13+
14+
defaultConfig {
15+
minSdkVersion 21
16+
- targetSdkVersion 28
17+
+ targetSdkVersion 36
18+
versionCode 1
19+
versionName "1.0"
20+
ndk {
21+
diff --git a/android/src/main/java/com/reactnativepayments/ReactNativePaymentsModule.java b/android/src/main/java/com/reactnativepayments/ReactNativePaymentsModule.java
22+
index f26dd586ec397f5259c00ba147974a58825a03fe..e671afcc3faf5368e7987e2d9b8d1797d4249a29 100644
23+
--- a/android/src/main/java/com/reactnativepayments/ReactNativePaymentsModule.java
24+
+++ b/android/src/main/java/com/reactnativepayments/ReactNativePaymentsModule.java
25+
@@ -13,7 +13,6 @@ import androidx.annotation.RequiresPermission;
26+
import android.util.Log;
27+
28+
import com.facebook.react.bridge.Callback;
29+
-import com.facebook.react.bridge.ReactBridge;
30+
import com.facebook.react.bridge.ReadableArray;
31+
import com.facebook.react.bridge.ReadableMapKeySetIterator;
32+
import com.google.android.gms.common.api.GoogleApiClient;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
diff --git a/package.json b/package.json
2+
index 6b9ca814f3e2b4630e81371235f6ad942d5eac3f..1d7129b41beb19c53b3fbe277b9670ff72b30d6d 100644
3+
--- a/package.json
4+
+++ b/package.json
5+
@@ -95,6 +95,14 @@
6+
"jsSrcsDir": "./src",
7+
"android": {
8+
"javaPackageName": "com.reactnativecommunity.webview"
9+
+ },
10+
+ "ios": {
11+
+ "componentProvider": {
12+
+ "RNCWebView": "RNCWebView"
13+
+ },
14+
+ "modulesProvider": {
15+
+ "RNCWebViewModule": "RNCWebViewModule"
16+
+ }
17+
}
18+
},
19+
"packageManager": "yarn@1.22.19"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
diff --git a/ios/ReactNativePageView.h b/ios/ReactNativePageView.h
2+
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000001 100644
3+
--- a/ios/ReactNativePageView.h
4+
+++ b/ios/ReactNativePageView.h
5+
@@ -1,3 +1,4 @@
6+
#import <React/RCTShadowView.h>
7+
+#import <React/RCTEventDispatcher.h>
8+
#import <React/UIView+React.h>
9+
#import <UIKit/UIKit.h>

0 commit comments

Comments
 (0)