Skip to content
Merged
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
52 changes: 43 additions & 9 deletions Perigee/web/components/ConnectButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

import { useWallet } from "../context/WalletContext";
import { motion, AnimatePresence } from "framer-motion";
import { useState, useRef, useEffect } from "react";
import { useState, useRef, useEffect, useMemo } from "react";
import { LogOut } from "lucide-react";
import {
getCurrentNetwork,
getNetworkFromChainId,
getContractsForNetwork,
} from "../lib/contracts.config";

const ArrowDownIcon = () => (
<svg
Expand All @@ -19,12 +24,18 @@ const ArrowDownIcon = () => (
fill="#33C5E0"
/>
</svg>
);
});

export function ConnectButton() {
const { isConnected, address, openModal, disconnect } = useWallet();
const { isConnected, address, openModal, disconnect, chainId } = useWallet();
const [dropdownOpen, setDropdownOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const dropdownRef = useRef<HTMLDiv><null>;

const currentNetwork = useMemo(() => getCurrentNetwork(), []);
const network = chainId ? getNetworkFromChainId(chainId) : null;
const contracts = network ? getContractsForNetwork(network) : null;
const isWrongNetwork =
isConnected && currentNetwork && network !== currentNetwork;

const formatAddress = (addr: string) => {
if (!addr) return "";
Expand Down Expand Up @@ -56,9 +67,15 @@ export function ConnectButton() {
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setDropdownOpen(!dropdownOpen)}
className="flex items-center gap-3 px-6 py-3 rounded-s-2xl bg-[#0F1621] border border-[#1e293b] hover:border-[#33C5E0]/50 transition-all group pointer-events-auto"
className="flex items-center gap-3 px-6 py-3 rounded-s-2xl bg-[#0F1021] border border-[#1e293b] hover:border-[#33C5E0]/50 transition-all group pointer-events-auto"
>
<div className="w-2 h-2 rounded-full bg-[#33C5E0] shadow-[0_0_8px_#33C5E0]" />
<div
className={`w-2 h-2 rounded-full ${
isWrongNetwork
? "bg-red-500 shadow-[0_0_8px_#ef4444]"
: "bg-[#33C5E0] shadow-[0_0_8px_#33C5E0]"
}`}
/>
<span className="text-[#33C5E0] font-medium tracking-wide">
{formatAddress(address)}
</span>
Expand All @@ -78,8 +95,25 @@ export function ConnectButton() {
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.15 }}
className="absolute top-full right-0 mt-2 w-full min-w-[180px] bg-[#0F1621] border border-[#1e293b] rounded-xl shadow-xl overflow-hidden z-50"
className="absolute top-full right-0 mt-2 w-full min-w-[180px] bg-[#0F1021] border border-[#1e293b] rounded-xl shadow-xl overflow-hidden z-50"
>
<div className="px-4 py-2 border-b border-[#1e293b]">
<div className="text-xs text-gray-400">Network</div>
<div className="text-sm font-medium text-white">
{network ?? "Unknown"}
</div>
{isWrongNetwork && (
<div className="text-xs text-red-400 mt-1">
Wrong network. Expected {currentNetwork}.
</div>
)}
{network && !contracts && (
<div className="text-xs text-yellow-400 mt-1">
Contracts not configured"
for this network.
</div>
)}
</div>
<button
onClick={handleDisconnect}
className="flex items-center gap-3 w-full px-4 py-3 text-red-400 hover:bg-white/5 transition-colors text-sm font-medium"
Expand All @@ -101,11 +135,11 @@ export function ConnectButton() {
onClick={openModal}
className="flex items-center gap-4"
>
<div className="flex items-center gap-4 px-8 py-3 rounded-s-2xl bg-[#0F1621] border border-[#1e293b] hover:border-[#33C5E0]/50 transition-all text-[#33C5E0] font-medium tracking-wide shadow-lg shadow-black/20">
<div className="flex items-center gap-4 px-8 py-3 rounded-s-2xl bg-[#0F1021] border border-[#1e293b] hover:border-[#33C5E0]/50 transition-all text-[#33C5E0] font-medium tracking-wide shadow-lg shadow-black/20">
<span>Connect Wallet</span>
<ArrowDownIcon />
</div>
<div className="w-1.5 h-8 bg-[#161E22] flex items-center justify-center transition-colors" />
</motion.button>
);
}
}
43 changes: 1 addition & 42 deletions Perigee/web/components/ContractInteraction.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1 @@
import React from 'react';
import { ContractFunction } from '../lib/sorobantypes';
import { DynamicForm } from './DynamicForm';

interface ContractInteractionProps {
selectedFunction: ContractFunction;
loading: boolean;
onSubmit: (inputs: Record<string, any>) => Promise<void>;
}

