@multiplatform.one/web3 6.0.3 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/cjs/connectkit/web3Bridge.cjs +1 -1
  2. package/dist/cjs/connectkit/web3Bridge.native.js +1 -1
  3. package/dist/cjs/connectkit/web3Bridge.native.js.map +1 -1
  4. package/dist/cjs/providers/WalletProvider.cjs +7 -11
  5. package/dist/cjs/providers/WalletProvider.native.js +10 -9
  6. package/dist/cjs/providers/WalletProvider.native.js.map +1 -1
  7. package/dist/cjs/providers/walletConfig.cjs +23 -0
  8. package/dist/cjs/providers/walletConfig.native.js +27 -0
  9. package/dist/cjs/providers/walletConfig.native.js.map +1 -1
  10. package/dist/cjs/providers/walletConfig.spec.cjs +44 -0
  11. package/dist/cjs/providers/walletConfig.spec.native.js +57 -0
  12. package/dist/cjs/providers/walletConfig.spec.native.js.map +1 -0
  13. package/dist/esm/connectkit/web3Bridge.mjs +1 -1
  14. package/dist/esm/connectkit/web3Bridge.mjs.map +1 -1
  15. package/dist/esm/connectkit/web3Bridge.native.js +1 -1
  16. package/dist/esm/connectkit/web3Bridge.native.js.map +1 -1
  17. package/dist/esm/providers/WalletProvider.mjs +10 -14
  18. package/dist/esm/providers/WalletProvider.mjs.map +1 -1
  19. package/dist/esm/providers/WalletProvider.native.js +14 -13
  20. package/dist/esm/providers/WalletProvider.native.js.map +1 -1
  21. package/dist/esm/providers/walletConfig.mjs +24 -2
  22. package/dist/esm/providers/walletConfig.mjs.map +1 -1
  23. package/dist/esm/providers/walletConfig.native.js +28 -2
  24. package/dist/esm/providers/walletConfig.native.js.map +1 -1
  25. package/dist/esm/providers/walletConfig.spec.mjs +45 -0
  26. package/dist/esm/providers/walletConfig.spec.mjs.map +1 -0
  27. package/dist/esm/providers/walletConfig.spec.native.js +55 -0
  28. package/dist/esm/providers/walletConfig.spec.native.js.map +1 -0
  29. package/dist/jsx/connectkit/web3Bridge.mjs +1 -1
  30. package/dist/jsx/connectkit/web3Bridge.mjs.map +1 -1
  31. package/dist/jsx/connectkit/web3Bridge.native.js +1 -1
  32. package/dist/jsx/connectkit/web3Bridge.native.js.map +1 -1
  33. package/dist/jsx/providers/WalletProvider.mjs +10 -14
  34. package/dist/jsx/providers/WalletProvider.mjs.map +1 -1
  35. package/dist/jsx/providers/WalletProvider.native.js +10 -9
  36. package/dist/jsx/providers/WalletProvider.native.js.map +1 -1
  37. package/dist/jsx/providers/walletConfig.mjs +24 -2
  38. package/dist/jsx/providers/walletConfig.mjs.map +1 -1
  39. package/dist/jsx/providers/walletConfig.native.js +27 -0
  40. package/dist/jsx/providers/walletConfig.native.js.map +1 -1
  41. package/dist/jsx/providers/walletConfig.spec.mjs +45 -0
  42. package/dist/jsx/providers/walletConfig.spec.mjs.map +1 -0
  43. package/dist/jsx/providers/walletConfig.spec.native.js +57 -0
  44. package/dist/jsx/providers/walletConfig.spec.native.js.map +1 -0
  45. package/package.json +4 -4
  46. package/src/connectkit/web3Bridge.tsx +8 -2
  47. package/src/providers/WalletProvider.native.tsx +22 -12
  48. package/src/providers/WalletProvider.tsx +11 -12
  49. package/src/providers/walletConfig.spec.ts +42 -0
  50. package/src/providers/walletConfig.ts +38 -1
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { hardhat, mainnet, sepolia } from "wagmi/chains";
3
+ import { defaultChains, resolveChains } from "./walletConfig.mjs";
4
+ const base = {
5
+ walletConnectProjectId: "",
6
+ appName: "test"
7
+ };
8
+ describe("resolveChains", () => {
9
+ it("filters testnets by default", () => {
10
+ const chains = resolveChains(base);
11
+ expect(chains.map(c => c.id)).not.toContain(sepolia.id);
12
+ expect(chains[0].id).toBe(mainnet.id);
13
+ });
14
+ it("keeps testnets when enableTestnets is on", () => {
15
+ const chains = resolveChains({
16
+ ...base,
17
+ enableTestnets: true
18
+ });
19
+ expect(chains.map(c => c.id)).toContain(sepolia.id);
20
+ expect(chains).toHaveLength(defaultChains.length);
21
+ });
22
+ it("prepends the hardhat dev chain as the default when localnet is on", () => {
23
+ const chains = resolveChains({
24
+ ...base,
25
+ localnet: true
26
+ });
27
+ expect(chains[0].id).toBe(hardhat.id);
28
+ expect(chains[0].rpcUrls.default.http[0]).toBe("http://127.0.0.1:8545");
29
+ expect(chains.filter(c => c.id === hardhat.id)).toHaveLength(1);
30
+ expect(chains.map(c => c.id)).toContain(mainnet.id);
31
+ });
32
+ it("honours a custom localnet RPC url (emulator host addresses)", () => {
33
+ const chains = resolveChains({
34
+ ...base,
35
+ localnet: true,
36
+ localnetRpcUrl: "http://10.0.2.2:8545"
37
+ });
38
+ expect(chains[0].id).toBe(hardhat.id);
39
+ expect(chains[0].rpcUrls.default.http[0]).toBe("http://10.0.2.2:8545");
40
+ });
41
+ it("does not add the dev chain when localnet is off", () => {
42
+ expect(resolveChains(base).map(c => c.id)).not.toContain(hardhat.id);
43
+ });
44
+ });
45
+ //# sourceMappingURL=walletConfig.spec.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["describe","expect","it","hardhat","mainnet","sepolia","defaultChains","resolveChains","base","walletConnectProjectId","appName","chains","map","c","id","not","toContain","toBe","enableTestnets","toHaveLength","length","localnet","rpcUrls","default","http","filter","localnetRpcUrl"],"sources":["../../../src/providers/walletConfig.spec.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,QAAA,EAAUC,MAAA,EAAQC,EAAA,QAAU;AACrC,SAASC,OAAA,EAASC,OAAA,EAASC,OAAA,QAAe;AAC1C,SAASC,aAAA,EAAeC,aAAA,QAAsC;AAE9D,MAAMC,IAAA,GAAmB;EAAEC,sBAAA,EAAwB;EAAIC,OAAA,EAAS;AAAO;AAEvEV,QAAA,CAAS,iBAAiB,MAAM;EAC9BE,EAAA,CAAG,+BAA+B,MAAM;IACtC,MAAMS,MAAA,GAASJ,aAAA,CAAcC,IAAI;IACjCP,MAAA,CAAOU,MAAA,CAAOC,GAAA,CAAKC,CAAA,IAAMA,CAAA,CAAEC,EAAE,CAAC,EAAEC,GAAA,CAAIC,SAAA,CAAUX,OAAA,CAAQS,EAAE;IACxDb,MAAA,CAAOU,MAAA,CAAO,CAAC,EAAEG,EAAE,EAAEG,IAAA,CAAKb,OAAA,CAAQU,EAAE;EACtC,CAAC;EAEDZ,EAAA,CAAG,4CAA4C,MAAM;IACnD,MAAMS,MAAA,GAASJ,aAAA,CAAc;MAAE,GAAGC,IAAA;MAAMU,cAAA,EAAgB;IAAK,CAAC;IAC9DjB,MAAA,CAAOU,MAAA,CAAOC,GAAA,CAAKC,CAAA,IAAMA,CAAA,CAAEC,EAAE,CAAC,EAAEE,SAAA,CAAUX,OAAA,CAAQS,EAAE;IACpDb,MAAA,CAAOU,MAAM,EAAEQ,YAAA,CAAab,aAAA,CAAcc,MAAM;EAClD,CAAC;EAEDlB,EAAA,CAAG,qEAAqE,MAAM;IAC5E,MAAMS,MAAA,GAASJ,aAAA,CAAc;MAAE,GAAGC,IAAA;MAAMa,QAAA,EAAU;IAAK,CAAC;IACxDpB,MAAA,CAAOU,MAAA,CAAO,CAAC,EAAEG,EAAE,EAAEG,IAAA,CAAKd,OAAA,CAAQW,EAAE;IACpCb,MAAA,CAAOU,MAAA,CAAO,CAAC,EAAEW,OAAA,CAAQC,OAAA,CAAQC,IAAA,CAAK,CAAC,CAAC,EAAEP,IAAA,CAAK,uBAAuB;IAEtEhB,MAAA,CAAOU,MAAA,CAAOc,MAAA,CAAQZ,CAAA,IAAMA,CAAA,CAAEC,EAAA,KAAOX,OAAA,CAAQW,EAAE,CAAC,EAAEK,YAAA,CAAa,CAAC;IAChElB,MAAA,CAAOU,MAAA,CAAOC,GAAA,CAAKC,CAAA,IAAMA,CAAA,CAAEC,EAAE,CAAC,EAAEE,SAAA,CAAUZ,OAAA,CAAQU,EAAE;EACtD,CAAC;EAEDZ,EAAA,CAAG,+DAA+D,MAAM;IACtE,MAAMS,MAAA,GAASJ,aAAA,CAAc;MAC3B,GAAGC,IAAA;MACHa,QAAA,EAAU;MACVK,cAAA,EAAgB;IAClB,CAAC;IACDzB,MAAA,CAAOU,MAAA,CAAO,CAAC,EAAEG,EAAE,EAAEG,IAAA,CAAKd,OAAA,CAAQW,EAAE;IACpCb,MAAA,CAAOU,MAAA,CAAO,CAAC,EAAEW,OAAA,CAAQC,OAAA,CAAQC,IAAA,CAAK,CAAC,CAAC,EAAEP,IAAA,CAAK,sBAAsB;EACvE,CAAC;EAEDf,EAAA,CAAG,mDAAmD,MAAM;IAC1DD,MAAA,CAAOM,aAAA,CAAcC,IAAI,EAAEI,GAAA,CAAKC,CAAA,IAAMA,CAAA,CAAEC,EAAE,CAAC,EAAEC,GAAA,CAAIC,SAAA,CAAUb,OAAA,CAAQW,EAAE;EACvE,CAAC;AACH,CAAC","ignoreList":[]}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+
3
+ var import_vitest = require("vitest");
4
+ var import_chains = require("wagmi/chains");
5
+ var import_walletConfig = require("./walletConfig.native.js");
6
+ var base = {
7
+ walletConnectProjectId: "",
8
+ appName: "test"
9
+ };
10
+ (0, import_vitest.describe)("resolveChains", function () {
11
+ (0, import_vitest.it)("filters testnets by default", function () {
12
+ var chains = (0, import_walletConfig.resolveChains)(base);
13
+ (0, import_vitest.expect)(chains.map(function (c) {
14
+ return c.id;
15
+ })).not.toContain(import_chains.sepolia.id);
16
+ (0, import_vitest.expect)(chains[0].id).toBe(import_chains.mainnet.id);
17
+ });
18
+ (0, import_vitest.it)("keeps testnets when enableTestnets is on", function () {
19
+ var chains = (0, import_walletConfig.resolveChains)({
20
+ ...base,
21
+ enableTestnets: true
22
+ });
23
+ (0, import_vitest.expect)(chains.map(function (c) {
24
+ return c.id;
25
+ })).toContain(import_chains.sepolia.id);
26
+ (0, import_vitest.expect)(chains).toHaveLength(import_walletConfig.defaultChains.length);
27
+ });
28
+ (0, import_vitest.it)("prepends the hardhat dev chain as the default when localnet is on", function () {
29
+ var chains = (0, import_walletConfig.resolveChains)({
30
+ ...base,
31
+ localnet: true
32
+ });
33
+ (0, import_vitest.expect)(chains[0].id).toBe(import_chains.hardhat.id);
34
+ (0, import_vitest.expect)(chains[0].rpcUrls.default.http[0]).toBe("http://127.0.0.1:8545");
35
+ (0, import_vitest.expect)(chains.filter(function (c) {
36
+ return c.id === import_chains.hardhat.id;
37
+ })).toHaveLength(1);
38
+ (0, import_vitest.expect)(chains.map(function (c) {
39
+ return c.id;
40
+ })).toContain(import_chains.mainnet.id);
41
+ });
42
+ (0, import_vitest.it)("honours a custom localnet RPC url (emulator host addresses)", function () {
43
+ var chains = (0, import_walletConfig.resolveChains)({
44
+ ...base,
45
+ localnet: true,
46
+ localnetRpcUrl: "http://10.0.2.2:8545"
47
+ });
48
+ (0, import_vitest.expect)(chains[0].id).toBe(import_chains.hardhat.id);
49
+ (0, import_vitest.expect)(chains[0].rpcUrls.default.http[0]).toBe("http://10.0.2.2:8545");
50
+ });
51
+ (0, import_vitest.it)("does not add the dev chain when localnet is off", function () {
52
+ (0, import_vitest.expect)((0, import_walletConfig.resolveChains)(base).map(function (c) {
53
+ return c.id;
54
+ })).not.toContain(import_chains.hardhat.id);
55
+ });
56
+ });
57
+ //# sourceMappingURL=walletConfig.spec.native.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["import_vitest","require","import_chains","import_walletConfig","base","walletConnectProjectId","appName","describe","it","chains","resolveChains","expect","map","c","id","not","toContain","sepolia","toBe","mainnet","enableTestnets","toHaveLength","defaultChains","length","localnet","hardhat","rpcUrls","default","http","filter"],"sources":["../../../src/providers/walletConfig.spec.ts"],"sourcesContent":[null],"mappings":"AAAA,YAAS;;AACT,IAAAA,aAAS,GAASC,OAAS;AAC3B,IAAAC,aAAS,GAAAD,OAAe;AAExB,IAAAE,mBAA2B,GAAAF,OAAA,2BAA4B;AAEvD,IAAAG,IAAA,GAAS;EACPC,sBAAG;EACDC,OAAA,EAAM;AACN;AACA,IAAAN,aAAO,CAAOO,QAAO,iBAAe;EACtC,CAAC,GAAAP,aAAA,CAAAQ,EAAA;IAED,IAAGC,MAAA,OAAAN,mBAAA,CAAAO,aAA4C,EAAMN,IAAA;IACnD,IAAAJ,aAAe,CAAAW,MAAA,EAAAF,MAAgB,CAAAG,GAAG,WAAMC,CAAA;MACxC,OAAOA,CAAA,CAAAC,EAAA;IACP,IAAAC,GAAO,CAAAC,SAAQ,CAAAd,aAAa,CAAAe,OAAA,CAAAH,EAAc;IAC3C,IAAAd,aAAA,CAAAW,MAAA,EAAAF,MAAA,IAAAK,EAAA,EAAAI,IAAA,CAAAhB,aAAA,CAAAiB,OAAA,CAAAL,EAAA;EAED;EACE,IAAAd,aAAe,CAAAQ,EAAA,4CAAyC;IACxD,IAAAC,MAAO,IAAO,CAAC,EAAEN,mBAAmB,CAAAO,aAAA;MACpC,GAAAN,IAAO;MAEPgB,cAAc;IACd;IACD,IAAApB,aAAA,CAAAW,MAAA,EAAAF,MAAA,CAAAG,GAAA,WAAAC,CAAA;MAEE,OAAAA,CAAA,CAAAC,EAAA;IACD,IAAAE,SAAM,CAAAd,aAAS,CAAAe,OAAc,CAAAH,EAAA;IAAA,EAC3B,EAAAd,aAAG,CAAAW,MAAA,EAAAF,MAAA,EAAAY,YAAA,CAAAlB,mBAAA,CAAAmB,aAAA,CAAAC,MAAA;EAAA;EACO,IACVvB,aAAA,CAAAQ,EAAgB;IAClB,IAACC,MAAA,OAAAN,mBAAA,CAAAO,aAAA;MACD,GAAAN,IAAO;MACPoB,QAAO;IACR;IAED,CAAG,GAAAxB,aAAA,CAAAW,MAAA,EAAAF,MAAA,IAAAK,EAAA,EAAAI,IAAA,CAAAhB,aAAyD,CAAAuB,OAAA,CAAAX,EAAA;IAC1D,IAAAd,aAAO,CAAAW,MAAc,EAAIF,MAAO,IAAAiB,OAAW,CAAAC,OAAM,CAAAC,IAAA,GAAU,EAAAV,IAAA,wBAAU;IACtE,IAAAlB,aAAA,CAAAW,MAAA,EAAAF,MAAA,CAAAoB,MAAA,WAAAhB,CAAA;MACF,OAAAA,CAAA,CAAAC,EAAA,KAAAZ,aAAA,CAAAuB,OAAA,CAAAX,EAAA","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/web3",
3
- "version": "6.0.3",
3
+ "version": "6.1.0",
4
4
  "description": "Web3 wallet integration for multiplatform.one ecosystem with ConnectKit and wagmi",
