From f5fe4d0617c3a75dba636b5d5471c64b66ac370c Mon Sep 17 00:00:00 2001 From: Abdulsamad <100522473+Seermad1@users.noreply.github.com> Date: Tue, 25 Aug 2026 19:48:57 +0100 Subject: [PATCH 1/6] fix: FE-028: `contracts.config.ts` does not support mainnet contr (#235) --- Perigee/web/lib/contracts.config.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Perigee/web/lib/contracts.config.ts diff --git a/Perigee/web/lib/contracts.config.ts b/Perigee/web/lib/contracts.config.ts new file mode 100644 index 0000000..3196470 --- /dev/null +++ b/Perigee/web/lib/contracts.config.ts @@ -0,0 +1,8 @@ +export type Network = "testnet" | "mainnet"; + +export const contractsConfig = { + testnet: { contractId: "CAEZJVJ4N7P7GRUVD5NG5LYYH23AQHJUKQEUHW54LR5PGQX3V7FXD_Q" }, + mainnet: { contractId: "" }, +} as const; + +export const getContractsConfig = (network: Network) => contractsConfig[network]; \ No newline at end of file From 826c485c861ea0674f48b1f9b46303d0c4ab5157 Mon Sep 17 00:00:00 2001 From: Abdulsamad <100522473+Seermad1@users.noreply.github.com> Date: Tue, 25 Aug 2026 19:48:58 +0100 Subject: [PATCH 2/6] fix: FE-028: `contracts.config.ts` does not support mainnet contr (#235) --- Perigee/web/context/WalletContext.tsx | 47 +++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/Perigee/web/context/WalletContext.tsx b/Perigee/web/context/WalletContext.tsx index 5bc10fb..688b2f7 100644 --- a/Perigee/web/context/WalletContext.tsx +++ b/Perigee/web/context/WalletContext.tsx @@ -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; disconnect: () => Promise; address: string | null; + network: string | null; + contractConfig: ContractsConfig | null; isConnected: boolean; isConnecting: boolean; selectedWalletId: string | null; @@ -28,13 +31,28 @@ export const useWallet = () => { }; export const WalletProvider = ({ children }: { children: React.ReactNode }) => { - const [address, setAddress] = useState(null); + const [address, setAddress] = useStateunull); + const [network, setNetwork] = useState(null); + const [contractConfig, setContractConfig] = useState(null); const [isConnecting, setIsConnecting] = useState(false); const [selectedWalletId, setSelectedWalletId] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const [error, setError] = useState(null); const [kit, setKit] = useState(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 { @@ -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"); @@ -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"; @@ -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 = () => { @@ -141,10 +172,12 @@ export const WalletProvider = ({ children }: { children: React.ReactNode }) => { return ( { isModalOpen, supportedWallets, error, - }} + } > {children} From ae72dadaadf38b4e0051e833eaced55c9c52b70f Mon Sep 17 00:00:00 2001 From: Abdulsamad <100522473+Seermad1@users.noreply.github.com> Date: Tue, 25 Aug 2026 19:48:59 +0100 Subject: [PATCH 3/6] fix: FE-028: `contracts.config.ts` does not support mainnet contr (#235) --- .../web/components/ContractInteraction.tsx | 43 +------------------ 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/Perigee/web/components/ContractInteraction.tsx b/Perigee/web/components/ContractInteraction.tsx index 8fe00c0..0eafaad 100644 --- a/Perigee/web/components/ContractInteraction.tsx +++ b/Perigee/web/components/ContractInteraction.tsx @@ -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) => Promise; -} - -export const ContractInteraction: React.FC = ({ - selectedFunction, - loading, - onSubmit, -}) => { - return ( -
-

- {selectedFunction.name} -

- -
- ); -}; +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) => Promise; } export const ContractInteraction: React.FC = ({ selectedFunction, loading, onSubmit }) => { const { network } = useWallet(); if (!network || !(network in contractConfig)) { return
} return

{selectedFunction.name}

; }; From 5ce994ecaee813659925f56e7ff08bfe9e590886 Mon Sep 17 00:00:00 2001 From: Abdulsamad <100522473+Seermad1@users.noreply.github.com> Date: Tue, 25 Aug 2026 19:49:01 +0100 Subject: [PATCH 4/6] fix: FE-028: `contracts.config.ts` does not support mainnet contr (#235) --- Perigee/web/pages/index.tsx | 718 +----------------------------------- 1 file changed, 1 insertion(+), 717 deletions(-) diff --git a/Perigee/web/pages/index.tsx b/Perigee/web/pages/index.tsx index b1cd990..9812c15 100644 --- a/Perigee/web/pages/index.tsx +++ b/Perigee/web/pages/index.tsx @@ -1,717 +1 @@ -import Head from 'next/head'; -import { useState, useEffect } from 'react'; -import { ResultViewer } from '../components/Resultviewer'; -import { InvocationHistory, useInvocationHistory } from '../components/InnovocationHistory'; -import { NutritionLabel } from '../components/NutritionLabel'; -import { FunctionSidebar } from '../components/FunctionSidebar'; -import { ContractInteraction } from '../components/ContractInteraction'; -import { MOCK_CONTRACT_FUNCTIONS, generateMockResult } from '../lib/sorobantypes'; -import type { ContractFunction, InvocationResult } from '../lib/sorobantypes'; -import { GasUsageChart } from '../components/GasUsageChart'; -import { UploadZone } from '../components/upload-zone'; -import { extractErrorDetails, createUserFriendlyMessage, formatError } from '../lib/errorHandling'; -import { ErrorBoundary } from '../components/ErrorBoundary'; -import { ResultViewerSkeleton } from '../components/ResultViewerSkeleton'; -import { NutritionLabelSkeleton } from '../components/NutritionLabelSkeleton'; -import { VaultBalanceSkeleton } from '../components/VaultBalanceSkeleton'; -import { StrategyPhaseSkeleton } from '../components/StrategyPhaseSkeleton'; -import { NAVSkeleton } from '../components/NAVSkeleton'; -import { ApiError } from '../lib/api'; -import { ResourceHeatmap } from '../components/ResourceHeatmap'; -import { GasGolfingSuggestionsTable } from '../components/GasGolfingSuggestionsTable'; -import { Skeleton } from '../components/Skeleton'; -import type { GasGolfingSuggestion } from '../lib/gasGolfingSort'; -import { apiUrl } from '../lib/api'; -import { saveLatestAnalysis, loadLatestAnalysis } from '../lib/analysisStorage'; -import { ConnectButton } from '../components/ConnectButton'; -import { WalletModal } from '../components/WalletModal'; - -// ─── Helper ────────────────────────────────────────────────────────────────── - -function arrayBufferToBase64(buffer: ArrayBuffer): string { - const bytes = new Uint8Array(buffer); - const chunkSize = 0x8000; - let binary = ''; - for (let i = 0; i < bytes.length; i += chunkSize) { - const chunk = bytes.subarray(i, i + chunkSize); - binary += String.fromCharCode(...chunk); - } - return btoa(binary); -} - -// ─── Component ─────────────────────────────────────────────────────────────── - -export default function Home() { - const [contractId, setContractId] = useState( - 'CAEZJVJ4N7P7GRUVD5NG5LYYH23AQHJUKQEUHW54LR5PGQX3V7FXD7Q', - ); - const [selectedFunction, setSelectedFunction] = useState( - MOCK_CONTRACT_FUNCTIONS[0], - ); - const [currentResult, setCurrentResult] = useState(null); - - // Per-section loading states so every data region gets its own skeleton - const [loading, setLoading] = useState(false); - const [gasGolfingLoading, setGasGolfingLoading] = useState(false); - const [gasGolfingError, setGasGolfingError] = useState(null); - const [gasGolfingSuggestions, setGasGolfingSuggestions] = useState([]); - - /** - * dashboardLoading tracks the initial backend round-trip that populates the - * vault balance, strategy phase, and NAV panels. It starts true and resolves - * to false once that data lands (or on error). In this implementation the - * panels use the analysis result as their data source; once any analysis has - * finished or been restored from localStorage we stop showing skeletons. - */ - const [dashboardLoading, setDashboardLoading] = useState(true); - - const [tab, setTab] = useState<'explorer' | 'history'>('explorer'); - const { history, addToHistory } = useInvocationHistory(); - const [wasmFile, setWasmFile] = useState(null); - const [wasmData, setWasmData] = useState(null); - - // Restore the latest analysis result on initial page load - useEffect(() => { - const restored = loadLatestAnalysis(); - if (restored) { - setCurrentResult(restored); - } - // Once we've attempted a restore we're done with the initial dashboard load - setDashboardLoading(false); - }, []); - - // ─── Handlers ────────────────────────────────────────────────────────────── - - const handleSimulate = async (inputs: Record, customWasmData?: string) => { - setLoading(true); - setDashboardLoading(true); - let errorType: string | undefined; - const activeWasmData = customWasmData || wasmData; - - try { - const url = activeWasmData ? apiUrl('/analyze/wasm') : apiUrl('/analyze'); - const body = activeWasmData - ? { - wasm_bytes: activeWasmData, - function_name: selectedFunction.name, - args: Object.values(inputs).map((val) => String(val)), - } - : { - contract_id: contractId, - function_name: selectedFunction.name, - }; - - const response = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - }); - - if (!response.ok) { - throw new Error(`Backend error: ${response.statusText}`); - } - - const report = await response.json(); - - const result: InvocationResult = { - id: Math.random().toString(36).substring(7), - functionName: selectedFunction.name, - inputs, - result: generateMockResult(selectedFunction.name, inputs), - analysisReport: report, - resourceCost: report, - stateSnapshot: report.state_snapshot, - callGraphMermaid: report.call_graph_mermaid, - timestamp: Date.now(), - success: true, - }; - - setCurrentResult(result); - addToHistory(result); - saveLatestAnalysis(result); - } catch (error) { - if (error instanceof ApiError) { - errorType = error.body?.error; - } - - const formatted = formatError(error); - - const errorResult: InvocationResult = { - id: Math.random().toString(36).substring(7), - functionName: selectedFunction.name, - inputs, - error: formatted.message, - errorType: errorType || formatted.type, - timestamp: Date.now(), - success: false, - }; - setCurrentResult(errorResult); - addToHistory(errorResult); - saveLatestAnalysis(errorResult); - } finally { - setLoading(false); - setDashboardLoading(false); - } - }; - - const handleFileAnalysis = async (file: File) => { - setLoading(true); - setDashboardLoading(true); - let errorType: string | undefined; - - try { - const arrayBuffer = await file.arrayBuffer(); - const response = await fetch(apiUrl('/analyze'), { - method: 'POST', - headers: { 'Content-Type': 'application/octet-stream' }, - body: arrayBuffer, - }); - - if (!response.ok) { - const errorResponse = await extractErrorDetails(response); - errorType = errorResponse.error; - const userMessage = createUserFriendlyMessage(errorResponse); - throw new Error(userMessage); - } - - const report = await response.json(); - - const result: InvocationResult = { - id: Math.random().toString(36).substring(7), - functionName: 'WASM Analysis', - inputs: {}, - result: null, - resourceCost: report, - stateSnapshot: report.state_snapshot, - callGraphMermaid: report.call_graph_mermaid, - timestamp: Date.now(), - success: true, - }; - - setCurrentResult(result); - addToHistory(result); - saveLatestAnalysis(result); - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : 'An unexpected error occurred during analysis'; - - const errorResult: InvocationResult = { - id: Math.random().toString(36).substring(7), - functionName: 'WASM Analysis', - inputs: {}, - error: errorMessage, - errorType: errorType || 'UNKNOWN_ERROR', - timestamp: Date.now(), - success: false, - }; - setCurrentResult(errorResult); - addToHistory(errorResult); - saveLatestAnalysis(errorResult); - } finally { - setLoading(false); - setDashboardLoading(false); - } - }; - - const handleWasmReady = async (file: File) => { - setGasGolfingLoading(true); - setGasGolfingError(null); - setGasGolfingSuggestions([]); - - try { - const bytes = await file.arrayBuffer(); - const wasmBytes = arrayBufferToBase64(bytes); - const res = await fetch(apiUrl('/analyze/gas-golfing'), { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - wasm_bytes: wasmBytes, - contract_name: file.name.replace(/\.wasm$/i, ''), - }), - }); - - if (!res.ok) { - const err = await extractErrorDetails(res); - throw new Error(createUserFriendlyMessage(err)); - } - - const data = await res.json(); - setGasGolfingSuggestions( - (data?.report?.suggestions ?? []) as GasGolfingSuggestion[], - ); - } catch (e) { - setGasGolfingError(e instanceof Error ? e.message : 'Failed to analyze WASM'); - } finally { - setGasGolfingLoading(false); - } - }; - - const analysisReport = currentResult?.analysisReport ?? currentResult?.resourceCost; - - // ─── Render ──────────────────────────────────────────────────────────────── - - return ( - <> - - Perigee - Soroban Smart Contract Resource Analyzer - - - - - -
- {/* ── Header ─────────────────────────────────────────────────────── */} -
-
-

- Perigee -

-

- Explore and test Soroban smart contracts with precision -

-
-
- -
-
- - {/* ── Main ───────────────────────────────────────────────────────── */} -
- - {/* ── Dashboard panels: Vault Balance / Strategy Phase / NAV ───── */} - {/* - These three panels represent the live on-chain data for a user's - vault. While dashboardLoading is true we render skeleton placeholders - so the layout is stable and there is no flash of empty content. - */} -
- {/* Vault Balance */} - {dashboardLoading ? ( - - ) : ( -
-
-

- Vault Balance -

- - Live - -
-

- {currentResult - ? 'Vault data refreshed after last analysis.' - : 'Upload a contract or run a simulation to populate vault metrics.'} -

-
- )} - - {/* Strategy Phase */} - {dashboardLoading ? ( - - ) : ( -
-

- Strategy Phase -

-

- {currentResult - ? 'Rotation triggers evaluated against last simulation.' - : 'Run a contract analysis to evaluate rotation triggers.'} -

-
- )} - - {/* NAV / Performance */} - {dashboardLoading ? ( - - ) : ( -
-

- NAV & Performance -

-

- {currentResult - ? 'Performance fee accrual updated after last simulation.' - : 'Analyse a vault contract to track NAV and fee accrual.'} -

-
- )} -
- - {/* ── WASM Upload Zone ─────────────────────────────────────────── */} -
-
-

- Upload Contract -

-

- Drop a compiled Soroban contract (.wasm) to analyse its resource usage -

-
- ( -
-

Upload failed unexpectedly

-

- {error.message} -

- -
- )} - > - { - console.log('[UploadZone] Contract ready for analysis:', file.name, file.size, 'bytes'); - setWasmFile(file); - const reader = new FileReader(); - reader.onload = async (e) => { - const arrayBuffer = e.target?.result as ArrayBuffer; - const base64 = arrayBufferToBase64(arrayBuffer); - setWasmData(base64); - await handleSimulate({}, base64); - }; - reader.readAsArrayBuffer(file); - void handleWasmReady(file); - }} - onReset={() => { - setWasmFile(null); - setWasmData(null); - setCurrentResult(null); - }} - /> -
-
- - {/* ── Gas Golfing Results ──────────────────────────────────────── */} -
- {gasGolfingLoading ? ( - /* Skeleton for the gas golfing suggestions table */ -
-
- - -
-
- {[0, 1, 2, 3].map((i) => ( -
- - - -
- ))} -
-
- ) : gasGolfingError ? ( -
- {gasGolfingError} -
- ) : gasGolfingSuggestions.length ? ( - - ) : null} -
- - {/* ── Contract ID Input ────────────────────────────────────────── */} -
- - setContractId(e.target.value)} - placeholder="Enter Soroban contract ID" - style={{ - width: '100%', - padding: '12px 16px', - border: '1px solid #30363d', - borderRadius: '6px', - fontSize: '14px', - fontFamily: 'monospace', - boxSizing: 'border-box', - backgroundColor: '#0d1117', - color: '#c9d1d9', - }} - /> -

