@idosgames/mcp 0.1.0 → 0.1.1

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.
@@ -6,11 +6,41 @@
6
6
  "engine": "phaser",
7
7
  "genre": "idle-rpg"
8
8
  },
9
+ "catalog": {
10
+ "type": "template",
11
+ "summary": "Idle RPG: heroes auto-battle and earn resources over time, with upgrades and offline progress.",
12
+ "description": "An incremental idle RPG built on Phaser. Heroes fight automatically, generate currency and loot over time, and keep progressing while the player is away. Includes an upgrade loop and offline-income accrual, so it fits any 'tap to grow / auto-battler / idle economy' request.",
13
+ "provides": [
14
+ "auto-battling heroes",
15
+ "idle resource generation over time",
16
+ "offline income accrual",
17
+ "upgrade / progression loop",
18
+ "2D sprite rendering (Phaser)"
19
+ ],
20
+ "tags": [
21
+ "idle",
22
+ "rpg",
23
+ "incremental",
24
+ "auto-battler",
25
+ "phaser",
26
+ "2d"
27
+ ],
28
+ "media": {
29
+ "image": "https://cloud.idosgames.com/drive/modules/idle-rpg/cover.png",
30
+ "video": "https://cloud.idosgames.com/drive/modules/idle-rpg/demo.mp4"
31
+ },
32
+ "demoUrl": "https://cloud.idosgames.com/drive/modules/idle-rpg/demo/",
33
+ "author": {
34
+ "name": "iDos Games",
35
+ "url": "https://idosgames.com"
36
+ },
37
+ "version": "0.1.0"
38
+ },
9
39
  "dependencies": {
10
- "@idosgames/core": "0.1.1",
11
- "@idosgames/module-sdk": "0.1.0",
40
+ "@idosgames/core": "0.1.3",
41
+ "@idosgames/module-sdk": "0.1.1",
12
42
  "@idosgames/react": "0.1.0",
13
- "@idosgames/wallet": "0.1.1",
43
+ "@idosgames/wallet": "0.1.2",
14
44
  "@solana/wallet-adapter-base": "0.9.27",
15
45
  "@solana/wallet-adapter-react": "0.15.39",
16
46
  "@tanstack/react-query": "5.101.2",
@@ -43,7 +73,7 @@
43
73
  },
