refactor(mcp-docs): split 2489-line MCPDocsPage into 14 component files

Extract 14 sections under src/components/mcp-docs/:
- utils.ts: shared types, CHAIN_DISPLAY, helpers, code generators
- CodeBlock, ToolDocCard: shared UI components
- 11 section components: PaymentFlowSection through DocLinksSection

MCPDocsPage.tsx reduced from 2489 to 514 lines.
TypeScript compiles with 0 errors.

Add .github/workflows/ci.yml with typecheck and build gates.
This commit is contained in:
Crypto Rug Munch 2026-07-08 10:44:02 +07:00
parent ff36952dc9
commit bb96ba0b08
16 changed files with 2027 additions and 1991 deletions

31
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,31 @@
name: Frontend CI
on:
push:
branches: [main, feat/*]
pull_request:
branches: [main]
jobs:
typecheck:
name: TypeScript Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- run: bun install --frozen-lockfile
- run: bunx tsc --noEmit
build:
name: Build Check
needs: typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- run: bun install --frozen-lockfile
- run: bun run build

View file

@ -0,0 +1,131 @@
import { Shield, Lock, Key } from 'lucide-react'
export function AuthenticationHierarchySection() {
const tiers = [
{
level: 1,
title: 'Wallet Connected',
icon: Wallet,
color: 'text-cyan-400',
bg: 'bg-cyan-500/10',
border: 'border-cyan-500/30',
bar: 'w-full',
benefits: [
'3 free queries per tool',
'30 req/min rate limit',
'Full API access',
'Refund eligible',
'Priority queue',
],
},
{
level: 2,
title: 'Device Fingerprint',
icon: Fingerprint,
color: 'text-green-400',
bg: 'bg-green-500/10',
border: 'border-green-500/30',
bar: 'w-3/4',
benefits: ['1 free query per tool', '10 req/min rate limit', 'Full API access', 'Refund eligible'],
},
{
level: 3,
title: 'Turnstile Verified',
icon: Shield,
color: 'text-amber-400',
bg: 'bg-amber-500/10',
border: 'border-amber-500/30',
bar: 'w-1/2',
benefits: ['0 free queries (no trial)', '5 req/min rate limit', 'Paid-only access'],
},
{
level: 4,
title: 'Unknown / No Auth',
icon: AlertTriangle,
color: 'text-red-400',
bg: 'bg-red-500/10',
border: 'border-red-500/30',
bar: 'w-1/4',
benefits: ['0 free queries', '2 req/min rate limit', 'Subject to challenges'],
},
]
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold text-white mb-2">Authentication Hierarchy</h2>
<p className="text-gray-400">
Rug Munch uses a tiered authentication system. Higher trust = more free queries + higher rate limits.
</p>
{/* Hierarchy visualization */}
<div className="space-y-3">
{tiers.map((tier) => {
const Icon = tier.icon
return (
<div key={tier.level} className={`p-4 rounded-xl bg-[#0e0e16] border ${tier.border}`}>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-3">
<div className={`w-8 h-8 rounded-lg ${tier.bg} flex items-center justify-center`}>
<Icon className={`w-4 h-4 ${tier.color}`} />
</div>
<div>
<div className="flex items-center gap-2">
<span className={`text-xs font-mono ${tier.color}`}>TIER {tier.level}</span>
<h3 className="text-sm font-semibold text-white">{tier.title}</h3>
</div>
</div>
</div>
<div className="w-32 h-2 rounded-full bg-black/40 overflow-hidden">
<div className={`h-full ${tier.bg} ${tier.bar}`} style={{ borderRadius: '4px' }} />
</div>
</div>
<div className="flex flex-wrap gap-1.5">
{tier.benefits.map((b) => (
<span key={b} className="text-[10px] px-2 py-1 rounded-md bg-black/20 text-gray-400">
{b}
</span>
))}
</div>
</div>
)
})}
</div>
{/* How auth is determined */}
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h3 className="text-sm font-semibold text-white mb-3">How Authentication Tier is Determined</h3>
<div className="space-y-2 text-sm text-gray-300">
<div className="flex items-start gap-3">
<span className="text-cyan-400 font-mono text-xs mt-0.5 w-20 shrink-0">x-pay header</span>
<span>Present = wallet verification. Absent = check for device fingerprint.</span>
</div>
<div className="flex items-start gap-3">
<span className="text-green-400 font-mono text-xs mt-0.5 w-20 shrink-0">X-Device-Id</span>
<span>Valid fingerprint hash = Tier 2. Missing or invalid = fall through.</span>
</div>
<div className="flex items-start gap-3">
<span className="text-amber-400 font-mono text-xs mt-0.5 w-20 shrink-0">cf-turnstile</span>
<span>Cloudflare Turnstile token present = Tier 3. Otherwise = Tier 4.</span>
</div>
<div className="flex items-start gap-3">
<span className="text-red-400 font-mono text-xs mt-0.5 w-20 shrink-0">No headers</span>
<span>Anonymous/unknown = Tier 4 (heavily rate-limited, may be challenged).</span>
</div>
</div>
</div>
{/* Upgrade path */}
<div className="p-4 rounded-xl bg-gradient-to-r from-green-500/5 to-cyan-500/5 border border-green-500/20">
<div className="flex items-center gap-2 mb-2">
<ArrowRight className="w-4 h-4 text-green-400" />
<h3 className="text-sm font-semibold text-green-300">Upgrade Your Tier</h3>
</div>
<p className="text-xs text-gray-400">
Connect a wallet to jump from Tier 2 to Tier 1 instantly. Get 3x more free queries and 3x higher rate limits.
No KYC, no signup just sign a message to prove wallet ownership.
</p>
</div>
</div>
)
}

View file

