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 {`${currency}; + }; + + 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 && ( +
+ {content} +
+
+ )} +
+ ); + }; + + // 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 ( + + ); + }; + + // 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. +

+
+
+
+
+
+
+ +
+
+
+ )} + + {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. +

+
+
+
+
+
+
+ +
+
+
+ )} + +
+ + + + + +
+ +
+ {/* Left side - Traditional Payments */} +
+
+ + + + + +
+ +
+
+
+

+ 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 */} +
+
+ + + + + +
+ +
+
+
+

+ 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)} +

+
+
+
+ {isPaid && request.txHash ? ( + <> +
+ + Tx ID +
+ + e.preventDefault()} + > + {request.txHash.slice(0, 8)}...{request.txHash.slice(-6)} + + + + + ) : ( + <> +
+ + Tx ID +
+ + 0x000000...000000 + + + + )} +
+
+
+ ); + }) + )} +
+
+
+
+
+
+ + +
+ ); +}; 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 + + +
+