44
74
  {
45
75
  "path": "components/WalletPanel.tsx",
46
- "content": "import {\n useEffect,\n useMemo,\n useReducer,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport { parseUnits } from \"viem\";\nimport {\n arbitrum,\n base,\n bsc,\n mainnet,\n optimism,\n polygon,\n polygonAmoy,\n sepolia,\n} from \"viem/chains\";\nimport { useAccount, useConnect, useDisconnect, useSwitchChain } from \"wagmi\";\nimport {\n createEvmWalletConfig,\n IDosGamesWalletProvider,\n useEvmBridge,\n} from \"@idosgames/wallet/react\";\nimport type { BridgeResult } from \"@idosgames/wallet\";\nimport type {\n BlockchainNetworkDefinition,\n CryptoCurrencyDefinition,\n} from \"@idosgames/core\";\nimport { useIDosGamesClient } from \"@idosgames/react\";\nimport { TITLE_ID } from \"../env\";\nimport { ENV_WALLETCONNECT_PROJECT_ID } from \"../env\";\n\n// A curated EVM chain set for the demo — enough to cover the networks a title is likely to use.\n// wagmi needs at least one chain up front; the actual network you deposit to comes from the\n// title's blockchain config (the picker below), and we switch the wallet's chain to match.\nconst SUPPORTED_CHAINS = [\n mainnet,\n polygon,\n bsc,\n arbitrum,\n base,\n optimism,\n sepolia,\n polygonAmoy,\n] as const;\n\n// Built once. Set VITE_WALLETCONNECT_PROJECT_ID (get one at cloud.walletconnect.com) to enable\n// MOBILE wallets via the WalletConnect QR/deep-link modal; without it, browser extensions still work.\nconst wagmiConfig = createEvmWalletConfig({\n chains: SUPPORTED_CHAINS,\n walletConnectProjectId: ENV_WALLETCONNECT_PROJECT_ID || undefined,\n appName: \"iDosGames Idle RPG\",\n});\n\nconst card: CSSProperties = {\n background: \"#181334\",\n border: \"1px solid #2c2650\",\n borderRadius: 12,\n padding: 16,\n color: \"#fff\",\n width: \"100%\",\n boxSizing: \"border-box\",\n fontFamily: \"system-ui, sans-serif\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: 10,\n};\nconst title: CSSProperties = { fontWeight: 700, fontSize: 16 };\nconst label: CSSProperties = { fontSize: 12, opacity: 0.7 };\nconst input: CSSProperties = {\n background: \"#241d40\",\n border: \"1px solid #34294f\",\n borderRadius: 8,\n color: \"#fff\",\n padding: \"8px 10px\",\n width: \"100%\",\n boxSizing: \"border-box\",\n};\nconst primary: CSSProperties = {\n background: \"#6c5ce7\",\n color: \"#fff\",\n border: \"none\",\n borderRadius: 8,\n padding: \"9px 14px\",\n fontWeight: 700,\n cursor: \"pointer\",\n};\nconst ghost: CSSProperties = {\n background: \"#34294f\",\n color: \"#fff\",\n border: \"none\",\n borderRadius: 8,\n padding: \"7px 12px\",\n fontWeight: 600,\n cursor: \"pointer\",\n};\nconst row: CSSProperties = { display: \"flex\", gap: 8 };\n\n/** Public entry: the wallet screen wrapped in its own wagmi/react-query provider. */\nexport function WalletPanel(): ReactNode {\n return (\n <IDosGamesWalletProvider wagmiConfig={wagmiConfig}>\n <WalletPanelInner />\n </IDosGamesWalletProvider>\n );\n}\n\nfunction WalletPanelInner(): ReactNode {\n const client = useIDosGamesClient();\n const bridge = useEvmBridge(client, TITLE_ID);\n const { address, isConnected, chainId } = useAccount();\n const { connect, connectors } = useConnect();\n const { disconnect } = useDisconnect();\n const { switchChainAsync } = useSwitchChain();\n const [, force] = useReducer((n: number) => n + 1, 0);\n\n const [networks, setNetworks] = useState<\n Record<string, BlockchainNetworkDefinition>\n >({});\n const [currencies, setCurrencies] = useState<\n Record<string, CryptoCurrencyDefinition>\n >({});\n const [networkID, setNetworkID] = useState<string>(\"\");\n const [currencyID, setCurrencyID] = useState<string>(\"\");\n const [amount, setAmount] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [result, setResult] = useState<BridgeResult<unknown> | null>(null);\n\n // Load blockchain config + on-chain state once; re-render on cache changes for the balance.\n useEffect(() => {\n let active = true;\n void (async () => {\n const defs = await client.blockchain.getDefinitions();\n await client.blockchain.getUserState();\n if (!active || !defs.ok) return;\n const nets = defs.data.Blockchain?.Networks ?? {};\n const evmNets: Record<string, BlockchainNetworkDefinition> = {};\n for (const [id, net] of Object.entries(nets))\n if (net.Type === \"EVM\") evmNets[id] = net;\n setNetworks(evmNets);\n setCurrencies(defs.data.CryptoCurrencies ?? {});\n const firstNet = Object.keys(evmNets)[0] ?? \"\";\n setNetworkID(firstNet);\n })();\n return () => {\n active = false;\n };\n }, [client]);\n\n useEffect(() => client.on(\"user:anyUpdated\", force), [client]);\n\n // Currencies that have an ERC-20 binding on the selected network (a contract we can deposit).\n const eligibleCurrencies = useMemo(() => {\n return Object.entries(currencies).filter(([, def]) =>\n def.Networks?.some(\n (b) => b.NetworkID === networkID && !!b.ContractAddress,\n ),\n );\n }, [currencies, networkID]);\n\n useEffect(() => {\n const first = eligibleCurrencies[0]?.[0] ?? \"\";\n setCurrencyID((prev) =>\n eligibleCurrencies.some(([id]) => id === prev) ? prev : first,\n );\n }, [eligibleCurrencies]);\n\n const network = networkID ? networks[networkID] : undefined;\n const currency = currencyID ? currencies[currencyID] : undefined;\n const binding = currency?.Networks?.find((b) => b.NetworkID === networkID);\n const balance = currencyID\n ? client.data.user.getCryptoCurrencyAmount(currencyID)\n : \"0\";\n\n async function ensureChain(): Promise<boolean> {\n if (!network?.ChainID || chainId === network.ChainID) return true;\n try {\n await switchChainAsync({ chainId: network.ChainID });\n return true;\n } catch {\n setResult({\n ok: false,\n stage: \"approve\",\n error: `Switch your wallet to chain ${network.ChainID} to continue.`,\n });\n return false;\n }\n }\n\n async function runDeposit(): Promise<void> {\n if (!network || !binding?.ContractAddress) return;\n setBusy(true);\n setResult(null);\n if (await ensureChain()) {\n const decimals = binding.Decimals ?? 18;\n let raw: bigint;\n try {\n raw = parseUnits(amount || \"0\", decimals);\n } catch {\n setResult({ ok: false, stage: \"approve\", error: \"Invalid amount.\" });\n setBusy(false);\n return;\n }\n const res = await bridge.depositToken({\n network,\n tokenAddress: binding.ContractAddress as `0x${string}`,\n amount: raw,\n });\n setResult(res);\n if (res.ok) await client.blockchain.getUserState();\n }\n setBusy(false);\n }\n\n async function runWithdraw(): Promise<void> {\n if (!network || !bridge.account) return;\n setBusy(true);\n setResult(null);\n if (await ensureChain()) {\n const res = await bridge.withdrawToken({\n currencyID,\n networkID,\n walletAddress: bridge.account,\n amount: amount || \"0\",\n });\n setResult(res);\n if (res.ok) await client.blockchain.getUserState();\n }\n setBusy(false);\n }\n\n return (\n <div style={card}>\n <span style={title}>Crypto wallet</span>\n\n {/* Connect */}\n {isConnected ? (\n <div style={row}>\n <span style={{ ...label, flex: 1, alignSelf: \"center\" }}>\n {address?.slice(0, 6)}…{address?.slice(-4)}\n </span>\n <button type=\"button\" style={ghost} onClick={() => disconnect()}>\n Disconnect\n </button>\n </div>\n ) : (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 6 }}>\n <span style={label}>Connect a browser or mobile wallet</span>\n {connectors.map((c) => (\n <button\n key={c.uid}\n type=\"button\"\n style={ghost}\n onClick={() => connect({ connector: c })}\n >\n {c.name}\n </button>\n ))}\n </div>\n )}\n\n {Object.keys(networks).length === 0 ? (\n <span style={label}>No EVM networks configured for this title.</span>\n ) : (\n <>\n <div>\n <div style={label}>Network</div>\n <select\n style={input}\n value={networkID}\n onChange={(e) => setNetworkID(e.target.value)}\n >\n {Object.entries(networks).map(([id, net]) => (\n <option key={id} value={id}>\n {net.DisplayName ?? id}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <div style={label}>Token</div>\n <select\n style={input}\n value={currencyID}\n onChange={(e) => setCurrencyID(e.target.value)}\n >\n {eligibleCurrencies.length === 0 && (\n <option value=\"\">— no depositable tokens —</option>\n )}\n {eligibleCurrencies.map(([id, def]) => (\n <option key={id} value={id}>\n {def.DisplayName ?? id}\n </option>\n ))}\n </select>\n </div>\n\n <div style={label}>\n In-game balance: <b>{balance}</b> {currencyID}\n </div>\n\n <div>\n <div style={label}>Amount</div>\n <input\n style={input}\n inputMode=\"decimal\"\n placeholder=\"0.0\"\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n />\n </div>\n\n <div style={row}>\n <button\n type=\"button\"\n style={{ ...primary, flex: 1, opacity: busy ? 0.6 : 1 }}\n disabled={busy || !isConnected || !binding?.ContractAddress}\n onClick={() => void runDeposit()}\n >\n Deposit\n </button>\n <button\n type=\"button\"\n style={{ ...ghost, flex: 1, opacity: busy ? 0.6 : 1 }}\n disabled={busy || !isConnected || !currencyID}\n onClick={() => void runWithdraw()}\n >\n Withdraw\n </button>\n </div>\n </>\n )}\n\n {result && <ResultLine result={result} />}\n </div>\n );\n}\n\nfunction ResultLine({ result }: { result: BridgeResult<unknown> }): ReactNode {\n const style: CSSProperties = {\n fontSize: 12,\n borderRadius: 8,\n padding: \"8px 10px\",\n background: result.ok ? \"#1e3a2a\" : \"#3a1e28\",\n color: result.ok ? \"#8ef0b0\" : \"#f2a0b4\",\n wordBreak: \"break-all\",\n };\n if (result.ok)\n return <div style={style}>✓ Done · tx {result.onChainTxHash}</div>;\n return (\n <div style={style}>\n ✕ [{result.stage}] {result.error}\n {result.titleTransactionID\n ? ` · already debited (tx ${result.titleTransactionID}) — retry/confirm, don't re-request`\n : \"\"}\n </div>\n );\n}\n"
76
+ "content": "import {\n useEffect,\n useMemo,\n useReducer,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport { parseUnits } from \"viem\";\nimport {\n arbitrum,\n base,\n bsc,\n mainnet,\n optimism,\n polygon,\n polygonAmoy,\n sepolia,\n} from \"viem/chains\";\nimport { useAccount, useConnect, useDisconnect, useSwitchChain } from \"wagmi\";\nimport {\n createEvmWalletConfig,\n IDosGamesWalletProvider,\n useEvmBridge,\n} from \"@idosgames/wallet/react\";\nimport type { BridgeResult } from \"@idosgames/wallet\";\nimport type {\n BlockchainNetworkDefinition,\n CryptoCurrencyDefinition,\n} from \"@idosgames/core\";\nimport { useIDosGamesClient } from \"@idosgames/react\";\nimport { ENV_WALLETCONNECT_PROJECT_ID } from \"../env\";\n\n// A curated EVM chain set for the demo — enough to cover the networks a title is likely to use.\n// wagmi needs at least one chain up front; the actual network you deposit to comes from the\n// title's blockchain config (the picker below), and we switch the wallet's chain to match.\nconst SUPPORTED_CHAINS = [\n mainnet,\n polygon,\n bsc,\n arbitrum,\n base,\n optimism,\n sepolia,\n polygonAmoy,\n] as const;\n\n// Built once. Set VITE_WALLETCONNECT_PROJECT_ID (get one at cloud.walletconnect.com) to enable\n// MOBILE wallets via the WalletConnect QR/deep-link modal; without it, browser extensions still work.\nconst wagmiConfig = createEvmWalletConfig({\n chains: SUPPORTED_CHAINS,\n walletConnectProjectId: ENV_WALLETCONNECT_PROJECT_ID || undefined,\n appName: \"iDosGames Idle RPG\",\n});\n\nconst card: CSSProperties = {\n background: \"#181334\",\n border: \"1px solid #2c2650\",\n borderRadius: 12,\n padding: 16,\n color: \"#fff\",\n width: \"100%\",\n boxSizing: \"border-box\",\n fontFamily: \"system-ui, sans-serif\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: 10,\n};\nconst title: CSSProperties = { fontWeight: 700, fontSize: 16 };\nconst label: CSSProperties = { fontSize: 12, opacity: 0.7 };\nconst input: CSSProperties = {\n background: \"#241d40\",\n border: \"1px solid #34294f\",\n borderRadius: 8,\n color: \"#fff\",\n padding: \"8px 10px\",\n width: \"100%\",\n boxSizing: \"border-box\",\n};\nconst primary: CSSProperties = {\n background: \"#6c5ce7\",\n color: \"#fff\",\n border: \"none\",\n borderRadius: 8,\n padding: \"9px 14px\",\n fontWeight: 700,\n cursor: \"pointer\",\n};\nconst ghost: CSSProperties = {\n background: \"#34294f\",\n color: \"#fff\",\n border: \"none\",\n borderRadius: 8,\n padding: \"7px 12px\",\n fontWeight: 600,\n cursor: \"pointer\",\n};\nconst row: CSSProperties = { display: \"flex\", gap: 8 };\n\n/** Public entry: the wallet screen wrapped in its own wagmi/react-query provider. */\nexport function WalletPanel(): ReactNode {\n return (\n <IDosGamesWalletProvider wagmiConfig={wagmiConfig}>\n <WalletPanelInner />\n </IDosGamesWalletProvider>\n );\n}\n\nfunction WalletPanelInner(): ReactNode {\n const client = useIDosGamesClient();\n // The title comes from the host's client, never re-derived here: a module that resolved its own\n // title could disagree with the host and bridge deposits into a different title's wallet.\n const bridge = useEvmBridge(client, client.titleID);\n const { address, isConnected, chainId } = useAccount();\n const { connect, connectors } = useConnect();\n const { disconnect } = useDisconnect();\n const { switchChainAsync } = useSwitchChain();\n const [, force] = useReducer((n: number) => n + 1, 0);\n\n const [networks, setNetworks] = useState<\n Record<string, BlockchainNetworkDefinition>\n >({});\n const [currencies, setCurrencies] = useState<\n Record<string, CryptoCurrencyDefinition>\n >({});\n const [networkID, setNetworkID] = useState<string>(\"\");\n const [currencyID, setCurrencyID] = useState<string>(\"\");\n const [amount, setAmount] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [result, setResult] = useState<BridgeResult<unknown> | null>(null);\n\n // Load blockchain config + on-chain state once; re-render on cache changes for the balance.\n useEffect(() => {\n let active = true;\n void (async () => {\n const defs = await client.blockchain.getDefinitions();\n await client.blockchain.getUserState();\n if (!active || !defs.ok) return;\n const nets = defs.data.Blockchain?.Networks ?? {};\n const evmNets: Record<string, BlockchainNetworkDefinition> = {};\n for (const [id, net] of Object.entries(nets))\n if (net.Type === \"EVM\") evmNets[id] = net;\n setNetworks(evmNets);\n setCurrencies(defs.data.CryptoCurrencies ?? {});\n const firstNet = Object.keys(evmNets)[0] ?? \"\";\n setNetworkID(firstNet);\n })();\n return () => {\n active = false;\n };\n }, [client]);\n\n useEffect(() => client.on(\"user:anyUpdated\", force), [client]);\n\n // Currencies that have an ERC-20 binding on the selected network (a contract we can deposit).\n const eligibleCurrencies = useMemo(() => {\n return Object.entries(currencies).filter(([, def]) =>\n def.Networks?.some(\n (b) => b.NetworkID === networkID && !!b.ContractAddress,\n ),\n );\n }, [currencies, networkID]);\n\n useEffect(() => {\n const first = eligibleCurrencies[0]?.[0] ?? \"\";\n setCurrencyID((prev) =>\n eligibleCurrencies.some(([id]) => id === prev) ? prev : first,\n );\n }, [eligibleCurrencies]);\n\n const network = networkID ? networks[networkID] : undefined;\n const currency = currencyID ? currencies[currencyID] : undefined;\n const binding = currency?.Networks?.find((b) => b.NetworkID === networkID);\n const balance = currencyID\n ? client.data.user.getCryptoCurrencyAmount(currencyID)\n : \"0\";\n\n async function ensureChain(): Promise<boolean> {\n if (!network?.ChainID || chainId === network.ChainID) return true;\n try {\n await switchChainAsync({ chainId: network.ChainID });\n return true;\n } catch {\n setResult({\n ok: false,\n stage: \"approve\",\n error: `Switch your wallet to chain ${network.ChainID} to continue.`,\n });\n return false;\n }\n }\n\n async function runDeposit(): Promise<void> {\n if (!network || !binding?.ContractAddress) return;\n setBusy(true);\n setResult(null);\n if (await ensureChain()) {\n const decimals = binding.Decimals ?? 18;\n let raw: bigint;\n try {\n raw = parseUnits(amount || \"0\", decimals);\n } catch {\n setResult({ ok: false, stage: \"approve\", error: \"Invalid amount.\" });\n setBusy(false);\n return;\n }\n const res = await bridge.depositToken({\n network,\n tokenAddress: binding.ContractAddress as `0x${string}`,\n amount: raw,\n });\n setResult(res);\n if (res.ok) await client.blockchain.getUserState();\n }\n setBusy(false);\n }\n\n async function runWithdraw(): Promise<void> {\n if (!network || !bridge.account) return;\n setBusy(true);\n setResult(null);\n if (await ensureChain()) {\n const res = await bridge.withdrawToken({\n currencyID,\n networkID,\n walletAddress: bridge.account,\n amount: amount || \"0\",\n });\n setResult(res);\n if (res.ok) await client.blockchain.getUserState();\n }\n setBusy(false);\n }\n\n return (\n <div style={card}>\n <span style={title}>Crypto wallet</span>\n\n {/* Connect */}\n {isConnected ? (\n <div style={row}>\n <span style={{ ...label, flex: 1, alignSelf: \"center\" }}>\n {address?.slice(0, 6)}…{address?.slice(-4)}\n </span>\n <button type=\"button\" style={ghost} onClick={() => disconnect()}>\n Disconnect\n </button>\n </div>\n ) : (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 6 }}>\n <span style={label}>Connect a browser or mobile wallet</span>\n {connectors.map((c) => (\n <button\n key={c.uid}\n type=\"button\"\n style={ghost}\n onClick={() => connect({ connector: c })}\n >\n {c.name}\n </button>\n ))}\n </div>\n )}\n\n {Object.keys(networks).length === 0 ? (\n <span style={label}>No EVM networks configured for this title.</span>\n ) : (\n <>\n <div>\n <div style={label}>Network</div>\n <select\n style={input}\n value={networkID}\n onChange={(e) => setNetworkID(e.target.value)}\n >\n {Object.entries(networks).map(([id, net]) => (\n <option key={id} value={id}>\n {net.DisplayName ?? id}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <div style={label}>Token</div>\n <select\n style={input}\n value={currencyID}\n onChange={(e) => setCurrencyID(e.target.value)}\n >\n {eligibleCurrencies.length === 0 && (\n <option value=\"\">— no depositable tokens —</option>\n )}\n {eligibleCurrencies.map(([id, def]) => (\n <option key={id} value={id}>\n {def.DisplayName ?? id}\n </option>\n ))}\n </select>\n </div>\n\n <div style={label}>\n In-game balance: <b>{balance}</b> {currencyID}\n </div>\n\n <div>\n <div style={label}>Amount</div>\n <input\n style={input}\n inputMode=\"decimal\"\n placeholder=\"0.0\"\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n />\n </div>\n\n <div style={row}>\n <button\n type=\"button\"\n style={{ ...primary, flex: 1, opacity: busy ? 0.6 : 1 }}\n disabled={busy || !isConnected || !binding?.ContractAddress}\n onClick={() => void runDeposit()}\n >\n Deposit\n </button>\n <button\n type=\"button\"\n style={{ ...ghost, flex: 1, opacity: busy ? 0.6 : 1 }}\n disabled={busy || !isConnected || !currencyID}\n onClick={() => void runWithdraw()}\n >\n Withdraw\n </button>\n </div>\n </>\n )}\n\n {result && <ResultLine result={result} />}\n </div>\n );\n}\n\nfunction ResultLine({ result }: { result: BridgeResult<unknown> }): ReactNode {\n const style: CSSProperties = {\n fontSize: 12,\n borderRadius: 8,\n padding: \"8px 10px\",\n background: result.ok ? \"#1e3a2a\" : \"#3a1e28\",\n color: result.ok ? \"#8ef0b0\" : \"#f2a0b4\",\n wordBreak: \"break-all\",\n };\n if (result.ok)\n return <div style={style}>✓ Done · tx {result.onChainTxHash}</div>;\n return (\n <div style={style}>\n ✕ [{result.stage}] {result.error}\n {result.titleTransactionID\n ? ` · already debited (tx ${result.titleTransactionID}) — retry/confirm, don't re-request`\n : \"\"}\n </div>\n );\n}\n"
47
77
  },