@ -0,0 +1,27 @@
import { Check, Copy } from 'lucide-react'
import { useState } from 'react'
export default export function CodeBlock({ code, lang, _id }: { code: string; lang: string; id: string }) {
const [copied, setCopied] = useState(false)
const copy = () => {
navigator.clipboard.writeText(code)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return (
<div className="relative rounded-lg bg-black/40 border border-white/5 overflow-hidden">
<div className="flex items-center justify-between px-3 py-1.5 border-b border-white/5">
<span className="text-xs text-gray-500 font-mono">{lang}</span>
<button
type="button"
onClick={copy}
className="flex items-center gap-1 text-xs text-gray-500 hover:text-white transition-colors"
>
{copied ? <Check className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
{copied ? 'Copied' : 'Copy'}
</button>
</div>
<pre className="p-3 text-xs text-gray-300 overflow-x-auto whitespace-pre font-mono leading-relaxed">{code}</pre>
</div>
)
}

View file

@ -0,0 +1,120 @@
import { Fingerprint, Shield, CheckCircle, Code } from 'lucide-react'
export function DeviceFingerprintSection() {
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold text-white mb-2">Device Fingerprint & Anti-Abuse</h2>
<p className="text-gray-400">
Every request must include a device fingerprint. This prevents trial abuse and enforces rate limits without
requiring accounts.
</p>
{/* The X-Device-Id header */}
<div className="p-4 rounded-xl bg-[#0e0e16] border border-cyan-500/20">
<div className="flex items-center gap-2 mb-2">
<Fingerprint className="w-4 h-4 text-cyan-400" />
<h3 className="text-sm font-semibold text-cyan-300">X-Device-Id Header</h3>
</div>
<p className="text-xs text-gray-400 mb-3">
A SHA-256 hash derived from browser Canvas, WebGL renderer, and installed fonts. Sent with every request.
</p>
<CodeBlock
lang="javascript"
id="device-fingerprint"
code={`// Generate device fingerprint in the browser
async function getDeviceFingerprint() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
// Canvas fingerprint
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('RugMunch', 2, 2);
const canvasData = canvas.toDataURL();
// WebGL renderer
const renderer = gl.getParameter(gl.RENDERER);
// Combine sources and hash
const raw = \`\${canvasData}|\${renderer}|\${navigator.fonts}\`;
const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(raw));
return Array.from(new Uint8Array(hash))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
// Use in requests:
const deviceId = await getDeviceFingerprint();
fetch('/api/v1/x402-tools/audit', {
headers: { 'X-Device-Id': deviceId }
});`}
/>
</div>
{/* Anti-abuse tiers */}
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h3 className="text-sm font-semibold text-white mb-3">Anti-Abuse Layers</h3>
<div className="space-y-3">
{[
{
icon: Fingerprint,
title: 'Device Fingerprint',
color: 'text-green-400',
desc: 'Canvas + WebGL + font hash. Identifies unique browsers. Prevents incognito/spam abuse.',
detail: 'X-Device-Id header. SHA-256 of browser fingerprint data.',
},
{
icon: Shield,
title: 'Turnstile Challenge',
color: 'text-amber-400',
desc: 'Cloudflare Turnstile invisible challenge for suspicious requests. Blocks bots.',
detail: 'Triggered automatically for anomalous fingerprint patterns.',
},
{
icon: Wallet,
title: 'Wallet Binding',
color: 'text-cyan-400',
desc: 'Connected wallet address bound to trial counter. Higher trust = more free queries.',
detail: '3 free queries per tool (vs 1 for fingerprint-only).',
},
{
icon: AlertTriangle,
title: 'Rate Limiting',
color: 'text-red-400',
desc: 'Per-device rate limits. Exponential backoff for repeated 402s without payment.',
detail: 'Max 10 requests/minute (fingerprint), 30 requests/minute (wallet).',
},
].map((layer) => {
const Icon = layer.icon
return (
<div key={layer.title} className="p-3 rounded-lg bg-black/20 border border-white/5">
<div className="flex items-center gap-2 mb-1">
<Icon className={`w-4 h-4 ${layer.color}`} />
<h4 className="text-sm font-medium text-white">{layer.title}</h4>
</div>
<p className="text-xs text-gray-400">{layer.desc}</p>
<p className="text-[10px] text-gray-600 mt-1 font-mono">{layer.detail}</p>
</div>
)
})}
</div>
</div>
{/* Important note */}
<div className="p-4 rounded-xl bg-amber-500/5 border border-amber-500/20">
<div className="flex items-start gap-2">
<AlertTriangle className="w-4 h-4 text-amber-400 mt-0.5 shrink-0" />
<div>
<h4 className="text-sm font-semibold text-amber-300">Fingerprint Spoofing</h4>
<p className="text-xs text-gray-400 mt-1">
Attempting to bypass fingerprint tracking by rotating device IDs is detected via behavioral analysis and
may result in permanent rate limiting. Connect a wallet for higher limits instead.
</p>
</div>
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,205 @@
import { Radio, Globe, ExternalLink } from 'lucide-react'
import { motion } from 'framer-motion'
export function DiscoveryEndpointsSection() {
const endpoints = [
{
method: 'GET',
url: '/.well-known/x402',
label: 'x402 Discovery',
desc: 'Returns the full x402 payment configuration—including all 103 tools, pricing, supported chains, and facilitator endpoints.',
responseType: `{ "tools": 201, "chains": 13, "facilitator": "...", "tools_endpoint": "..." }`,
},
{
method: 'GET',
url: '/api/v1/x402/tools-catalog',
label: 'Tools Catalog',
desc: 'Returns 125+ unique tools across 13 chains with full metadata—name, description, price, category, trial count, chains, service info.',
responseType: `{ "tools": [...], "total_tools": 97, "total_chains": 7, "chains": {...} }`,
},
{
method: 'GET',
url: `${WORKER_BASE}/mcp`,
label: 'Base MCP Endpoint',
desc: 'MCP protocol endpoint on the Base worker. Returns MCP server capabilities and tool definitions for EVM-chain tools.',
responseType: `{ "protocol": "mcp", "version": "1.0", "tools": [...] }`,
},
{
method: 'GET',
url: `${WORKER_SOL}/mcp`,
label: 'Solana MCP Endpoint',
desc: 'MCP protocol endpoint on the Solana worker. Returns MCP server capabilities and tool definitions for Solana tools.',
responseType: `{ "protocol": "mcp", "version": "1.0", "tools": [...] }`,
},
{
method: 'POST',
url: '/api/v1/x402/refund',
label: 'Refund Request',
desc: 'Request a refund for a tool execution that returned no data. Requires original transaction hash and tool ID.',
responseType: `{ "status": "refunded", "amount": "0.05", "refund_tx": "..." }`,
},
{
method: 'POST',
url: '/api/v1/x402/verify',
label: 'Payment Verify',
desc: 'Verify an x402 payment was received and confirmed. Used by self-verification chains to prove on-chain settlement.',
responseType: `{ "verified": true, "chain": "ethereum", "tx_hash": "..." }`,
},
{
method: 'GET',
url: '/api/v1/bulletin/latest',
label: 'Bulletin Board Posts',
desc: 'The first bot-human message board in crypto. Bots and humans post alerts, analysis, and discussion. Bots register via x402 $1 fee.',
responseType: `{ "posts": [...], "total": 50 }`,
},
{
method: 'POST',
url: '/api/v1/bulletin/bots/register',
label: 'Bot Registration',
desc: 'Register an AI bot to join the bulletin board. $1 x402 fee required. Bots get verified badges and can post across categories.',
responseType: `{ "bot_profile": {...}, "fee_required": 1.0, "status": "active" }`,
},
{
method: 'POST',
url: '/api/v1/bulletin/posts',
label: 'Create Post',
desc: 'Create a new post on the bulletin board. Supports alerts, analysis, discussion, rehab stories, debates, and more.',
responseType: `{ "id": "...", "title": "...", "category": "alert" }`,
},
]
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold text-white mb-2">Bot Discovery Endpoints</h2>
<p className="text-gray-400">
Machine-readable endpoints for bots, agents, and CI/CD pipelines to discover available tools, pricing, and
payment configuration.
</p>
{/* Why discovery matters */}
<div className="p-4 rounded-xl bg-[#0e0e16] border border-purple-500/20">
<div className="flex items-center gap-2 mb-2">
<Bot className="w-4 h-4 text-purple-400" />
<h3 className="text-sm font-semibold text-purple-300">Why Discovery?</h3>
</div>
<p className="text-xs text-gray-400">
AI agents and automated systems need to discover available tools without manual configuration. The
/.well-known/x402 endpoint follows the x402 protocol standard for programmatic tool discovery.
</p>
</div>
{/* Endpoint list */}
<div className="space-y-3">
{endpoints.map((ep) => (
<div key={ep.url} className="p-4 rounded-xl bg-[#0e0e16] border border-white/5">
<div className="flex items-center gap-2 mb-2">
<span
className={`text-xs px-2 py-0.5 rounded font-mono font-bold ${
ep.method === 'GET' ? 'bg-green-500/10 text-green-400' : 'bg-blue-500/10 text-blue-400'
}`}
>
{ep.method}
</span>
<h3 className="text-sm font-semibold text-white">{ep.label}</h3>
</div>
<code className="text-xs text-cyan-400 font-mono break-all block mb-2">{ep.url}</code>
<p className="text-xs text-gray-400 mb-3">{ep.desc}</p>
<div className="p-2 rounded-lg bg-black/20">
<p className="text-[10px] text-gray-500 uppercase tracking-wider mb-1">Response Shape</p>
<pre className="text-[10px] text-gray-500 font-mono">{ep.responseType}</pre>
</div>
</div>
))}
</div>
{/* Discovery code examples */}
<div className="space-y-3">
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h4 className="text-sm font-semibold text-white mb-2">Discover Tools (Python Bot)</h4>
<CodeBlock
lang="python"
id="discover-py"
code={`import requests
# Discover all available tools via x402 standard
discovery = requests.get('https://rugmunch.io/.well-known/x402').json()
print(f"Found {discovery['tools']} tools on {discovery['chains']} chains")
# Get full tool catalog with metadata
catalog = requests.get('https://rugmunch.io/api/v1/x402/tools-catalog').json()
for tool in catalog['tools']:
print(f" {tool['id']}: {tool['price']} ({tool['category']})")`}
/>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h4 className="text-sm font-semibold text-white mb-2">Discover Tools (cURL)</h4>
<CodeBlock
lang="bash"
id="discover-curl"
code={`# x402 standard discovery endpoint
curl https://rugmunch.io/.well-known/x402 | jq
# Full tools catalog
curl https://rugmunch.io/api/v1/x402/tools-catalog | jq '.total_tools, .chains'
# MCP discovery (primary endpoint)
curl https://mcp.rugmunch.io/mcp | jq '.tools | length'
# MCP discovery on Solana worker (fallback)
curl https://x402-sol.cryptorugmuncher.workers.dev/mcp | jq '.tools | length'`}
/>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h4 className="text-sm font-semibold text-white mb-2">Discover Tools (JavaScript / AI Agent)</h4>
<CodeBlock
lang="javascript"
id="discover-js"
code={`// Discover tools for an AI agent
const discovery = await fetch('https://rugmunch.io/.well-known/x402');
const config = await discovery.json();
console.log(\`\${config.tools} tools, \${config.chains} chains\`);
// Get detailed catalog
const catalog = await fetch('https://rugmunch.io/api/v1/x402/tools-catalog');
const { tools, total_tools, total_chains } = await catalog.json();
// Filter security tools under $0.05
const cheapSecurity = tools.filter(t =>
t.category === 'security' && t.priceUsd <= 0.05
);
console.log(\`Found \${cheapSecurity.length} security tools under $0.05\`);`}
/>
</div>
</div>
{/* 2 Workers architecture */}
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h3 className="text-sm font-semibold text-white mb-3">Cloudflare Worker Architecture</h3>
<p className="text-xs text-gray-400 mb-3">
Tools are served through 2 Cloudflare Workers for low-latency, edge-first delivery:
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div className="p-3 rounded-lg bg-blue-500/5 border border-blue-500/20">
<div className="flex items-center gap-2 mb-2">
<Server className="w-4 h-4 text-blue-400" />
<h4 className="text-xs font-semibold text-blue-300">Base Worker</h4>
</div>
<code className="text-[10px] text-cyan-400 font-mono">{WORKER_BASE}</code>
<p className="text-[10px] text-gray-500 mt-1">EVM-chain tools (Base, ETH, BSC, ARB, OPT, POL)</p>
</div>
<div className="p-3 rounded-lg bg-purple-500/5 border border-purple-500/20">
<div className="flex items-center gap-2 mb-2">
<Server className="w-4 h-4 text-purple-400" />
<h4 className="text-xs font-semibold text-purple-300">Solana Worker</h4>
</div>
<code className="text-[10px] text-cyan-400 font-mono">{WORKER_SOL}</code>
<p className="text-[10px] text-gray-500 mt-1">Solana-specific tools with SPL-USDC support</p>
</div>
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,102 @@
import { BookOpen, ExternalLink, Globe } from 'lucide-react'
import { motion } from 'framer-motion'
export function DocLinksSection() {
const links = [
{
title: 'README',
desc: 'Project overview, setup, and architecture',
href: 'https://github.com/cryptorugmuncher/rug-munch-intelligence#readme',
icon: BookOpen,
color: 'text-purple-400',
},
{
title: 'User Guide',
desc: 'Step-by-step guide for getting started',
href: 'https://docs.rugmunch.io/user-guide',
icon: Book,
color: 'text-cyan-400',
},
{
title: 'API Reference',
desc: 'Full REST API documentation with examples',
href: 'https://docs.rugmunch.io/api',
icon: Code,
color: 'text-green-400',
},
{
title: 'GitHub Repository',
desc: 'Source code, issues, and contributions',
href: 'https://github.com/cryptorugmuncher/rug-munch-intelligence',
icon: Globe,
color: 'text-blue-400',
},
{
title: 'x402 Protocol Spec',
desc: 'HTTP 402 micropayment protocol specification',
href: 'https://x402.org',
icon: CreditCard,
color: 'text-amber-400',
},
{
title: 'MCP Specification',
desc: 'Model Context Protocol standard documentation',
href: 'https://modelcontextprotocol.io',
icon: Bot,
color: 'text-pink-400',
},
{
title: 'Smithery Registry',
desc: 'Discover RMI on the Smithery MCP registry',
href: 'https://smithery.ai/server/@cryptorugmuncher/rug-munch-intelligence',
icon: BadgeCheck,
color: 'text-emerald-400',
},
{
title: 'Glama Registry',
desc: 'Discover RMI on the Glama MCP registry',
href: 'https://glama.ai/mcp/servers/@cryptorugmuncher/rug-munch-intelligence',
icon: BadgeCheck,
color: 'text-indigo-400',
},
]
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold text-white mb-2">Documentation & Links</h2>
<p className="text-gray-400">Official docs, repositories, and registries for Rug Munch Intelligence.</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{links.map((link, i) => {
const Icon = link.icon
return (
<motion.a
key={link.title}
href={link.href}
target="_blank"
rel="noopener noreferrer"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.05 }}
className="flex items-start gap-3 p-4 rounded-xl bg-[#0e0e16] border border-white/5 hover:border-purple-500/30 transition-all group"
>
<div className="w-8 h-8 rounded-lg bg-white/[0.03] flex items-center justify-center shrink-0">
<Icon className={`w-4 h-4 ${link.color}`} />
</div>
<div className="min-w-0">
<div className="flex items-center gap-2">
<p className="text-sm font-semibold text-white group-hover:text-purple-300 transition-colors">
{link.title}
</p>
<ExternalLink className="w-3 h-3 text-gray-600 group-hover:text-purple-400 transition-colors" />
</div>
<p className="text-xs text-gray-500 mt-0.5">{link.desc}</p>
</div>
</motion.a>
)
})}
</div>
</div>
)
}

View file

@ -0,0 +1,29 @@
import { HelpCircle } from 'lucide-react'
export function FAQSection() {
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold text-white mb-2">Frequently Asked Questions</h2>
<p className="text-gray-400">Common questions about Rug Munch Intelligence, MCP, payments, wallets, and more.</p>
<Accordion type="single" collapsible className="space-y-2">
{FAQ_ITEMS.map((item, i) => (
<AccordionItem
key={i}
value={`faq-${i}`}
className="rounded-xl bg-[#0e0e16] border border-white/5 px-4 data-[state=open]:border-purple-500/20 transition-colors"
>
<AccordionTrigger className="text-sm font-medium text-white hover:text-purple-300 hover:no-underline py-4">
<div className="flex items-center gap-3 text-left">
<HelpCircle className="w-4 h-4 text-purple-400 shrink-0" />
<span>{item.q}</span>
</div>
</AccordionTrigger>
<AccordionContent className="text-sm text-gray-400 leading-relaxed pb-4">{item.a}</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
)
}

View file

@ -0,0 +1,223 @@
import { Bot, Code, Terminal } from 'lucide-react'
export function MCPIntegrationSection() {
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold text-white mb-2">MCP Integration</h2>
<p className="text-gray-400">
Connect AI agents (Claude, ChatGPT, Cursor, etc.) to all 103 tools via Model Context Protocol. Each Cloudflare
Worker exposes its own /mcp endpoint.
</p>
<div className="space-y-4">
{/* MCP endpoints */}
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h3 className="text-sm font-semibold text-white mb-3">MCP Server Endpoints</h3>
<div className="space-y-2">
<div className="flex items-center justify-between p-2.5 rounded-lg bg-blue-500/5 border border-blue-500/20">
<div className="flex items-center gap-2">
<span className="text-xs text-blue-300 font-semibold">Base / EVM</span>
</div>
<code className="text-xs text-cyan-400 font-mono">{WORKER_BASE}/mcp</code>
</div>
<div className="flex items-center justify-between p-2.5 rounded-lg bg-purple-500/5 border border-purple-500/20">
<div className="flex items-center gap-2">
<span className="text-xs text-purple-300 font-semibold">Solana</span>
</div>
<code className="text-xs text-cyan-400 font-mono">{WORKER_SOL}/mcp</code>
</div>
<div className="flex items-center justify-between p-2.5 rounded-lg bg-white/[0.02] border border-white/5">
<div className="flex items-center gap-2">
<span className="text-xs text-gray-300 font-semibold">Rugmunch Router</span>
<span className="text-[10px] text-gray-500">(legacy)</span>
</div>
<code className="text-xs text-gray-500 font-mono">https://mcp-router.rugmunch.io/mcp</code>
</div>
</div>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h3 className="text-sm font-semibold text-white mb-3">Claude Desktop Config</h3>
<CodeBlock
lang="json"
id="claude-config"
code={`{
"mcpServers": {
"rugmunch-base": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-http"],
"env": {
"MCP_SERVER_URL": "https://mcp.rugmunch.io/mcp"
}
},
"rugmunch-solana": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-http"],
"env": {
"MCP_SERVER_URL": "https://mcp.rugmunch.io/mcp"
}
}
}
}`}
/>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h3 className="text-sm font-semibold text-white mb-3">Cursor MCP Setup</h3>
<CodeBlock
lang="bash"
id="cursor-mcp"
code={`# Add to Cursor MCP settings:
{
"mcpServers": {
"rugmunch-base": {
"url": "https://mcp.rugmunch.io/mcp"
},
"rugmunch-solana": {
"url": "https://mcp.rugmunch.io/mcp"
}
}
}`}
/>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h3 className="text-sm font-semibold text-white mb-3">MCP Tool Call Example</h3>
<CodeBlock
lang="json"
id="mcp-call"
code={`// MCP tool call payload (sent via MCP protocol):
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "audit",
"arguments": {
"address": "So11111111111111111111111111111111111111112",
"chain": "solana"
}
},
"id": 1
}
// The MCP server handles x402 payment automatically
// if configured with your wallet credentials.`}
/>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h3 className="text-sm font-semibold text-white mb-3">Supported AI Agents</h3>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2 mt-3">
{[
'Claude Desktop',
'ChatGPT',
'Cursor',
'Windsurf',
'Claude Code',
'Codex CLI',
'OpenCode',
'Any MCP Client',
].map((name) => (
<div key={name} className="p-2 rounded-lg bg-black/20 text-center">
<p className="text-xs text-white">{name}</p>
</div>
))}
</div>
</div>
</div>
</div>
)
}
/* ── Payment Facilitator Cards ── */
const PAYMENT_FACILITATORS = [
{
id: 'coinbase-cdp',
name: 'Coinbase CDP',
icon: DollarSign,
color: 'text-blue-400',
border: 'border-blue-500/20',
bg: 'bg-blue-500/5',
chains: 'Base, Solana',
desc: 'Fee-free USDC payments via Coinbase Developer Platform. Facilitator-verified settlement in <1s.',
tag: 'Recommended',
},
{
id: 'payai',
name: 'PayAI',
icon: Brain,
color: 'text-purple-400',
border: 'border-purple-500/20',
bg: 'bg-purple-500/5',
chains: 'Multi-chain',
desc: 'Deferred settlement for AI agents. Allows async payment confirmation with guaranteed payout.',
tag: 'AI Agents',
},
{
id: 'cloudflare-x402',
name: 'Cloudflare x402',
icon: Server,
color: 'text-orange-400',
border: 'border-orange-500/20',
bg: 'bg-orange-500/5',
chains: 'Base, Solana',
desc: 'Cloudflare Worker-based x402 gateway. Edge-verified payment with global low-latency settlement.',
tag: 'Edge',
},
{
id: 'eip7702',
name: 'EIP-7702',
icon: Shield,
color: 'text-cyan-400',
border: 'border-cyan-500/20',
bg: 'bg-cyan-500/5',
chains: 'All EVM',
desc: 'Universal EVM payment via EIP-7702 code delegation. One signature works across Ethereum, Base, BSC, ARB, OPT, POL.',
tag: 'Universal',
},
{
id: 'tron-self-verify',
name: 'TRON Self-Verify',
icon: CheckCircle,
color: 'text-red-400',
border: 'border-red-500/20',
bg: 'bg-red-500/5',
chains: 'TRON',
desc: 'Self-verified USDT/USDC/USDD payments on TRON. Client reads on-chain receipt for proof of settlement.',
tag: 'Stablecoin',
},
{
id: 'btc-self-verify',
name: 'Bitcoin Self-Verify',
icon: DollarSign,
color: 'text-amber-400',
border: 'border-amber-500/20',
bg: 'bg-amber-500/5',
chains: 'Bitcoin',
desc: 'Self-verified BTC payments. On-chain transaction receipt verified client-side after broadcast.',
tag: 'BTC',
},
{
id: 'asterpay',
name: 'AsterPay',
icon: Globe,
color: 'text-green-400',
border: 'border-green-500/20',
bg: 'bg-green-500/5',
chains: 'EUR / SEPA',
desc: 'Fiat on-ramp via SEPA bank transfer. EUR-denominated payments for European users and businesses.',
tag: 'Fiat',
},
{
id: 'x402-rs',
name: 'x402-rs',
icon: Terminal,
color: 'text-gray-400',
border: 'border-white/10',
bg: 'bg-white/[0.02]',
chains: 'Self-hosted',
desc: 'Self-hosted x402 facilitator in Rust. Run your own payment gateway for full sovereignty and custom chains.',
tag: 'Self-hosted',
},
]

View file

@ -0,0 +1,230 @@
import { motion } from 'framer-motion'
import { CheckCircle, CreditCard, Lock, ShieldCheck, Terminal, Wallet } from 'lucide-react'
import { useState } from 'react'
import { normalizeChain } from './utils'
import CodeBlock from './CodeBlock'
export default export function PaymentFlowSection() {
const steps = [
{
icon: Wallet,
title: 'Connect Wallet',
desc: 'Click Connect Wallet in the UI. MetaMask, WalletConnect, Phantom, or any Web3 wallet.',
detail: 'Supports all 13 chains: Base, Solana, ETH, BSC, ARB, OPT, POL, AVAX, FTM, GNO, TRX, BTC, SEPA',
},
{
icon: CreditCard,
title: 'x402 EIP-712 Signing',
desc: 'Your wallet signs a USDC transfer authorization. No gas fees, no contract interaction.',
detail: 'Base & Solana: facilitator-verified | ETH/BSC/ARB/OPT/POL: self-verification (EIP-3009)',
},
{
icon: Terminal,
title: 'Send Request',
desc: 'Signed authorization sent in the x-pay header along with the tool request and device fingerprint.',
detail: 'Headers: x-pay (signed payload) + X-Device-Id (fingerprint hash)',
},
{
icon: CheckCircle,
title: 'Instant Result',
desc: 'Payment verified on-chain, tool executes, results returned immediately.',
detail: 'Facilitator chains: <1s | Self-verify chains: <3s',
},
]
const [openStep, setOpenStep] = useState<number | null>(0)
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold text-white mb-2">x402 Payment Flow</h2>
<p className="text-gray-400 mb-2">
Rug Munch uses the x402 protocol for trustless micropayments across 13 chains. Pay per query with USDC, USDT,
BTC, or EUR no subscriptions, no API keys. 8 facilitators available.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-6">
<div className="p-4 rounded-xl bg-[#0e0e16] border border-cyan-500/20">
<div className="flex items-center gap-2 mb-2">
<ShieldCheck className="w-4 h-4 text-cyan-400" />
<h3 className="text-sm font-semibold text-cyan-300">Facilitator-Verified Chains</h3>
</div>
<p className="text-xs text-gray-400 mb-2">
Payment verified server-side by the x402 facilitator. Fastest settlement.
</p>
<div className="flex flex-wrap gap-1.5">
{['BASE', 'SOLANA'].map((c) => (
<span
key={c}
className={`text-xs px-2 py-1 rounded-md font-mono ${
c === 'SOLANA' ? 'bg-purple-500/10 text-purple-300' : 'bg-blue-500/10 text-blue-300'
}`}
>
{normalizeChain(c)}
</span>
))}
</div>
<p className="text-[10px] text-gray-500 mt-2">
Solana uses SPL-USDC with Phantom signing. Base uses EIP-3009 with MetaMask/WalletConnect.
</p>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-purple-500/20">
<div className="flex items-center gap-2 mb-2">
<Lock className="w-4 h-4 text-purple-400" />
<h3 className="text-sm font-semibold text-purple-300">Self-Verification Chains</h3>
</div>
<p className="text-xs text-gray-400 mb-2">
Payment verified client-side by reading on-chain receipt. No facilitator needed.
</p>
<div className="flex flex-wrap gap-1.5">
{['ETHEREUM', 'BSC', 'ARBITRUM', 'OPTIMISM', 'POLYGON'].map((c) => (
<span key={c} className="text-xs px-2 py-1 rounded-md bg-white/5 text-gray-300 font-mono">
{normalizeChain(c)}
</span>
))}
</div>
<p className="text-[10px] text-gray-500 mt-2">
Uses EIP-3009 TransferWithAuthorization. Your client reads the tx receipt for proof of payment.
</p>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-4 gap-4 mb-8">
{steps.map((step, i) => {
const Icon = step.icon
return (
<div key={step.title} className="relative">
{i < steps.length - 1 && (
<div className="hidden sm:block absolute top-8 left-full w-full h-px bg-gradient-to-r from-purple-500/50 to-transparent z-0" />
)}
<div
className={`relative p-4 rounded-xl border transition-all cursor-pointer ${
openStep === i
? 'bg-purple-500/10 border-purple-500/30'
: 'bg-[#0e0e16] border-white/5 hover:border-white/10'
}`}
onClick={() => setOpenStep(openStep === i ? null : i)}
>
<div className="w-8 h-8 rounded-lg bg-purple-500/20 flex items-center justify-center mb-3">
<Icon className="w-4 h-4 text-purple-400" />
</div>
<p className="text-sm font-semibold text-white">{step.title}</p>
<p className="text-xs text-gray-400 mt-1">{step.desc}</p>
</div>
</div>
)
})}
</div>
{openStep !== null && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="p-4 rounded-xl bg-[#0e0e16] border border-white/10"
>
<p className="text-sm text-gray-300">
<strong className="text-white">{steps[openStep].title}:</strong> {steps[openStep].detail}
</p>
</motion.div>
)}
<div className="mt-6 space-y-4">
<h3 className="text-lg font-semibold text-white">Payment Code Examples</h3>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h4 className="text-sm font-semibold text-white mb-2">JavaScript Sign & Pay with x402 (Base)</h4>
<CodeBlock
lang="javascript"
id="x402-js-base"
code={`import { createWalletClient, custom } from 'viem';
import { base } from 'viem/chains';
import { databus } from '@/services/databus';
// Connect wallet
const client = createWalletClient({
chain: base,
transport: custom(window.ethereum),
});
const [address] = await client.requestAddresses();
// Sign x402 payment for a $0.05 tool on Base
const payment = await signX402Payment('audit', '50000', 8453);
// Execute tool with payment + device fingerprint
const res = await fetch('https://mcp.rugmunch.io/api/v1/x402-tools/audit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-pay': payment.header,
'X-Device-Id': getDeviceFingerprint(),
},
body: JSON.stringify({ address: '0x...', chain: 'base' }),
});
const result = await res.json();`}
/>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h4 className="text-sm font-semibold text-white mb-2">JavaScript Sign & Pay with x402 (Solana)</h4>
<CodeBlock
lang="javascript"
id="x402-js-sol"
code={`import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom';
// Connect Phantom wallet
const wallet = new PhantomWalletAdapter();
await wallet.connect();
// Sign x402 payment for a $0.05 tool on Solana
const payment = await signSolanaX402Payment('audit', '50000', wallet);
// Execute tool with payment + device fingerprint
const res = await fetch('https://mcp.rugmunch.io/api/v1/x402-tools/audit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-pay': payment.header,
'X-Device-Id': getDeviceFingerprint(),
},
body: JSON.stringify({ address: wallet.publicKey, chain: 'solana' }),
});
const result = await res.json();`}
/>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h4 className="text-sm font-semibold text-white mb-2">cURL Execute Tool with Payment Header</h4>
<CodeBlock
lang="bash"
id="x402-curl"
code={`# Get the x402 payment header from your wallet signer
# Then include it in the request:
curl -X POST https://mcp.rugmunch.io/api/v1/x402-tools/audit \\
-H "Content-Type: application/json" \\
-H "x-pay: x402 {signed-payment-payload}" \\
-H "X-Device-Id: {device-fingerprint-hash}" \\
-d '{"address": "0x1234...", "chain": "base"}'`}
/>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h4 className="text-sm font-semibold text-white mb-2">Free Trial No Payment Needed</h4>
<CodeBlock
lang="bash"
id="trial-curl"
code={`# Many tools offer free trial queries.
# Just call the endpoint without the x-pay header:
curl -X POST https://x402-base.cryptorugmuncher.workers.dev/api/v1/x402-tools/urlcheck \\
-H "Content-Type: application/json" \\
-H "X-Device-Id: {device-fingerprint-hash}" \\
-d '{"url": "https://suspicious-site.com"}'
# Returns: { "risk_score": 85, "verdict": "SCAM", ... }
# After trial limit reached: 402 Payment Required`}
/>
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,126 @@
import { DollarSign, CreditCard, Wallet } from 'lucide-react'
export function PaymentOptionsSection() {
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold text-white mb-2">Payment Options</h2>
<p className="text-gray-400">
RMI supports 8 payment facilitators across multiple chains and fiat rails. Pay per query with USDC, BTC, EUR, or
stablecoins no subscriptions required.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{PAYMENT_FACILITATORS.map((fac, i) => {
const Icon = fac.icon
return (
<motion.div
key={fac.id}
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.05 }}
className={`p-4 rounded-xl ${fac.bg} border ${fac.border}`}
>
<div className="flex items-center gap-3 mb-2">
<div className={`w-8 h-8 rounded-lg ${fac.bg} flex items-center justify-center border ${fac.border}`}>
<Icon className={`w-4 h-4 ${fac.color}`} />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h3 className="text-sm font-semibold text-white">{fac.name}</h3>
<span className={`text-[10px] px-1.5 py-0.5 rounded ${fac.bg} ${fac.color} border ${fac.border}`}>
{fac.tag}
</span>
</div>
<p className="text-[10px] text-gray-500 font-mono">{fac.chains}</p>
</div>
</div>
<p className="text-xs text-gray-400 leading-relaxed">{fac.desc}</p>
</motion.div>
)
})}
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h3 className="text-sm font-semibold text-white mb-2">How Payments Route</h3>
<p className="text-xs text-gray-400 mb-3">
When you call a tool endpoint, the x402 protocol automatically routes payment to the correct facilitator based
on the chain and payment method in your signed authorization.
</p>
<div className="space-y-2 text-xs text-gray-300">
<div className="flex items-start gap-2">
<span className="text-cyan-400 font-mono shrink-0">Base/SOL</span>
<span> Coinbase CDP facilitator (fee-free, &lt;1s settlement)</span>
</div>
<div className="flex items-start gap-2">
<span className="text-purple-400 font-mono shrink-0">ETH/BSC/ARB/OPT/POL</span>
<span> EIP-3009 self-verification (1-5s settlement)</span>
</div>
<div className="flex items-start gap-2">
<span className="text-red-400 font-mono shrink-0">TRON</span>
<span> Self-verified USDT/USDC/USDD transfer</span>
</div>
<div className="flex items-start gap-2">
<span className="text-amber-400 font-mono shrink-0">BTC</span>
<span> Self-verified on-chain transaction</span>
</div>
<div className="flex items-start gap-2">
<span className="text-green-400 font-mono shrink-0">EUR/SEPA</span>
<span> AsterPay fiat on-ramp</span>
</div>
</div>
</div>
</div>
)
}
/* ── FAQ Accordion Section ── */
const FAQ_ITEMS = [
{
q: 'What is Rug Munch Intelligence (RMI)?',
a: 'RMI is an AI-powered crypto security and market intelligence platform offering 201+ tools via REST API and Model Context Protocol (MCP). Tools cover security audits, wallet profiling, whale tracking, DeFi scanning, social sentiment, and more across 13 blockchains.',
},
{
q: 'What is Model Context Protocol (MCP)?',
a: 'MCP is an open standard that enables AI agents like Claude, ChatGPT, and Cursor to discover and call external tools. RMI exposes its full tool catalog via MCP endpoints — your AI agent can call any of the 201 tools directly without writing custom integration code.',
},
{
q: 'How does x402 payment work?',
a: 'x402 is an HTTP 402 Payment Required protocol for micropayments. Your wallet signs a USDC transfer authorization (EIP-712 on EVM, SPL transfer on Solana). The signed authorization goes in the x-pay header. The facilitator verifies on-chain and settles — no gas fees, no contract approvals needed.',
},
{
q: 'Which wallets are supported?',
a: 'MetaMask, Phantom, Solflare, Coinbase Wallet, Rainbow, WalletConnect, TronLink, and Backpack are all supported. Any wallet that injects window.ethereum (EVM) or window.solana (Solana) works — including Rabby, Trust Wallet, and other Web3 wallets.',
},
{
q: 'Which chains does RMI support?',
a: 'RMI supports 13 chains: Base, Solana, Ethereum, BSC, Arbitrum, Optimism, Polygon, Avalanche, Fantom, Gnosis, TRON, Bitcoin, and SEPA (EUR). 8 payment facilitators cover all chains — Coinbase CDP (fee-free), EIP-7702 (universal EVM), TRON Self-Verify, Bitcoin Self-Verify, and AsterPay (EUR/SEPA).',
},
{
q: 'Are there free trials?',
a: "Yes! Every tool offers a free trial: 1 free query with device fingerprint only, 3 free queries with a connected wallet. Just include the X-Device-Id header — no API key or credit card required. After trial limits are reached, you'll get a 402 Payment Required response.",
},
{
q: 'What is the refund policy?',
a: 'Full refund if a tool returns no data, an empty result, or a backend error. Refunds are automatic on facilitator chains (<30s). Self-verify chains refund in 1-5 minutes. You can also manually request a refund via the /api/v1/x402/refund endpoint.',
},
{
q: 'How do I connect RMI to Claude or Cursor?',
a: 'Add the MCP server URL to your Claude Desktop config or Cursor MCP settings. Use https://mcp.rugmunch.io/mcp for all tools (both EVM and Solana). See the MCP Integration section for full config examples.',
},
{
q: 'What is device fingerprinting?',
a: 'Every request must include an X-Device-Id header containing a SHA-256 hash of browser Canvas, WebGL renderer, and font data. This uniquely identifies your browser without requiring accounts. It enforces trial limits and rate limits to prevent abuse.',
},
{
q: 'Can I run my own facilitator?',
a: 'Yes. The x402-rs facilitator (written in Rust) lets you run your own payment gateway for full sovereignty. It supports custom chain configurations and can be self-hosted. See the Payment Options section for details.',
},
{
q: 'How do I find available tools programmatically?',
a: 'Use the discovery endpoints: GET /.well-known/x402 for the x402 standard config, or GET /api/v1/x402/tools-catalog for the full catalog with metadata. Both return machine-readable JSON suitable for AI agents and CI/CD pipelines.',
},
{
q: 'What happens after my free trial expires?',
a: 'After exceeding your free trial queries (1 per tool fingerprint-only, 3 per tool wallet-connected), subsequent calls return HTTP 402 Payment Required with the price in the response body. You then need to include an x-pay header with a signed USDC authorization.',
},
]

