This implementation adds a highly visible slippage warning system for NGN/USD FX rate volatility during payment confirmation in the SwiftChain escrow system. The feature monitors FX rate changes and alerts users when rates shift dramatically, requiring explicit acknowledgment for critical volatility (>5%).
The implementation follows a Component → Hook → Service layered architecture pattern consistent with the SwiftChain frontend codebase:
FiatXlmPreview (Component)
↓
useFiatXlmSlippage (Hook)
↓
fiatXlmSlippageService (Service)
↓
fxService (Backend Integration)
Handles rate tracking and slippage calculations:
startTracking(initialRate)- Initialize quote tracking with the rate shown to the userrecordRateUpdate(currentRate)- Record new rate updates from the APIcalculateSlippage(currentRate)- Calculate slippage percentage and warning thresholdsstopTracking()- Reset tracking state (on cancel or completion)
Thresholds:
- Warning Level: > 2% variance
- Critical Level: > 5% variance (requires checkbox acknowledgment)
Features:
- 60-second rate history with automatic pruning
- In-memory state management
- Zero external dependencies
// Service Usage Example
fiatXlmSlippageService.startTracking(1000); // Quote at 1000 NGN per XLM
fiatXlmSlippageService.recordRateUpdate(1030); // Rate updated to 1030
const slippage = fiatXlmSlippageService.calculateSlippage(1030);
// Result: { slippagePercent: 3, isVolatile: true, requiresAcknowledgment: false, ... }React hook integrating the service with React Query for real-time rate monitoring:
- State Management: Tracks quoted rate, slippage, and acknowledgment state
- Rate Polling: Re-fetches FX rates every 60 seconds via React Query
- Lifecycle Management: Automatically updates slippage calculations as rates change
Returned Values:
{
quotedRate: number | null,
slippage: SlippageResult | null,
isVolatile: boolean,
requiresAcknowledgment: boolean,
isAcknowledged: boolean,
setAcknowledged: (acked: boolean) => void,
startQuote: (rate: number) => void,
stopQuote: () => void,
isLoadingRate: boolean
}React component displaying payment preview with integrated slippage warnings:
Features:
- Payment details display (XLM amount, quoted NGN, current rate)
- Automatic rate change detection and trending indicators
- Two-tier warning system:
- Warning (2%-5%): Icon + descriptive text
- Critical (>5%): Alert box + required checkbox
- Loading states during rate fetching
- Submission blocking until acknowledgment when critical
- Dark mode support
- Full accessibility (ARIA labels, semantic HTML)
Props:
interface FiatXlmPreviewProps {
xlmAmount: number;
quotedNgnAmount: number;
currentRate: number | null;
onConfirm: () => Promise<void> | void;
onCancel: () => void;
isSubmitting?: boolean;
confirmLabel?: string;
cancelLabel?: string;
}'use client';
import { useState, useEffect } from 'react';
import { FiatXlmPreview } from '@/components/escrow/FiatXlmPreview';
export function PaymentFlow() {
const [xlmAmount] = useState(10);
const [quotedNgnAmount] = useState(10000); // 1000 NGN per XLM
const [currentRate, setCurrentRate] = useState(1000);
const [isSubmitting, setIsSubmitting] = useState(false);
const handleConfirm = async () => {
setIsSubmitting(true);
try {
// Proceed with payment
await submitPayment();
} finally {
setIsSubmitting(false);
}
};
const handleCancel = () => {
// Return to previous screen
navigate('/back');
};
return (
<FiatXlmPreview
xlmAmount={xlmAmount}
quotedNgnAmount={quotedNgnAmount}
currentRate={currentRate}
onConfirm={handleConfirm}
onCancel={handleCancel}
isSubmitting={isSubmitting}
confirmLabel="Confirm Payment"
cancelLabel="Cancel"
/>
);
}import { useFiatXlmSlippage } from '@/hooks/useFiatXlmSlippage';
export function PaymentPreview() {
const slippage = useFiatXlmSlippage();
useEffect(() => {
// Start tracking when component mounts
slippage.startQuote(currentRate);
return () => {
// Stop tracking on unmount
slippage.stopQuote();
};
}, [currentRate]);
if (slippage.requiresAcknowledgment) {
// Show critical warning with checkbox
}
if (slippage.isVolatile) {
// Show warning with trending indicator
}
return (
<div>
{/* Your component UI */}
{slippage.isAcknowledged && (
<p>User acknowledged volatility</p>
)}
</div>
);
}Rate when quoted: 1000 NGN/XLM
Current rate: 1015 NGN/XLM
Slippage: +1.5%
Result: No warning displayed
User can confirm immediately
Rate when quoted: 1000 NGN/XLM
Current rate: 1030 NGN/XLM
Slippage: +3%
Result: Warning icon + text displayed
Example: "FX Rate Volatility Detected - The rate has shifted 3.00%"
User can confirm with awareness
Rate when quoted: 1000 NGN/XLM
Current rate: 1060 NGN/XLM
Slippage: +6%
Result:
1. Alert box with critical warning
2. Checkbox required: "I acknowledge the rate volatility and accept the current rate"
3. Submit button DISABLED until checkbox is checked
4. User must explicitly acknowledge before confirming
- Refresh Interval: 60 seconds (matches backend cache TTL)
- History Window: 60 seconds of rate data retained
- Pruning: Automatic removal of stale entries (>60s old)
slippagePercent = ((currentRate - quotedRate) / quotedRate) * 100
// Examples:
// Rate 1000 → 1050: ((1050 - 1000) / 1000) * 100 = 5%
// Rate 1000 → 950: ((950 - 1000) / 1000) * 100 = -5%The service maintains in-memory state:
{
quotedRate: 1000, // Initial quoted rate
quotedAt: 1719676500000, // Timestamp when quote started
rateHistory: [ // Last 60 seconds of rates
{ ngnPerXlm: 1000, timestamp: 1719676500000 },
{ ngnPerXlm: 1010, timestamp: 1719676530000 },
{ ngnPerXlm: 1020, timestamp: 1719676560000 },
// ... more entries
]
}1. User initiates payment confirmation
↓
2. FiatXlmPreview component renders with quoted rate
↓
3. useFiatXlmSlippage hook starts tracking:
- fiatXlmSlippageService.startTracking(quotedRate)
- React Query begins polling fxService for rate updates
↓
4. Every 60 seconds (or on rate change):
- New rate fetched from backend via fxService
- fiatXlmSlippageService.recordRateUpdate(newRate)
- Slippage calculated via calculateSlippage()
↓
5. Component re-renders based on slippage state:
- isVolatile: true → Show warning message
- requiresAcknowledgment: true → Show checkbox + block submission
↓
6. User acknowledges (if needed) and confirms
↓
7. onConfirm callback executed
↓
8. Tracking stopped via stopTracking()
The implementation is defensive against edge cases:
- Null rates: Returns
nullfromcalculateSlippage()until tracking starts - Loading states: Component displays "Checking current rates…" while fetching
- API failures: Falls back to showing known rates, retries automatically
- Stale history: Automatically pruned entries older than 60 seconds
Covers:
- Rate tracking initialization and lifecycle
- Positive and negative slippage calculations
- Threshold detection (2% and 5%)
- History pruning and state management
- Edge cases (small/large changes, zero slippage)
Covers:
- Payment details rendering
- Warning display based on slippage levels
- Checkbox behavior for critical warnings
- Submission blocking when unchecked
- Button state management
- Rate change indicators (trending icons)
- Loading and success states
# Run service tests
npm test -- --testPathPattern="fiatXlmSlippageService"
# Run component tests
npm test -- --testPathPattern="FiatXlmPreview"
# Run all escrow tests
npm test -- services/__tests__/fiatXlmSlippageService.test.ts
npm test -- components/escrow/__tests__/FiatXlmPreview.test.tsx┌─────────────────────────────────┐
│ Payment Confirmation │
│ Review before confirming... │
│ │
│ Amount: 10.00 XLM │
│ Quoted Rate: ₦10,000 │
│ │
│ [Cancel] [Confirm Payment] │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ Payment Confirmation │
│ Review before confirming... │
│ │
│ Amount: 10.00 XLM │
│ Quoted Rate: ₦10,000 │
│ Current Rate: ₦10,300 │
│ │
│ ⚠️ FX Rate Volatility Detected │
│ The rate has shifted 3.00% │
│ │
│ [Cancel] [Confirm Payment] │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ Payment Confirmation │
│ Review before confirming... │
│ │
│ Amount: 10.00 XLM │
│ Quoted Rate: ₦10,000 │
│ Current Rate: ₦10,600 │
│ │
│ ⚠️ High FX Rate Volatility │
│ The rate has shifted > 5% │
│ You must acknowledge this │
│ │
│ ☐ I acknowledge the volatility │
│ and accept the current rate │
│ │
│ [Cancel] [Confirm Payment] │
│ (DISABLED) │
└─────────────────────────────────┘
- Semantic HTML with proper heading hierarchy
- ARIA labels and descriptions for all interactive elements
role="alert"on warning messages for screen readers- High contrast colors (AA compliant)
- Keyboard navigation support
- Loading and state indicators
- Memory: O(60) entries in history (minimal overhead)
- Computation: O(1) slippage calculation
- Network: Single API call every 60 seconds (matches existing rate polling)
- Re-renders: Only when slippage state changes (optimized with React Query)
- Modern browsers (ES2020+)
- Chrome, Firefox, Safari, Edge
- Mobile browsers (responsive design)
The service relies on the existing fxService for rate data:
// fxService returns
{
ngnPerXlm: 1000,
updatedAt: "2024-06-29T18:42:00Z"
}No new API endpoints required. Uses existing:
GET /api/currency-rates?fiat=NGN(via currencyRateService)
- No database changes required - Uses in-memory state
- No new environment variables - Reuses existing FX rate endpoint
- Backward compatible - Doesn't affect existing escrow flows
- Feature flag ready - Can be conditionally rendered
- Zero breaking changes - Optional component
- Configurable thresholds via environment variables
- Historical slippage tracking for user analytics
- Slippage notifications in NotificationCenter
- Multi-currency support (not just NGN)
- Predictive volatility alerts using historical data
SwiftChain-Frontend/
├── services/
│ ├── fiatXlmSlippageService.ts # Service layer
│ └── __tests__/
│ └── fiatXlmSlippageService.test.ts # Service tests
├── hooks/
│ └── useFiatXlmSlippage.ts # Hook layer
├── components/escrow/
│ ├── FiatXlmPreview.tsx # Component
│ └── __tests__/
│ └── FiatXlmPreview.test.tsx # Component tests
- Ensure
startQuote()is called with correct initial rate - Check that React Query is fetching rates (check Network tab)
- Verify
requiresAcknowledgmentis true (slippage > 5%) - Check that
isAcknowledgedis tracked in component state
- Verify
fxService.getNgnXlmRate()returns valid data - Check React Query refetch interval (should be 60s)
- Inspect browser console for API errors
For issues or questions:
- Check test files for usage examples
- Review the issue requirements in GitHub (#[issue_id])
- Refer to existing escrow components (PaymentLock, EscrowLock) for patterns