Skip to content
Open
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
11 changes: 9 additions & 2 deletions packages/paywall/src/browser/useStellarBalance.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { AssembledTransaction } from "@stellar/stellar-sdk/contract";
import { nativeToScVal, scValToNative } from "@stellar/stellar-sdk";
import type { Network } from "@x402/core/types";
Expand Down Expand Up @@ -45,6 +45,7 @@ export function useStellarBalance({
onStatus,
}: UseBalanceParams): UseBalanceReturn {
const runtimeRpcUrl = window.x402?.config?.rpcUrl;
const cancelledRef = useRef(false);
const [tokenBalanceRaw, setTokenBalanceRaw] = useState<bigint | null>(null);
const [tokenBalanceFormatted, setTokenBalanceFormatted] = useState<string>("");
const [isFetchingBalance, setIsFetchingBalance] = useState(false);
Expand Down Expand Up @@ -81,7 +82,7 @@ export function useStellarBalance({
const name = scValToNative(nameTx.result) as string;
const parts = name.split(":");

if (parts.length === 2) {
if (parts.length === 2 && !cancelledRef.current) {
setAssetMetadata({ code: parts[0], issuer: parts[1] });
}
} catch (error) {
Expand Down Expand Up @@ -138,13 +139,15 @@ export function useStellarBalance({
// Format the balance
const balanceFormatted = formatUnits(balanceRaw, decimals);

if (cancelledRef.current) return "";
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cancellation guard at line 142 returns early from the try block, and the one at line 150 returns early from the catch block, but the finally block (line 160-162) still calls setIsFetchingBalance(false) unconditionally. Since finally always runs regardless of early returns, this state update will still fire after unmount/cancellation, undermining the purpose of these guards. You should add a cancelledRef.current check inside the finally block as well, or restructure so that the setIsFetchingBalance(false) call is placed before each return instead of in finally.

Copilot uses AI. Check for mistakes.
setTokenBalanceRaw(balanceRaw);
setTokenBalanceFormatted(balanceFormatted);
setIsMissingTrustline(false);
return balanceFormatted;
} catch (error) {
console.error("Failed to fetch Stellar USDC balance", error);
const msg = error instanceof Error ? error.message : "Unable to read balance. Please retry.";
if (cancelledRef.current) return "";
const isTrustlineError = msg.includes("trustline entry is missing for account");
if (isTrustlineError) {
setIsMissingTrustline(true);
Expand All @@ -160,9 +163,13 @@ export function useStellarBalance({
}, [address, network, asset, onStatus, resetBalance, fetchAssetMetadata, runtimeRpcUrl]);

useEffect(() => {
cancelledRef.current = false;
if (address) {
void refreshBalance();
}
return () => {
cancelledRef.current = true;
};
}, [address, refreshBalance]);

return {
Expand Down
Loading