View file

@ -0,0 +1,104 @@
import { RotateCcw, DollarSign, AlertTriangle } from 'lucide-react'
export function RefundPolicySection() {
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold text-white mb-2">Refund Policy</h2>
<p className="text-gray-400">Full refund if no data is returned. We stand behind every query.</p>
{/* Refund guarantee callout */}
<div className="p-5 rounded-xl bg-gradient-to-r from-green-500/5 to-cyan-500/5 border border-green-500/20">
<div className="flex items-center gap-3 mb-3">
<div className="w-10 h-10 rounded-lg bg-green-500/20 flex items-center justify-center">
<RotateCcw className="w-5 h-5 text-green-400" />
</div>
<div>
<h3 className="text-lg font-bold text-white">No Data = Full Refund</h3>
<p className="text-sm text-gray-400">Automatic refund if the tool returns empty or error responses</p>
</div>
</div>
<p className="text-sm text-gray-300">
If a paid tool execution returns no data (empty result, null, or an error that is our fault), your USDC
payment is automatically refunded. No support ticket needed.
</p>
</div>
{/* Refund rules */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div className="p-4 rounded-xl bg-[#0e0e16] border border-green-500/20">
<div className="flex items-center gap-2 mb-2">
<CheckCircle className="w-4 h-4 text-green-400" />
<h3 className="text-sm font-semibold text-green-300">Refunded</h3>
</div>
<ul className="space-y-1.5 text-xs text-gray-400">
<li>Tool returns empty or null data</li>
<li>Backend error (5xx) before execution</li>
<li>Tool times out before returning results</li>
<li>Data source returns malformed response</li>
</ul>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-red-500/20">
<div className="flex items-center gap-2 mb-2">
<XCircle className="w-4 h-4 text-red-400" />
<h3 className="text-sm font-semibold text-red-300">Not Refunded</h3>
</div>
<ul className="space-y-1.5 text-xs text-gray-400">
<li>Valid data returned (even if unfavorable)</li>
<li>Invalid input parameters from client</li>
<li>Rate-limited or blocked (abuse)</li>
<li>Payment signature expired or invalid</li>
</ul>
</div>
</div>
{/* Refund API endpoint */}
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h3 className="text-sm font-semibold text-white mb-3">Refund API</h3>
<p className="text-xs text-gray-400 mb-3">
Refunds are automatically initiated by the backend when no data is returned. You can also manually request a
refund:
</p>
<CodeBlock
lang="bash"
id="refund-api"
code={`# Request a refund for a tool execution
# Requires the original transaction hash
curl -X POST https://rugmunch.io/api/v1/x402/refund \\
-H "Content-Type: application/json" \\
-d '{
"tx_hash": "0xabc123...",
"tool_id": "audit",
"reason": "no_data_returned"
}'
# Response:
# {
# "status": "refunded",
# "amount": "0.05",
# "refund_tx": "0xdef456...",
# "chain": "base"
# }`}
/>
</div>
{/* Refund timeline */}
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h3 className="text-sm font-semibold text-white mb-3">Refund Timeline</h3>
<div className="space-y-2">
{[
{ label: 'Facilitator chains (Base, Solana)', time: '< 30 seconds', color: 'text-cyan-400' },
{ label: 'Self-verify chains (ETH, BSC, ARB, OPT, POL)', time: '1-5 minutes', color: 'text-purple-400' },
{ label: 'Disputed refunds', time: '24-48 hours', color: 'text-amber-400' },
].map((item) => (
<div key={item.label} className="flex items-center justify-between p-2 rounded-lg bg-black/20">
<span className="text-xs text-gray-300">{item.label}</span>
<span className={`text-xs font-mono ${item.color}`}>{item.time}</span>
</div>
))}
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,165 @@
import { AnimatePresence, motion } from 'framer-motion'
import { ChevronDown, ChevronRight, Globe } from 'lucide-react'
import { useState } from 'react'
import {
capitalizeName,
CATEGORY_COLORS,
CATEGORY_ICONS,
generateCurlExample,
generateJSExample,
generatePythonExample,
generateX402Example,
getWorkerUrl,
normalizeChain,
type ToolCatalogEntry,
} from './utils'
import CodeBlock from './CodeBlock'
export default export function ToolDocCard({ tool, index }: { tool: ToolCatalogEntry; index: number }) {
const [expanded, setExpanded] = useState(false)
const [activeTab, setActiveTab] = useState<'curl' | 'python' | 'js' | 'x402'>('curl')
const Icon = CATEGORY_ICONS[tool.category] || Globe
const catColor = CATEGORY_COLORS[tool.category] || 'text-gray-400'
const curlEx = generateCurlExample(tool)
const pyEx = generatePythonExample(tool)
const jsEx = generateJSExample(tool)
const x402Ex = generateX402Example(tool)
const workerUrl = getWorkerUrl(tool.chains[0] || 'base')
return (
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: Math.min(index * 0.01, 0.5) }}
className="rounded-xl bg-[#0e0e16] border border-white/5 overflow-hidden"
>
<button
type="button"
onClick={() => setExpanded(!expanded)}
className="w-full text-left p-4 hover:bg-white/[0.02] transition-colors"
>
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3 min-w-0">
<Icon className={`w-5 h-5 ${catColor} mt-0.5 shrink-0`} />
<div className="min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<h3 className="font-mono text-sm font-bold text-white">{tool.id}</h3>
<span className="text-xs text-gray-500">{tool.method}</span>
<span className={`text-xs px-1.5 py-0.5 rounded bg-purple-500/10 text-purple-300`}>{tool.price}</span>
{tool.trialFree > 0 && (
<span className="text-xs px-1.5 py-0.5 rounded bg-green-500/10 text-green-400">
{tool.trialFree} free
</span>
)}
</div>
<p className="text-xs text-gray-400 mt-1 line-clamp-2">{tool.description}</p>
</div>
</div>
<div className="shrink-0 flex items-center gap-2">
<div className="flex gap-1">
{tool.chains.slice(0, 4).map((c) => (
<span key={c} className="text-[10px] px-1 py-0.5 rounded bg-white/5 text-gray-500">
{normalizeChain(c)}
</span>
))}
{tool.chains.length > 4 && <span className="text-[10px] text-gray-600">+{tool.chains.length - 4}</span>}
</div>
{expanded ? (
<ChevronDown className="w-4 h-4 text-gray-500" />
) : (
<ChevronRight className="w-4 h-4 text-gray-500" />
)}
</div>
</div>
</button>
<AnimatePresence>
{expanded && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2 }}
className="overflow-hidden"
>
<div className="px-4 pb-4 border-t border-white/5 pt-4 space-y-4">
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div className="p-2.5 rounded-lg bg-black/20">
<p className="text-[10px] text-gray-500 uppercase tracking-wider">Service</p>
<p className="text-xs text-white font-mono mt-0.5">{tool.service}</p>
</div>
<div className="p-2.5 rounded-lg bg-black/20">
<p className="text-[10px] text-gray-500 uppercase tracking-wider">Category</p>
<p className="text-xs text-white mt-0.5">{capitalizeName(tool.category)}</p>
</div>
<div className="p-2.5 rounded-lg bg-black/20">
<p className="text-[10px] text-gray-500 uppercase tracking-wider">Source</p>
<p className="text-xs text-white mt-0.5">
{tool.source === 'native' ? 'RMI Native' : 'External API'}
</p>
</div>
<div className="p-2.5 rounded-lg bg-black/20">
<p className="text-[10px] text-gray-500 uppercase tracking-wider">Free Trials</p>
<p className="text-xs text-green-400 mt-0.5">{tool.trialFree} queries</p>
</div>
</div>
<div>
<p className="text-[10px] text-gray-500 uppercase tracking-wider mb-2">Supported Chains</p>
<div className="flex flex-wrap gap-1.5">
{tool.chains.map((c) => (
<span
key={c}
className={`text-xs px-2 py-1 rounded-md font-mono ${
c === 'SOLANA'
? 'bg-purple-500/10 text-purple-300'
: c === 'BASE'
? 'bg-blue-500/10 text-blue-300'
: 'bg-white/5 text-gray-300'
}`}
>
{normalizeChain(c)}
</span>
))}
</div>
</div>
<div>
<p className="text-[10px] text-gray-500 uppercase tracking-wider mb-2">Code Examples</p>
<div className="flex gap-1 mb-2">
{(['curl', 'python', 'js', 'x402'] as const).map((tab) => (
<button
type="button"
key={tab}
onClick={() => setActiveTab(tab)}
className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-colors ${
activeTab === tab
? 'bg-purple-500/20 text-purple-300 border border-purple-500/30'
: 'text-gray-500 hover:text-white'
}`}
>
{tab === 'curl' ? 'cURL' : tab === 'python' ? 'Python' : tab === 'js' ? 'JavaScript' : 'x402 Pay'}
</button>
))}
</div>
{activeTab === 'curl' && <CodeBlock code={curlEx} lang="bash" id={`${tool.id}-curl`} />}
{activeTab === 'python' && <CodeBlock code={pyEx} lang="python" id={`${tool.id}-py`} />}
{activeTab === 'js' && <CodeBlock code={jsEx} lang="javascript" id={`${tool.id}-js`} />}
{activeTab === 'x402' && <CodeBlock code={x402Ex} lang="javascript" id={`${tool.id}-x402`} />}
</div>
<div className="p-3 rounded-lg bg-black/20 border border-white/5">
<p className="text-[10px] text-gray-500 uppercase tracking-wider mb-1">Endpoint</p>
<code className="text-xs text-cyan-400 font-mono break-all">
{tool.method === 'POST' ? 'POST' : 'GET'} {workerUrl}/api/v1/x402-tools/{tool.id}
</code>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
)
}

View file

@ -0,0 +1,116 @@
import { Fingerprint, Wallet, CheckCircle, XCircle } from 'lucide-react'
import { useMemo } from 'react'
import { CodeBlock } from '@/components/mcp-docs/CodeBlock'
import type { ToolCatalogEntry } from '@/components/mcp-docs/utils'
export function TrialSystemSection({ tools }: { tools: ToolCatalogEntry[] }) {
const trialStats = useMemo(() => {
const withTrials = tools.filter((t) => t.trialFree > 0)
const totalFree = withTrials.reduce((sum, t) => sum + t.trialFree, 0)
const byCount: Record<number, number> = {}
tools.forEach((t) => {
byCount[t.trialFree] = (byCount[t.trialFree] || 0) + 1
})
return { withTrials: withTrials.length, totalFree, byCount, all: tools.length }
}, [tools])
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold text-white mb-2">Free Trial System</h2>
<p className="text-gray-400">
Every tool offers a free trial 1 free query with device fingerprint only, 3 free with a connected wallet.
Trial counts reset periodically.
</p>
{/* Stats */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10 text-center">
<p className="text-3xl font-bold text-green-400">{trialStats.withTrials}</p>
<p className="text-xs text-gray-500 mt-1">Tools with Free Trials</p>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10 text-center">
<p className="text-3xl font-bold text-green-400">{trialStats.totalFree}</p>
<p className="text-xs text-gray-500 mt-1">Total Free Queries</p>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10 text-center">
<p className="text-3xl font-bold text-purple-400">{Object.keys(trialStats.byCount).length}</p>
<p className="text-xs text-gray-500 mt-1">Trial Tiers</p>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10 text-center">
<p className="text-3xl font-bold text-gray-400">{trialStats.all || 103}</p>
<p className="text-xs text-gray-500 mt-1">Total Tools</p>
</div>
</div>
{/* Trial tier hierarchy */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div className="p-4 rounded-xl bg-[#0e0e16] border border-green-500/20">
<div className="flex items-center gap-2 mb-2">
<Fingerprint className="w-4 h-4 text-green-400" />
<h3 className="text-sm font-semibold text-green-300">Device Fingerprint Only</h3>
</div>
<p className="text-xs text-gray-400 mb-1">No wallet needed. 1 free query per tool.</p>
<p className="text-[10px] text-gray-500">Tracked via X-Device-Id header (canvas/WebGL/font hash)</p>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-cyan-500/20">
<div className="flex items-center gap-2 mb-2">
<Wallet className="w-4 h-4 text-cyan-400" />
<h3 className="text-sm font-semibold text-cyan-300">Connected Wallet</h3>
</div>
<p className="text-xs text-gray-400 mb-1">3 free queries per tool when wallet is connected.</p>
<p className="text-[10px] text-gray-500">Higher trust tier = more free queries</p>
</div>
</div>
{/* How trials work */}
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h3 className="text-sm font-semibold text-white mb-3">How Trials Work</h3>
<ul className="space-y-2 text-sm text-gray-300">
<li className="flex items-start gap-2">
<CheckCircle className="w-4 h-4 text-green-400 mt-0.5 shrink-0" /> Call any tool endpoint without an x-pay
header
</li>
<li className="flex items-start gap-2">
<CheckCircle className="w-4 h-4 text-green-400 mt-0.5 shrink-0" /> Include X-Device-Id header for trial
tracking
</li>
<li className="flex items-start gap-2">
<Fingerprint className="w-4 h-4 text-green-400 mt-0.5 shrink-0" /> Fingerprint-only: 1 free query per tool
</li>
<li className="flex items-start gap-2">
<Wallet className="w-4 h-4 text-cyan-400 mt-0.5 shrink-0" /> Connected wallet: 3 free queries per tool
</li>
<li className="flex items-start gap-2">
<CheckCircle className="w-4 h-4 text-green-400 mt-0.5 shrink-0" /> After trial limit: backend returns 402
Payment Required
</li>
<li className="flex items-start gap-2">
<XCircle className="w-4 h-4 text-red-400 mt-0.5 shrink-0" /> No credit card, no signup, no API key needed
for trials
</li>
</ul>
</div>
{/* Try a free tool */}
<div className="p-4 rounded-xl bg-green-500/5 border border-green-500/20">
<h3 className="text-sm font-semibold text-green-300 mb-2">Try a Free Tool Now</h3>
<CodeBlock
lang="bash"
id="free-tool"
code={`# No payment needed — just include device fingerprint:
# X-Device-Id is a hash of canvas/WebGL/font data from your browser.
curl -X POST https://mcp.rugmunch.io/api/v1/x402-tools/urlcheck \\
-H "Content-Type: application/json" \\
-H "X-Device-Id: \${YOUR_DEVICE_FINGERPRINT}" \\
-d '{"url": "https://suspicious-site.com"}'
# Or try market data (no params needed):
curl https://mcp.rugmunch.io/api/v1/x402-tools/pulse \\
-H "X-Device-Id: \${YOUR_DEVICE_FINGERPRINT}"`}
/>
</div>
</div>
)
}

View file

@ -0,0 +1,165 @@
import { motion } from 'framer-motion'
import { Wallet } from 'lucide-react'
import { useAuth } from '@/contexts/AuthContext'
export default export function WalletConnectionSection() {
const { walletAddress, walletProvider, walletLogin, disconnectWallet, isAuthenticated } = useAuth()
const wallets = [
{
name: 'MetaMask',
icon: '🦊',
desc: 'Browser extension. EVM chains (Base, ETH, BSC, ARB, OPT, POL).',
provider: 'metamask',
},
{ name: 'Phantom', icon: '👻', desc: 'Solana + multi-chain. Built-in swap & NFT support.', provider: 'phantom' },
{
name: 'Solflare',
icon: '☀️',
desc: 'Solana-focused wallet with staking and DeFi features.',
provider: 'solflare',
},
{
name: 'Coinbase Wallet',
icon: '🔵',
desc: 'Self-custody. Multi-chain — Base, ETH, Solana.',
provider: 'coinbase',
},
{ name: 'Rainbow', icon: '🌈', desc: 'Mobile-first EVM wallet. Clean UX, multi-chain.', provider: 'rainbow' },
{
name: 'WalletConnect',
icon: '🔗',
desc: 'QR code scan. 300+ mobile wallets supported.',
provider: 'walletconnect',
},
{ name: 'TronLink', icon: '🔴', desc: 'TRON ecosystem wallet. USDT/USDC/USDD on TRON.', provider: null },
{ name: 'Backpack', icon: '🎒', desc: 'Solana + EVM. xNFT support, multi-chain.', provider: 'backpack' },
]
const supportedChains = [
{ name: 'Base', id: 8453, mode: 'Facilitator', color: 'bg-blue-500/20 text-blue-300' },
{ name: 'Solana', id: '—', mode: 'Facilitator', color: 'bg-purple-500/20 text-purple-300' },
{ name: 'Ethereum', id: 1, mode: 'Self-Verify', color: 'bg-gray-500/20 text-gray-300' },
{ name: 'BSC', id: 56, mode: 'Self-Verify', color: 'bg-yellow-500/20 text-yellow-300' },
{ name: 'Arbitrum', id: 42161, mode: 'Self-Verify', color: 'bg-sky-500/20 text-sky-300' },
{ name: 'Optimism', id: 10, mode: 'Self-Verify', color: 'bg-red-500/20 text-red-300' },
{ name: 'Polygon', id: 137, mode: 'Self-Verify', color: 'bg-indigo-500/20 text-indigo-300' },
]
return (
<div className="space-y-6">
<h2 className="text-2xl font-bold text-white mb-2">Wallet Connection</h2>
<p className="text-gray-400">
Connect any Web3 wallet to pay for tools with USDC across 13 chains. The wallet signs EIP-712 or Solana
authorizations no gas, no contract approvals needed.
</p>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
{wallets.map((w) => {
const isConnected = isAuthenticated && walletProvider === w.provider
return (
<div
key={w.name}
className={`p-3 rounded-xl bg-[#0e0e16] border transition-colors ${isConnected ? 'border-green-500/30' : 'border-white/5'}`}
>
<div className="text-xl mb-1">{w.icon}</div>
<p className="text-sm font-medium text-white">{w.name}</p>
<p className="text-xs text-gray-500 mt-0.5">{w.desc}</p>
{w.provider && (
<button
type="button"
onClick={() => (isConnected ? disconnectWallet() : walletLogin(w.provider as any))}
className={`mt-2 w-full text-xs py-1.5 px-2 rounded-lg transition-colors ${
isConnected
? 'bg-green-500/10 border border-green-500/20 text-green-400 hover:bg-green-500/20'
: 'bg-purple-500/10 border border-purple-500/20 text-purple-300 hover:bg-purple-500/20'
}`}
>
{isConnected ? 'Disconnect' : 'Connect'}
</button>
)}
</div>
)
})}
</div>
{walletAddress && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="p-4 rounded-xl bg-green-500/5 border border-green-500/20"
>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-green-500/10 flex items-center justify-center">
<Wallet className="w-5 h-5 text-green-400" />
</div>
<div>
<p className="text-sm font-semibold text-green-300">Wallet Connected</p>
<p className="text-xs text-gray-400 font-mono">
{walletAddress.slice(0, 6)}...{walletAddress.slice(-4)}
</p>
<p className="text-[10px] text-gray-500 mt-0.5">Provider: {walletProvider}</p>
</div>
<button
type="button"
onClick={disconnectWallet}
className="ml-auto text-xs text-gray-500 hover:text-red-400 transition-colors"
>
Disconnect
</button>
</div>
</motion.div>
)}
<div>
<h3 className="text-sm font-semibold text-white mb-3">Supported Payment Chains (7)</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{supportedChains.map((c) => (
<div
key={c.name}
className="flex items-center justify-between p-2.5 rounded-lg bg-black/20 border border-white/5"
>
<div className="flex items-center gap-2">
<span className={`text-xs px-2 py-0.5 rounded font-mono ${c.color}`}>{c.name}</span>
{c.id !== '—' && <span className="text-[10px] text-gray-600 font-mono">ID: {c.id}</span>}
</div>
<span
className={`text-[10px] px-1.5 py-0.5 rounded ${
c.mode === 'Facilitator' ? 'bg-cyan-500/10 text-cyan-400' : 'bg-purple-500/10 text-purple-400'
}`}
>
{c.mode}
</span>
</div>
))}
</div>
</div>
<div className="p-4 rounded-xl bg-[#0e0e16] border border-white/10">
<h3 className="text-sm font-semibold text-white mb-3">How Wallet Connection Works</h3>
<ol className="space-y-2 text-sm text-gray-300">
<li className="flex gap-2">
<span className="text-purple-400 font-mono">1.</span> Click "Connect Wallet" your wallet prompts for
account access
</li>
<li className="flex gap-2">
<span className="text-purple-400 font-mono">2.</span> When executing a tool, a payment signature is
requested no gas fee
</li>
<li className="flex gap-2">
<span className="text-purple-400 font-mono">3.</span> Solana: Phantom signs SPL-USDC transfer. EVM: EIP-3009
TransferWithAuthorization
</li>
<li className="flex gap-2">
<span className="text-purple-400 font-mono">4.</span> The signed authorization is sent with your tool
request + device fingerprint
</li>
<li className="flex gap-2">
<span className="text-purple-400 font-mono">5.</span> Backend verifies (facilitator or self-verify),
processes USDC transfer, returns results
</li>
</ol>
</div>
</div>
)
}

