From 11766bd6bf8275417320789b2a12233f5bd9bcfb Mon Sep 17 00:00:00 2001 From: Tham Kei Lok Date: Tue, 21 Jul 2026 17:28:06 +0700 Subject: [PATCH 1/6] Bump @formo/analytics-react-native to 1.0.0 Was pinned at ^0.1.6. 1.0.0 is the first release carrying SDK-owned session_id, the synthesized mobile user agent, and Android install attribution (P-2199 / P-2207). Co-Authored-By: Claude Opus 4.8 --- with-react-native/package.json | 2 +- with-react-native/pnpm-lock.yaml | 29 ++++++++++++++++------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/with-react-native/package.json b/with-react-native/package.json index a05ea9d..c0102dc 100644 --- a/with-react-native/package.json +++ b/with-react-native/package.json @@ -16,7 +16,7 @@ }, "dependencies": { "@babel/runtime": "^7.28.6", - "@formo/analytics-react-native": "^0.1.6", + "@formo/analytics-react-native": "^1.0.0", "@react-native-async-storage/async-storage": "^1.21.0", "@tanstack/react-query": "^5.90.20", "@wagmi/connectors": "5.7.7", diff --git a/with-react-native/pnpm-lock.yaml b/with-react-native/pnpm-lock.yaml index d60a174..7da30fb 100644 --- a/with-react-native/pnpm-lock.yaml +++ b/with-react-native/pnpm-lock.yaml @@ -16,8 +16,8 @@ importers: specifier: ^7.28.6 version: 7.28.6 '@formo/analytics-react-native': - specifier: ^0.1.6 - version: 0.1.6(78ed9643f6886a8af59692e6f0b5effa) + specifier: ^1.0.0 + version: 1.0.0(78ed9643f6886a8af59692e6f0b5effa) '@react-native-async-storage/async-storage': specifier: ^1.21.0 version: 1.24.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10)) @@ -32,7 +32,7 @@ importers: version: 2.16.4(@tanstack/query-core@5.90.20)(@types/react@18.3.27)(react@18.3.1)(typescript@5.3.3)(use-sync-external-store@1.6.0(react@18.3.1))(viem@2.45.0(bufferutil@4.1.0)(typescript@5.3.3)(utf-8-validate@5.0.10)) '@walletconnect/react-native-compat': specifier: ^2.23.4 - version: 2.23.4(556bde76d3f9909724794be21dd0f734) + version: 2.23.4(20ce97657fb850f78397f4e3cb92a3fe) ethereum-cryptography: specifier: 3.2.0 version: 3.2.0 @@ -1001,8 +1001,8 @@ packages: resolution: {integrity: sha512-o2qDlTqJ606h4xR36H2zWTywmZ3v3842K6TU8Ik2n1mfW0S580VHlt3eItVYdLYz+klaPp7CXqanja8eASZjRw==} hasBin: true - '@formo/analytics-react-native@0.1.6': - resolution: {integrity: sha512-xnABfmKYO4cPTImYUT4da61uY6G6sojav+C4o/uBFbQMQWhjlB/2XETb8k6fWSQFbUoTZ/OFpXvlzKvqk9uu8g==} + '@formo/analytics-react-native@1.0.0': + resolution: {integrity: sha512-b9ZxbNjfe4/4Z8c4/cC63cwB8qo5dhIYK3VljPEV9Y8l+diBzWZnvoZvWsFktiFlUzWcztafzFanfgnQgzA+uA==} peerDependencies: '@react-native-async-storage/async-storage': '>=1.17.0' '@tanstack/react-query': '>=5.0.0' @@ -1011,6 +1011,7 @@ packages: react: '>=18.0.0' react-native: '>=0.70.0' react-native-device-info: '>=10.0.0' + react-native-play-install-referrer: '>=1.1.8' wagmi: '>=2.0.0' peerDependenciesMeta: '@tanstack/react-query': @@ -1021,6 +1022,8 @@ packages: optional: true react-native-device-info: optional: true + react-native-play-install-referrer: + optional: true wagmi: optional: true @@ -1297,7 +1300,7 @@ packages: '@paulmillr/qr@0.2.1': resolution: {integrity: sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ==} - deprecated: 'The package is now available as "qr": npm install qr' + deprecated: 'Switch to "qr" (new package name) for security updates: npm install qr' '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} @@ -1318,8 +1321,8 @@ packages: peerDependencies: react-native: ^0.0.0-0 || >=0.60 <1.0 - '@react-native-community/netinfo@11.5.2': - resolution: {integrity: sha512-/g0m65BtX9HU+bPiCH2517bOHpEIUsGrWFXDzi1a5nNKn5KujQgm04WhL7/OSXWKHyrT8VVtUoJA0XKRxueBpQ==} + '@react-native-community/netinfo@12.0.1': + resolution: {integrity: sha512-P/3caXIvfYSJG8AWJVefukg+ZGRPs+M4Lp3pNJtgcTYoJxCjWrKQGNnCkj/Cz//zWa/avGed0i/wzm0T8vV2IQ==} peerDependencies: react: '*' react-native: '>=0.59' @@ -7575,10 +7578,10 @@ snapshots: chalk: 4.1.2 js-yaml: 4.1.1 - '@formo/analytics-react-native@0.1.6(78ed9643f6886a8af59692e6f0b5effa)': + '@formo/analytics-react-native@1.0.0(78ed9643f6886a8af59692e6f0b5effa)': dependencies: '@react-native-async-storage/async-storage': 1.24.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10)) - '@react-native-community/netinfo': 11.5.2(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) + '@react-native-community/netinfo': 12.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) ethereum-cryptography: 3.2.0 react: 18.3.1 react-native: 0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10) @@ -8086,7 +8089,7 @@ snapshots: merge-options: 3.0.4 react-native: 0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10) - '@react-native-community/netinfo@11.5.2(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)': + '@react-native-community/netinfo@12.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)': dependencies: react: 18.3.1 react-native: 0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10) @@ -9078,10 +9081,10 @@ snapshots: - '@types/react' - react - '@walletconnect/react-native-compat@2.23.4(556bde76d3f9909724794be21dd0f734)': + '@walletconnect/react-native-compat@2.23.4(20ce97657fb850f78397f4e3cb92a3fe)': dependencies: '@react-native-async-storage/async-storage': 1.24.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10)) - '@react-native-community/netinfo': 11.5.2(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) + '@react-native-community/netinfo': 12.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) events: 3.3.0 fast-text-encoding: 1.0.6 react-native: 0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10) From 8be972f12522e03b5d02c3238389c372641999bf Mon Sep 17 00:00:00 2001 From: Tham Kei Lok Date: Tue, 21 Jul 2026 17:28:16 +0700 Subject: [PATCH 2/6] Point the mock wallet at a local node so Send Tx / Sign Message work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wagmi `mock` connector intercepts only a handful of RPC methods and forwards the rest — including eth_sendTransaction and eth_sign — to a real node. The chains resolved to their public RPCs, which hold no keys, so Send Tx failed with `-32601 rpc method is unsupported` (viem retries as wallet_sendTransaction, equally unsupported) and Sign Message with "unknown account". Override rpcUrls on the chain objects rather than on `transports`: the mock connector never reads transports, it takes chain.rpcUrls.default.http[0] directly in getProvider(), so a transport-only override silently leaves the mock wallet on the public endpoint. Hardcoded rather than env-driven — no hook in this app reads chain state, so the only calls reaching these URLs are the two mock-wallet actions, which need a local node regardless. Co-Authored-By: Claude Opus 4.8 --- with-react-native/README.md | 42 ++++++++++++++++++++++++++++++- with-react-native/config/wagmi.ts | 35 +++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/with-react-native/README.md b/with-react-native/README.md index f134007..0f5ac8d 100644 --- a/with-react-native/README.md +++ b/with-react-native/README.md @@ -20,6 +20,7 @@ This is an example React Native app demonstrating the [@formo/react-native-analy - Xcode (for iOS development) - Android Studio (for Android development) - iOS Simulator or Android Emulator (Expo Go does not support custom native modules) +- [Foundry](https://getfoundry.sh) — optional, only for the Mock Wallet buttons (see step 4) ### Installation @@ -50,7 +51,36 @@ EXPO_PUBLIC_FORMO_WRITE_KEY=your_formo_write_key You can get your Formo write key from [app.formo.so](https://app.formo.so). -4. Start the development server: +4. (Optional) Start local chains for the Mock Wallet: + +The **Mock Wallet** buttons — Send Tx and Sign Message — need an RPC node that has +the test account unlocked. wagmi's `mock` connector only intercepts a handful of +methods and proxies the rest (`eth_sendTransaction`, `eth_sign`) straight to the +transport. Public RPCs hold no keys, so against them Send Tx fails with +`-32601 rpc method is unsupported` and Sign Message with `unknown account`. + +Start an [anvil](https://getfoundry.sh) node on the chain you want to test: + +```bash +curl -L https://foundry.paradigm.xyz | bash && foundryup # first time only + +anvil --chain-id 84532 # Base Sepolia → localhost:8545 +anvil --chain-id 11155420 --port 8546 # OP Sepolia → localhost:8546 +``` + +Anvil pre-funds and unlocks `0xf39Fd6e5...fFb92266` — the same address the mock +connector is configured with. The URLs are already wired up in `config/wagmi.ts`, +so there is nothing to configure; just have the node running before you press the +buttons. Chain state is in-memory and resets whenever anvil restarts. + +The second node is only needed if you want to send or sign **while on OP +Sepolia** — switching chains itself works without any node, since the mock +connector handles `wallet_switchEthereumChain` internally. + +You can skip this step entirely if you only need the **Direct SDK Testing** +buttons, which call the SDK directly with no chain involved. + +5. Start the development server: ```bash pnpm start @@ -95,6 +125,16 @@ cd ios && pod install && cd .. Make sure you have the Android SDK installed and `ANDROID_HOME` environment variable set. +### Send Tx or Sign Message fails + +A connection error means anvil isn't running — start it as described in +[step 4](#installation). Nothing else needs configuring. + +`-32601 rpc method is unsupported` or `unknown account` means the Mock Wallet +reached a public RPC instead of a local node. Check that `config/wagmi.ts` still +overrides `rpcUrls` on the chain objects — overriding `transports` alone has no +effect, since the mock connector reads the URL off the chain. + ## Project Structure ``` diff --git a/with-react-native/config/wagmi.ts b/with-react-native/config/wagmi.ts index 91b81ec..bd815fb 100644 --- a/with-react-native/config/wagmi.ts +++ b/with-react-native/config/wagmi.ts @@ -1,4 +1,5 @@ import { http, createConfig } from "wagmi"; +import type { Chain } from "viem"; import { baseSepolia, optimismSepolia } from "wagmi/chains"; import { walletConnect } from "wagmi/connectors"; import { mock } from "@wagmi/core"; @@ -16,7 +17,39 @@ const metadata = { // Mock wallet address for testing (Hardhat default account #0) const MOCK_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" as const; -export const chains = [baseSepolia, optimismSepolia] as const; +// The `mock` connector only intercepts a few RPC methods; everything else — +// including eth_sendTransaction and eth_sign — is forwarded to a real node. +// Public RPCs hold no keys, so against them "Send Tx" fails with -32601 (viem +// then retries as wallet_sendTransaction, also unsupported) and "Sign Message" +// fails with "unknown account". Pointing at a local anvil that has MOCK_ADDRESS +// unlocked fixes both: +// +// anvil --chain-id 84532 # Base Sepolia +// anvil --chain-id 11155420 --port 8546 # OP Sepolia +// +// The override has to happen on the CHAIN, not on `transports`. The mock +// connector never reads wagmi's transports — it takes the URL straight off +// `chain.rpcUrls.default.http[0]` (see @wagmi/core/src/connectors/mock.ts, +// `getProvider`). Overriding only the transport silently leaves the mock wallet +// pointed at the public endpoint. +// +// These are hardcoded rather than env-driven because pointing at a local node +// costs nothing when one isn't running: no hook in this app reads chain state, +// so the only calls that reach these URLs are the Mock Wallet's Send Tx and +// Sign Message — which don't work without a local node either way. Without +// anvil you get a connection error instead of -32601; both mean "start anvil". +const withRpcUrl = (chain: T, url: string): T => ({ + ...chain, + rpcUrls: { + ...chain.rpcUrls, + default: { ...chain.rpcUrls.default, http: [url] }, + }, +}); + +export const chains = [ + withRpcUrl(baseSepolia, "http://localhost:8545"), + withRpcUrl(optimismSepolia, "http://localhost:8546"), +] as const; export const wagmiConfig = createConfig({ chains, From 20f48074dcd6f5d92cc18185db8d05879b33092c Mon Sep 17 00:00:00 2001 From: Tham Kei Lok Date: Tue, 21 Jul 2026 17:30:48 +0700 Subject: [PATCH 3/6] Link to the Foundry install guide instead of piping curl to bash Documenting `curl ... | bash` hands readers a remote-code-execution habit, and security scanning flags it. Point at the official installation page and let readers choose their install method. Co-Authored-By: Claude Opus 4.8 --- with-react-native/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/with-react-native/README.md b/with-react-native/README.md index 0f5ac8d..5380732 100644 --- a/with-react-native/README.md +++ b/with-react-native/README.md @@ -61,9 +61,10 @@ transport. Public RPCs hold no keys, so against them Send Tx fails with Start an [anvil](https://getfoundry.sh) node on the chain you want to test: -```bash -curl -L https://foundry.paradigm.xyz | bash && foundryup # first time only +Install Foundry first if you don't have it — see the +[installation guide](https://getfoundry.sh/introduction/installation). Then: +```bash anvil --chain-id 84532 # Base Sepolia → localhost:8545 anvil --chain-id 11155420 --port 8546 # OP Sepolia → localhost:8546 ``` From 5cb8d860287c941d9e3defcc2cbc5600a2bbff51 Mon Sep 17 00:00:00 2001 From: Tham Kei Lok Date: Tue, 21 Jul 2026 17:38:05 +0700 Subject: [PATCH 4/6] Restore security overrides that pnpm 11 was silently ignoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pnpm 11 no longer reads the "pnpm" field from package.json — it only warns that the keys were ignored. Five overrides had been stranded there, so the repo believed it was patching packages it was not: undici wanted ^7.24.1, resolved 6.25.0 @xmldom/xmldom wanted 0.8.13, resolved 0.7.13 alongside 0.8.13 h3 / node-forge / @wagmi/core happened to already satisfy their pins Moved all five into pnpm-workspace.yaml, where pnpm 11 reads overrides, and added two more to clear the criticals failing the audit gate: tar GHSA-23hp-3jrh-7fpw — no patched 6.x line, needs the 7.x major shell-quote GHSA-w7jw-789q-3m8p — same pin the SDK repo uses Both criticals predate this branch; main resolves the identical tar@6.2.1 and its CI has not run since June. 52 vulnerabilities -> 34, 1 critical -> 0. tar 6->7 and undici 6->7 are major bumps, so verified beyond the audit: tsc --noEmit clean and `expo export --platform ios` still produces a working bundle. Co-Authored-By: Claude Opus 4.8 --- with-react-native/package.json | 9 --- with-react-native/pnpm-lock.yaml | 107 +++++++++++++------------- with-react-native/pnpm-workspace.yaml | 19 +++++ 3 files changed, 71 insertions(+), 64 deletions(-) diff --git a/with-react-native/package.json b/with-react-native/package.json index c0102dc..14a0f67 100644 --- a/with-react-native/package.json +++ b/with-react-native/package.json @@ -53,14 +53,5 @@ "jest-expo": "~52.0.0", "typescript": "~5.3.3" }, - "pnpm": { - "overrides": { - "h3": "^1.15.9", - "undici": "^7.24.1", - "node-forge": "^1.4.0", - "@wagmi/core": "2.16.4", - "@xmldom/xmldom": "0.8.13" - } - }, "private": true } diff --git a/with-react-native/pnpm-lock.yaml b/with-react-native/pnpm-lock.yaml index 7da30fb..a68464f 100644 --- a/with-react-native/pnpm-lock.yaml +++ b/with-react-native/pnpm-lock.yaml @@ -7,6 +7,13 @@ settings: overrides: fast-uri@<3.1.2: 3.1.2 socket.io-parser@<4.2.6: 4.2.6 + h3@<1.15.9: ^1.15.9 + undici@<7.24.1: ^7.24.1 + node-forge@<1.4.0: ^1.4.0 + '@wagmi/core': 2.16.4 + '@xmldom/xmldom@<0.8.13': 0.8.13 + tar@<7.5.19: ^7.5.19 + shell-quote@<1.8.4: ^1.8.4 importers: @@ -1044,6 +1051,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@isaacs/ttlcache@1.4.1': resolution: {integrity: sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==} engines: {node: '>=12'} @@ -2010,11 +2021,6 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} - '@xmldom/xmldom@0.7.13': - resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==} - engines: {node: '>=10.0.0'} - deprecated: this version has critical issues, please update to the latest version - '@xmldom/xmldom@0.8.13': resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} engines: {node: '>=10.0.0'} @@ -2459,9 +2465,9 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} chrome-launcher@0.15.2: resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} @@ -3405,10 +3411,6 @@ packages: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - fs-minipass@3.0.3: resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -4469,17 +4471,13 @@ packages: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} mipd@0.0.7: resolution: {integrity: sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==} @@ -5350,8 +5348,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.8.3: - resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + shell-quote@1.10.0: + resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} engines: {node: '>= 0.4'} side-channel-list@1.0.0: @@ -5615,10 +5613,9 @@ packages: resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + tar@7.5.20: + resolution: {integrity: sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==} + engines: {node: '>=18'} temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} @@ -5797,9 +5794,9 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici@6.25.0: - resolution: {integrity: sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==} - engines: {node: '>=18.17'} + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + engines: {node: '>=20.18.1'} unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} @@ -6245,6 +6242,10 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -7354,11 +7355,11 @@ snapshots: source-map-support: 0.5.21 stacktrace-parser: 0.1.11 structured-headers: 0.4.1 - tar: 6.2.1 + tar: 7.5.20 temp-dir: 2.0.0 tempy: 0.7.1 terminal-link: 2.1.1 - undici: 6.25.0 + undici: 7.28.0 unique-string: 2.0.0 wrap-ansi: 7.0.0 ws: 8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) @@ -7517,7 +7518,7 @@ snapshots: '@expo/plist@0.2.2': dependencies: - '@xmldom/xmldom': 0.7.13 + '@xmldom/xmldom': 0.8.13 base64-js: 1.5.1 xmlbuilder: 14.0.0 @@ -7556,7 +7557,7 @@ snapshots: abort-controller: 3.0.0 debug: 4.4.3 source-map-support: 0.5.21 - undici: 6.25.0 + undici: 7.28.0 transitivePeerDependencies: - supports-color @@ -7612,6 +7613,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + '@isaacs/ttlcache@1.4.1': {} '@istanbuljs/load-nyc-config@1.1.0': @@ -9336,8 +9341,6 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@xmldom/xmldom@0.7.13': {} - '@xmldom/xmldom@0.8.13': {} '@xtuc/ieee754@1.2.0': {} @@ -9769,7 +9772,7 @@ snapshots: minipass-pipeline: 1.2.4 p-map: 4.0.0 ssri: 10.0.6 - tar: 6.2.1 + tar: 7.5.20 unique-filename: 3.0.0 call-bind-apply-helpers@1.0.2: @@ -9833,7 +9836,7 @@ snapshots: dependencies: readdirp: 5.0.0 - chownr@2.0.0: {} + chownr@3.0.0: {} chrome-launcher@0.15.2: dependencies: @@ -11012,10 +11015,6 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - fs-minipass@3.0.3: dependencies: minipass: 7.1.2 @@ -12421,14 +12420,11 @@ snapshots: dependencies: yallist: 4.0.0 - minipass@5.0.0: {} - minipass@7.1.2: {} - minizlib@2.1.2: + minizlib@3.1.0: dependencies: - minipass: 3.3.6 - yallist: 4.0.0 + minipass: 7.1.2 mipd@0.0.7(typescript@5.3.3): optionalDependencies: @@ -12891,7 +12887,7 @@ snapshots: react-devtools-core@5.3.2(bufferutil@4.1.0)(utf-8-validate@5.0.10): dependencies: - shell-quote: 1.8.3 + shell-quote: 1.10.0 ws: 7.5.10(bufferutil@4.1.0)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil @@ -13361,7 +13357,7 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.8.3: {} + shell-quote@1.10.0: {} side-channel-list@1.0.0: dependencies: @@ -13646,14 +13642,13 @@ snapshots: tapable@2.3.2: {} - tar@6.2.1: + tar@7.5.20: dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 temp-dir@2.0.0: {} @@ -13834,7 +13829,7 @@ snapshots: undici-types@7.16.0: {} - undici@6.25.0: {} + undici@7.28.0: {} unicode-canonical-property-names-ecmascript@2.0.1: {} @@ -14262,6 +14257,8 @@ snapshots: yallist@4.0.0: {} + yallist@5.0.0: {} + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 diff --git a/with-react-native/pnpm-workspace.yaml b/with-react-native/pnpm-workspace.yaml index e2ac1ea..613252e 100644 --- a/with-react-native/pnpm-workspace.yaml +++ b/with-react-native/pnpm-workspace.yaml @@ -3,9 +3,28 @@ nodeLinker: hoisted minimumReleaseAge: 10080 # Security: force patched transitive versions (Aikido CVE remediation). +# +# These MUST live here, not in package.json. pnpm 11 no longer reads the +# "pnpm" field from package.json and silently ignores it — it only warns: +# [WARN] The "pnpm" field in package.json is no longer read by pnpm. +# The following keys were ignored: "pnpm.overrides" +# The h3/undici/node-forge/@wagmi/core/@xmldom/xmldom pins below were stranded +# in package.json by that change; undici stayed on 6.25.0 and @xmldom/xmldom +# kept resolving 0.7.13 alongside 0.8.13 for as long as they sat there. overrides: fast-uri@<3.1.2: 3.1.2 socket.io-parser@<4.2.6: 4.2.6 + h3@<1.15.9: ^1.15.9 + undici@<7.24.1: ^7.24.1 + node-forge@<1.4.0: ^1.4.0 + '@wagmi/core': 2.16.4 + '@xmldom/xmldom@<0.8.13': 0.8.13 + # GHSA-23hp-3jrh-7fpw (decompression DoS). No patched 6.x line exists, so + # remediation requires the 7.x major. Reached via expo > @expo/cli > tar. + tar@<7.5.19: ^7.5.19 + # GHSA-w7jw-789q-3m8p, reached via react-native > react-devtools-core. + # Same pin the SDK repo uses. + shell-quote@<1.8.4: ^1.8.4 # Block git/http/file-protocol subdependencies (pnpm 11 default; set explicitly). blockExoticSubdeps: true From 49d2bf96dbfb5cb10dfa8724b5a36cc917dd2ac3 Mon Sep 17 00:00:00 2001 From: Tham Kei Lok Date: Wed, 22 Jul 2026 09:53:19 +0700 Subject: [PATCH 5/6] Address Codex review: reachable RPC host, install-referrer peer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P1 — hardcoding localhost only works on the iOS simulator. An Android emulator resolves localhost to itself (the host is 10.0.2.2) and a physical device to the device, so the mock-wallet fix did not actually apply on those targets. Derive the host from Expo's dev-server address instead: that is by definition the machine serving the bundle, which is the machine the README says to run anvil on, so all three targets reach it with nothing to configure. P2 — react-native-play-install-referrer is an optional peer of the SDK, so it was never installed and the example could not exercise the Android install attribution the 1.0.0 bump ships. Added it as a direct dependency; the example already requires a development build, so this costs no Expo Go compatibility. Verified: tsc --noEmit clean, and both `expo export --platform ios` and `--platform android` bundle. The Android bundle now contains PlayInstallReferrer, confirming the native module resolves rather than falling into the SDK's missing-module warning path. Audit gate still reports 0 criticals. Co-Authored-By: Claude Opus 4.8 --- with-react-native/README.md | 12 +++++++++--- with-react-native/config/wagmi.ts | 17 ++++++++++++++--- with-react-native/package.json | 2 ++ with-react-native/pnpm-lock.yaml | 16 ++++++++++++++-- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/with-react-native/README.md b/with-react-native/README.md index 5380732..769985f 100644 --- a/with-react-native/README.md +++ b/with-react-native/README.md @@ -9,6 +9,7 @@ This is an example React Native app demonstrating the [@formo/react-native-analy - **Semantic Events**: Track revenue, points, and volume events - **Automatic Wallet Event Tracking**: Wallet connect, disconnect, signatures, and transactions are automatically tracked via wagmi integration - **Consent Management**: Built-in opt-out/opt-in functionality for GDPR compliance +- **Android Install Attribution**: `react-native-play-install-referrer` is installed so the SDK can read the Play Store install referrer. Real referrer data only comes back for installs that actually came from the Play Store — a dev build or sideload reports an organic install. ## Getting Started @@ -70,9 +71,14 @@ anvil --chain-id 11155420 --port 8546 # OP Sepolia → localhost:8546 ``` Anvil pre-funds and unlocks `0xf39Fd6e5...fFb92266` — the same address the mock -connector is configured with. The URLs are already wired up in `config/wagmi.ts`, -so there is nothing to configure; just have the node running before you press the -buttons. Chain state is in-memory and resets whenever anvil restarts. +connector is configured with. Chain state is in-memory and resets whenever anvil +restarts. + +There is nothing to configure: `config/wagmi.ts` derives the RPC host from +Expo's dev-server address, which is the machine running Metro — the same one +running anvil. That matters because `localhost` only reaches the dev machine +from the iOS simulator; an Android emulator resolves it to the emulator itself, +and a physical device to the device. The second node is only needed if you want to send or sign **while on OP Sepolia** — switching chains itself works without any node, since the mock diff --git a/with-react-native/config/wagmi.ts b/with-react-native/config/wagmi.ts index bd815fb..fae3246 100644 --- a/with-react-native/config/wagmi.ts +++ b/with-react-native/config/wagmi.ts @@ -1,5 +1,6 @@ import { http, createConfig } from "wagmi"; import type { Chain } from "viem"; +import Constants from "expo-constants"; import { baseSepolia, optimismSepolia } from "wagmi/chains"; import { walletConnect } from "wagmi/connectors"; import { mock } from "@wagmi/core"; @@ -33,11 +34,21 @@ const MOCK_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" as const; // `getProvider`). Overriding only the transport silently leaves the mock wallet // pointed at the public endpoint. // -// These are hardcoded rather than env-driven because pointing at a local node +// These are derived rather than env-configured because pointing at a local node // costs nothing when one isn't running: no hook in this app reads chain state, // so the only calls that reach these URLs are the Mock Wallet's Send Tx and // Sign Message — which don't work without a local node either way. Without // anvil you get a connection error instead of -32601; both mean "start anvil". +// +// The host cannot be hardcoded to localhost: that only resolves to the dev +// machine on the iOS simulator. An Android emulator resolves localhost to +// itself (the host is reachable at 10.0.2.2), and a physical device resolves it +// to the device. Expo reports the machine serving the JS bundle in `hostUri` — +// necessarily the same machine the README says to run anvil on — so use that +// and every target reaches the right host. Falls back to localhost when +// hostUri is absent (production builds, where none of this applies anyway). +const devHost = Constants.expoConfig?.hostUri?.split(":")[0] ?? "localhost"; +const localRpc = (port: number) => `http://${devHost}:${port}`; const withRpcUrl = (chain: T, url: string): T => ({ ...chain, rpcUrls: { @@ -47,8 +58,8 @@ const withRpcUrl = (chain: T, url: string): T => ({ }); export const chains = [ - withRpcUrl(baseSepolia, "http://localhost:8545"), - withRpcUrl(optimismSepolia, "http://localhost:8546"), + withRpcUrl(baseSepolia, localRpc(8545)), + withRpcUrl(optimismSepolia, localRpc(8546)), ] as const; export const wagmiConfig = createConfig({ diff --git a/with-react-native/package.json b/with-react-native/package.json index 14a0f67..8ce8530 100644 --- a/with-react-native/package.json +++ b/with-react-native/package.json @@ -25,6 +25,7 @@ "ethereum-cryptography": "3.2.0", "expo": "~52.0.0", "expo-application": "~6.0.0", + "expo-constants": "~17.0.8", "expo-crypto": "~14.0.0", "expo-device": "~7.0.0", "expo-linking": "~7.0.0", @@ -35,6 +36,7 @@ "react-native": "0.76.0", "react-native-get-random-values": "^1.11.0", "react-native-modal": "14.0.0-rc.1", + "react-native-play-install-referrer": "^1.1.9", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.0.0", "react-native-svg": "^15.15.1", diff --git a/with-react-native/pnpm-lock.yaml b/with-react-native/pnpm-lock.yaml index f15f038..690b649 100644 --- a/with-react-native/pnpm-lock.yaml +++ b/with-react-native/pnpm-lock.yaml @@ -27,7 +27,7 @@ importers: version: 7.28.6 '@formo/analytics-react-native': specifier: ^1.0.0 - version: 1.0.0(78ed9643f6886a8af59692e6f0b5effa) + version: 1.0.0(860098ce253bd693c9df0e9f7732888c) '@react-native-async-storage/async-storage': specifier: ^1.21.0 version: 1.24.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10)) @@ -52,6 +52,9 @@ importers: expo-application: specifier: ~6.0.0 version: 6.0.2(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10)))(bufferutil@4.1.0)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(utf-8-validate@5.0.10)) + expo-constants: + specifier: ~17.0.8 + version: 17.0.8(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10)))(bufferutil@4.1.0)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(utf-8-validate@5.0.10))(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10)) expo-crypto: specifier: ~14.0.0 version: 14.0.2(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10)))(bufferutil@4.1.0)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(utf-8-validate@5.0.10)) @@ -82,6 +85,9 @@ importers: react-native-modal: specifier: 14.0.0-rc.1 version: 14.0.0-rc.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) + react-native-play-install-referrer: + specifier: ^1.1.9 + version: 1.1.9 react-native-safe-area-context: specifier: ^4.12.0 version: 4.14.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) @@ -5017,6 +5023,9 @@ packages: react: '*' react-native: '>=0.70.0' + react-native-play-install-referrer@1.1.9: + resolution: {integrity: sha512-COKQA/eS2I9NigxZDVZuLFzGNLBXF4+o3o9EIgULx+aagjVQexlPBh4g9fdnhiSvZk1rzq/asVwIJKRPc9Cy7A==} + react-native-safe-area-context@4.14.1: resolution: {integrity: sha512-+tUhT5WBl8nh5+P+chYhAjR470iCByf9z5EYdCEbPaAK3Yfzw+o8VRPnUgmPAKlSccOgQBxx3NOl/Wzckn9ujg==} peerDependencies: @@ -7564,7 +7573,7 @@ snapshots: chalk: 4.1.2 js-yaml: 4.1.1 - '@formo/analytics-react-native@1.0.0(78ed9643f6886a8af59692e6f0b5effa)': + '@formo/analytics-react-native@1.0.0(860098ce253bd693c9df0e9f7732888c)': dependencies: '@react-native-async-storage/async-storage': 1.24.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10)) '@react-native-community/netinfo': 12.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1) @@ -7575,6 +7584,7 @@ snapshots: '@tanstack/react-query': 5.90.20(react@18.3.1) expo-application: 6.0.2(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10)))(bufferutil@4.1.0)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(utf-8-validate@5.0.10)) expo-device: 7.0.3(expo@52.0.48(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@expo/metro-runtime@4.0.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10)))(bufferutil@4.1.0)(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(utf-8-validate@5.0.10)) + react-native-play-install-referrer: 1.1.9 wagmi: 2.14.11(@react-native-async-storage/async-storage@1.24.0(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10)))(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.20(react@18.3.1))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(typescript@5.3.3)(utf-8-validate@5.0.10)(viem@2.45.0(bufferutil@4.1.0)(typescript@5.3.3)(utf-8-validate@5.0.10)) '@humanwhocodes/config-array@0.13.0': @@ -12933,6 +12943,8 @@ snapshots: react-native: 0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10) react-native-animatable: 1.4.0 + react-native-play-install-referrer@1.1.9: {} + react-native-safe-area-context@4.14.1(react-native@0.76.0(@babel/core@7.28.6)(@babel/preset-env@7.28.6(@babel/core@7.28.6))(@types/react@18.3.27)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1): dependencies: react: 18.3.1 From 4115648b69088a3c35d6cfee35109a35754d8833 Mon Sep 17 00:00:00 2001 From: Tham Kei Lok Date: Wed, 22 Jul 2026 13:16:58 +0700 Subject: [PATCH 6/6] Fix the test suite and guard the RPC-host regression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex flagged that pnpm test could not run. Two causes, only one of them mine: - Pre-existing: jest.setup.js mocked "@formo/react-native-analytics", a package that has never existed — the real name transposes the words. Every suite died at setup, so the tests have been dead on main too, not just on this branch. No CI job runs them, which is why it went unnoticed. - Mine: withRpcUrl dereferences chain.rpcUrls.default, and the wagmi/chains mock stubbed only id and name. Gave the stubs a realistic rpcUrls shape rather than making the helper tolerate a malformed chain. 13 tests now pass. Added two guards to wagmi.test.ts: one that each chain points at a local node (the mock connector reads rpcUrls directly and ignores transports, so an override in the wrong place is silent), and one that the host is derived from Expo's dev server rather than hardcoded. The second guard needs a non-localhost hostUri to be meaningful — the fallback when hostUri is absent is localhost, so a hardcoded localhost would otherwise satisfy it. Verified by reintroducing the exact bug: the guard fails, and passes again once reverted. Co-Authored-By: Claude Opus 4.8 --- with-react-native/__tests__/wagmi.test.ts | 28 +++++++++++++++++++++++ with-react-native/jest.setup.js | 18 +++++++++++---- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/with-react-native/__tests__/wagmi.test.ts b/with-react-native/__tests__/wagmi.test.ts index 220ccce..b114e39 100644 --- a/with-react-native/__tests__/wagmi.test.ts +++ b/with-react-native/__tests__/wagmi.test.ts @@ -1,3 +1,11 @@ +// Stands in for the Metro dev server. Deliberately NOT localhost: the fallback +// when hostUri is absent is localhost, so a hardcoded localhost would satisfy +// the host assertion below and the regression guard would never fail. +jest.mock("expo-constants", () => ({ + __esModule: true, + default: { expoConfig: { hostUri: "192.168.1.5:8081" } }, +})); + import { chains, wagmiConfig } from "../config/wagmi"; describe("Wagmi Configuration", () => { @@ -17,6 +25,26 @@ describe("Wagmi Configuration", () => { it("should have exactly 2 chains configured", () => { expect(chains).toHaveLength(2); }); + + // The mock connector reads chain.rpcUrls.default.http[0] directly and + // ignores wagmi's `transports`, so the local-node override has to land + // here or Send Tx / Sign Message silently talk to the public RPC instead. + it("points each chain at a local node rather than the public RPC", () => { + for (const chain of chains) { + expect(chain.rpcUrls.default.http[0]).toMatch(/^http:\/\/[^/]+:854[56]$/); + } + }); + + // Regression guard: hardcoding localhost only reaches the dev machine from + // the iOS simulator. An Android emulator resolves it to the emulator and a + // physical device to itself, so the host must come from Expo's dev-server + // address — the machine actually running anvil. + it("derives the RPC host from the Expo dev server, not localhost", () => { + for (const chain of chains) { + expect(chain.rpcUrls.default.http[0]).toContain("//192.168.1.5:"); + expect(chain.rpcUrls.default.http[0]).not.toContain("localhost"); + } + }); }); describe("wagmiConfig", () => { diff --git a/with-react-native/jest.setup.js b/with-react-native/jest.setup.js index 5940cae..7769e2e 100644 --- a/with-react-native/jest.setup.js +++ b/with-react-native/jest.setup.js @@ -59,9 +59,19 @@ jest.mock("wagmi", () => ({ WagmiProvider: ({ children }) => children, })); +// rpcUrls is part of viem's Chain shape and config/wagmi.ts overrides it to +// point the mock wallet at a local node, so the stubs need it to stay realistic. jest.mock("wagmi/chains", () => ({ - baseSepolia: { id: 84532, name: "Base Sepolia" }, - optimismSepolia: { id: 11155420, name: "OP Sepolia" }, + baseSepolia: { + id: 84532, + name: "Base Sepolia", + rpcUrls: { default: { http: ["https://sepolia.base.org"] } }, + }, + optimismSepolia: { + id: 11155420, + name: "OP Sepolia", + rpcUrls: { default: { http: ["https://sepolia.optimism.io"] } }, + }, })); jest.mock("wagmi/connectors", () => ({ @@ -74,8 +84,8 @@ jest.mock("@tanstack/react-query", () => ({ QueryClientProvider: ({ children }) => children, })); -// Mock @formo/react-native-analytics -jest.mock("@formo/react-native-analytics", () => ({ +// Mock @formo/analytics-react-native +jest.mock("@formo/analytics-react-native", () => ({ FormoAnalyticsProvider: ({ children }) => children, useFormo: () => ({ track: jest.fn(),