From ad3beda8b6d6f52a28f23c03099946ad6f7c68c1 Mon Sep 17 00:00:00 2001 From: "Eric (OpenClaw)" Date: Sun, 19 Apr 2026 04:15:10 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20resolve=20issue=20#80=20=E2=80=94=20Add?= =?UTF-8?q?=20support=20for=20multiple=20networks=20(Testnet/Mainnet=20swi?= =?UTF-8?q?tch)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/providers.tsx | 25 +++++++++++- src/components/Navbar.tsx | 23 ++++++++++- src/lib/sorosave.ts | 82 ++++++++++++++++++++++++++++++++------- 3 files changed, 114 insertions(+), 16 deletions(-) diff --git a/src/app/providers.tsx b/src/app/providers.tsx index 98f8bf5..7d4b7e8 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -2,6 +2,7 @@ import React, { createContext, useContext, useState, useCallback, useEffect } from "react"; import { connectWallet, getPublicKey, isFreighterInstalled } from "@/lib/wallet"; +import { getCurrentNetwork, switchNetwork, NETWORKS } from "@/lib/sorosave"; interface WalletContextType { address: string | null; @@ -9,6 +10,8 @@ interface WalletContextType { isFreighterAvailable: boolean; connect: () => Promise; disconnect: () => void; + currentNetwork: keyof typeof NETWORKS; + switchNetwork: (network: keyof typeof NETWORKS) => void; } const WalletContext = createContext({ @@ -17,6 +20,8 @@ const WalletContext = createContext({ isFreighterAvailable: false, connect: async () => {}, disconnect: () => {}, + currentNetwork: "TESTNET", + switchNetwork: () => {}, }); export function useWallet() { @@ -26,6 +31,7 @@ export function useWallet() { export function Providers({ children }: { children: React.ReactNode }) { const [address, setAddress] = useState(null); const [isFreighterAvailable, setIsFreighterAvailable] = useState(false); + const [currentNetwork, setCurrentNetwork] = useState("TESTNET"); useEffect(() => { isFreighterInstalled().then(setIsFreighterAvailable); @@ -33,6 +39,7 @@ export function Providers({ children }: { children: React.ReactNode }) { getPublicKey().then((key) => { if (key) setAddress(key); }); + setCurrentNetwork(getCurrentNetwork()); }, []); const connect = useCallback(async () => { @@ -44,6 +51,20 @@ export function Providers({ children }: { children: React.ReactNode }) { setAddress(null); }, []); + const handleSwitchNetwork = useCallback((network: keyof typeof NETWORKS) => { + if (network !== currentNetwork) { + if (window.confirm( + `Switching networks will clear your cached data. Are you sure you want to switch to ${network}?` + )) { + switchNetwork(network); + setCurrentNetwork(network); + // Clear localStorage for cached data + localStorage.removeItem("sorosave_cache"); + window.location.reload(); + } + } + }, [currentNetwork]); + return ( {children} ); -} +} \ No newline at end of file diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 2d673aa..c4ea4bb 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -2,8 +2,16 @@ import Link from "next/link"; import { ConnectWallet } from "./ConnectWallet"; +import { useWallet } from "@/app/providers"; export function Navbar() { + const { currentNetwork, switchNetwork } = useWallet(); + + const handleNetworkChange = (e: React.ChangeEvent) => { + const network = e.target.value as keyof typeof NETWORKS; + switchNetwork(network); + }; + return ( ); -} +} \ No newline at end of file diff --git a/src/lib/sorosave.ts b/src/lib/sorosave.ts index ca84abb..6c54174 100644 --- a/src/lib/sorosave.ts +++ b/src/lib/sorosave.ts @@ -1,15 +1,71 @@ 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 { TESTNET_RPC_URL, NETWORK_PASSPHRASE, CONTRACT_ID }; +const TESTNET_RPC_URL = "https://soroban-testnet.stellar.org"; +const MAINNET_RPC_URL = "https://soroban-mainnet.stellar.org"; +const TESTNET_NETWORK_PASSPHRASE = "Test SDF Network ; September 2015"; +const MAINNET_NETWORK_PASSPHRASE = "Public Global Stellar Network ; September 2015"; +const TESTNET_CONTRACT_ID = ""; +const MAINNET_CONTRACT_ID = ""; + +export const NETWORKS = { + TESTNET: { + rpcUrl: TESTNET_RPC_URL, + networkPassphrase: TESTNET_NETWORK_PASSPHRASE, + contractId: TESTNET_CONTRACT_ID, + }, + MAINNET: { + rpcUrl: MAINNET_RPC_URL, + networkPassphrase: MAINNET_NETWORK_PASSPHRASE, + contractId: MAINNET_CONTRACT_ID, + }, +}; + +const DEFAULT_NETWORK = "TESTNET"; + +const getNetworkFromLocalStorage = (): keyof typeof NETWORKS => { + if (typeof window !== "undefined") { + const saved = localStorage.getItem("sorosave_network"); + if (saved === "MAINNET" || saved === "TESTNET") { + return saved; + } + } + return DEFAULT_NETWORK; +}; + +const getNetworkConfig = () => { + const network = getNetworkFromLocalStorage(); + return NETWORKS[network]; +}; + +let currentNetwork: keyof typeof NETWORKS = getNetworkFromLocalStorage(); + +const updateNetwork = (network: keyof typeof NETWORKS) => { + currentNetwork = network; + if (typeof window !== "undefined") { + localStorage.setItem("sorosave_network", network); + } +}; + +export const getCurrentNetwork = () => currentNetwork; + +export const getSoroSaveClient = () => { + const config = getNetworkConfig(); + return new SoroSaveClient({ + contractId: config.contractId, + rpcUrl: config.rpcUrl, + networkPassphrase: config.networkPassphrase, + }); +}; + +export const switchNetwork = (network: keyof typeof NETWORKS) => { + updateNetwork(network); +}; + +export { + TESTNET_RPC_URL, + MAINNET_RPC_URL, + TESTNET_NETWORK_PASSPHRASE, + MAINNET_NETWORK_PASSPHRASE, + TESTNET_CONTRACT_ID, + MAINNET_CONTRACT_ID, +}; \ No newline at end of file