diff --git a/docs.json b/docs.json
index 62899d1..20f18d6 100644
--- a/docs.json
+++ b/docs.json
@@ -1,6 +1,6 @@
{
"$schema": "https://mintlify.com/docs.json",
- "theme": "mint",
+ "theme": "aspen",
"name": "Request Network Docs",
"colors": {
"primary": "#01B089",
@@ -36,15 +36,17 @@
},
"navigation": {
"tabs": [
- {
- "tab": "Welcome",
- "pages": ["index"]
- },
{
"tab": "Use Cases",
"groups": [
{
- "group": "📄 Primary Use Cases",
+ "group": "🚀 Get Started",
+ "pages": [
+ "use-cases/welcome"
+ ]
+ },
+ {
+ "group": "🎯 Use Cases",
"pages": [
"use-cases/invoicing",
"use-cases/payouts",
diff --git a/index.mdx b/index.mdx
deleted file mode 100644
index 986a08d..0000000
--- a/index.mdx
+++ /dev/null
@@ -1,84 +0,0 @@
----
-title: "Request Network Docs"
-description: "A protocol for requests, payments, and **100% automated reconciliation**. Requests add business context to payments, **eliminating manual accounting** with cryptographic certainty."
-sidebarTitle: "Welcome"
-mode: "center"
----
-
-## Interactive Demo
-
-
- Experience how creating an invoice and getting paid is better than asking someone to send you money and hoping they remember to tell you they sent it.
-
- *Interactive demo will be embedded here*
-
-
-## Get Started
-
-Choose your path based on what you want to build:
-
-
-
- Explore specific business scenarios: invoicing, payouts, payroll, checkout, and subscriptions
-
-
-
- The easiest way to integrate. Payment types, webhooks, and developer tools.
-
-
-
- Supported chains, currencies, smart contracts, and community resources
-
-
-
-
-
- Legacy SDK and protocol documentation for advanced users
-
-
-
- Stay updated with the latest features and improvements
-
-
-
-## Popular
-
-
-
- 1. [Get API Keys](/api-setup/getting-started) (2 minutes)
- 2. [Create your first request](/use-cases/invoicing) (1 minute)
- 3. [Test payment flow](/api-features/payment-types) (2 minutes)
-
-
-
- 1. [Try EasyInvoice Demo](/use-cases/invoicing) - See full invoicing workflow
- 2. [Explore Use Cases](/use-cases/payouts) - Discover business applications
- 3. [Fork & Customize](/use-cases/invoicing) - Make it your own
-
-
-
- 1. [Check Supported Chains](/resources/supported-chains-and-currencies)
- 2. [Review Payment Types](/api-features/payment-types)
- 3. [Explore Use Cases](/use-cases/invoicing) that match your needs
-
-
\ No newline at end of file
diff --git a/logo/icons/dai.png b/logo/icons/dai.png
new file mode 100644
index 0000000..ef1b3c6
Binary files /dev/null and b/logo/icons/dai.png differ
diff --git a/logo/icons/usdc.png b/logo/icons/usdc.png
new file mode 100644
index 0000000..f472ec6
Binary files /dev/null and b/logo/icons/usdc.png differ
diff --git a/logo/icons/usdt.png b/logo/icons/usdt.png
new file mode 100644
index 0000000..f896614
Binary files /dev/null and b/logo/icons/usdt.png differ
diff --git a/snippets/comparison-table.jsx b/snippets/comparison-table.jsx
new file mode 100644
index 0000000..b87c24f
--- /dev/null
+++ b/snippets/comparison-table.jsx
@@ -0,0 +1,301 @@
+import { useEffect, useState } from 'react';
+
+export const ComparisonTable = () => {
+ const [isDark, setIsDark] = useState(false);
+
+ useEffect(() => {
+ // Check initial theme
+ const checkTheme = () => {
+ setIsDark(document.documentElement.classList.contains('dark'));
+ };
+
+ checkTheme();
+
+ // Watch for theme changes
+ const observer = new MutationObserver(checkTheme);
+ observer.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ['class']
+ });
+
+ return () => observer.disconnect();
+ }, []);
+
+ const comparisonData = [
+ {
+ method: "Request Network",
+ accuracy: 100,
+ implementation: "Easy",
+ linesOfCode: 10,
+ userExperience: "Good",
+ notes: "Built-in payment metadata",
+ isHighlighted: true,
+ },
+ {
+ method: "Transfers API Webhook",
+ accuracy: 60,
+ accuracyDisplay: "< 100%",
+ implementation: "Hard",
+ linesOfCode: 50,
+ userExperience: "Compromised",
+ notes: "Requires amount and currency matching",
+ },
+ {
+ method: "Blockchain Node RPC Polling",
+ accuracy: 60,
+ accuracyDisplay: "< 100%",
+ implementation: "Very Hard",
+ linesOfCode: 150,
+ userExperience: "Compromised",
+ notes: "Poor performance, requires polling",
+ },
+ {
+ method: "New wallet address per payment",
+ accuracy: 100,
+ implementation: "Very Hard",
+ linesOfCode: 100,
+ userExperience: "Poor",
+ notes: "Requires gas, many addresses, often custodial",
+ },
+ ];
+
+ // Icon components using inline SVG
+ const CheckIcon = ({ className = "h-4 w-4" }) => (
+
+
+
+ );
+
+ const XIcon = ({ className = "h-4 w-4" }) => (
+
+
+
+
+ );
+
+ const AlertCircleIcon = ({ className = "h-4 w-4" }) => (
+
+
+
+
+
+ );
+
+ const Badge = ({ children, className = "" }) => (
+
+ {children}
+
+ );
+
+ const getUXIcon = (ux) => {
+ switch (ux) {
+ case "Good":
+ return ;
+ case "Compromised":
+ return ;
+ case "Poor":
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ const getUXColor = (ux) => {
+ switch (ux) {
+ case "Good":
+ return { color: isDark ? "#4ade80" : "#15803d" }; // dark: green-400, light: green-700
+ case "Compromised":
+ return { color: isDark ? "#fb923c" : "#c2410c" }; // dark: orange-400, light: orange-700
+ case "Poor":
+ return { color: isDark ? "#f87171" : "#b91c1c" }; // dark: red-400, light: red-700
+ default:
+ return { color: isDark ? "#9ca3af" : "#4b5563" }; // dark: gray-400, light: gray-600
+ }
+ };
+
+ const getImplementationIcon = (difficulty) => {
+ switch (difficulty) {
+ case "Easy":
+ return ;
+ case "Hard":
+ return ;
+ case "Very Hard":
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ const getImplementationColor = (difficulty) => {
+ switch (difficulty) {
+ case "Easy":
+ return { color: isDark ? "#4ade80" : "#15803d" }; // dark: green-400, light: green-700
+ case "Hard":
+ return { color: isDark ? "#fb923c" : "#c2410c" }; // dark: orange-400, light: orange-700
+ case "Very Hard":
+ return { color: isDark ? "#f87171" : "#b91c1c" }; // dark: red-400, light: red-700
+ default:
+ return { color: isDark ? "#9ca3af" : "#4b5563" }; // dark: gray-400, light: gray-600
+ }
+ };
+
+ const getLinesOfCodeIcon = (linesOfCode) => {
+ if (linesOfCode <= 10) {
+ return ;
+ } else if (linesOfCode <= 50) {
+ return ;
+ } else {
+ return ;
+ }
+ };
+
+ const getLinesOfCodeColor = (linesOfCode) => {
+ if (linesOfCode <= 10) {
+ return { color: isDark ? "#4ade80" : "#15803d" }; // dark: green-400, light: green-700
+ } else if (linesOfCode <= 50) {
+ return { color: isDark ? "#fb923c" : "#c2410c" }; // dark: orange-400, light: orange-700
+ } else {
+ return { color: isDark ? "#f87171" : "#b91c1c" }; // dark: red-400, light: red-700
+ }
+ };
+
+ return (
+
+
+
+ {/* Table Header */}
+
+
Method
+
+ Automated Reconciliation
+
+
Implementation
+
Lines of Code
+
User Experience
+
Notes
+
+
+ {/* Table Rows */}
+
+ {comparisonData.map((row, index) => (
+
+ {/* Method Name */}
+
+
+ {row.method}
+
+
+
+ {/* Accuracy */}
+
+
+ {row.accuracyDisplay || `${row.accuracy}%`}
+
+
+
+ {/* Implementation Difficulty */}
+
+
+ {getImplementationIcon(row.implementation)}
+
+ {row.implementation}
+
+
+
+
+ {/* Lines of Code */}
+
+
+ {getLinesOfCodeIcon(row.linesOfCode)}
+
+ ~{row.linesOfCode}
+
+
+
+
+ {/* User Experience */}
+
+
+ {getUXIcon(row.userExperience)}
+
+ {row.userExperience}
+
+
+
+
+ {/* Notes */}
+
+ {row.notes}
+
+
+ ))}
+
+
+
+
+ {/* Summary Section */}
+
+
+
+
+
+
+
+ Alternative Trade-offs
+
+
+ Other methods sacrifice automated reconciliation (requiring manual review), burden users with poor UX, or require complex implementations.
+
+
+
+
+
+
+
+
+
+
+ Request Network Advantage
+
+
+ The only solution that combines 100% automated reconciliation with easy implementation and great user
+ experience. Built-in payment metadata eliminates guesswork entirely.
+
+
+
+
+
+
+
+ );
+};
diff --git a/snippets/integrated-demo.jsx b/snippets/integrated-demo.jsx
new file mode 100644
index 0000000..866d8f7
--- /dev/null
+++ b/snippets/integrated-demo.jsx
@@ -0,0 +1,1198 @@
+export const IntegratedDemo = () => {
+ // Constants - moved inside component
+ const CUSTOMER_DATA = [
+ { name: "Johnson Corp" },
+ { name: "Martinez LLC" },
+ { name: "Chen Industries" },
+ { name: "Patel Enterprises" },
+ { name: "Kim Solutions" },
+ ];
+
+ const CURRENCIES = ["USDC", "USDT", "DAI"];
+
+ // Helper functions
+ const generateHexString = (length) => {
+ return Array.from({length}, () => Math.floor(Math.random() * 16).toString(16)).join('');
+ };
+
+ const generateAmount = () => {
+ const amount = Math.floor(Math.random() * 900) + 100;
+ const currency = CURRENCIES[Math.floor(Math.random() * CURRENCIES.length)];
+ return { amount, currency };
+ };
+
+ // Icon components
+ const SendIcon = ({ className = "h-4 w-4" }) => (
+
+
+
+
+ );
+
+ const ExternalLinkIcon = ({ className = "h-3 w-3" }) => (
+
+
+
+
+
+ );
+
+ const CheckIcon = ({ className = "h-4 w-4" }) => (
+
+
+
+ );
+
+ const XIcon = ({ className = "h-4 w-4" }) => (
+
+
+
+
+ );
+
+ const AlertCircleIcon = ({ className = "h-4 w-4" }) => (
+
+
+
+
+
+ );
+
+ const CheckCircle2Icon = ({ className = "h-4 w-4" }) => (
+
+
+
+
+ );
+
+ const UserIcon = ({ className = "h-4 w-4" }) => (
+
+
+
+
+ );
+
+ const WalletIcon = ({ className = "h-4 w-4" }) => (
+
+
+
+
+
+ );
+
+ const TxIcon = ({ className = "h-3 w-3" }) => (
+
+
+
+
+ );
+
+ const CryptoIcon = ({ currency, className = "w-4 h-4" }) => {
+ const iconMap = {
+ USDC: "/logo/icons/usdc.png",
+ USDT: "/logo/icons/usdt.png",
+ DAI: "/logo/icons/dai.png",
+ };
+
+ const iconSrc = iconMap[currency];
+ if (!iconSrc) return null;
+
+ return ;
+ };
+
+ const getCurrencyDisplay = (amount, currency) => {
+ return (
+
+
+
+ {amount} {currency}
+
+
+ );
+ };
+
+ // Simple Badge component
+ const Badge = ({ children, variant = "default", className = "" }) => {
+ const variantClasses = {
+ default: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200",
+ destructive: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200",
+ secondary: "bg-gray-600 text-white dark:bg-gray-500 dark:text-white",
+ success: "bg-green-600 text-white dark:bg-green-700 dark:text-white",
+ };
+
+ return (
+
+ {children}
+
+ );
+ };
+
+ // Tooltip component
+ const Tooltip = ({ children, content }) => {
+ const [show, setShow] = React.useState(false);
+ const [position, setPosition] = React.useState({ top: 0, left: 0, placement: 'top' });
+ const tooltipRef = React.useRef(null);
+ const triggerRef = React.useRef(null);
+
+ React.useEffect(() => {
+ if (show && triggerRef.current) {
+ const rect = triggerRef.current.getBoundingClientRect();
+ const spaceAbove = rect.top;
+ const spaceBelow = window.innerHeight - rect.bottom;
+
+ const placement = (spaceAbove < 100 && spaceBelow > spaceAbove) ? 'bottom' : 'top';
+
+ const left = rect.left + rect.width / 2;
+ const top = placement === 'top'
+ ? rect.top - 8 // Position above trigger with gap
+ : rect.bottom + 8; // Position below trigger with gap
+
+ setPosition({ top, left, placement });
+ }
+ }, [show]);
+
+ return (
+
+
setShow(true)}
+ onMouseLeave={() => setShow(false)}
+ >
+ {children}
+
+ {show && (
+
+ )}
+
+ );
+ };
+
+ // Simple Button component
+ const Button = ({ children, onClick, disabled, variant = "default", size = "md", className = "", pulse = false }) => {
+ const variantClasses = {
+ default: "bg-primary-600 hover:bg-primary-700 text-white",
+ outline: "bg-transparent border-2 border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800",
+ };
+
+ const sizeClasses = {
+ md: "px-4 py-2 text-sm",
+ lg: "px-6 py-3 text-base",
+ };
+
+ const pulseAnimation = variant === 'outline' ? 'animate-ring-pulse-outline' : 'animate-ring-pulse';
+
+ return (
+
+ {children}
+
+
+ );
+ };
+
+ // Component state
+ const rightCardRef = useRef(null);
+ const leftCardRef = useRef(null);
+ const leftScrollRef = useRef(null);
+ const rightScrollRef = useRef(null);
+ const demoContainerRef = useRef(null);
+
+ const [leftPayments, setLeftPayments] = useState([]);
+ const [rightRequests, setRightRequests] = useState([]);
+ const [isProcessing, setIsProcessing] = useState(false);
+ const [hasStarted, setHasStarted] = useState(false);
+ const [showDialog, setShowDialog] = useState(true);
+ const [hasSeenCollisionExplainer, setHasSeenCollisionExplainer] = useState(false);
+ const [showCollisionExplainer, setShowCollisionExplainer] = useState(false);
+ const [paymentCount, setPaymentCount] = useState(0);
+ const [requestCount, setRequestCount] = useState(0);
+ const [scrollOffset, setScrollOffset] = useState(0);
+ const [isShaking, setIsShaking] = useState(false);
+ const [hasPrePopulated, setHasPrePopulated] = useState(false);
+ const [isAutoPlaying, setIsAutoPlaying] = useState(false);
+ const [shouldScrollToTop, setShouldScrollToTop] = useState(0);
+
+ // Auto-scroll to top when needed
+ useEffect(() => {
+ if (shouldScrollToTop > 0 && leftScrollRef.current && rightScrollRef.current) {
+ // Use instant scroll to ensure it works
+ setTimeout(() => {
+ if (leftScrollRef.current && rightScrollRef.current) {
+ leftScrollRef.current.scrollTop = 0;
+ rightScrollRef.current.scrollTop = 0;
+ }
+ }, 50);
+ }
+ }, [shouldScrollToTop]);
+
+ // Sync scrolling
+ useEffect(() => {
+ const leftScroll = leftScrollRef.current;
+ const rightScroll = rightScrollRef.current;
+
+ if (!leftScroll || !rightScroll) return;
+
+ const handleLeftScroll = () => {
+ if (rightScroll) {
+ rightScroll.scrollTop = leftScroll.scrollTop;
+ }
+ };
+
+ const handleRightScroll = () => {
+ if (leftScroll) {
+ leftScroll.scrollTop = rightScroll.scrollTop;
+ }
+ };
+
+ leftScroll.addEventListener("scroll", handleLeftScroll);
+ rightScroll.addEventListener("scroll", handleRightScroll);
+
+ return () => {
+ leftScroll.removeEventListener("scroll", handleLeftScroll);
+ rightScroll.removeEventListener("scroll", handleRightScroll);
+ };
+ }, []);
+
+ // Clear isNew flags after animation
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setLeftPayments((prev) => prev.map((p) => ({ ...p, isNew: false })));
+ setRightRequests((prev) => prev.map((r) => ({ ...r, isNew: false })));
+ }, 500);
+
+ return () => clearTimeout(timer);
+ }, [leftPayments.length, rightRequests.length]);
+
+
+
+ const handleStartDemo = () => {
+ setHasStarted(true);
+ setShowDialog(false);
+
+ // Only pre-populate on first start
+ if (!hasPrePopulated) {
+ setHasPrePopulated(true);
+
+ // For initial auto-play: collision always happens on 3rd payment (indices 1 and 2)
+ const collisionPair = [1, 2];
+
+ // Generate collision amount that will be shared by 2nd and 3rd requests
+ const collisionAmountData = generateAmount();
+ const collisionAmount = collisionAmountData.amount;
+ const collisionCurrency = collisionAmountData.currency;
+
+ // Get 3 different customers for the 3 requests
+ const shuffledCustomers = [...CUSTOMER_DATA].sort(() => Math.random() - 0.5);
+
+ // Create 3 requests
+ const newRequests = [];
+ const newPlaceholders = [];
+
+ for (let i = 0; i < 3; i++) {
+ const timestamp = new Date(Date.now() + i); // Slight offset for unique timestamps
+ // Generate a realistic Request ID (64 character hex string like the example)
+ const id = generateHexString(64);
+
+ // Determine if this request should be part of the collision pair
+ const isCollisionRequest = collisionPair.includes(i);
+
+ // Use collision amount for collision pair, random amount for the first request
+ let amount, currency;
+ if (isCollisionRequest) {
+ amount = collisionAmount;
+ currency = collisionCurrency;
+ } else {
+ const uniqueAmount = generateAmount();
+ amount = uniqueAmount.amount;
+ currency = uniqueAmount.currency;
+ }
+
+ const newRequest = {
+ id,
+ amount,
+ currency,
+ customer: shuffledCustomers[i].name,
+ customerAddress: `0x${generateHexString(40)}`,
+ status: "awaiting_payment",
+ timestamp,
+ isNew: true,
+ };
+
+ const placeholder = {
+ id: `placeholder-${id}`,
+ amount,
+ currency,
+ from: "",
+ timestamp,
+ status: "possibly_reconciled",
+ requestId: id,
+ txHash: "",
+ isNew: true,
+ isPlaceholder: true,
+ };
+
+ newRequests.push(newRequest);
+ newPlaceholders.push(placeholder);
+ }
+
+ setRightRequests(newRequests);
+ setLeftPayments(newPlaceholders);
+ setRequestCount(3);
+
+ // Set auto-playing state and start auto-simulation
+ setIsAutoPlaying(true);
+ setTimeout(() => {
+ autoSimulatePayments(newRequests);
+ }, 1500); // 1.5s delay before first payment
+ }
+ };
+
+ const autoSimulatePayments = async (requests) => {
+ // Simulate payments for all three requests with delays between each
+ for (let i = 0; i < requests.length; i++) {
+ if (i > 0) {
+ await new Promise(resolve => setTimeout(resolve, 1500)); // 1.5s delay between payments
+ }
+ await simulatePaymentForRequest(requests[i]);
+ }
+ };
+
+ const simulatePaymentForRequest = async (selectedRequest) => {
+ return new Promise((resolve) => {
+ const paymentAmount = selectedRequest.amount;
+ const paymentCurrency = selectedRequest.currency;
+
+ // Use the customer's address from the request (for consistency between left and right)
+ const randomAddress = selectedRequest.customerAddress;
+ // Generate valid 64-character tx hash (32 bytes in hex)
+ const txHash = `0x${generateHexString(64)}`;
+
+ setLeftPayments((prev) => {
+ const matchingPayments = prev.filter(
+ (p) => !p.isPlaceholder && p.amount === paymentAmount && p.currency === paymentCurrency,
+ );
+
+ const hasCollision = matchingPayments.length > 0;
+
+ const newPayment = {
+ id: `payment-${Date.now()}`,
+ amount: paymentAmount,
+ currency: paymentCurrency,
+ from: randomAddress,
+ timestamp: new Date(),
+ status: hasCollision ? "payment_collision" : "possibly_reconciled",
+ requestId: selectedRequest.id,
+ txHash,
+ isNew: true,
+ };
+
+ const placeholderIndex = prev.findIndex((p) => p.isPlaceholder && p.requestId === selectedRequest.id);
+
+ if (placeholderIndex !== -1) {
+ const updated = [...prev];
+ updated[placeholderIndex] = newPayment;
+
+ if (hasCollision) {
+ const collisionUpdated = updated.map((p) =>
+ !p.isPlaceholder && p.amount === paymentAmount && p.currency === paymentCurrency
+ ? { ...p, status: "payment_collision" }
+ : p,
+ );
+
+ // Trigger collision effects
+ setTimeout(() => {
+ setIsShaking(true);
+ setTimeout(() => setIsShaking(false), 500);
+
+ if (!hasSeenCollisionExplainer) {
+ setTimeout(() => {
+ setShowCollisionExplainer(true);
+ setHasSeenCollisionExplainer(true);
+ setIsAutoPlaying(false); // Re-enable buttons when dialog appears
+ }, 1500); // Increased delay to 1.5s after shake
+ }
+ }, 100);
+
+ return collisionUpdated;
+ }
+
+ return updated;
+ }
+
+ return [newPayment, ...prev];
+ });
+
+ setRightRequests((prev) =>
+ prev.map((r) =>
+ r.id === selectedRequest.id ? { ...r, status: "paid_reconciled", txHash, isNew: true } : r,
+ ),
+ );
+
+ // Trigger scroll to top via useEffect
+ setShouldScrollToTop(prev => prev + 1);
+
+ setTimeout(() => {
+ triggerConfetti();
+ resolve();
+ }, 100);
+ });
+ };
+
+ const handleCreateRequest = () => {
+ const randomCustomer = CUSTOMER_DATA[Math.floor(Math.random() * CUSTOMER_DATA.length)];
+ let amount;
+ let currency;
+
+ setRequestCount(prev => {
+ const newCount = prev + 1;
+
+ if (newCount % 3 === 0 && rightRequests.length > 0) {
+ const existingRequest = rightRequests[Math.floor(Math.random() * rightRequests.length)];
+ amount = existingRequest.amount;
+ currency = existingRequest.currency;
+ } else {
+ const generated = generateAmount();
+ amount = generated.amount;
+ currency = generated.currency;
+ }
+
+ const timestamp = new Date();
+ // Generate a realistic Request ID (64 character hex string)
+ const id = generateHexString(64);
+
+ const newRequest = {
+ id,
+ amount,
+ currency,
+ customer: randomCustomer.name,
+ customerAddress: `0x${generateHexString(40)}`,
+ status: "awaiting_payment",
+ timestamp,
+ isNew: true,
+ };
+
+ const placeholder = {
+ id: `placeholder-${id}`,
+ amount,
+ currency,
+ from: "",
+ timestamp,
+ status: "possibly_reconciled",
+ requestId: id,
+ txHash: "",
+ isNew: true,
+ isPlaceholder: true,
+ };
+
+ setRightRequests((prev) => [newRequest, ...prev]);
+ setLeftPayments((prev) => [placeholder, ...prev]);
+
+ return newCount;
+ });
+
+ // Trigger scroll to top via useEffect
+ setShouldScrollToTop(prev => prev + 1);
+ };
+
+ const calculateLeftAccuracy = () => {
+ const nonPlaceholderPayments = leftPayments.filter((p) => !p.isPlaceholder);
+ if (nonPlaceholderPayments.length === 0) return "N/A";
+
+ const reconciledCount = nonPlaceholderPayments.filter((p) => p.status === "possibly_reconciled").length;
+ const percentage = Math.round((reconciledCount / nonPlaceholderPayments.length) * 100);
+ return `${percentage}%`;
+ };
+
+ // Helper function to count left-side payment matches (excluding placeholders)
+ const getLeftSideMatchCount = (amount, currency) => {
+ return leftPayments.filter(p =>
+ !p.isPlaceholder &&
+ p.amount === amount &&
+ p.currency === currency
+ ).length;
+ };
+
+ const handleSimulatePayment = async () => {
+ setIsProcessing(true);
+
+ const awaitingRequests = rightRequests.filter((r) => r.status === "awaiting_payment");
+
+ if (awaitingRequests.length === 0) {
+ setIsProcessing(false);
+ return;
+ }
+
+ setPaymentCount(prev => prev + 1);
+
+ const randomIndex = Math.floor(Math.random() * awaitingRequests.length);
+ const selectedRequest = awaitingRequests[randomIndex];
+
+ const paymentAmount = selectedRequest.amount;
+ const paymentCurrency = selectedRequest.currency;
+
+ // Generate valid 40-character wallet address (20 bytes in hex)
+ const randomAddress = `0x${generateHexString(40)}`;
+ // Generate valid 64-character tx hash (32 bytes in hex)
+ const txHash = `0x${generateHexString(64)}`;
+
+ const matchingPayments = leftPayments.filter(
+ (p) => !p.isPlaceholder && p.amount === paymentAmount && p.currency === paymentCurrency,
+ );
+
+ const hasCollision = matchingPayments.length > 0;
+
+ const newPayment = {
+ id: `payment-${Date.now()}`,
+ amount: paymentAmount,
+ currency: paymentCurrency,
+ from: randomAddress,
+ timestamp: new Date(),
+ status: hasCollision ? "payment_collision" : "possibly_reconciled",
+ requestId: selectedRequest.id,
+ txHash,
+ isNew: true,
+ };
+
+ setLeftPayments((prev) => {
+ const placeholderIndex = prev.findIndex((p) => p.isPlaceholder && p.requestId === selectedRequest.id);
+
+ if (placeholderIndex !== -1) {
+ const updated = [...prev];
+ updated[placeholderIndex] = newPayment;
+
+ if (hasCollision) {
+ return updated.map((p) =>
+ !p.isPlaceholder && p.amount === paymentAmount && p.currency === paymentCurrency
+ ? { ...p, status: "payment_collision" }
+ : p,
+ );
+ }
+
+ return updated;
+ }
+
+ return [newPayment, ...prev];
+ });
+
+ setRightRequests((prev) =>
+ prev.map((r) =>
+ r.id === selectedRequest.id ? { ...r, status: "paid_reconciled", txHash, isNew: true } : r,
+ ),
+ );
+
+ // Trigger scroll to top via useEffect
+ setShouldScrollToTop(prev => prev + 1);
+
+ setTimeout(() => {
+ triggerConfetti();
+ }, 100);
+
+ if (hasCollision) {
+ // Trigger shake animation
+ setIsShaking(true);
+ setTimeout(() => setIsShaking(false), 500);
+
+ // Show collision explainer dialog only on first collision (after shake completes)
+ if (!hasSeenCollisionExplainer) {
+ setTimeout(() => {
+ setShowCollisionExplainer(true);
+ setHasSeenCollisionExplainer(true);
+ }, 600); // After shake animation (500ms + 100ms buffer)
+ }
+ }
+
+ setIsProcessing(false);
+ };
+
+ const triggerConfetti = () => {
+ if (typeof window !== 'undefined' && window.confetti) {
+ if (rightCardRef.current) {
+ const rect = rightCardRef.current.getBoundingClientRect();
+ const x = (rect.left + rect.width / 2) / window.innerWidth;
+ const y = (rect.top + rect.height / 2) / window.innerHeight;
+
+ window.confetti({
+ particleCount: 50,
+ spread: 60,
+ origin: { x, y },
+ colors: ["#10b981", "#059669", "#047857"],
+ });
+ }
+ } else if (typeof window !== 'undefined' && !window.confetti) {
+ const script = document.createElement("script");
+ script.src = "https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/dist/confetti.browser.min.js";
+ script.onload = () => {
+ if (window.confetti && rightCardRef.current) {
+ const rect = rightCardRef.current.getBoundingClientRect();
+ const x = (rect.left + rect.width / 2) / window.innerWidth;
+ const y = (rect.top + rect.height / 2) / window.innerHeight;
+
+ window.confetti({
+ particleCount: 50,
+ spread: 60,
+ origin: { x, y },
+ colors: ["#10b981", "#059669", "#047857"],
+ });
+ }
+ };
+ document.head.appendChild(script);
+ }
+ };
+
+ const handleClearTables = () => {
+ setHasStarted(false);
+ setPaymentCount(0);
+ setRequestCount(0);
+ setHasPrePopulated(false);
+ setIsAutoPlaying(false);
+
+ setRightRequests([]);
+ setLeftPayments([]);
+ setHasSeenCollisionExplainer(false);
+ setShowCollisionExplainer(false);
+ };
+
+ const awaitingCount = rightRequests.filter((r) => r.status === "awaiting_payment").length;
+ const canSimulatePayment = awaitingCount > 0 && !isAutoPlaying;
+ const canCreateRequest = awaitingCount < 3 && !isAutoPlaying;
+ const hasContent = leftPayments.length > 0 || rightRequests.length > 0;
+ const shouldCreateRequestPulse = rightRequests.length === 0 || rightRequests.every(r => r.status === 'paid_reconciled');
+
+ return (
+
+
+ {showDialog && !hasStarted && (
+
+
e.stopPropagation()}
+ style={{
+ position: 'sticky',
+ top: '150px'
+ }}
+ >
+
+
+ Identify Every Payment
+
+
+
+
+
+
+
Anonymous Transactions
+
+ Traditional blockchain payments lack business context or payment identifiers.
+
+
+
+
+
+
+
+
+
Unique Identifiers
+
+ Request Network adds unique identifiers to every payment, enabling instant, automatic, and 100% automated reconciliation.
+
+
+
+
+
+
+
+
+ Start Demo
+
+
+
+
+ )}
+
+ {showCollisionExplainer && (
+
setShowCollisionExplainer(false)}
+ >
+
e.stopPropagation()}
+ style={{
+ position: 'sticky',
+ top: '150px'
+ }}
+ >
+
+
+ Payment Collision Detected
+
+
+
+
+
+
+
The Problem
+
+ Two payments have the same amount and currency. Which payment belongs to which customer?
+ Manual review required.
+
+
+
+
+
+
+
+
+
Request Network Solution
+
+ Each payment is automatically matched to its correct request using onchain identifiers. No
+ ambiguity, no manual work.
+
+
+
+
+
+
+
+ setShowCollisionExplainer(false)} size="lg">
+ Got it
+
+
+
+
+ )}
+
+
+
+
+ Create Request
+
+
+
+
+ {isProcessing ? "Processing..." : "Simulate Payment"}
+
+
+
+ Clear
+
+
+
+
+ {/* Left side - Traditional Payments */}
+
+
+
+
+ Create Request
+
+
+
+
+ {isProcessing ? "Processing..." : "Simulate Payment"}
+
+
+
+ Clear
+
+
+
+
+
+
+
+ Traditional Blockchain Payments
+
+
+
+
+
+ Reconciled: {calculateLeftAccuracy()}
+
+
+
+
+
+ Anonymous transactions without business context
+
+
+
+
+ {leftPayments.length === 0 ? (
+
+
Click "Create Request" to begin
+
+ ) : (
+ leftPayments.map((payment) => {
+ if (payment.isPlaceholder) {
+ return (
+
+ Awaiting Payment
+
+ );
+ }
+
+ const isCollision = payment.status === "payment_collision";
+ const matchCount = getLeftSideMatchCount(payment.amount, payment.currency);
+ const showWarning = matchCount >= 2;
+
+ return (
+
+
+
+
+ {getCurrencyDisplay(payment.amount, payment.currency)}
+
+ {showWarning && (
+
+ ⚠️ {matchCount} matches
+
+ )}
+
+ {isCollision ? (
+
+
+ Needs Review
+
+
+ ) : (
+
+
+ Possibly Reconciled
+
+
+ )}
+
+
+
+
+
+ From
+
+
+
+ {payment.from.slice(0, 10)}...{payment.from.slice(-8)}
+
+
+
+
+
+
+ );
+ })
+ )}
+
+
+
+
+
+ {/* Right side - Request Network Payments */}
+
+
+
+
+ Create Request
+
+
+
+
+ {isProcessing ? "Processing..." : "Simulate Payment"}
+
+
+
+ Clear
+
+
+
+
+
+
+
+ Request Network Payments
+
+
+
+
+
+ Reconciled: 100%
+
+
+
+
+
+ Payments with unique IDs for instant reconciliation
+
+
+
+
+ {rightRequests.length === 0 ? (
+
+
Click "Create Request" to begin
+
+ ) : (
+ rightRequests.map((request) => {
+ const isPaid = request.status === "paid_reconciled";
+
+ return (
+
+
+
+ {getCurrencyDisplay(request.amount, request.currency)}
+
+ {isPaid ? (
+
+
+ Reconciled
+
+
+ ) : (
+
+ Awaiting Payment
+
+ )}
+
+
+
+
+
+ Customer
+
+
+ {request.customer}
+
+
+
+
+
+
+
+
+
+
+
+
Request ID
+
+
+
+ {request.id.slice(0, 10)}...{request.id.slice(-6)}
+
+
+
+
+
+
+ );
+ })
+ )}
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/use-cases/welcome.mdx b/use-cases/welcome.mdx
new file mode 100644
index 0000000..24c9f8c
--- /dev/null
+++ b/use-cases/welcome.mdx
@@ -0,0 +1,94 @@
+---
+title: "Request Network Docs"
+description: "A protocol for requests, payments, and **100% automated reconciliation**. Requests add business context to payments, eliminating manual accounting with cryptographic certainty."
+sidebarTitle: "Welcome"
+mode: "frame"
+---
+
+import { IntegratedDemo } from '/snippets/integrated-demo.jsx'
+import { ComparisonTable } from '/snippets/comparison-table.jsx'
+
+
+
+
Request Network Docs
+
+ A protocol for requests, payments, and 100% automated reconciliation .
+
+
+
+
+ Identify Every Payment
+
+
+
+ Requests add business context to payments, eliminating manual accounting with cryptographic certainty. See how unique Request IDs prevent payment collisions:
+
+
+
+
+
+
+
+ Blockchain Payment Detection Comparison
+
+
+
+ How does Request Network compare to other approaches?
+
+
+
+
+
+
+
+
+
Get Started
+
+
+
+
+ Explore specific business scenarios: invoicing, payouts, payroll, checkout, and subscriptions
+
+
+
+ The easiest way to integrate. Payment types, webhooks, and developer tools.
+
+
+
+ Supported chains, currencies, smart contracts, and community resources
+
+
+
+
+
+
+
+ Legacy SDK and protocol documentation for advanced users
+
+
+
+ Stay updated with the latest features and improvements
+
+
+
+