From a8c59e422d216123c74f1d761801e05349998349 Mon Sep 17 00:00:00 2001 From: RustyCoderX Date: Tue, 14 Oct 2025 17:43:46 +0530 Subject: [PATCH 1/2] fixed the spinnig ui eror --- .../components/Unlocked/Settings/Xnfts/Detail.tsx | 10 +++++++++- .../src/SolanaClient/utils/confirmTransaction.ts | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/app-extension/src/components/Unlocked/Settings/Xnfts/Detail.tsx b/packages/app-extension/src/components/Unlocked/Settings/Xnfts/Detail.tsx index 8987b81ec..a969d4401 100644 --- a/packages/app-extension/src/components/Unlocked/Settings/Xnfts/Detail.tsx +++ b/packages/app-extension/src/components/Unlocked/Settings/Xnfts/Detail.tsx @@ -252,13 +252,21 @@ const UninstallConfirmationCard = ({ xnft }: { xnft: any }) => { // // Confirm tx. // + logger.debug("starting transaction confirmation", { txSig }); try { - await confirmTransaction( + // Add a UI-level timeout to prevent indefinite hanging + const confirmationPromise = confirmTransaction( ctx.connection, txSig, ctx.commitment === "finalized" ? "finalized" : "confirmed" ); + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error("UI timeout: confirmation took too long")), 45000) + ); + + await Promise.race([confirmationPromise, timeoutPromise]); + logger.debug("transaction confirmed successfully", { txSig }); setCardType("complete"); } catch (err: any) { logger.error("unable to confirm", err); diff --git a/packages/secure-clients/src/SolanaClient/utils/confirmTransaction.ts b/packages/secure-clients/src/SolanaClient/utils/confirmTransaction.ts index e7b53aa8b..c4381455c 100644 --- a/packages/secure-clients/src/SolanaClient/utils/confirmTransaction.ts +++ b/packages/secure-clients/src/SolanaClient/utils/confirmTransaction.ts @@ -4,18 +4,24 @@ import type { GetVersionedTransactionConfig, } from "@solana/web3.js"; +import { getLogger } from "@coral-xyz/common"; + +const logger = getLogger("confirm-transaction"); + export async function confirmTransaction( c: Connection, txSig: string, commitmentOrConfig?: GetVersionedTransactionConfig | Finality ): Promise> { return new Promise(async (resolve, reject) => { + logger.debug("starting confirmation with timeout", { txSig }); setTimeout( () => reject(new Error(`30 second timeout: unable to confirm transaction`)), 30000 ); await new Promise((resolve) => setTimeout(resolve, 5000)); + logger.debug("initial 5s delay complete, starting polling", { txSig }); const config = { // Support confirming Versioned Transactions @@ -27,11 +33,20 @@ export async function confirmTransaction( : commitmentOrConfig), }; + let attempts = 0; let tx = await c.getParsedTransaction(txSig, config); + logger.debug("first getParsedTransaction attempt", { txSig, found: tx !== null }); while (tx === null) { + attempts++; + logger.debug(`polling attempt ${attempts}`, { txSig }); tx = await c.getParsedTransaction(txSig, config); await new Promise((resolve) => setTimeout(resolve, 1000)); + if (attempts > 25) { // Prevent infinite loop, though timeout should catch it + logger.warn("exceeded max polling attempts", { txSig, attempts }); + break; + } } + logger.debug("confirmation complete", { txSig, found: tx !== null, attempts }); resolve(tx); }); } From 7f20e7dd0b6833bf25231f38dd595bdb5e7c0303 Mon Sep 17 00:00:00 2001 From: RustyCoderX Date: Tue, 14 Oct 2025 17:56:51 +0530 Subject: [PATCH 2/2] Fix: xNFT blank screen issue --- .../src/components/Unlocked/Apps/Plugin.tsx | 1 + .../components/Unlocked/Apps/Simulator.tsx | 19 +++++++++----- .../recoil/src/hooks/solana/usePlugins.tsx | 26 ++++++++++++++++--- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/packages/app-extension/src/components/Unlocked/Apps/Plugin.tsx b/packages/app-extension/src/components/Unlocked/Apps/Plugin.tsx index a4fa128e0..78941b2d5 100644 --- a/packages/app-extension/src/components/Unlocked/Apps/Plugin.tsx +++ b/packages/app-extension/src/components/Unlocked/Apps/Plugin.tsx @@ -69,6 +69,7 @@ function LoadPlugin({ }); if (xnftAddress === DEFAULT_PUBKEY_STR) { + console.log("LoadPlugin: Loading Simulator plugin", { xnftAddress, plugin: plugin.xnftAddress.toString() }); return ; } return ; diff --git a/packages/app-extension/src/components/Unlocked/Apps/Simulator.tsx b/packages/app-extension/src/components/Unlocked/Apps/Simulator.tsx index 2353efc18..b76449777 100644 --- a/packages/app-extension/src/components/Unlocked/Apps/Simulator.tsx +++ b/packages/app-extension/src/components/Unlocked/Apps/Simulator.tsx @@ -15,6 +15,7 @@ export function Simulator({ deepXnftPath: string; }) { const refresh = useJavaScriptRefresh(SIMULATOR_URL); + console.log("Simulator: refresh triggered", { refresh, SIMULATOR_URL }); return ( ); @@ -27,16 +28,22 @@ function useJavaScriptRefresh(url: string): number { let previous: any = null; const i = setInterval(() => { (async () => { - const js = await (await fetch(url)).text(); - const noTSjs = js?.replaceAll(removeTimestamps, ""); // remove cachebusting timestamps next.js - if (previous !== null && previous !== noTSjs) { - setRefresh((r) => r + 1); + try { + const js = await (await fetch(url)).text(); + const noTSjs = js?.replaceAll(removeTimestamps, ""); // remove cachebusting timestamps next.js + console.log("Simulator: checking for refresh", { url, hasPrevious: previous !== null, jsLength: js?.length }); + if (previous !== null && previous !== noTSjs) { + console.log("Simulator: refresh triggered due to JS change"); + setRefresh((r) => r + 1); + } + previous = noTSjs; + } catch (error) { + console.error("Simulator: error fetching JS", error); } - previous = noTSjs; })(); }, 1000); return () => clearInterval(i); - }, []); + }, [url]); return refresh; } diff --git a/packages/recoil/src/hooks/solana/usePlugins.tsx b/packages/recoil/src/hooks/solana/usePlugins.tsx index e3cad97c7..152cdb2de 100644 --- a/packages/recoil/src/hooks/solana/usePlugins.tsx +++ b/packages/recoil/src/hooks/solana/usePlugins.tsx @@ -71,6 +71,7 @@ export function usePluginUrl(address?: string) { useEffect(() => { (async () => { if (address?.toString() === "11111111111111111111111111111111") { + console.log("usePluginUrl: Setting Simulator URL", { address }); setUrl("Simulator"); } else if (cached) { setUrl(cached.iframeRootUrl); @@ -83,7 +84,7 @@ export function usePluginUrl(address?: string) { } } })(); - }, [cached]); + }, [cached, address]); return url; } @@ -96,7 +97,8 @@ export function useFreshPlugin(address?: string): { const connectionUrls = useConnectionUrls(); const activePublicKeys = useActivePublicKeys(); const [result, setResult] = useState( - PLUGIN_CACHE.get(address ?? "") + // For Simulator, don't use cache to ensure fresh instance + address === "11111111111111111111111111111111" ? undefined : PLUGIN_CACHE.get(address ?? "") ); const [state, setState] = useState<"loading" | "done" | "error">("loading"); @@ -127,7 +129,10 @@ export function useFreshPlugin(address?: string): { request: setTransactionRequest, openPlugin, }); - PLUGIN_CACHE.set(address, plugin); + // For Simulator, don't cache to prevent stale connections + if (address !== "11111111111111111111111111111111") { + PLUGIN_CACHE.set(address, plugin); + } setResult(plugin); setState("done"); } catch (err) { @@ -144,6 +149,21 @@ export function useFreshPlugin(address?: string): { } export function getPlugin(p: any): Plugin { + // For Simulator, don't cache to prevent stale connections + if (p.install.account.xnft.toString() === "11111111111111111111111111111111") { + console.log("getPlugin: Creating fresh Simulator plugin instance", { xnftAddress: p.install.account.xnft.toString() }); + return new Plugin( + p.install.account.xnft, + p.install.publicKey, + p.url, + p.iconUrl, + p.splashUrls ?? {}, + p.title, + p.activeWallets, + p.connectionUrls + ); + } + let plug = PLUGIN_CACHE.get(p.install.account.xnft.toString()); if (!plug) { plug = new Plugin(