export const ContractInteraction: React.FC<ContractInteractionProps> = ({
selectedFunction,
loading,
onSubmit,
}) => {
return (
<div
style={{
backgroundColor: '#161b22',
borderRadius: '8px',
padding: '24px',
border: '1px solid #30363d',
}}
>
<h2
style={{
margin: '0 0 16px 0',
fontSize: '18px',
fontWeight: '600',
color: '#58a6ff',
}}
>
{selectedFunction.name}
</h2>
<DynamicForm
func={selectedFunction}
onSubmit={onSubmit}
loading={loading}
/>
</div>
);
};
import React from 'react'; import { ContractFunction } from '../lib/sorobantypes'; import { DynamicForm } from './DynamicForm'; import { contractConfig } from '../lib/contracts.config'; import { useWallet } from '../lib/useWallet'; interface Props { selectedFunction: ContractFunction; loading: boolean; onSubmit: (inputs: Record<string, any>) => Promise<void>; } export const ContractInteraction: React.FC<Props> = ({ selectedFunction, loading, onSubmit }) => { const { network } = useWallet(); if (!network || !(network in contractConfig)) { return <div style={{color:'#f85149',padding:16}}Unsupported network. Please connect to testnet or mainnet.</div> } return <div style={{backgroundColor:'#161b22',borderRadius:8,padding:24,border:'1px solid #30363d'}}><h2 style={{margin:'0 0 16px 0',fontSize:18,fontWeight:600,color:'#58a6ff'}}>{selectedFunction.name}</h2><DynamicForm func={selectedFunction} onSubmit={onSubmit} loading={loading} /></div>; };
47 changes: 40 additions & 7 deletions Perigee/web/context/WalletContext.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
"use client";
"Use client";

import React, { createContext, useContext, useEffect, useState } from "react";
import { logger } from "../lib/logger";
import { getContractsConfig, ContractsConfig } from "../lib/contracts.config";

interface WalletContextType {
connect: (moduleId: string) => Promise<void>;
disconnect: () => Promise<void>;
address: string | null;
network: string | null;
contractConfig: ContractsConfig | null;
isConnected: boolean;
isConnecting: boolean;
selectedWalletId: string | null;
Expand All @@ -28,13 +31,28 @@ export const useWallet = () => {
};

export const WalletProvider = ({ children }: { children: React.ReactNode }) => {
const [address, setAddress] = useState<string | null>(null);
const [address, setAddress] = useState<string | null>unull);
const [network, setNetwork] = useState<string | null>(null);
const [contractConfig, setContractConfig] = useState<ContractsConfig | null>(null);
const [isConnecting, setIsConnecting] = useState(false);
const [selectedWalletId, setSelectedWalletId] = useState<string | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [error, setError] = useState<string | null>(null);
const [kit, setKit] = useState<any>(null);

useEffect(() => {
if (network === "testnet" || network === "mainnet") {
try {
setContractConfig(getContractsConfig(network));
} catch (err) {
logger.error("Unsupported network:", network, err);
setContractConfig(null);
}
} else {
setContractConfig(null);
}
}, [network]);

useEffect(() => {
const initKit = async () => {
try {
Expand Down Expand Up @@ -70,6 +88,11 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }) => {
setAddress(savedAddress);
setSelectedWalletId(savedWalletId);
}

const savedNetwork = localStorage.getItem("perigee_wallet_network");
if (savedNetwork) {
setNetwork(savedNetwork);
}
} catch (err) {
logger.error("Failed to initialize wallet kit:", err);
setError("Failed to load wallet kit");
Expand Down Expand Up @@ -100,10 +123,16 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }) => {
kit.setWallet(moduleId);
const { address: walletAddress } = await kit.getAddress();

const walletNetwork = await kit.getNetwork();
// Stellar wallets use "public" for mainnet, "testnet" for testnet
const networkStr = walletNetwork === "public" ? "mainnet" : "testnet";

setAddress(walletAddress);
setNetwork(networkStr);
setSelectedWalletId(moduleId);
localStorage.setItem("perigee_wallet_address", walletAddress);
localStorage.setItem("perigee_wallet_id", moduleId);
localStorage.setItem("perigee_wallet_network", networkStr);
setIsModalOpen(false);
} catch (err: any) {
const errorMessage = err?.message || "Connection failed";
Expand All @@ -118,15 +147,17 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }) => {
if (kit) {
try {
await kit.disconnect();
} catch (err) {
logger.error("Disconnect error:", err);
}
} catch (err) {
logger.error("Disconnect error:", err);
}
}
setAddress(null);
setNetwork(null);
setSelectedWalletId(null);
setError(null);
localStorage.removeItem("perigee_wallet_address");
localStorage.removeItem("perigee_wallet_id");
localStorage.removeItem("perigee_wallet_network");
};

const openModal = () => {
Expand All @@ -141,10 +172,12 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }) => {

return (
<WalletContext.Provider
value={{
value={
connect: connectWallet,
disconnect,
address,
network,
contractConfig,
isConnected: !!address,
isConnecting,
selectedWalletId,
Expand All @@ -153,7 +186,7 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }) => {
isModalOpen,
supportedWallets,
error,
}}
}
>
{children}
</WalletContext.Provider>
Expand Down
13 changes: 13 additions & 0 deletions Perigee/web/lib/contracts.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type Network = 'testnet' | 'mainnet';
export const contractsConfig = {
testnet: { token: 'testnet-token-address' },
mainnet: { token: 'mainnet-token-address' },
} as const;

export function getContractsConfig(network) {
return contractsConfig[network];
}

export function validateNetwork(selected, connected) {
return selected === connected;
}
Loading