diff --git a/components/game/CraftingScreen.tsx b/components/game/CraftingScreen.tsx
index 0770819..ae7254b 100644
--- a/components/game/CraftingScreen.tsx
+++ b/components/game/CraftingScreen.tsx
@@ -3,6 +3,7 @@
import { useEffect, useState, useCallback, useRef } from 'react'
import { GiAnvil, GiCheckedShield, GiSandsOfTime } from 'react-icons/gi'
import { useWallet, CELO_CHAIN_ID } from '@/lib/WalletProvider'
+import WrongNetworkBar from './WrongNetworkBar'
import { pickBestPaymentToken } from '@/lib/constants/tokens'
import {
ACCEPTED_TOKENS, getMarketplaceItem, resolveItemId, maxWeaponTier, tokenLabel, TOKEN_LOGOS,
@@ -492,6 +493,10 @@ export default function CraftingScreen({ onBack, onGoToRun, address }: CraftingS
+ {/* Connected but on the wrong chain — see WrongNetworkBar. Renders
+ nothing at all on Celo, and cannot appear inside MiniPay. */}
+
+
{/* shard balance strip */}
{(['t1', 't2', 't3'] as TierKey[]).map(k => (
diff --git a/components/game/MarketplaceScreen.tsx b/components/game/MarketplaceScreen.tsx
index 524f5e1..ab2282f 100644
--- a/components/game/MarketplaceScreen.tsx
+++ b/components/game/MarketplaceScreen.tsx
@@ -2,6 +2,7 @@
import { useEffect, useState, useCallback, useRef } from 'react'
import { useWallet, CELO_CHAIN_ID } from '@/lib/WalletProvider'
+import WrongNetworkBar from './WrongNetworkBar'
import { GiCrossedSwords, GiCheckedShield, GiMagnifyingGlass } from 'react-icons/gi'
import { pickBestPaymentToken, readStablecoinBalances } from '@/lib/constants/tokens'
import { MARKETPLACE_ITEMS, ACCEPTED_TOKENS, getMarketplaceItem, resolveItemId, tokenLabel, TOKEN_LOGOS, type MarketplaceItem, type MarketplaceTokenSymbol } from '@/lib/constants/marketplace'
@@ -364,6 +365,10 @@ export default function MarketplaceScreen({ onBack, address }: MarketplaceScreen
+ {/* Connected but on the wrong chain — see WrongNetworkBar. Renders
+ nothing at all on Celo, and cannot appear inside MiniPay. */}
+
+
{/* ARMORY TRIAL picker — shows once: Act 1 cleared + never granted.
Icon-first, two taps + one button, no paragraphs to read. */}
{act1Cleared && trialEligible && !isGuest && (
diff --git a/components/game/SeasonPassScreen.tsx b/components/game/SeasonPassScreen.tsx
index 3942304..84378ad 100644
--- a/components/game/SeasonPassScreen.tsx
+++ b/components/game/SeasonPassScreen.tsx
@@ -5,6 +5,7 @@ import { usePassSBT, SeasonInfo } from '@/hooks/usePassSBT'
import { getUserFriendlyError } from '@/lib/errorUtils'
import SeasonPassCard from './SeasonPassCard'
import { SEASON_IDS, currentSeasonId } from '@/lib/season'
+import WrongNetworkBar from './WrongNetworkBar'
// SEASON_IDS / currentSeasonId moved to lib/season.ts — the world map shows
// pass status too now, and two copies of "which season is it" would drift.
@@ -184,6 +185,8 @@ export default function SeasonPassScreen({ onBack, address }: SeasonPassScreenPr
{/* Not "connect your wallet": inside MiniPay the address arrives with
no interaction, so a connect prompt is only ever shown to someone it
cannot help. Naming MiniPay is the actionable step. */}
+
+
{!address && (
Open NullState in MiniPay to mint a pass.
diff --git a/components/game/WrongNetworkBar.tsx b/components/game/WrongNetworkBar.tsx
new file mode 100644
index 0000000..775e217
--- /dev/null
+++ b/components/game/WrongNetworkBar.tsx
@@ -0,0 +1,62 @@
+'use client'
+
+import { useCallback, useState } from 'react'
+import { useWallet } from '@/lib/WalletProvider'
+
+// ─── Connected, but not on Celo ──────────────────────────────────────────────
+//
+// OWNER, testing in the OKX in-app browser: *"posisi auto connect tapi gabisa
+// mint pass dan buy ada tulisan 'wallet not connected'."*
+//
+// His wallet was connected. It was on OKX's default chain, and wagmi's
+// getConnectorClient throws ConnectorChainMismatchError when the connector's
+// chain is not the one asked for — so useWalletClient({chainId: CELO}) resolved
+// to undefined and every payment reported a connection problem that did not
+// exist.
+//
+// WagmiIsland now asks the wallet to switch the moment it lands on the wrong
+// chain, which fixes it for anyone who taps Approve. This bar is for the rest:
+// someone who declined, or a wallet that ignores the request. It says the true
+// thing and gives them the one control that fixes it.
+//
+// Rendered only on the three screens that can actually spend. Not global,
+// because a player who never opens the shop does not need to be told which
+// chain the shop wants — and never inside MiniPay, which cannot reach this
+// state at all.
+//
+// The copy deliberately avoids the phrase check:copy bans. It also names Celo
+// rather than saying "wrong network", because "wrong" without a target is a
+// complaint rather than an instruction.
+
+export default function WrongNetworkBar() {
+ const { wrongNetwork, switchToCelo } = useWallet()
+ const [busy, setBusy] = useState(false)
+
+ const onSwitch = useCallback(async () => {
+ setBusy(true)
+ try {
+ await switchToCelo()
+ } finally {
+ // The wallet owns the prompt, so there is nothing to await past the
+ // request. Clearing shortly after keeps a declined prompt from leaving
+ // the button stuck.
+ setTimeout(() => setBusy(false), 1200)
+ }
+ }, [switchToCelo])
+
+ if (!wrongNetwork) return null
+
+ return (
+
+
Your wallet is on another network. NullState runs on Celo.
+
+
+ )
+}
diff --git a/hooks/usePassSBT.ts b/hooks/usePassSBT.ts
index 6300cd1..4d0da60 100644
--- a/hooks/usePassSBT.ts
+++ b/hooks/usePassSBT.ts
@@ -28,7 +28,7 @@ export function usePassSBT(walletAddress: string | undefined) {
// stablecoin, to TREASURY_WALLET (see lib/WalletProvider.tsx). Reused
// here for the v2.1 flexible-payment pass mint flow — see
// mintPaidPassFlexible below.
- const { payToTreasury, publicClient, walletClient } = useWallet()
+ const { payToTreasury, publicClient, walletClient, wrongNetwork } = useWallet()
const [hasPass, setHasPass] = useState(false)
const [passSeasonId, setPassSeasonId] = useState(BigInt(0))
@@ -145,6 +145,14 @@ export function usePassSBT(walletAddress: string | undefined) {
// charged varies based on the wallet's balances.
const mintPaidPassFlexible = useCallback(
async (seasonId: bigint): Promise<{ success: boolean; mintTxHash: `0x${string}` }> => {
+ // The message the owner actually hit in the OKX browser. walletClient is
+ // undefined on any chain but Celo — wagmi's getConnectorClient throws
+ // ConnectorChainMismatchError, useWalletClient swallows it into
+ // `data: undefined` — so "not connected" was reported to someone whose
+ // wallet was connected and one tap from working. Name the real problem.
+ if (wrongNetwork) {
+ throw new Error('Wrong network — NullState runs on Celo. Switch your wallet to Celo and try again.')
+ }
if (!walletClient || !publicClient || !walletClient.account) {
throw new Error('Wallet not connected')
}
@@ -198,7 +206,7 @@ export function usePassSBT(walletAddress: string | undefined) {
setPayingToken(null)
}
},
- [walletClient, publicClient, payToTreasury, fetchPassStatus],
+ [walletClient, publicClient, payToTreasury, fetchPassStatus, wrongNetwork],
)
useEffect(() => {
diff --git a/hooks/useReward.ts b/hooks/useReward.ts
index fd8304e..c2e0388 100644
--- a/hooks/useReward.ts
+++ b/hooks/useReward.ts
@@ -32,7 +32,7 @@ export function useReward(walletAddress: string | undefined) {
// WagmiProviderNotFoundError — which is exactly what it did: opening Rewards
// in MiniPay died with "a client-side exception has occurred". Both are null
// until wagmi has loaded, and every path below already guards on that.
- const { publicClient, walletClient } = useWallet()
+ const { publicClient, walletClient, wrongNetwork } = useWallet()
const [seasonLeaderboard, setSeasonLeaderboard] = useState(null)
const [hasClaimedSeasonBonus, setHasClaimedSeasonBonus] = useState(false)
@@ -98,6 +98,11 @@ export function useReward(walletAddress: string | undefined) {
// `account` is narrower on viem's generic WalletClient than on the one
// wagmi's useWalletClient() returned, so it is checked here rather than
// asserted — a client with no account cannot sign anyway.
+ // Same wrong-chain trap as the pass mint: on any chain but Celo the
+ // wallet client is undefined and this used to blame the connection.
+ if (wrongNetwork) {
+ throw new Error('Wrong network — NullState runs on Celo. Switch your wallet to Celo and try again.')
+ }
if (!walletClient?.account || !publicClient) throw new Error('Wallet not connected')
const account = walletClient.account
@@ -128,7 +133,7 @@ export function useReward(walletAddress: string | undefined) {
setIsLoading(false)
}
},
- [walletClient, publicClient, currentSeason, checkSeasonBonusClaimed],
+ [walletClient, publicClient, currentSeason, checkSeasonBonusClaimed, wrongNetwork],
)
useEffect(() => {
diff --git a/lib/WagmiIsland.tsx b/lib/WagmiIsland.tsx
index 2bbb0e3..b9090c0 100644
--- a/lib/WagmiIsland.tsx
+++ b/lib/WagmiIsland.tsx
@@ -22,7 +22,7 @@ import { celo, celoSepolia } from 'wagmi/chains'
import { celoTransport } from '@/lib/celoRpc'
import { injected } from 'wagmi/connectors'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
-import { useCallback, useEffect, useMemo, useState } from 'react'
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { encodeFunctionData } from 'viem'
import { USDM_ADDRESS, USDM_ABI } from './contract-abi'
import { getUserFriendlyError, WalletFriendlyError, MINIPAY_ADD_CASH_URL } from './errorUtils'
@@ -86,7 +86,7 @@ export default function WagmiIsland() {
export function WagmiWalletIsland() {
const { address, isConnected, chain } = useAccount()
const { disconnect } = useDisconnect()
- const { switchChain } = useSwitchChain()
+ const { switchChain, switchChainAsync } = useSwitchChain()
const publicClient = usePublicClient({ chainId: CELO_CHAIN_ID })
const { data: walletClient } = useWalletClient({ chainId: CELO_CHAIN_ID })
const { data: balanceData } = useBalance({ address, chainId: CELO_CHAIN_ID })
@@ -115,14 +115,60 @@ export function WagmiWalletIsland() {
if (injectedConnector) connect({ connector: injectedConnector, chainId: CELO_CHAIN_ID })
}, [isConnected, connect, connectors])
+ // ── AND ONTO CELO, IMMEDIATELY ───────────────────────────────────────────
+ //
+ // OWNER, testing in the OKX in-app browser: *"posisi auto connect tapi gabisa
+ // mint pass dan buy ada tulisan 'wallet not connected'."* The wallet was
+ // connected. It was on OKX's own default chain.
+ //
+ // That is fatal rather than cosmetic, because of one line inside wagmi
+ // (getConnectorClient.js):
+ //
+ // if (assertChainId && connectorChainId !== chainId)
+ // throw new ConnectorChainMismatchError(...)
+ //
+ // useWalletClient({chainId: CELO}) therefore resolves to undefined on any
+ // other chain, every payment hits its `!walletClient` guard, and the player
+ // is told their wallet is not connected. `switchToCelo` already existed for
+ // exactly this — plumbed through the bridge and never called from anywhere.
+ //
+ // Owner's decision, because MiniPay's reviewers may well test in a browser
+ // wallet: *"itu bener2 autoconnect dan autoswitch jaringan ke celo dr awal
+ // login dengan wallet."* So the switch is requested the moment a connection
+ // lands on the wrong chain, not deferred to the first purchase.
+ //
+ // Attempted ONCE per chain the wallet lands on. A player who declines is not
+ // asked again on every render — that is a popup loop, and it is worse than
+ // the bug. They get the banner and its button instead, and moving the wallet
+ // to another chain arms one fresh attempt.
+ const askedFor = useRef(null)
+ const onWrongChain = isConnected && !!chain && chain.id !== CELO_CHAIN_ID
+ useEffect(() => {
+ if (!onWrongChain || !chain) return
+ if (askedFor.current === chain.id) return
+ askedFor.current = chain.id
+ try { switchChain({ chainId: CELO_CHAIN_ID }) } catch { /* declined or unsupported */ }
+ }, [onWrongChain, chain, switchChain])
+
const celoBalance = balanceData
? (Number(balanceData.value) / 10 ** balanceData.decimals).toFixed(2)
: '0.00'
// ── Send transaction helper ───────────────────────────────────────────────
+ // "Wallet not connected" was a lie on every wrong-chain failure, and it sent
+ // the owner looking for a connection problem that did not exist. One helper,
+ // so all three payment paths tell the same truth.
+ const assertReady = useCallback(() => {
+ if (onWrongChain) {
+ throw new Error('Wrong network — NullState runs on Celo. Switch your wallet to Celo and try again.')
+ }
+ if (!walletClient || !address) throw new Error('Wallet not connected')
+ }, [onWrongChain, walletClient, address])
+
const sendTx = useCallback(
async (data: `0x${string}`, value?: bigint): Promise => {
+ assertReady()
if (!walletClient || !address) throw new Error('Wallet not connected')
setError(null)
setInsufficientFunds(false)
@@ -152,7 +198,7 @@ export function WagmiWalletIsland() {
throw new WalletFriendlyError(friendlyError, e)
}
},
- [walletClient, publicClient, address]
+ [walletClient, publicClient, address, assertReady]
)
// ── Contract write functions ──────────────────────────────────────────────
@@ -171,6 +217,7 @@ export function WagmiWalletIsland() {
// directly to `toAddress` (the reward contract), NOT through NullState.sol.
const payUsdmFee = useCallback(
async (amountWei: bigint, toAddress: `0x${string}`): Promise => {
+ assertReady()
if (!walletClient || !address) throw new Error('Wallet not connected')
if (typeof amountWei !== 'bigint' || amountWei <= BigInt(0)) {
throw new Error('Invalid USDm fee amount')
@@ -204,7 +251,7 @@ export function WagmiWalletIsland() {
throw new WalletFriendlyError(friendlyError, e)
}
},
- [walletClient, publicClient, address]
+ [walletClient, publicClient, address, assertReady]
)
// ── Generic treasury payment ─────────────────────────────────────────────
@@ -218,6 +265,7 @@ export function WagmiWalletIsland() {
// under both names below so each call site reads clearly.
const payToTreasury = useCallback(
async (priceUsd: number, token: MarketplaceTokenSymbol): Promise => {
+ assertReady()
if (!walletClient || !address) throw new Error('Wallet not connected')
const cfg = MARKETPLACE_TOKENS[token]
if (!cfg) throw new Error('Unsupported token')
@@ -249,7 +297,7 @@ export function WagmiWalletIsland() {
throw new WalletFriendlyError(friendlyError, e)
}
},
- [walletClient, address]
+ [walletClient, address, assertReady]
)
// NOTE (removed 2026-07-12): `attackRaid`, `readPlayer`, and `readRaid`
@@ -264,15 +312,27 @@ export function WagmiWalletIsland() {
if (target) connect({ connector: target, chainId: CELO_CHAIN_ID })
}, [connect, connectors])
+ // The async variant, because the button in WrongNetworkBar shows a pending
+ // state and the fire-and-forget `switchChain` resolves instantly whether the
+ // wallet agreed or not — a spinner that means nothing. A decline is not an
+ // app error: the bar simply stays up, which is already the honest report.
const switchToCelo = useCallback(async () => {
- try { switchChain({ chainId: CELO_CHAIN_ID }) } catch { /* user declined */ }
- }, [switchChain])
+ try {
+ await switchChainAsync({ chainId: CELO_CHAIN_ID })
+ setError(null)
+ } catch {
+ // Wallets that cannot switch (some in-app browsers pin their chain) end
+ // up here. Saying so beats a button that silently does nothing.
+ setError('Could not switch automatically — please choose Celo in your wallet app.')
+ }
+ }, [switchChainAsync])
const value: WalletBridgeValue = useMemo(() => ({
ready: true,
address: address ?? null,
isConnected,
chainId: chain?.id ?? null,
+ wrongNetwork: onWrongChain,
isMiniPay,
celoBalance,
error,
@@ -285,7 +345,7 @@ export function WagmiWalletIsland() {
switchToCelo,
payUsdmFee,
payToTreasury,
- }), [address, isConnected, chain?.id, isMiniPay, celoBalance, error, insufficientFunds,
+ }), [address, isConnected, chain?.id, isMiniPay, onWrongChain, celoBalance, error, insufficientFunds,
publicClient, walletClient, connectWallet, disconnect, switchToCelo, payUsdmFee, payToTreasury])
const publish = useWalletPublish()
diff --git a/lib/WalletProvider.tsx b/lib/WalletProvider.tsx
index 3ffd11c..f4792ab 100644
--- a/lib/WalletProvider.tsx
+++ b/lib/WalletProvider.tsx
@@ -146,6 +146,10 @@ export function useWallet() {
isConnecting: !w.ready,
walletReady: w.ready,
isMiniPay: w.isMiniPay,
+ // Connected, wrong chain. Screens that can spend need this because it is
+ // the difference between "you have no wallet" and "your wallet is one tap
+ // away from working" — and until now both reported the former.
+ wrongNetwork: w.wrongNetwork,
celoBalance: w.celoBalance,
error: w.error,
insufficientFunds: w.insufficientFunds,
diff --git a/lib/walletBridge.tsx b/lib/walletBridge.tsx
index 9db0e51..e2b2ad6 100644
--- a/lib/walletBridge.tsx
+++ b/lib/walletBridge.tsx
@@ -41,6 +41,19 @@ export interface WalletBridgeValue {
isConnected: boolean
chainId: number | null
isMiniPay: boolean
+ /**
+ * Connected, but the wallet is sitting on some other chain.
+ *
+ * NOT a cosmetic flag. wagmi's getConnectorClient throws
+ * ConnectorChainMismatchError when the connector's chain is not the one
+ * asked for, so useWalletClient({chainId: CELO}) resolves to undefined and
+ * every payment path fails its `!walletClient` guard — which used to report
+ * "Wallet not connected" to somebody whose wallet was perfectly connected.
+ * OKX and most browser wallets open on their own default chain, so this is
+ * the normal state for them until the switch lands. MiniPay is Celo-only and
+ * can never be in it.
+ */
+ wrongNetwork: boolean
celoBalance: string
error: string | null
insufficientFunds: boolean
@@ -72,6 +85,7 @@ export const WALLET_BRIDGE_DEFAULT: WalletBridgeValue = {
isConnected: false,
chainId: null,
isMiniPay: false,
+ wrongNetwork: false,
celoBalance: '0.00',
error: null,
insufficientFunds: false,
diff --git a/package.json b/package.json
index de72820..9ddd7e0 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,8 @@
"test:exit-streak": "node scripts/test-exit-streak.js",
"test:privy": "node scripts/test-privy-gate.js --serve",
"test:kills": "node scripts/test-leaderboard-writes.js",
- "test:proploot": "node scripts/test-prop-loot.js"
+ "test:proploot": "node scripts/test-prop-loot.js",
+ "test:network": "node scripts/test-wrong-network.js"
},
"overrides": {
"permissionless": {
diff --git a/scripts/test-wrong-network.js b/scripts/test-wrong-network.js
new file mode 100644
index 0000000..b5cecca
--- /dev/null
+++ b/scripts/test-wrong-network.js
@@ -0,0 +1,109 @@
+#!/usr/bin/env node
+/**
+ * test-wrong-network.js — "Wallet not connected" was a lie.
+ *
+ * OWNER, testing in the OKX in-app browser: *"posisi auto connect tapi gabisa
+ * mint pass dan buy ada tulisan 'wallet not connected', itu kenapa?"*
+ *
+ * His wallet was connected. It was on OKX's default chain, and this line lives
+ * inside wagmi (@wagmi/core/actions/getConnectorClient):
+ *
+ * if (assertChainId && connectorChainId !== chainId)
+ * throw new ConnectorChainMismatchError(...)
+ *
+ * So useWalletClient({chainId: CELO_CHAIN_ID}) resolves to undefined on ANY
+ * other chain, every payment path hit its `!walletClient` guard, and the player
+ * was told they had no wallet. MiniPay is Celo-only and never saw it, which is
+ * why it survived to the first OKX test.
+ *
+ * The nastiest part was already-written-and-never-called: `switchToCelo` had
+ * been plumbed through the bridge and had no caller anywhere in the app.
+ *
+ * These are source-level assertions rather than a browser run, deliberately.
+ * Reproducing this needs a wallet that connects on a non-Celo chain, and the
+ * sandbox has no wallet at all — so what can honestly be checked is that the
+ * guarantees exist, are wired, and cannot quietly come undone.
+ *
+ * node scripts/test-wrong-network.js
+ */
+const fs = require('fs'), path = require('path')
+
+let fails = 0
+const ok = (l, c, d) => { console.log((c ? ' ✓ ' : ' ✗ FAIL: ') + l + (d !== undefined ? ' (' + d + ')' : '')); if (!c) fails++ }
+const read = (p) => fs.readFileSync(path.join(__dirname, '..', p), 'utf8')
+
+const island = read('lib/WagmiIsland.tsx')
+const bridge = read('lib/walletBridge.tsx')
+const provider = read('lib/WalletProvider.tsx')
+const bar = read('components/game/WrongNetworkBar.tsx')
+
+// ── the upstream rule this whole file exists because of ─────────────────────
+// Asserted against wagmi's own source, so a future upgrade that changes the
+// behaviour shows up here rather than in a player's failed purchase.
+const wagmi = fs.readFileSync(
+ path.join(__dirname, '..', 'node_modules/@wagmi/core/dist/esm/actions/getConnectorClient.js'), 'utf8')
+ok('wagmi still throws when the connector is on another chain',
+ /assertChainId && connectorChainId !== chainId/.test(wagmi) &&
+ /ConnectorChainMismatchError/.test(wagmi))
+ok('and the app still asks for a Celo-pinned wallet client, which is what trips it',
+ /useWalletClient\(\{ chainId: CELO_CHAIN_ID \}\)/.test(island))
+
+// ── the fix: ask, once per chain ────────────────────────────────────────────
+ok('a connection on the wrong chain triggers a switch',
+ /const onWrongChain = isConnected && !!chain && chain\.id !== CELO_CHAIN_ID/.test(island) &&
+ /switchChain\(\{ chainId: CELO_CHAIN_ID \}\)/.test(island))
+// Asking on every render is a popup loop, which is worse than the bug it fixes.
+ok('and only ONCE per chain, never in a render loop',
+ /askedFor = useRef\(null\)/.test(island) &&
+ /if \(askedFor\.current === chain\.id\) return/.test(island) &&
+ /askedFor\.current = chain\.id/.test(island))
+
+// ── the message stops lying ─────────────────────────────────────────────────
+// sendTx, payUsdmFee and payToTreasury — every path in the island that signs.
+ok('all three payment paths in the island check the network first',
+ (island.match(/^\s+assertReady\(\)$/gm) || []).length === 3,
+ String((island.match(/^\s+assertReady\(\)$/gm) || []).length))
+ok('and the message names Celo instead of claiming there is no wallet',
+ /Wrong network — NullState runs on Celo/.test(island))
+
+// The two hooks that guard on walletClient THEMSELVES rather than going
+// through the island — the pass mint is the exact screen the owner reported,
+// and the reward claim has the identical shape.
+for (const [hook, file] of [['the Season Pass mint', 'hooks/usePassSBT.ts'],
+ ['the reward claim', 'hooks/useReward.ts']]) {
+ const src = read(file)
+ ok(`${hook} reports the network too, not a missing wallet`,
+ /if \(wrongNetwork\) \{/.test(src) && /Wrong network — NullState runs on Celo/.test(src))
+ ok(` …and re-runs when the chain changes`, /wrongNetwork\]/.test(src) || /wrongNetwork,/.test(src))
+}
+
+// ── the escape hatch is finally wired ───────────────────────────────────────
+// switchToCelo existed, was plumbed through the bridge, and had no caller.
+ok('switchToCelo is awaited, so the button can show a truthful pending state',
+ /await switchChainAsync\(\{ chainId: CELO_CHAIN_ID \}\)/.test(island))
+ok('and something actually calls it now',
+ /switchToCelo\(\)/.test(bar), 'WrongNetworkBar')
+
+// ── the flag reaches the screens that can spend ─────────────────────────────
+ok('wrongNetwork is on the bridge', /wrongNetwork: boolean/.test(bridge))
+ok('defaulted false, so nothing shows before wagmi mounts', /wrongNetwork: false,/.test(bridge))
+ok('and published through WalletProvider', /wrongNetwork: w\.wrongNetwork,/.test(provider))
+
+for (const screen of ['MarketplaceScreen', 'CraftingScreen', 'SeasonPassScreen']) {
+ const src = read(`components/game/${screen}.tsx`)
+ ok(`${screen} shows the bar`, //.test(src) &&
+ /import WrongNetworkBar from '\.\/WrongNetworkBar'/.test(src))
+}
+
+// ── and it cannot become a screen MiniPay would reject ──────────────────────
+// MiniPay is Celo-only, so wrongNetwork is unreachable there — but the copy
+// rule is checked anyway, because that is the check that would catch a future
+// edit adding the banned phrase to this file.
+ok('the bar never uses the phrase MiniPay bans',
+ !/connect\s+(a|an|the|your|my)?\s*wallet/i.test(bar))
+ok('and it renders nothing at all when the network is right',
+ /if \(!wrongNetwork\) return null/.test(bar))
+
+console.log(fails ? ` ${fails} GAGAL` : ' semua lolos')
+console.log(' NOTE: a real wrong-chain wallet is NOT simulated — the sandbox has no wallet.')
+process.exit(fails ? 1 : 0)