View file

@ -0,0 +1,237 @@
import {
BarChart3,
Code,
Crosshair,
Crown,
Database,
Eye,
Globe,
Package,
Scan,
Shield,
Target,
Users,
Zap,
type LucideIcon,
} from 'lucide-react'
import { formatPayload, generateSamplePayload } from '@/data/tool-params'
export interface ToolCatalogEntry {
id: string
name: string
description: string
price: string
priceUsd: number
category: string
trialFree: number
method: string
service: string
source: string
chains: string[]
}
export interface ToolCatalogResponse {
tools: ToolCatalogEntry[]
total_tools: number
total_chains: number
total_services: number
chains: Record<string, number>
}
export const CHAIN_DISPLAY: Record<string, string> = {
SOLANA: 'SOL',
ETHEREUM: 'ETH',
BASE: 'BASE',
BSC: 'BSC',
ARBITRUM: 'ARB',
OPTIMISM: 'OPT',
POLYGON: 'POL',
AVALANCHE: 'AVAX',
FANTOM: 'FTM',
GNOSIS: 'GNO',
TRON: 'TRX',
BITCOIN: 'BTC',
SEPA: 'SEPA',
}
export function normalizeChain(c: string): string {
return CHAIN_DISPLAY[c] || c
}
export const CATEGORIES: { id: string; label: string; icon: LucideIcon; color: string }[] = [
{ id: 'all', label: 'All', icon: Globe, color: 'text-gray-300' },
{ id: 'security', label: 'Security', icon: Shield, color: 'text-red-400' },
{ id: 'intelligence', label: 'Intelligence', icon: Eye, color: 'text-purple-400' },
{ id: 'analysis', label: 'Analysis', icon: Scan, color: 'text-teal-400' },
{ id: 'market', label: 'Market', icon: BarChart3, color: 'text-green-400' },
{ id: 'social', label: 'Social', icon: Users, color: 'text-pink-400' },
{ id: 'launchpad', label: 'Launchpad', icon: Zap, color: 'text-yellow-400' },
{ id: 'forensics', label: 'Forensics', icon: Crosshair, color: 'text-rose-400' },
{ id: 'defi', label: 'DeFi', icon: Database, color: 'text-blue-400' },
{ id: 'nft', label: 'NFT', icon: Target, color: 'text-orange-400' },
{ id: 'premium', label: 'Premium', icon: Crown, color: 'text-amber-400' },
{ id: 'bundle', label: 'Bundle', icon: Package, color: 'text-violet-400' },
{ id: 'api', label: 'API', icon: Code, color: 'text-cyan-400' },
]
export const CATEGORY_ICONS: Record<string, LucideIcon> = {
security: Shield,
intelligence: Eye,
analysis: Scan,
market: BarChart3,
social: Users,
launchpad: Zap,
forensics: Crosshair,
defi: Database,
nft: Target,
premium: Crown,
bundle: Package,
api: Code,
}
export const CATEGORY_COLORS: Record<string, string> = {
security: 'text-red-400',
intelligence: 'text-purple-400',
analysis: 'text-teal-400',
market: 'text-green-400',
social: 'text-pink-400',
launchpad: 'text-yellow-400',
forensics: 'text-rose-400',
defi: 'text-blue-400',
nft: 'text-orange-400',
premium: 'text-amber-400',
bundle: 'text-violet-400',
api: 'text-cyan-400',
}
export function capitalizeName(name: string): string {
return name
.split(/[_\s]+/)
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(' ')
}
export const _API_BASE = 'https://mcp.rugmunch.io/api/v1/x402-tools'
export const WORKER_BASE = 'https://x402-base.cryptorugmuncher.workers.dev'
export const WORKER_SOL = 'https://x402-sol.cryptorugmuncher.workers.dev'
export const _MARKETPLACE_URL = 'https://x402.rugmunch.io'
export const _MCP_ENDPOINT = 'https://mcp.rugmunch.io/mcp'
export const _X402_DISCOVERY = 'https://mcp.rugmunch.io/.well-known/x402'
export const PREFERRED_API = 'https://mcp.rugmunch.io/api/v1/x402-tools'
export function _getPreferredApiUrl(_chain: string): string {
return PREFERRED_API
}
export function getWorkerUrl(chain: string): string {
const normalized = chain.toUpperCase()
if (normalized === 'SOLANA' || normalized === 'SOL') return WORKER_SOL
return WORKER_BASE
}
export function getApiUrl(toolId: string): string {
return `${PREFERRED_API}/${toolId}`
}
export function generateCurlExample(tool: ToolCatalogEntry): string {
const url = getApiUrl(tool.id)
const payload = generateSamplePayload(tool.id)
const hasParams = Object.keys(payload).length > 0
const formattedPayload = hasParams ? formatPayload(payload) : '{}'
if (tool.method === 'POST') {
return `# Recommended: Use mcp.rugmunch.io (all chains)
curl -X POST ${url} \\
-H "Content-Type: application/json" \\
-H "X-Device-Id: \${YOUR_DEVICE_ID}" \\
-d '${formattedPayload}'
# Alternative: x402 Worker (chain-specific)
curl -X POST ${getWorkerUrl(tool.chains[0] || 'base')}/api/v1/x402-tools/${tool.id} \\
-H "Content-Type: application/json" \\
-H "X-Device-Id: \${YOUR_DEVICE_ID}" \\
-d '${formattedPayload}'`
}
const qs = new URLSearchParams(payload).toString()
return `curl "${url}${qs ? `?${qs}` : ''}" \\
-H "X-Device-Id: \${YOUR_DEVICE_ID}"`
}
export function generatePythonExample(tool: ToolCatalogEntry): string {
const url = getApiUrl(tool.id)
const payload = generateSamplePayload(tool.id)
if (tool.method === 'POST') {
return `import requests
# Recommended: mcp.rugmunch.io (all chains, x402 protocol)
url = "${url}"
headers = {"X-Device-Id": "your-device-fingerprint"}
payload = ${JSON.stringify(payload, null, 2)
.replace(/"(.+)"/g, '"$1"')
.replace(/^( {2})/gm, ' ')}
response = requests.post(url, json=payload, headers=headers)
print(response.json())`
}
return `import requests
url = "${url}"
headers = {"X-Device-Id": "your-device-fingerprint"}
params = ${JSON.stringify(payload, null, 2)
.replace(/"(.+)"/g, '"$1"')
.replace(/^( {2})/gm, ' ')}
response = requests.get(url, params=params, headers=headers)
print(response.json())`
}
export function generateJSExample(tool: ToolCatalogEntry): string {
const url = getApiUrl(tool.id)
const payload = generateSamplePayload(tool.id)
if (tool.method === 'POST') {
return `// Recommended: mcp.rugmunch.io (all chains, x402 protocol)
const response = await fetch('${url}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Device-Id': getDeviceFingerprint(),
},
body: JSON.stringify(${JSON.stringify(payload, null, 2)})
});
const data = await response.json();
console.log(data);`
}
const _qs = new URLSearchParams(payload).toString()
return `const response = await databus.fetch('mcp_bridge', { url, params: qs }) ? '?' + qs : ''}', {
headers: { 'X-Device-Id': getDeviceFingerprint() }
});
const data = await response.json();
console.log(data);`
}
export function generateX402Example(tool: ToolCatalogEntry): string {
const workerUrl = getWorkerUrl(tool.chains[0] || 'base')
const url = `${workerUrl}/api/v1/x402-tools/${tool.id}`
const payload = generateSamplePayload(tool.id)
const formattedPayload = formatPayload(payload)
const primaryChain = tool.chains[0] || 'BASE'
const chainId = primaryChain === 'SOLANA' ? 'solana' : '8453'
return `// Sign x402 payment for ${tool.name} (${tool.price}) on ${normalizeChain(primaryChain)}
const payment = await signX402Payment('${tool.id}', '${tool.price}', '${chainId}');
const res = await fetch('${url}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-pay': payment.header,
'X-Device-Id': getDeviceFingerprint(),
},
body: JSON.stringify(${formattedPayload}),
});
const result = await res.json();`
}

2007
src/pages/MCPDocsPage.tsx Executable file → Normal file

File diff suppressed because it is too large Load diff