harb/tmp/usertest-results/BALANCE-BUG-ANALYSIS.md

168 lines
5 KiB
Markdown
Raw Normal View History

2026-02-18 00:19:05 +01:00
# 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:
1. The wallet account changes (address or chainId)
2. The blockchain chain changes
**There is NO polling mechanism** and **no automatic refresh after transactions**.
### The Flow
1. User swaps ETH→KRK in `CheatsView.vue` via `buyKrk()`
2. The swap transaction completes successfully
3. **`buyKrk()` does NOT call `wallet.loadBalance()`** after the transaction
4. User navigates to Stake page
5. Navigation doesn't trigger account/chain change events
6. `StakeHolder.vue` reads stale `wallet.balance.value` (still 0 KRK)
7. `maxStakeAmount` computed property returns 0
8. User sees "Insufficient Balance"
### Code Evidence
**useWallet.ts (lines 82-98):**
```typescript
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 change
- `watchChainId()` - only on explicit chain switch
- **NO interval polling**
- **NO transaction completion hooks**
**CheatsView.vue buyKrk() (lines ~941-1052):**
```typescript
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):**
```typescript
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`:
```typescript
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)
1. **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
2. **Vue Router navigation guard:**
- Refresh balance on route enter
- Pro: Works for all navigation patterns
- Con: Slight delay on every page load
3. **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
1. No loading indicator for balance refresh
2. No error message explaining the delay
3. Users don't know to wait or refresh page
4. Some users never see balance load (likely due to wallet/RPC issues)
## Recommended Action
1.**Immediate:** Implement the quick fix (add `loadBalance()` call after swap)
2. 🔍 **Investigation:** Test why some balances "never" load - possible RPC/wallet issues?
3. 💡 **Future:** Consider adding a manual "Refresh Balance" button on stake page
4. 📊 **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)