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:
parent
ff36952dc9
commit
bb96ba0b08
16 changed files with 2027 additions and 1991 deletions
31
.github/workflows/ci.yml
vendored
Normal file
31
.github/workflows/ci.yml
vendored
Normal 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
|
||||
131
src/components/mcp-docs/AuthenticationHierarchySection.tsx
Normal file
131
src/components/mcp-docs/AuthenticationHierarchySection.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
27
src/components/mcp-docs/CodeBlock.tsx
Normal file
27
src/components/mcp-docs/CodeBlock.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
120
src/components/mcp-docs/DeviceFingerprintSection.tsx
Normal file
120
src/components/mcp-docs/DeviceFingerprintSection.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
205
src/components/mcp-docs/DiscoveryEndpointsSection.tsx
Normal file
205
src/components/mcp-docs/DiscoveryEndpointsSection.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
102
src/components/mcp-docs/DocLinksSection.tsx
Normal file
102
src/components/mcp-docs/DocLinksSection.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
29
src/components/mcp-docs/FAQSection.tsx
Normal file
29
src/components/mcp-docs/FAQSection.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
223
src/components/mcp-docs/MCPIntegrationSection.tsx
Normal file
223
src/components/mcp-docs/MCPIntegrationSection.tsx
Normal 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',
|
||||
},
|
||||
]
|
||||
|
||||
230
src/components/mcp-docs/PaymentFlowSection.tsx
Normal file
230
src/components/mcp-docs/PaymentFlowSection.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
126
src/components/mcp-docs/PaymentOptionsSection.tsx
Normal file
126
src/components/mcp-docs/PaymentOptionsSection.tsx
Normal 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, <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.',
|
||||
},
|
||||
]
|
||||
104
src/components/mcp-docs/RefundPolicySection.tsx
Normal file
104
src/components/mcp-docs/RefundPolicySection.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
165
src/components/mcp-docs/ToolDocCard.tsx
Normal file
165
src/components/mcp-docs/ToolDocCard.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
116
src/components/mcp-docs/TrialSystemSection.tsx
Normal file
116
src/components/mcp-docs/TrialSystemSection.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
165
src/components/mcp-docs/WalletConnectionSection.tsx
Normal file
165
src/components/mcp-docs/WalletConnectionSection.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
237
src/components/mcp-docs/utils.ts
Normal file
237
src/components/mcp-docs/utils.ts
Normal 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
2007
src/pages/MCPDocsPage.tsx
Executable file → Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue