Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion src/app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

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;
isConnected: boolean;
isFreighterAvailable: boolean;
connect: () => Promise<void>;
disconnect: () => void;
currentNetwork: keyof typeof NETWORKS;
switchNetwork: (network: keyof typeof NETWORKS) => void;
}

const WalletContext = createContext<WalletContextType>({
Expand All @@ -17,6 +20,8 @@ const WalletContext = createContext<WalletContextType>({
isFreighterAvailable: false,
connect: async () => {},
disconnect: () => {},
currentNetwork: "TESTNET",
switchNetwork: () => {},
});

export function useWallet() {
Expand All @@ -26,13 +31,15 @@ export function useWallet() {
export function Providers({ children }: { children: React.ReactNode }) {
const [address, setAddress] = useState<string | null>(null);
const [isFreighterAvailable, setIsFreighterAvailable] = useState(false);
const [currentNetwork, setCurrentNetwork] = useState<keyof typeof NETWORKS>("TESTNET");

useEffect(() => {
isFreighterInstalled().then(setIsFreighterAvailable);
// Try to reconnect on load
getPublicKey().then((key) => {
if (key) setAddress(key);
});
setCurrentNetwork(getCurrentNetwork());
}, []);

const connect = useCallback(async () => {
Expand All @@ -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 (
<WalletContext.Provider
value={{
Expand All @@ -52,9 +73,11 @@ export function Providers({ children }: { children: React.ReactNode }) {
isFreighterAvailable,
connect,
disconnect,
currentNetwork,
switchNetwork: handleSwitchNetwork,
}}
>
{children}
</WalletContext.Provider>
);
}
}
23 changes: 21 additions & 2 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLSelectElement>) => {
const network = e.target.value as keyof typeof NETWORKS;
switchNetwork(network);
};

return (
<nav className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
Expand All @@ -27,9 +35,20 @@ export function Navbar() {
</Link>
</div>
</div>
<ConnectWallet />
<div className="flex items-center space-x-4">
<select
value={currentNetwork}
onChange={handleNetworkChange}
className="border rounded px-2 py-1 text-sm"
aria-label="Network selector"
>
<option value="TESTNET">Testnet</option>
<option value="MAINNET">Mainnet</option>
</select>
<ConnectWallet />
</div>
</div>
</div>
</nav>
);
}
}
82 changes: 69 additions & 13 deletions src/lib/sorosave.ts
Original file line number Diff line number Diff line change
@@ -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,
};