48
78
  {
49
79
  "path": "controller-box.ts",
@@ -55,7 +85,7 @@
55
85
  },
56
86
  {
57
87
  "path": "env.ts",
58
- "content": "// Runtime config the module reads from the page it runs in — the same source the host used: URL\n// params and the __IDOS_ENV__ global the host's bundler defines (see the host template's\n// vite.config). A module copied into a project runs in that same page, so these are available;\n// when absent (e.g. the classic preview bundler), safe defaults apply.\n//\n// titleID lives here because the wallet bridge needs it explicitly and the SDK client doesn't\n// expose it publicly. Resolving at runtime (not build time) lets one build serve staged + prod.\n\ntype IdosEnv = {\n TITLE_ID?: string;\n WALLETCONNECT_PROJECT_ID?: string;\n};\n\ndeclare const __IDOS_ENV__: IdosEnv | undefined;\n\nconst env: IdosEnv =\n typeof __IDOS_ENV__ !== \"undefined\" && __IDOS_ENV__ ? __IDOS_ENV__ : {};\n\nconst launchParams =\n typeof window === \"undefined\"\n ? null\n : new URLSearchParams(window.location.search);\n\nexport const TITLE_ID =\n launchParams?.get(\"titleID\") ?? (env.TITLE_ID || \"URLV9SUP\");\nexport const ENV_WALLETCONNECT_PROJECT_ID = env.WALLETCONNECT_PROJECT_ID ?? \"\";\n"
88
+ "content": "// Runtime config the module reads from the page it runs in: the __IDOS_ENV__ global the host's\n// bundler defines (see the host template's vite.config). A module copied into a project runs in\n// that same page, so it is available; when absent (e.g. the classic preview bundler), the default\n// applies.\n//\n// The TITLE is deliberately NOT here. It comes from the host `client.titleID`, or `ctx.titleId`\n// in a module's setup(). A module that re-derived the title from the URL could disagree with its\n// host, which is how a build ends up reading one title's data and writing another's.\n\ntype IdosEnv = {\n WALLETCONNECT_PROJECT_ID?: string;\n};\n\ndeclare const __IDOS_ENV__: IdosEnv | undefined;\n\nconst env: IdosEnv =\n typeof __IDOS_ENV__ !== \"undefined\" && __IDOS_ENV__ ? __IDOS_ENV__ : {};\n\nexport const ENV_WALLETCONNECT_PROJECT_ID = env.WALLETCONNECT_PROJECT_ID ?? \"\";\n"
59
89
  },
60
90
  {
61
91
  "path": "game/IdleRpgController.ts",