diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index b0c3f22..3263e75 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,31 +1,35 @@
-import type { Metadata } from "next";
-import { Inter } from "next/font/google";
-import "./globals.css";
-import { Providers } from "@/components/providers";
-import { Sidebar } from "@/components/ui/sidebar";
-import { Toaster } from "@/components/ui/toaster";
-
-const inter = Inter({ subsets: ["latin"] });
-
-export const metadata: Metadata = {
- title: "CoopFinance — Cooperative Finance on Stellar",
- description:
- "Open-source platform for savings groups, cooperatives, and rotating-credit associations built on Stellar.",
- keywords: ["cooperative", "savings", "Stellar", "USDC", "Africa", "SACCO", "Ajo", "Esusu"],
-};
-
-export default function RootLayout({ children }: { children: React.ReactNode }) {
- return (
-
-
-
-
-
- {children}
-
-
-
-
-
- );
-}
+import type { Metadata } from "next";
+import { Inter } from "next/font/google";
+import "./globals.css";
+import { Providers } from "@/components/providers";
+import { Sidebar } from "@/components/ui/sidebar";
+import { Header } from "@/components/ui/header";
+import { Toaster } from "@/components/ui/toaster";
+
+const inter = Inter({ subsets: ["latin"] });
+
+export const metadata: Metadata = {
+ title: "CoopFinance — Cooperative Finance on Stellar",
+ description:
+ "Open-source platform for savings groups, cooperatives, and rotating-credit associations built on Stellar.",
+ keywords: ["cooperative", "savings", "Stellar", "USDC", "Africa", "SACCO", "Ajo", "Esusu"],
+};
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/providers.tsx b/src/components/providers.tsx
index 9bba174..cfd1da1 100644
--- a/src/components/providers.tsx
+++ b/src/components/providers.tsx
@@ -1,20 +1,21 @@
-"use client";
-
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { useState } from "react";
-
-export function Providers({ children }: { children: React.ReactNode }) {
- const [queryClient] = useState(
- () => new QueryClient({
- defaultOptions: {
- queries: { staleTime: 30_000, retry: 2 },
- },
- })
- );
-
- return (
-
- {children}
-
- );
-}
+"use client";
+
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { useState } from "react";
+import { WalletProvider } from "@/hooks/use-wallet";
+
+export function Providers({ children }: { children: React.ReactNode }) {
+ const [queryClient] = useState(
+ () => new QueryClient({
+ defaultOptions: {
+ queries: { staleTime: 30_000, retry: 2 },
+ },
+ })
+ );
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/ui/header.tsx b/src/components/ui/header.tsx
new file mode 100644
index 0000000..fccc4a8
--- /dev/null
+++ b/src/components/ui/header.tsx
@@ -0,0 +1,11 @@
+"use client";
+
+import { WalletButton } from "@/components/ui/wallet-button";
+
+export function Header() {
+ return (
+
+ );
+}
diff --git a/src/components/ui/wallet-button.tsx b/src/components/ui/wallet-button.tsx
new file mode 100644
index 0000000..92dd58a
--- /dev/null
+++ b/src/components/ui/wallet-button.tsx
@@ -0,0 +1,113 @@
+"use client";
+
+import { useState } from "react";
+import Link from "next/link";
+import { Copy, ExternalLink, Loader2, Wallet } from "lucide-react";
+import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
+import { clsx } from "clsx";
+import { useWallet } from "@/hooks/use-wallet";
+
+function shortenAddress(address: string): string {
+ if (address.length <= 10) return address;
+ return `${address.slice(0, 4)}...${address.slice(-4)}`;
+}
+
+export function WalletButton() {
+ const { address, isConnecting, connect, disconnect } = useWallet();
+ const [copied, setCopied] = useState(false);
+
+ if (isConnecting) {
+ return (
+
+ );
+ }
+
+ if (!address) {
+ return (
+
+ );
+ }
+
+ const explorerUrl = `https://stellar.expert/explorer/testnet/account/${address}`;
+
+ const copyAddress = async () => {
+ await navigator.clipboard.writeText(address);
+ setCopied(true);
+ window.setTimeout(() => setCopied(false), 2000);
+ };
+
+ return (
+
+
+
+
+
+
+
+
Connected address
+
+
{address}
+
+
+ {copied &&
Copied!
}
+
+
+
+
+
+ View on Stellar Explorer
+
+
+ disconnect()}
+ className={clsx(
+ "flex items-center px-2 py-1.5 text-sm text-red-600 rounded-md",
+ "outline-none cursor-pointer hover:bg-red-50",
+ )}
+ >
+ Disconnect
+
+
+
+
+ );
+}
diff --git a/src/hooks/use-wallet.ts b/src/hooks/use-wallet.ts
index f8530fe..5a6a279 100644
--- a/src/hooks/use-wallet.ts
+++ b/src/hooks/use-wallet.ts
@@ -1,55 +1,89 @@
-"use client";
-
-import { useState, useCallback } from "react";
-import {
- StellarWalletsKit,
- WalletNetwork,
- FREIGHTER_ID,
- LOBSTR_ID,
- xBullWalletId,
-} from "@creit.tech/stellar-wallets-kit";
-
-const kit = new StellarWalletsKit({
- network: WalletNetwork.TESTNET,
- selectedWalletId: FREIGHTER_ID,
- wallets: [FREIGHTER_ID, LOBSTR_ID, xBullWalletId],
-});
-
-export function useWallet() {
- const [address, setAddress] = useState(null);
- const [isConnecting, setIsConnecting] = useState(false);
- const [error, setError] = useState(null);
-
- const connect = useCallback(async () => {
- setIsConnecting(true);
- setError(null);
- try {
- await kit.openModal({
- onWalletSelected: async (option) => {
- kit.setWallet(option.id);
- const { address: addr } = await kit.getAddress();
- setAddress(addr);
- },
- });
- } catch (err: unknown) {
- setError(err instanceof Error ? err.message : "Failed to connect wallet");
- } finally {
- setIsConnecting(false);
- }
- }, []);
-
- const disconnect = useCallback(() => {
- setAddress(null);
- }, []);
-
- const signTransaction = useCallback(async (xdr: string) => {
- if (!address) throw new Error("Wallet not connected");
- const { signedTxXdr } = await kit.signTransaction(xdr, {
- address,
- networkPassphrase: WalletNetwork.TESTNET,
- });
- return signedTxXdr;
- }, [address]);
-
- return { address, isConnecting, error, connect, disconnect, signTransaction };
-}
+"use client";
+
+import {
+ createContext,
+ useCallback,
+ useContext,
+ useState,
+ type ReactNode,
+} from "react";
+import {
+ StellarWalletsKit,
+ WalletNetwork,
+ FREIGHTER_ID,
+ LOBSTR_ID,
+ xBullWalletId,
+} from "@creit.tech/stellar-wallets-kit";
+
+const kit = new StellarWalletsKit({
+ network: WalletNetwork.TESTNET,
+ selectedWalletId: FREIGHTER_ID,
+ wallets: [FREIGHTER_ID, LOBSTR_ID, xBullWalletId],
+});
+
+type WalletContextValue = {
+ address: string | null;
+ isConnecting: boolean;
+ error: string | null;
+ connect: () => Promise;
+ disconnect: () => void;
+ signTransaction: (xdr: string) => Promise;
+};
+
+const WalletContext = createContext(null);
+
+export function WalletProvider({ children }: { children: ReactNode }) {
+ const [address, setAddress] = useState(null);
+ const [isConnecting, setIsConnecting] = useState(false);
+ const [error, setError] = useState(null);
+
+ const connect = useCallback(async () => {
+ setIsConnecting(true);
+ setError(null);
+ try {
+ await kit.openModal({
+ onWalletSelected: async (option) => {
+ kit.setWallet(option.id);
+ const { address: addr } = await kit.getAddress();
+ setAddress(addr);
+ },
+ });
+ } catch (err: unknown) {
+ setError(err instanceof Error ? err.message : "Failed to connect wallet");
+ } finally {
+ setIsConnecting(false);
+ }
+ }, []);
+
+ const disconnect = useCallback(() => {
+ setAddress(null);
+ }, []);
+
+ const signTransaction = useCallback(
+ async (xdr: string) => {
+ if (!address) throw new Error("Wallet not connected");
+ const { signedTxXdr } = await kit.signTransaction(xdr, {
+ address,
+ networkPassphrase: WalletNetwork.TESTNET,
+ });
+ return signedTxXdr;
+ },
+ [address],
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useWallet(): WalletContextValue {
+ const ctx = useContext(WalletContext);
+ if (!ctx) {
+ throw new Error("useWallet must be used within WalletProvider");
+ }
+ return ctx;
+}