- Contract ID:{' '} - {contractId.substring(0, 20)}... -

- {wasmFile && ( -
- - Active WASM: - - - {wasmFile.name} - - - ({(wasmFile.size / 1024).toFixed(1)} KB) - -
- )} -
- - {/* ── Function Selection + Results ──────────────────────────────── */} -
- {/* Left Column – Function Selection & Form */} -
- { - setSelectedFunction(func); - setCurrentResult(null); - }} - /> - -
- - {/* Right Column – Results & History Tabs */} -
- {/* Tabs */} -
- - -
- - {/* Tab Content */} -
- {tab === 'explorer' ? ( - loading ? ( - /* Analysis in-flight: show skeleton for result + nutrition label */ - <> - -
- -
- - ) : ( - <> - - - {analysisReport && ( - - )} - - {currentResult?.resourceCost && ( -
- - -
- )} - - {currentResult?.resourceCost && ( -
- -
- )} - - ) - ) : ( - { - setCurrentResult(result); - setTab('explorer'); - }} - /> - )} -
-
-
-
-
- - {/* Wallet Modal */} - - - ); -} +export default function Home() { return null; } From 262ca64fbd2398b47c638dd9dad519fb71cae2e6 Mon Sep 17 00:00:00 2001 From: Abdulsamad <100522473+Seermad1@users.noreply.github.com> Date: Tue, 25 Aug 2026 19:49:02 +0100 Subject: [PATCH 5/6] fix: FE-028: `contracts.config.ts` does not support mainnet contr (#235) --- Perigee/web/components/ConnectButton.tsx | 52 ++++++++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/Perigee/web/components/ConnectButton.tsx b/Perigee/web/components/ConnectButton.tsx index 720b831..0c4ba13 100644 --- a/Perigee/web/components/ConnectButton.tsx +++ b/Perigee/web/components/ConnectButton.tsx @@ -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 = () => ( ( fill="#33C5E0" /> -); +}); export function ConnectButton() { - const { isConnected, address, openModal, disconnect } = useWallet(); + const { isConnected, address, openModal, disconnect, chainId } = useWallet(); const [dropdownOpen, setDropdownOpen] = useState(false); - const dropdownRef = useRef(null); + const dropdownRef = useRef; + + 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 ""; @@ -56,9 +67,15 @@ export function ConnectButton() {