diff --git a/src/app/providers.tsx b/src/app/providers.tsx index 98f8bf5..d0d7edd 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -2,11 +2,13 @@ import React, { createContext, useContext, useState, useCallback, useEffect } from "react"; import { connectWallet, getPublicKey, isFreighterInstalled } from "@/lib/wallet"; +import { getCurrentNetwork, type NetworkType } from "@/lib/sorosave"; interface WalletContextType { address: string | null; isConnected: boolean; isFreighterAvailable: boolean; + network: NetworkType; connect: () => Promise; disconnect: () => void; } @@ -15,6 +17,7 @@ const WalletContext = createContext({ address: null, isConnected: false, isFreighterAvailable: false, + network: "testnet", connect: async () => {}, disconnect: () => {}, }); @@ -26,9 +29,12 @@ export function useWallet() { export function Providers({ children }: { children: React.ReactNode }) { const [address, setAddress] = useState(null); const [isFreighterAvailable, setIsFreighterAvailable] = useState(false); + const [network, setNetwork] = useState("testnet"); useEffect(() => { isFreighterInstalled().then(setIsFreighterAvailable); + setNetwork(getCurrentNetwork()); + // Try to reconnect on load getPublicKey().then((key) => { if (key) setAddress(key); @@ -50,6 +56,7 @@ export function Providers({ children }: { children: React.ReactNode }) { address, isConnected: !!address, isFreighterAvailable, + network, connect, disconnect, }} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 2d673aa..9612fd3 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -2,6 +2,7 @@ import Link from "next/link"; import { ConnectWallet } from "./ConnectWallet"; +import { NetworkSwitcher } from "./NetworkSwitcher"; export function Navbar() { return ( @@ -27,7 +28,10 @@ export function Navbar() { - +
+ + +
diff --git a/src/components/NetworkSwitcher.tsx b/src/components/NetworkSwitcher.tsx new file mode 100644 index 0000000..68e1167 --- /dev/null +++ b/src/components/NetworkSwitcher.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { getCurrentNetwork, switchNetwork, type NetworkType } from "@/lib/sorosave"; + +export function NetworkSwitcher() { + const [network, setNetwork] = useState("testnet"); + const [showConfirm, setShowConfirm] = useState(false); + const [pendingNetwork, setPendingNetwork] = useState(null); + + useEffect(() => { + setNetwork(getCurrentNetwork()); + }, []); + + const handleNetworkChange = (newNetwork: NetworkType) => { + if (newNetwork === network) return; + setPendingNetwork(newNetwork); + setShowConfirm(true); + }; + + const confirmSwitch = () => { + if (!pendingNetwork) return; + + // Clear cache data + if (typeof window !== "undefined") { + const keysToRemove = Object.keys(localStorage).filter( + (key) => key.startsWith("sorosave_") && key !== "sorosave_network" + ); + keysToRemove.forEach((key) => localStorage.removeItem(key)); + sessionStorage.clear(); + } + + switchNetwork(pendingNetwork); + setNetwork(pendingNetwork); + setShowConfirm(false); + setPendingNetwork(null); + + // Reload page to reinitialize with new network + window.location.reload(); + }; + + const cancelSwitch = () => { + setShowConfirm(false); + setPendingNetwork(null); + }; + + return ( + <> +
+ +
+ + + +
+
+ + {showConfirm && ( +
+
+

Switch Network?

+

+ Switching to {pendingNetwork} will clear all cached data and reload the page. + Are you sure you want to continue? +

+
+ + +
+
+
+ )} + + ); +} diff --git a/src/lib/sorosave.ts b/src/lib/sorosave.ts index ca84abb..fb323fb 100644 --- a/src/lib/sorosave.ts +++ b/src/lib/sorosave.ts @@ -1,15 +1,70 @@ import { SoroSaveClient } from "@sorosave/sdk"; -const TESTNET_RPC_URL = - process.env.NEXT_PUBLIC_RPC_URL || "https://soroban-testnet.stellar.org"; -const NETWORK_PASSPHRASE = - process.env.NEXT_PUBLIC_NETWORK_PASSPHRASE || "Test SDF Network ; September 2015"; -const CONTRACT_ID = process.env.NEXT_PUBLIC_CONTRACT_ID || ""; - -export const sorosaveClient = new SoroSaveClient({ - contractId: CONTRACT_ID, - rpcUrl: TESTNET_RPC_URL, - networkPassphrase: NETWORK_PASSPHRASE, +export type NetworkType = "testnet" | "mainnet"; + +export interface NetworkConfig { + rpcUrl: string; + networkPassphrase: string; + contractId: string; +} + +const NETWORK_CONFIGS: Record = { + testnet: { + rpcUrl: process.env.NEXT_PUBLIC_TESTNET_RPC_URL || "https://soroban-testnet.stellar.org", + networkPassphrase: "Test SDF Network ; September 2015", + contractId: process.env.NEXT_PUBLIC_TESTNET_CONTRACT_ID || "", + }, + mainnet: { + rpcUrl: process.env.NEXT_PUBLIC_MAINNET_RPC_URL || "https://soroban-mainnet.stellar.org", + networkPassphrase: "Public Global Stellar Network ; September 2015", + contractId: process.env.NEXT_PUBLIC_MAINNET_CONTRACT_ID || "", + }, +}; + +let currentClient: SoroSaveClient; +let currentNetwork: NetworkType = "testnet"; + +// Initialize with testnet by default +if (typeof window !== "undefined") { + const savedNetwork = localStorage.getItem("sorosave_network") as NetworkType | null; + currentNetwork = savedNetwork || "testnet"; +} + +const config = NETWORK_CONFIGS[currentNetwork]; +currentClient = new SoroSaveClient({ + contractId: config.contractId, + rpcUrl: config.rpcUrl, + networkPassphrase: config.networkPassphrase, }); -export { TESTNET_RPC_URL, NETWORK_PASSPHRASE, CONTRACT_ID }; +export function getCurrentNetwork(): NetworkType { + return currentNetwork; +} + +export function switchNetwork(network: NetworkType): void { + currentNetwork = network; + if (typeof window !== "undefined") { + localStorage.setItem("sorosave_network", network); + } + + const config = NETWORK_CONFIGS[network]; + currentClient = new SoroSaveClient({ + contractId: config.contractId, + rpcUrl: config.rpcUrl, + networkPassphrase: config.networkPassphrase, + }); +} + +export function getSoroSaveClient(): SoroSaveClient { + return currentClient; +} + +export function getNetworkConfig(network: NetworkType): NetworkConfig { + return NETWORK_CONFIGS[network]; +} + +// Legacy exports for backward compatibility +export const sorosaveClient = currentClient; +export const TESTNET_RPC_URL = NETWORK_CONFIGS.testnet.rpcUrl; +export const NETWORK_PASSPHRASE = NETWORK_CONFIGS.testnet.networkPassphrase; +export const CONTRACT_ID = NETWORK_CONFIGS.testnet.contractId;