From f8c9c786547dfab7f0b80923bd869eb0766426fa Mon Sep 17 00:00:00 2001 From: dankelleher Date: Wed, 10 Dec 2025 16:42:18 +0100 Subject: [PATCH 1/6] Disable forest fix --- .../app/src/common/context/forestContext.tsx | 39 ++++++++++++++++++- .../common/context/sunriseStakeContext.tsx | 11 ++++-- packages/app/src/hub/HubApp.tsx | 19 +++++---- packages/app/src/index.tsx | 9 +++-- 4 files changed, 60 insertions(+), 18 deletions(-) diff --git a/packages/app/src/common/context/forestContext.tsx b/packages/app/src/common/context/forestContext.tsx index d8f96d9d..cb5c29af 100644 --- a/packages/app/src/common/context/forestContext.tsx +++ b/packages/app/src/common/context/forestContext.tsx @@ -36,7 +36,7 @@ const ForestProvider: FC<{ children: ReactNode; depth?: number }> = ({ const { connection } = useConnection(); const [service, setService] = useState(); const [neighbours] = useState([]); - const [myTree] = useState(); + const [myTree, setMyTree] = useState(); const address: PublicKey | null = useMemo( () => safeParsePublicKeyFromUrl() ?? wallet.publicKey, @@ -46,8 +46,43 @@ const ForestProvider: FC<{ children: ReactNode; depth?: number }> = ({ const loadTree = useCallback( (reload = false) => { // Forest feature disabled - no neighbor retrieval + // Create a minimal tree object based on wallet connection status + if (address && details) { + try { + // Create a simple tree representation without MongoDB calls + const balance = details.balances.gsolBalance.amount || 0; + const hasBalance = balance > 0; + + const minimalTree: TreeComponent = { + address, + translate: { x: 0, y: 0, z: 0 }, + metadata: { + type: { + level: hasBalance ? 1 : 0, + species: 1, + instance: 0, + translucent: false, + }, + node: { + address, + balance: Number(balance), + startDate: new Date(), + mostRecentTransfer: new Date(), + children: [], + parents: [] + }, + layer: 0, + } + }; + + setMyTree(minimalTree); + } catch (error) { + console.error('Error creating minimal tree:', error); + // Don't set tree on error to avoid breaking the UI + } + } }, - [service, address] + [address, details] ); // reload tree when details change. doesn't matter what changed. diff --git a/packages/app/src/common/context/sunriseStakeContext.tsx b/packages/app/src/common/context/sunriseStakeContext.tsx index 563f2d90..f21ad758 100644 --- a/packages/app/src/common/context/sunriseStakeContext.tsx +++ b/packages/app/src/common/context/sunriseStakeContext.tsx @@ -67,7 +67,10 @@ const SunriseProvider: FC<{ children: ReactNode }> = ({ children }) => { setLoading ) .then(updateClient) - .catch(console.error); + .catch((error) => { + console.error("Failed to initialize SunriseClient for wallet:", error); + setLoading(false); + }); } else if (addressFromUrl !== null) { // we have an address in the url, but no wallet // this is a readonly client @@ -85,7 +88,7 @@ const SunriseProvider: FC<{ children: ReactNode }> = ({ children }) => { ) .then(updateClient) .catch((e) => { - console.error(e); + console.error("Failed to initialize readonly client:", e); }); } else { // just get the details from the chain - no client available yet @@ -106,7 +109,9 @@ const SunriseProvider: FC<{ children: ReactNode }> = ({ children }) => { return client.getDetails(); }) .then(initDetails) - .catch(console.error); + .catch((error) => { + console.error("Failed to get initial details:", error); + }); } }, [wallet?.publicKey, location.state?.address]); diff --git a/packages/app/src/hub/HubApp.tsx b/packages/app/src/hub/HubApp.tsx index 49b6b04c..fde7af11 100644 --- a/packages/app/src/hub/HubApp.tsx +++ b/packages/app/src/hub/HubApp.tsx @@ -9,7 +9,7 @@ import { useState, } from "react"; import { - // IoChevronBackOutline, + IoChevronBackOutline, IoChevronDownOutline, IoChevronForwardOutline, } from "react-icons/io5"; @@ -154,12 +154,11 @@ const _HubApp: ForwardRefRenderFunction< />
- {/* Forest navigation disabled - */} +
{myTree && ( - {/* Forest navigation disabled - + {/* Forest navigation - hidden but maintains layout */} +
Forest
- */} +
Grow diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 04ed5081..f71b6409 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -5,15 +5,18 @@ import { BrowserRouter } from "react-router-dom"; import "./index.css"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; +import { ErrorBoundary } from "./common/components/ErrorBoundary"; const root = ReactDOM.createRoot( document.getElementById("root") as HTMLElement ); root.render( - - - + + + + + ); From b24ad03856a3754ae8bcaf3110931b3dafd8870a Mon Sep 17 00:00:00 2001 From: dankelleher Date: Wed, 10 Dec 2025 16:42:18 +0100 Subject: [PATCH 2/6] Disable forest fix --- packages/app/src/common/components/index.ts | 1 + packages/app/src/index.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/app/src/common/components/index.ts b/packages/app/src/common/components/index.ts index 6a974e4b..fb9d7eb2 100644 --- a/packages/app/src/common/components/index.ts +++ b/packages/app/src/common/components/index.ts @@ -2,6 +2,7 @@ export * from "./AmountInput"; export * from "./Button"; export * from "./CarbonRecovered"; export * from "./DetailsBox"; +export * from "./ErrorBoundary"; export * from "./InfoBox"; export * from "./LockedGSol"; export * from "./Logo"; diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index f71b6409..aa525e91 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -5,7 +5,7 @@ import { BrowserRouter } from "react-router-dom"; import "./index.css"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; -import { ErrorBoundary } from "./common/components/ErrorBoundary"; +import { ErrorBoundary } from "./common/components/"; const root = ReactDOM.createRoot( document.getElementById("root") as HTMLElement From 6fa508f9bc972dbe0c7ee4effaeed9de4a104c39 Mon Sep 17 00:00:00 2001 From: dankelleher Date: Wed, 10 Dec 2025 16:47:38 +0100 Subject: [PATCH 3/6] Lint fixes --- packages/app/src/common/context/forestContext.tsx | 10 +++++----- .../app/src/common/context/sunriseStakeContext.tsx | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/app/src/common/context/forestContext.tsx b/packages/app/src/common/context/forestContext.tsx index cb5c29af..356c99e8 100644 --- a/packages/app/src/common/context/forestContext.tsx +++ b/packages/app/src/common/context/forestContext.tsx @@ -52,7 +52,7 @@ const ForestProvider: FC<{ children: ReactNode; depth?: number }> = ({ // Create a simple tree representation without MongoDB calls const balance = details.balances.gsolBalance.amount || 0; const hasBalance = balance > 0; - + const minimalTree: TreeComponent = { address, translate: { x: 0, y: 0, z: 0 }, @@ -69,15 +69,15 @@ const ForestProvider: FC<{ children: ReactNode; depth?: number }> = ({ startDate: new Date(), mostRecentTransfer: new Date(), children: [], - parents: [] + parents: [], }, layer: 0, - } + }, }; - + setMyTree(minimalTree); } catch (error) { - console.error('Error creating minimal tree:', error); + console.error("Error creating minimal tree:", error); // Don't set tree on error to avoid breaking the UI } } diff --git a/packages/app/src/common/context/sunriseStakeContext.tsx b/packages/app/src/common/context/sunriseStakeContext.tsx index f21ad758..2622888a 100644 --- a/packages/app/src/common/context/sunriseStakeContext.tsx +++ b/packages/app/src/common/context/sunriseStakeContext.tsx @@ -68,7 +68,10 @@ const SunriseProvider: FC<{ children: ReactNode }> = ({ children }) => { ) .then(updateClient) .catch((error) => { - console.error("Failed to initialize SunriseClient for wallet:", error); + console.error( + "Failed to initialize SunriseClient for wallet:", + error + ); setLoading(false); }); } else if (addressFromUrl !== null) { From b7c6d50e523af8a720d95b62de0290618c6c89f9 Mon Sep 17 00:00:00 2001 From: dankelleher Date: Wed, 10 Dec 2025 16:52:25 +0100 Subject: [PATCH 4/6] Remove error boundary --- packages/app/src/index.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index aa525e91..04ed5081 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -5,18 +5,15 @@ import { BrowserRouter } from "react-router-dom"; import "./index.css"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; -import { ErrorBoundary } from "./common/components/"; const root = ReactDOM.createRoot( document.getElementById("root") as HTMLElement ); root.render( - - - - - + + + ); From b32836e69a57b5f68e1d30b775149615f617cac9 Mon Sep 17 00:00:00 2001 From: dankelleher Date: Wed, 10 Dec 2025 16:55:08 +0100 Subject: [PATCH 5/6] Add missing files --- .../src/common/components/ErrorBoundary.tsx | 53 +++++++++ .../src/common/context/forestContext-fix.tsx | 105 ++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 packages/app/src/common/components/ErrorBoundary.tsx create mode 100644 packages/app/src/common/context/forestContext-fix.tsx diff --git a/packages/app/src/common/components/ErrorBoundary.tsx b/packages/app/src/common/components/ErrorBoundary.tsx new file mode 100644 index 00000000..3d82fc2a --- /dev/null +++ b/packages/app/src/common/components/ErrorBoundary.tsx @@ -0,0 +1,53 @@ +import React, { Component, type ReactNode } from "react"; + +interface Props { + children: ReactNode; + fallback?: (error: Error) => ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + console.error("ErrorBoundary caught an error:", error, errorInfo); + } + + render(): ReactNode { + if (this.state.hasError && this.state.error) { + if (this.props.fallback) { + return this.props.fallback(this.state.error); + } + + return ( +
+

+ Something went wrong +

+

{this.state.error.message}

+ +
+ ); + } + + return this.props.children; + } +} diff --git a/packages/app/src/common/context/forestContext-fix.tsx b/packages/app/src/common/context/forestContext-fix.tsx new file mode 100644 index 00000000..bc5d8c16 --- /dev/null +++ b/packages/app/src/common/context/forestContext-fix.tsx @@ -0,0 +1,105 @@ +import { useConnection, useWallet } from "@solana/wallet-adapter-react"; +import { + createContext, + type FC, + type ReactNode, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from "react"; +import { type TreeComponent } from "../../forest/utils"; +import { ForestService, MAX_FOREST_DEPTH } from "../../api/forest"; +import { useSunriseStake } from "./sunriseStakeContext"; +import { type PublicKey } from "@solana/web3.js"; +import { safeParsePublicKeyFromUrl } from "../utils"; + +interface ForestContextProps { + myTree: TreeComponent | undefined; + neighbours: TreeComponent[]; + update: () => void; +} +const defaultValue: ForestContextProps = { + myTree: undefined, + neighbours: [], + update: () => {}, +}; +const ForestContext = createContext(defaultValue); + +const ForestProvider: FC<{ children: ReactNode; depth?: number }> = ({ + children, + depth = MAX_FOREST_DEPTH, +}) => { + const { client, details } = useSunriseStake(); + const wallet = useWallet(); + const { connection } = useConnection(); + const [service, setService] = useState(); + const [neighbours] = useState([]); + const [myTree, setMyTree] = useState(); + + const address: PublicKey | null = useMemo( + () => safeParsePublicKeyFromUrl() ?? wallet.publicKey, + [wallet.publicKey] + ); + + const loadTree = useCallback( + (reload = false) => { + // Forest feature disabled - no neighbor retrieval + // Set a default tree based on user details instead of fetching from MongoDB + if (address && details) { + // Create a minimal tree object from the user's details + const defaultTree: TreeComponent = { + address, + metadata: { + type: { + level: (details.balances.gsolBalance.uiAmount ?? 0) > 0 ? 1 : 0, + translucent: false, + }, + }, + // Add any other required TreeComponent properties with defaults + } as TreeComponent; + + setMyTree(defaultTree); + } + }, + [service, address, details] + ); + + // reload tree when details change. doesn't matter what changed. + useEffect(() => { + if (details) { + // add a delay to give the backend database a chance to hear the transaction before we reload the tree + setTimeout(() => { + loadTree(true); + }, 5000); + } + }, [details]); + + useEffect(() => { + if (client) { + const service = new ForestService(connection, client); + setService(service); + } + }, [client]); + + useEffect(loadTree, [service, address]); + + return ( + { + loadTree(true); + }, + }} + > + {children} + + ); +}; + +const useForest = (): ForestContextProps => useContext(ForestContext); + +export { ForestProvider, useForest }; From 997acf878fb372fdfe75f7fc54d6484661b2b3d7 Mon Sep 17 00:00:00 2001 From: dankelleher Date: Wed, 10 Dec 2025 17:41:58 +0100 Subject: [PATCH 6/6] Forest decommissioning fix --- .../src/common/context/forestContext-fix.tsx | 105 ------------------ .../app/src/common/context/forestContext.tsx | 43 ++++++- 2 files changed, 37 insertions(+), 111 deletions(-) delete mode 100644 packages/app/src/common/context/forestContext-fix.tsx diff --git a/packages/app/src/common/context/forestContext-fix.tsx b/packages/app/src/common/context/forestContext-fix.tsx deleted file mode 100644 index bc5d8c16..00000000 --- a/packages/app/src/common/context/forestContext-fix.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { useConnection, useWallet } from "@solana/wallet-adapter-react"; -import { - createContext, - type FC, - type ReactNode, - useCallback, - useContext, - useEffect, - useMemo, - useState, -} from "react"; -import { type TreeComponent } from "../../forest/utils"; -import { ForestService, MAX_FOREST_DEPTH } from "../../api/forest"; -import { useSunriseStake } from "./sunriseStakeContext"; -import { type PublicKey } from "@solana/web3.js"; -import { safeParsePublicKeyFromUrl } from "../utils"; - -interface ForestContextProps { - myTree: TreeComponent | undefined; - neighbours: TreeComponent[]; - update: () => void; -} -const defaultValue: ForestContextProps = { - myTree: undefined, - neighbours: [], - update: () => {}, -}; -const ForestContext = createContext(defaultValue); - -const ForestProvider: FC<{ children: ReactNode; depth?: number }> = ({ - children, - depth = MAX_FOREST_DEPTH, -}) => { - const { client, details } = useSunriseStake(); - const wallet = useWallet(); - const { connection } = useConnection(); - const [service, setService] = useState(); - const [neighbours] = useState([]); - const [myTree, setMyTree] = useState(); - - const address: PublicKey | null = useMemo( - () => safeParsePublicKeyFromUrl() ?? wallet.publicKey, - [wallet.publicKey] - ); - - const loadTree = useCallback( - (reload = false) => { - // Forest feature disabled - no neighbor retrieval - // Set a default tree based on user details instead of fetching from MongoDB - if (address && details) { - // Create a minimal tree object from the user's details - const defaultTree: TreeComponent = { - address, - metadata: { - type: { - level: (details.balances.gsolBalance.uiAmount ?? 0) > 0 ? 1 : 0, - translucent: false, - }, - }, - // Add any other required TreeComponent properties with defaults - } as TreeComponent; - - setMyTree(defaultTree); - } - }, - [service, address, details] - ); - - // reload tree when details change. doesn't matter what changed. - useEffect(() => { - if (details) { - // add a delay to give the backend database a chance to hear the transaction before we reload the tree - setTimeout(() => { - loadTree(true); - }, 5000); - } - }, [details]); - - useEffect(() => { - if (client) { - const service = new ForestService(connection, client); - setService(service); - } - }, [client]); - - useEffect(loadTree, [service, address]); - - return ( - { - loadTree(true); - }, - }} - > - {children} - - ); -}; - -const useForest = (): ForestContextProps => useContext(ForestContext); - -export { ForestProvider, useForest }; diff --git a/packages/app/src/common/context/forestContext.tsx b/packages/app/src/common/context/forestContext.tsx index 356c99e8..fe6a7290 100644 --- a/packages/app/src/common/context/forestContext.tsx +++ b/packages/app/src/common/context/forestContext.tsx @@ -50,22 +50,53 @@ const ForestProvider: FC<{ children: ReactNode; depth?: number }> = ({ if (address && details) { try { // Create a simple tree representation without MongoDB calls - const balance = details.balances.gsolBalance.amount || 0; - const hasBalance = balance > 0; + const gsolBalance = Number(details.balances.gsolBalance.amount) || 0; + // lockDetails.amountLocked is in BN (lamports), convert to SOL + const lockedBalance = details.lockDetails?.amountLocked + ? Number(details.lockDetails.amountLocked.toString()) / 1e9 + : 0; + const totalBalance = gsolBalance + lockedBalance; + + // Calculate level based on balance thresholds + // Level 0: 0 gSOL + // Level 1: 0.001 - 1 gSOL + // Level 2: 1 - 10 gSOL + // Level 3: 10 - 50 gSOL + // Level 4: 50 - 100 gSOL + // Level 5: 100 - 500 gSOL + // Level 6: 500 - 1000 gSOL + // Level 7: 1000 - 5000 gSOL + // Level 8: 5000+ gSOL + let level = 0; + if (totalBalance > 0) { + if (totalBalance < 1) level = 1; + else if (totalBalance < 10) level = 2; + else if (totalBalance < 50) level = 3; + else if (totalBalance < 100) level = 4; + else if (totalBalance < 500) level = 5; + else if (totalBalance < 1000) level = 6; + else if (totalBalance < 5000) level = 7; + else level = 8; + } + + // Calculate species and instance from address for visual variety + const addressBytes = address.toBuffer(); + const instance = addressBytes[31] % 3; // Assuming 3 tree variations per species + const species = (addressBytes[30] % 3) + 1; // Assuming 3 species (1-3) const minimalTree: TreeComponent = { address, translate: { x: 0, y: 0, z: 0 }, metadata: { type: { - level: hasBalance ? 1 : 0, - species: 1, - instance: 0, + level, + species, + instance, translucent: false, }, node: { address, - balance: Number(balance), + balance: totalBalance, startDate: new Date(), mostRecentTransfer: new Date(), children: [],