Skip to content
Open
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
4,898 changes: 4,115 additions & 783 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
"@vitest/coverage-v8": "3.2.4",
"autoprefixer": "10.4.19",
"buffer": "6.0.3",
"eruda": "^3.4.3",
"eslint": "8.57.0",
"eslint-config-airbnb": "19.0.4",
"eslint-config-airbnb-typescript": "18.0.0",
Expand Down
34 changes: 34 additions & 0 deletions src/apps/pulse/components/App/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ export default function HomeScreen(props: HomeScreenProps) {
const { intentSdk } = useIntentSdk();
const [previewBuy, setPreviewBuy] = useState(false);
const [previewSell, setPreviewSell] = useState(false);

// Debug: Log previewBuy state changes
useEffect(() => {
console.log('=== HOMESCREEN: previewBuy state changed to:', previewBuy);
}, [previewBuy]);
const [transactionStatus, setTransactionStatus] = useState(false);
const [userOpHash, setUserOpHash] = useState<string>('');
const [transactionGasFee, setTransactionGasFee] = useState<string>('≈ $0.00');
Expand Down Expand Up @@ -286,16 +291,26 @@ export default function HomeScreen(props: HomeScreenProps) {
}, [selectedChainIdForSettlement, maxStableCoinBalance]);

const handleRefresh = useCallback(async () => {
// console.log('=== HOMESCREEN: handleRefresh called ===');
// console.log('isRefreshingHome:', isRefreshingHome);
// console.log('isSellFlowPaused:', isSellFlowPaused);
// console.log('isBuy:', isBuy);
// console.log('previewBuy:', previewBuy);
// console.log('buyRefreshCallback:', !!buyRefreshCallback);

// Prevent multiple simultaneous refresh calls
if (isRefreshingHome || isSellFlowPaused) {
console.log('Refresh blocked: already refreshing or sell flow paused');
return;
}

setIsRefreshingHome(true);

try {
// Always refresh wallet portfolio
// console.log('Refreshing wallet portfolio...');
await refetchWalletPortfolio();
// console.log('Wallet portfolio refreshed');

// If we have the required data, refresh the sell offer
if (
Expand All @@ -305,6 +320,7 @@ export default function HomeScreen(props: HomeScreenProps) {
isInitialized &&
!isSellFlowPaused
) {
// console.log('Refreshing sell offer...');
try {
const newOffer = await getBestSellOffer({
fromAmount: tokenAmount,
Expand All @@ -314,6 +330,7 @@ export default function HomeScreen(props: HomeScreenProps) {
toChainId: selectedChainIdForSettlement,
});
setSellOffer(newOffer);
// console.log('Sell offer refreshed:', newOffer);
} catch (error) {
console.error('Failed to refresh sell offer:', error);
setSellOffer(null);
Expand All @@ -323,12 +340,21 @@ export default function HomeScreen(props: HomeScreenProps) {
// If we have the required data, refresh the buy intent
// Only refresh if PreviewBuy is not open (to avoid duplicate calls)
if (isBuy && buyRefreshCallback && !previewBuy) {
// console.log('=== HOMESCREEN: Calling buyRefreshCallback ===');
await buyRefreshCallback();
// console.log('=== HOMESCREEN: buyRefreshCallback completed ===');
} else {
console.log('Buy refresh skipped - conditions not met:', {
isBuy,
hasBuyRefreshCallback: !!buyRefreshCallback,
previewBuy,
});
}
} catch (error) {
console.error('Refresh failed:', error);
} finally {
setIsRefreshingHome(false);
console.log('=== HOMESCREEN: handleRefresh completed ===');
}
}, [
refetchWalletPortfolio,
Expand Down Expand Up @@ -660,6 +686,7 @@ export default function HomeScreen(props: HomeScreenProps) {
try {
if (isBuyTransaction) {
if (!intentSdk) {
console.error('Intent SDK not initialized');
return;
}
Comment on lines 686 to 691
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Prevent endless polling loop when Intent SDK is not initialized

In the buy‑polling branch:

if (isBuyTransaction) {
  if (!intentSdk) {
    console.error('Intent SDK not initialized');
    return;
  }
  // ...
}

If intentSdk is null, this logs an error and returns, but:

  • isPollingActive / isBackgroundPolling are not changed, and
  • The outer setInterval(pollStatus, 2000) continues to run,

so you’ll keep logging every 2s and the transaction status never moves to a final state.

Consider either preventing polling from starting when intentSdk is missing, or failing fast here by stopping polling and surfacing an error, e.g.:

-        if (isBuyTransaction) {
-          if (!intentSdk) {
-            console.error('Intent SDK not initialized');
-            return;
-          }
+        if (isBuyTransaction) {
+          if (!intentSdk) {
+            console.error('Intent SDK not initialized – stopping polling');
+            setIsPollingActive(false);
+            setIsBackgroundPolling(false);
+            setCurrentTransactionStatus('Transaction Failed');
+            setErrorDetails('Unable to fetch buy transaction status.');
+            return;
+          }

You could also guard the whole polling effect so it never starts a buy‑poll interval until intentSdk is truthy.



🏁 Script executed:

#!/bin/bash
# Find the polling effect in HomeScreen.tsx and the setInterval call
rg -n -A 30 "setInterval.*pollStatus" src/apps/pulse/components/App/HomeScreen.tsx

Length of output: 833


🏁 Script executed:

#!/bin/bash
# Look for the polling effect definition and dependencies around lines 686-691
rg -n -B 20 -A 20 "if \(!intentSdk\)" src/apps/pulse/components/App/HomeScreen.tsx | head -80

Length of output: 1618


🏁 Script executed:

#!/bin/bash
# Find where isPollingActive and isBackgroundPolling are set/used in the polling context
rg -n "setIsPollingActive|setIsBackgroundPolling" src/apps/pulse/components/App/HomeScreen.tsx

Length of output: 832


🏁 Script executed:

#!/bin/bash
# Look for the overall polling effect structure
ast-grep --pattern $'useEffect(() => {
  $$$
  setInterval($$$)
  $$$
}, $_)'  src/apps/pulse/components/App/HomeScreen.tsx

Length of output: 0


Prevent endless polling loop when Intent SDK is not initialized

In the buy‑polling effect (line 671):

The effect's early return guard at line 676 does not check for intentSdk:

if (!userOpHash || !chainId || (!isPollingActive && !isBackgroundPolling)) {
  return undefined;
}

If intentSdk is null, this does not prevent the polling interval from starting at line 846. Once running, pollStatus will be called every 2 seconds, hit the check at lines 688–691, log an error, and return—but the interval continues indefinitely.

The suggested fix is valid: either add !intentSdk to the early return condition, or stop polling inside pollStatus when intentSdk is missing:

      try {
        if (isBuyTransaction) {
          if (!intentSdk) {
            console.error('Intent SDK not initialized – stopping polling');
            setIsPollingActive(false);
            setIsBackgroundPolling(false);
            setCurrentTransactionStatus('Transaction Failed');
            setErrorDetails('Unable to fetch buy transaction status.');
            return;
          }

Alternatively, add !intentSdk to the effect's early return guard at line 676 to prevent the interval from starting at all.

🤖 Prompt for AI Agents
In src/apps/pulse/components/App/HomeScreen.tsx around lines 686 to 691, the
buy-polling effect can start an interval even when intentSdk is null, causing
endless polling that only logs an error; modify the effect's early return
condition (the guard a few lines above) to include a check for !intentSdk so the
interval never starts when intentSdk is missing, or alternatively add logic at
the start of pollStatus to clear/stop the interval when intentSdk is null —
implement the first option by adding !intentSdk to the existing OR condition so
the effect returns undefined and does not set the polling interval.


Expand Down Expand Up @@ -881,7 +908,14 @@ export default function HomeScreen(props: HomeScreenProps) {
}, [isBuy, buyRefreshCallback, previewBuy, handleRefresh]);

const renderPreview = () => {
console.log('=== HOMESCREEN: renderPreview called ===');
console.log('previewBuy:', previewBuy);
console.log('previewSell:', previewSell);
console.log('transactionStatus:', transactionStatus);
console.log('displaySettingsMenu:', displaySettingsMenu);

if (previewBuy) {
console.log('=== HOMESCREEN: Rendering PreviewBuy ===');
return (
<div className="w-full flex justify-center px-3 md:p-3 mb-[70px]">
<PreviewBuy
Expand Down
60 changes: 59 additions & 1 deletion src/apps/pulse/components/Buy/Buy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,24 @@ export default function Buy(props: BuyProps) {
};

const handleBuySubmit = async () => {
console.log('=== BUY BUTTON CLICKED ===');
console.log('areModulesInstalled:', areModulesInstalled);
console.log('expressIntentResponse:', expressIntentResponse);
console.log('token:', token);
console.log('debouncedUsdAmount:', debouncedUsdAmount);
console.log('payingTokens:', payingTokens);

if (!areModulesInstalled) {
console.log('Modules not installed, installing...');
await installModules();
console.log('Modules installation complete');
} else {
console.log('Modules already installed, navigating to preview...');
console.log('Setting expressIntentResponse to:', expressIntentResponse);
setExInResp(expressIntentResponse);
console.log('Setting previewBuy to true');
setPreviewBuy(true);
console.log('=== PREVIEW BUY SHOULD NOW BE VISIBLE ===');
}
};

Expand All @@ -205,26 +218,38 @@ export default function Buy(props: BuyProps) {
setNoEnoughLiquidity(false);
setInsufficientWalletBalance(false);
setDebouncedUsdAmount(usdAmount);
console.log(
'=== BUY: Calculating dispensable assets for usdAmount:',
usdAmount
);
const [dAssets, pChains, pTokens] = getDispensableAssets(
usdAmount,
walletPortfolioData?.result.data,
maxStableCoinBalance.chainId
);
console.log('=== BUY: Dispensable assets calculated ===');
console.log('dispensableAssets:', dAssets);
console.log('permittedChains:', pChains);
console.log('payingTokens:', pTokens);

if (
pChains.length === 0 ||
dAssets.length === 0 ||
pTokens.length === 0
) {
console.log('BUY: Not enough liquidity - no assets/chains/tokens');
setNoEnoughLiquidity(true);
return;
}

// Always update payingTokens to ensure correct USD amounts are passed to PreviewBuy
console.log('=== BUY: Updating state with calculated values ===');
setDispensableAssets(dAssets);
setPermittedChains(pChains);
setPayingTokens(pTokens);
setParentDispensableAssets(dAssets);
setParentUsdAmount(usdAmount);
console.log('=== BUY: State updated ===');
}
}, 1000);

Expand All @@ -239,8 +264,17 @@ export default function Buy(props: BuyProps) {
]);

const refreshBuyIntent = useCallback(async () => {
console.log('=== BUY: refreshBuyIntent called ===');
// console.log('isLoading:', isLoading);
// console.log('debouncedUsdAmount:', debouncedUsdAmount);
// console.log('token:', token);
// console.log('accountAddress:', accountAddress);
console.log('intentSdk:', intentSdk);
// console.log('areModulesInstalled:', areModulesInstalled);

// Prevent multiple simultaneous calls
if (isLoading) {
console.log('BUY: Refresh blocked - already loading');
return;
}

Expand All @@ -252,13 +286,17 @@ export default function Buy(props: BuyProps) {
!areModulesInstalled ||
parseFloat(debouncedUsdAmount) <= 0
) {
console.log('BUY: Refresh blocked - missing required data');
return;
}

const inputAmount = parseFloat(debouncedUsdAmount);
console.log('inputAmount:', inputAmount);
console.log('maxStableCoinBalance:', maxStableCoinBalance);

// Check if input amount higher than stable currency balance
if (inputAmount > maxStableCoinBalance.balance) {
console.log('BUY: Insufficient wallet balance');
setInsufficientWalletBalance(true);
setNoEnoughLiquidity(false);
return;
Expand All @@ -267,13 +305,19 @@ export default function Buy(props: BuyProps) {
// Reset stable currency balance error if first check passes
setInsufficientWalletBalance(false);

console.log('dispensableAssets:', dispensableAssets);
console.log('permittedChains:', permittedChains);
console.log('payingTokens:', payingTokens);

// Check if enough dispensable assets liquidity
if (dispensableAssets.length === 0 && permittedChains.length === 0) {
console.log('BUY: Not enough liquidity');
setNoEnoughLiquidity(true);
return;
}

if (notEnoughLiquidity) {
console.log('BUY: Blocked by notEnoughLiquidity flag');
return;
}

Expand Down Expand Up @@ -303,13 +347,27 @@ export default function Buy(props: BuyProps) {
'0x000000000000000000000000000000000000000000000000000000000000000',
account: accountAddress as Hex,
};
console.log('=== BUY: Calling intentSdk.expressIntent ===');
console.log(
'Intent:',
JSON.stringify(
intent,
(key, value) =>
typeof value === 'bigint' ? value.toString() : value,
2
)
);
const response = await intentSdk.expressIntent(intent);
console.log('=== BUY: expressIntent response received ===');
console.log('Response bids count:', response?.bids?.length || 0);
console.log('Response:', response);
setExpressIntentResponse(response);
} catch (error) {
console.error('express intent failed:: ', error);
console.error('=== BUY: express intent failed ===', error);
setExpressIntentResponse(null);
} finally {
setIsLoading(false);
console.log('=== BUY: refreshBuyIntent completed ===');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
Expand Down
18 changes: 12 additions & 6 deletions src/apps/pulse/components/Buy/BuyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,28 +107,34 @@ export default function BuyButton(props: BuyButtonProps) {

const isDisabled = () => {
if (isInstalling || isFetching) {
console.log('Button disabled: installing or fetching');
return true;
}
if (notEnoughLiquidity) {
console.log('Button disabled: not enough liquidity');
return true;
}
if (!areModulesInstalled && payingTokens.length > 0) {
console.log('Button enabled: modules need installation');
return false;
}
return (

const disabled =
isLoading ||
!token ||
!(parseFloat(usdAmount) > 0) ||
!expressIntentResponse ||
!!(expressIntentResponse as { error: string }).error ||
(expressIntentResponse as ExpressIntentResponse)?.bids?.length === 0
);
!expressIntentResponse;

console.log('Final disabled state:', disabled);
return disabled;
};

return (
<button
className="flex-1 items-center justify-center"
onClick={handleBuySubmit}
onClick={() => {
handleBuySubmit();
}}
disabled={isDisabled()}
style={{
backgroundColor: isDisabled() ? '#29292F' : '#8A77FF',
Expand Down
Loading
Loading