5
5
  "keywords": [
6
6
  "connectkit",
@@ -79,8 +79,8 @@
79
79
  "tamagui": "2.0.0-rc.41",
80
80
  "viem": "^2.48.7",
81
81
  "wagmi": "^3.6.9",
82
- "@multiplatform.one/gpg": "6.0.3",
83
- "@multiplatform.one/connectkit": "6.0.3"
82
+ "@multiplatform.one/connectkit": "6.1.0",
83
+ "@multiplatform.one/gpg": "6.1.0"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@tamagui/build": "2.0.0-rc.41",
@@ -97,7 +97,7 @@
97
97
  "vite": "^8.0.10",
98
98
  "vite-plugin-singlefile": "^2.3.3",
99
99
  "vitest": "^4.1.5",
100
- "@multiplatform.one/config": "6.0.3"
100
+ "@multiplatform.one/config": "6.1.0"
101
101
  },
102
102
  "peerDependencies": {
103
103
  "@tanstack/react-query": "^5.62.0",
@@ -80,8 +80,14 @@ export const Web3ModalBridge: React.FC<Web3ModalBridgeProps> = ({
80
80
  useEffect(() => {
81
81
  if (isConnected) {
82
82
  // Skip the redirect/close when already on PROFILE or the SIWE screen
83
- // (must be &&: with || the predicate was a tautology and always fired).
84
- if (context.route !== routes.PROFILE && context.route !== routes.SIGNINWITHETHEREUM) {
83
+ // (must be &&: with || the predicate was a tautology and always fired),
84
+ // or on the GPG screen — wagmi's async auto-reconnect must not hijack
85
+ // (or close) an in-progress GPG sign-in.
86
+ if (
87
+ context.route !== routes.PROFILE &&
88
+ context.route !== routes.SIGNINWITHETHEREUM &&
89
+ context.route !== routes.SIGNINWITHGPG
90
+ ) {
85
91
  if (signInWithEthereum && !context.options?.disableSiweRedirect) {
86
92
  context.setRoute(routes.SIGNINWITHETHEREUM);
87
93
  } else {
@@ -1,7 +1,7 @@
1
1
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2
2
  import type { PropsWithChildren } from "react";
3
3
  import { createContext, useContext, useEffect, useMemo, useReducer, useRef, useState } from "react";
4
- import { AppState, Linking, StyleSheet, View } from "react-native";
4
+ import { AppState, Linking, Platform, StyleSheet, View } from "react-native";
5
5
  import { WebView, type WebViewMessageEvent, type WebViewProps } from "react-native-webview";
6
6
  import { http, WagmiProvider, createConfig } from "wagmi";
7
7
  import { BridgeClient } from "../bridge/BridgeClient";
@@ -11,7 +11,7 @@ import { createPostMessageTransport } from "../bridge/postMessageTransport";
11
11
  import { parseWalletReturn, shouldOpenExternally } from "./native/deepLink";
12
12
  import { buildConfigInjection, buildInjectScript } from "./native/inject";
13
13
  import { initialVisibility, nextVisibility } from "./native/visibility";
14
- import { type Web3Config, Web3ConfigContext, defaultChains } from "./walletConfig";
14
+ import { type Web3Config, Web3ConfigContext, resolveChains } from "./walletConfig";
15
15
 
16
16
  type WebViewSource = NonNullable<WebViewProps["source"]>;
17
17
 
@@ -114,14 +114,25 @@ export function WalletProvider({
114
114
  }, [bridge]);
115
115
 
116
116
  const wagmiConfig = useMemo(() => {
117
- const transports = Object.fromEntries(defaultChains.map((chain) => [chain.id, http()]));
117
+ // Same chain resolution as web (testnet filter + optional localnet dev
118
+ // chain) -- on native, pass localnetRpcUrl with the host address the
119
+ // device/emulator can reach (e.g. http://10.0.2.2:8545 on Android).
120
+ const chains = resolveChains(web3Config);
121
+ const transports = Object.fromEntries(chains.map((chain) => [chain.id, http()]));
118
122
  return createConfig({
119
- chains: defaultChains,
123
+ chains,
120
124
  transports,
121
125
  connectors: [createBridgeConnector(bridge)],
122
126
  multiInjectedProviderDiscovery: false,
123
127
  });
124
- }, [bridge]);
128
+ // eslint-disable-next-line react-hooks/exhaustive-deps
129
+ }, [
130
+ bridge,
131
+ web3Config.chains,
132
+ web3Config.enableTestnets,
133
+ web3Config.localnet,
134
+ web3Config.localnetRpcUrl,
135
+ ]);
125
136
 
126
137
  const solanaValue = useMemo<SolanaWalletContextValue>(
127
138
  () => ({ bridge: solana, publicKey, connected: publicKey !== null }),
@@ -131,13 +142,12 @@ export function WalletProvider({
131
142
  const onMessage = (event: WebViewMessageEvent) => transport.receive(event.nativeEvent.data);
132
143
 
133
144
  // No inline HTML fallback: the bundle is too large for WKWebView source={{html}}.
134
- const resolvedSource =
135
- webViewSource ?? (web3Config.webViewUrl ? { uri: web3Config.webViewUrl } : null);
136
- if (!resolvedSource) {
137
- throw new Error(
138
- "WalletProvider (native): set config.webViewUrl (or pass webViewSource) to a served copy of @multiplatform.one/web3/assets/webview.html",
139
- );
140
- }
145
+ // Fall back to the documented dev setup (a static server on :8099 serving
146
+ // @multiplatform.one/web3/assets/webview.html; Android emulators reach the
147
+ // host at 10.0.2.2) instead of crashing the whole app shell when the config
148
+ // didn't carry a URL.
149
+ const defaultWebViewUrl = `http://${Platform.OS === "android" ? "10.0.2.2" : "localhost"}:8099/webview.html`;
150
+ const resolvedSource = webViewSource ?? { uri: web3Config.webViewUrl || defaultWebViewUrl };
141
151
 
142
152
  return (
143
153
  <Web3ConfigContext.Provider value={web3Config}>
@@ -6,7 +6,7 @@ import { useMemo } from "react";
6
6
  import type { Chain } from "viem";
7
7
  import { WagmiProvider, createConfig, http } from "wagmi";
8
8
  import { createMockConnector } from "./mockConnector";
9
- import { type Web3Config, Web3ConfigContext, defaultChains } from "./walletConfig";
9
+ import { type Web3Config, Web3ConfigContext, resolveChains } from "./walletConfig";
10
10
 
11
11
  export interface WalletProviderProps extends PropsWithChildren {
12
12
  config: Web3Config;
@@ -34,21 +34,19 @@ export function WalletProvider({
34
34
  appDescription = "Connect your wallet",
35
35
  appUrl = isWeb && typeof window !== "undefined" ? window.location.origin : "",
36
36
  appIcon,
37
- chains = defaultChains,
37
+ chains,
38
38
  enableTestnets = false,
39
+ localnet = false,
40
+ localnetRpcUrl,
39
41
  mockWallet = false,
40
42
  } = web3Config;
41
43
 
42
- const activeChains = useMemo(() => {
43
- if (enableTestnets) {
44
- return chains;
45
- }
46
- const filtered = chains.filter((chain) => !chain.testnet);
47
- if (filtered.length === 0) {
48
- return chains;
49
- }
50
- return filtered as unknown as readonly [Chain, ...Chain[]];
51
- }, [chains, enableTestnets]);
44
+ // Testnet filter + optional localnet dev chain (prepended -> default chain).
45
+ const activeChains = useMemo(
46
+ () => resolveChains(web3Config),
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ [chains, enableTestnets, localnet, localnetRpcUrl],
49
+ );
52
50
 
53
51
  const wagmiConfig = useMemo(() => {
54
52
  const chainIds = activeChains.map((chain) => chain.id).join(",");
@@ -59,6 +57,7 @@ export function WalletProvider({
59
57
  appIcon,
60
58
  walletConnectProjectId,
61
59
  chainIds,
60
+ localnetRpcUrl,
62
61
  mockWallet,
63
62
  });
64
63
  const cached = wagmiConfigCache.get(cacheKey);
@@ -0,0 +1,42 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { hardhat, mainnet, sepolia } from "wagmi/chains";
3
+ import { defaultChains, resolveChains, type Web3Config } from "./walletConfig";
4
+
5
+ const base: Web3Config = { walletConnectProjectId: "", appName: "test" };
6
+
7
+ describe("resolveChains", () => {
8
+ it("filters testnets by default", () => {
9
+ const chains = resolveChains(base);
10
+ expect(chains.map((c) => c.id)).not.toContain(sepolia.id);
11
+ expect(chains[0].id).toBe(mainnet.id);
12
+ });
13
+
14
+ it("keeps testnets when enableTestnets is on", () => {
15
+ const chains = resolveChains({ ...base, enableTestnets: true });
16
+ expect(chains.map((c) => c.id)).toContain(sepolia.id);
17
+ expect(chains).toHaveLength(defaultChains.length);
18
+ });
19
+
20
+ it("prepends the hardhat dev chain as the default when localnet is on", () => {
21
+ const chains = resolveChains({ ...base, localnet: true });
22
+ expect(chains[0].id).toBe(hardhat.id);
23
+ expect(chains[0].rpcUrls.default.http[0]).toBe("http://127.0.0.1:8545");
24
+ // still exactly one localnet entry ahead of the regular chains
25
+ expect(chains.filter((c) => c.id === hardhat.id)).toHaveLength(1);
26
+ expect(chains.map((c) => c.id)).toContain(mainnet.id);
27
+ });
28
+
29
+ it("honours a custom localnet RPC url (emulator host addresses)", () => {
30
+ const chains = resolveChains({
31
+ ...base,
32
+ localnet: true,
33
+ localnetRpcUrl: "http://10.0.2.2:8545",
34
+ });
35
+ expect(chains[0].id).toBe(hardhat.id);
36
+ expect(chains[0].rpcUrls.default.http[0]).toBe("http://10.0.2.2:8545");
37
+ });
38
+
39
+ it("does not add the dev chain when localnet is off", () => {
40
+ expect(resolveChains(base).map((c) => c.id)).not.toContain(hardhat.id);
41
+ });
42
+ });
@@ -1,6 +1,6 @@
1
1
  import { createContext, useContext } from "react";
2
2
  import type { Chain } from "viem";
3
- import { arbitrum, base, mainnet, optimism, polygon, sepolia } from "wagmi/chains";
3
+ import { arbitrum, base, hardhat, mainnet, optimism, polygon, sepolia } from "wagmi/chains";
4
4
 
5
5
  /**
6
6
  * Shared config seam for both platform variants of WalletProvider. Keeping the
@@ -25,14 +25,51 @@ export interface Web3Config {
25
25
  appIcon?: string;
26
26
  chains?: readonly [Chain, ...Chain[]];
27
27
  enableTestnets?: boolean;
28
+ /**
29
+ * Add the local dev chain (hardhat, chain 31337, `make localnet`) and make it
30
+ * the DEFAULT chain, so dev builds point at the local node out of the box.
31
+ */
32
+ localnet?: boolean;
33
+ /**
34
+ * Local EVM JSON-RPC endpoint when `localnet` is on. Defaults to hardhat's
35
+ * http://127.0.0.1:8545 -- emulators need the host address instead (e.g.
36
+ * http://10.0.2.2:8545 on Android).
37
+ */
38
+ localnetRpcUrl?: string;
28
39
  /** Solana JSON-RPC endpoint (native bridge + web). */
29
40
  solanaRpcUrl?: string;
41
+ /** Sui JSON-RPC endpoint (native bridge + web). */
42
+ suiRpcUrl?: string;
30
43
  /** E2E only (native): route connect through the in-WebView mock wallet. */
31
44
  mockWallet?: boolean;
32
45
  /** Native: URL serving the wallet WebView HTML (preferred over the inline string). */
33
46
  webViewUrl?: string;
34
47
  }
35
48
 
49
+ /**
50
+ * Resolve the active chain list for a Web3Config: apply the testnet filter,
51
+ * then (when `localnet` is on) prepend the hardhat dev chain so it becomes
52
+ * wagmi's default chain. Shared by the web and native WalletProviders so both
53
+ * platforms point at the same networks.
54
+ */
55
+ export function resolveChains(config: Web3Config): readonly [Chain, ...Chain[]] {
56
+ const {
57
+ chains = defaultChains,
58
+ enableTestnets = false,
59
+ localnet = false,
60
+ localnetRpcUrl,
61
+ } = config;
62
+ let active: Chain[] = enableTestnets ? [...chains] : chains.filter((chain) => !chain.testnet);
63
+ if (active.length === 0) active = [...chains];
64
+ if (localnet) {
65
+ const local: Chain = localnetRpcUrl
66
+ ? { ...hardhat, rpcUrls: { default: { http: [localnetRpcUrl] } } }
67
+ : hardhat;
68
+ active = [local, ...active.filter((chain) => chain.id !== hardhat.id)];
69
+ }
70
+ return active as unknown as readonly [Chain, ...Chain[]];
71
+ }
72
+
36
73
  export const Web3ConfigContext = createContext<Web3Config | null>(null);
37
74
 
38
75
  export function useWeb3Config(): Web3Config {