7.3. Optimistic UI Updates
In decentralized environments, transaction finality typically requires waiting for block confirmations, which introduces friction and disrupts user flow. Optimistic UI Updates resolve this by instantly reflecting user intent in the interface upon signature generation, while maintaining a secure, reversible state pipeline that automatically rolls back if the on-chain transaction fails, drops, or is replaced.
Fhenix-FairMarket v2.0 implements this pattern through a tightly coupled stack of Wagmi/Viem transaction monitoring, Zustand state persistence, and atomic rollback logic. Users experience <500ms feedback loops identical to centralized platforms, without compromising cryptographic finality or exposing themselves to stuck states during network congestion.
Core Design Principles
| Principle | Technical Implementation |
|---|---|
| Instant State Mutation | UI transitions to Processing or Confirmed immediately upon cryptographic signature, bypassing initial mempool latency. |
| Atomic Rollback Guarantee | If waitForTransactionReceipt returns reverted, dropped, or replaced, the interface automatically reverts to pre-signature state. |
| Zustand Draft Persistence | Bid amounts, escrow locks, and form inputs are cached locally until finality, preventing data loss on page refresh or network drop. |
| Bounded Latency Target | Optimistic feedback triggers in <500ms. Prolonged pending states trigger a graceful loading indicator rather than hard freezes. |
| Explicit Error Messaging | Rollbacks surface precise, user-friendly error codes (e.g., InsufficientEscrow, DynamicTimeoutExceeded) instead of raw EVM revert strings. |
️ Technical Implementation
1. Optimistic State Hook (useOptimisticUI.ts)
The core hook bridges Wagmi’s receipt monitoring with UI state management, ensuring deterministic transitions.
// packages/frontend/src/lib/hooks/useOptimisticUI.ts
import { useState, useEffect, useCallback } from 'react';
import { useWaitForTransactionReceipt } from 'wagmi';
import { useAuctionStore } from '@store/auction';
export function useOptimisticUI(txHash?: string) {
const [status, setStatus] = useState<'idle' | 'optimistic' | 'confirmed' | 'failed'>('idle');
const { setDraftBid, clearDraftBid } = useAuctionStore();
// Wagmi v2: Wait for on-chain receipt
const { data: receipt, isError, isLoading } = useWaitForTransactionReceipt({
hash: txHash,
confirmations: 1,
});
const rollback = useCallback(() => {
setStatus('failed');
clearDraftBid();
// Trigger UI toast/notification
}, [clearDraftBid]);
useEffect(() => {
if (!txHash) return;
if (isLoading) {
setStatus('optimistic');
} else if (receipt?.status === 'success') {
setStatus('confirmed');
} else if (isError || receipt?.status === 'reverted') {
rollback();
}
}, [txHash, receipt, isLoading, isError, rollback]);
return { status, rollback };
}2. Zustand Draft Store (@store/auction.ts)
Ensures user inputs survive page reloads, tab switches, or temporary network disconnections until finality.
// packages/frontend/src/lib/store/auction.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface AuctionState {
draftBid: { amount: string; auctionId: string } | null;
setDraftBid: (data: { amount: string; auctionId: string }) => void;
clearDraftBid: () => void;
optimisticQueue: string[];
}
export const useAuctionStore = create<AuctionState>()(
persist(
(set) => ({
draftBid: null,
setDraftBid: (data) => set({ draftBid: data }),
clearDraftBid: () => set({ draftBid: null }),
optimisticQueue: [],
}),
{ name: 'ffm-auction-drafts' } // Persist to sessionStorage/IndexedDB safely
)
);3. Integration in BidForm.tsx
The form orchestrates encryption, signature, optimistic feedback, and rollback in a single flow.
// packages/frontend/src/app/auction/[id]/components/BidForm.tsx
import { useOptimisticUI } from '@lib/hooks/useOptimisticUI';
import { useAuctionStore } from '@store/auction';
import { encryptBid } from '@lib/cofhe/encryption';
export function BidForm({ auctionId }: { auctionId: string }) {
const { status, rollback } = useOptimisticUI();
const { setDraftBid, clearDraftBid } = useAuctionStore();
const handlePlaceBid = async (amount: string) => {
setDraftBid({ amount, auctionId });
try {
const encryptedBid = await encryptBid(Number(amount));
const txHash = await fairMarket.write.placeBid([BigInt(auctionId), encryptedBid]);
// UI instantly reflects optimistic state
// useOptimisticUI hook automatically monitors txHash
} catch (err) {
rollback();
throw err;
}
};
return (
<form onSubmit={(e) => { e.preventDefault(); /* call handlePlaceBid */ }}>
{status === 'optimistic' && <Spinner label=" Encrypting & Submitting..." />}
{status === 'confirmed' && <SuccessToast label=" Bid sealed & confirmed on-chain" />}
{status === 'failed' && <ErrorToast label="️ Transaction reverted. Draft saved for retry." />}
</form>
);
}Optimistic UI Data Flow
️ UX & Security Guarantees
- Zero Stuck States: The
useOptimisticUIhook includes a timeout fallback (>30spending) that reverts to a neutral loading state and prompts manual reconnection, preventing UI freeze during extreme network congestion. - Gas Waste Prevention: Pre-flight validation (
escrow >= bid,euint32range) runs before signature. If optimistic submission somehow bypasses validation, chain-level revert triggers instant UI rollback without user confusion. - Draft Resilience: Zustand persistence ensures that if a user accidentally closes the tab mid-confirmation, reopening the auction page restores their exact bid input, allowing seamless retry without re-typing.
- Transparent Error Mapping: Raw EVM revert strings are intercepted and mapped to user-friendly messages:
"InsufficientFunds"→"Escrow balance too low. Please lock more funds.""AuctionExpired"→"Auction has closed. Funds remain in your wallet.""DynamicTimeoutExceeded"→"Network delay triggered auto-void. Full refund available."
Audit Gate Compliance (P0)
Progression through Phase 5 is strictly blocked until all P0 items pass:
- [] Instant Feedback: UI state changes to
optimisticwithin<500msof successful signature generation. - [] 100% Rollback Coverage:
revert,dropped,replaced, or timeout events trigger automatic state reversion without UI hang. - [] No Stale State Post-Reload: Zustand persistence accurately restores draft bids after page refresh or tab switch.
- [] Error Mapping Accuracy: All chain-level reverts map to clear, actionable user messages. Zero raw hex or EVM strings exposed.
- [] Gas Estimation Buffer: Dynamic gas calculation includes
×1.2safety margin to preventOut-of-Gasoptimistic failures. - [] Timeout Fallback: Pending transactions exceeding
30sgracefully degrade to a retry prompt instead of infinite loading.
Unresolved Points & Explicit Gaps
| Gap / Unresolved Point | Impact | Current Status | Recommended Action |
|---|---|---|---|
| Offline Queue & Resubmission | Can optimistic bids be queued locally and broadcast when connectivity returns? | Not implemented in Phase 5 specs. | Add Service Worker background sync to store signed UserOperation and retry when online. |
| Multi-Tab State Sync | Opening the same auction in two tabs may cause duplicate optimistic submissions. | Zustand is tab-scoped by default. | Implement BroadcastChannel API to sync draft state and block duplicate signatures across tabs. |
| Bundler Rejection Handling | If ERC-4337 Bundler rejects UserOperation before mempool entry, Wagmi receipt hook may timeout silently. | Fallback logic exists but lacks explicit Bundler error parsing. | Add bundlerResponse interceptor to parse reason field and map to UI toast before chain timeout. |
Fact vs. Analysis Distinction: The
<500mstarget, Zustand persistence, Wagmi receipt monitoring, and 100% rollback requirement are verified facts fromPhase 5 Sub-Tasks Matrix(Task 5.2) and theREADME.mdUX 2.0 principles. Offline queuing, multi-tab sync, and Bundler rejection parsing are unresolved UX enhancements requiring explicit definition before Mainnet launch.
Next Steps
- Proceed to 7.4. Confidence Dashboard to understand progressive disclosure, safe metric visualization, and real-time state tracking.
- Review Security Model → Session Key Isolation for Web Crypto API implementation and XSS mitigation strategies.
- Explore Developer Quickstart → Frontend Setup for local environment configuration and Wagmi/Viem integration testing.