5 KiB
5 KiB
KRK Balance Loading Bug - Root Cause Analysis
Summary
After swapping ETH→KRK on the cheats page, the KRK token balance takes 10-90+ seconds to load (or never loads), causing "Insufficient Balance" errors on the stake page. This blocked 2 of 5 user test personas from staking.
Root Cause
The Problem
The useWallet() composable fetches the KRK token balance only when:
- The wallet account changes (address or chainId)
- The blockchain chain changes
There is NO polling mechanism and no automatic refresh after transactions.
The Flow
- User swaps ETH→KRK in
CheatsView.vueviabuyKrk() - The swap transaction completes successfully
buyKrk()does NOT callwallet.loadBalance()after the transaction- User navigates to Stake page
- Navigation doesn't trigger account/chain change events
StakeHolder.vuereads stalewallet.balance.value(still 0 KRK)maxStakeAmountcomputed property returns 0- User sees "Insufficient Balance"
Code Evidence
useWallet.ts (lines 82-98):
async function loadBalance() {
logger.contract('loadBalance');
const userAddress = account.value.address;
if (!userAddress) {
return 0n;
}
let publicClient = getWalletPublicClient();
if (!publicClient) {
publicClient = await syncWalletPublicClient();
}
if (!publicClient) {
return 0n;
}
const value = (await publicClient.readContract({
abi: HarbContract.abi,
address: HarbContract.contractAddress,
functionName: 'balanceOf',
args: [userAddress],
})) as bigint;
// ... sets balance.value
}
Balance refresh triggers (lines 102-154):
watchAccount()- only on address/chainId changewatchChainId()- only on explicit chain switch- NO interval polling
- NO transaction completion hooks
CheatsView.vue buyKrk() (lines ~941-1052):
async function buyKrk() {
// ... performs swap transaction
await writeContract(wagmiConfig, {
abi: SWAP_ROUTER_ABI,
address: router,
functionName: 'exactInputSingle',
args: [/* swap params */],
chainId,
});
toast.success('Swap submitted. Watch your wallet for KRK.');
// ❌ MISSING: wallet.loadBalance() call here!
} finally {
swapping.value = false;
}
StakeHolder.vue (lines 220-227):
const maxStakeAmount = computed(() => {
if (wallet.balance?.value) {
return bigInt2Number(wallet.balance.value, 18);
}
return 0; // ❌ Returns 0 when balance is stale
});
Solution
Quick Fix (Recommended)
Add wallet.loadBalance() call after the swap transaction completes in CheatsView.vue:
async function buyKrk() {
if (!canSwap.value || swapping.value) return;
try {
swapping.value = true;
// ... existing swap logic ...
await writeContract(wagmiConfig, {
abi: SWAP_ROUTER_ABI,
address: router,
functionName: 'exactInputSingle',
args: [/* swap params */],
chainId,
});
// ✅ FIX: Refresh KRK balance after swap
const { loadBalance } = useWallet();
await loadBalance();
toast.success('Swap submitted. Watch your wallet for KRK.');
} catch (error: unknown) {
toast.error(getErrorMessage(error, 'Swap failed'));
} finally {
swapping.value = false;
}
}
Alternative Solutions (For Consideration)
-
Add polling to useWallet():
- Poll balance every 5-10 seconds when wallet is connected
- Pro: Auto-updates across all pages
- Con: Increased RPC calls, may hit rate limits
-
Vue Router navigation guard:
- Refresh balance on route enter
- Pro: Works for all navigation patterns
- Con: Slight delay on every page load
-
Event bus for transaction completion:
- Emit event after any token-affecting transaction
- Subscribe in useWallet to refresh balance
- Pro: Clean separation of concerns
- Con: More complex architecture
Impact
Severity: CRITICAL
- Blocks core user flow (swap → stake)
- 40% of test users affected (2/5 personas)
- Confusing UX ("I just bought tokens, why can't I stake?")
User Experience Issues
- No loading indicator for balance refresh
- No error message explaining the delay
- Users don't know to wait or refresh page
- Some users never see balance load (likely due to wallet/RPC issues)
Recommended Action
- ✅ Immediate: Implement the quick fix (add
loadBalance()call after swap) - 🔍 Investigation: Test why some balances "never" load - possible RPC/wallet issues?
- 💡 Future: Consider adding a manual "Refresh Balance" button on stake page
- 📊 Monitoring: Track balance load times in production
Testing Checklist
- Swap ETH→KRK on cheats page
- Immediately navigate to stake page
- Verify KRK balance displays correctly within 1-2 seconds
- Test with multiple wallet providers (MetaMask, Coinbase Wallet)
- Test with slow RPC endpoints
- Verify balance updates after page navigation
Files Modified
/home/debian/harb/web-app/src/views/CheatsView.vue(add loadBalance call)