Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions typescript/site/app/api/x402/session-token/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { POST } from "x402-next";
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "1Shot API",
"category": "Ecosystem Infrastructure & Tooling",
"logoUrl": "/logos/1shot-api.png",
"description": "A general purpose fascilitator to monetize any n8n workflow with your favorite ERC-20 token.",
"websiteUrl": "https://docs.1shotapi.com/automation/n8n.html#monetize-n8n-workflows-with-x402"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "AurraCloud",
"category": "Services/Endpoints",
"logoUrl": "/logos/aurracloud.png",
"description": "AI agents hosting and Tooling Platform, with MCP, smartWallets, OpenAI API compatibility and X402 support.",
"websiteUrl": "https://aurracloud.com/x402"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "Bonsai",
"description": "Create, remix, and trade evolving media. Use our API to generate AI content using smart media protocol (SMP) templates.",
"logoUrl": "/logos/bonsai.png",
"websiteUrl": "https://onbonsai.gitbook.io/docs/smart-media/content-generation-api",
"category": "Services/Endpoints"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "Firecrawl",
"description": "Firecrawl is a web scraping API that allows you to turn websites into LLM-ready data.",
"logoUrl": "/logos/firecrawl.png",
"websiteUrl": "https://firecrawl.dev",
"category": "Services/Endpoints"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "Questflow",
"category": "Services/Endpoints",
"logoUrl": "/logos/questflow.png",
"description": "The orchestration layer for the multi-agent economy. Orchestrate multiple AI agents to research, take action and earn rewards on-chain, autonomously.",
"websiteUrl": "https://questflow.ai?utm_source=x402_website&utm_medium=ecosystem"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "tip.md",
"description": "A crypto tipping service that enables AI assistants to help users send cryptocurrency tips to content creators directly from their chat interface. USDC tips via MCP are powered by x402. The MCP service allows for checking wallet types and preparing cryptocurrency tips for users/agents to complete.",
"logoUrl": "/logos/tipdotmd.png",
"websiteUrl": "https://www.tip.md/documentation?tab=mcp-server",
"category": "Services/Endpoints"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "x402.rs Facilitator",
"category": "Ecosystem Infrastructure & Tooling",
"logoUrl": "/logos/x402-rs.svg",
"description": "Independent, open-source facilitator in Rust. Easy to self-host, extend to new networks, or use via our hosted instance.",
"websiteUrl": "https://facilitator.x402.rs"
}
48 changes: 48 additions & 0 deletions typescript/site/app/facilitator/discovery/resources/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { NextRequest, NextResponse } from "next/server";
import {
ListDiscoveryResourcesRequest,
ListDiscoveryResourcesResponse,
ListDiscoveryResourcesResponseSchema,
} from "x402/types";

/**
* This route is used to discover the available services on the facilitator.
* It returns a list of services that are available on the facilitator.
*
* @param request - The request object
* @returns A list of services that are available on the facilitator
*/
export async function GET(request: NextRequest) {
try {
// TODO: Implement actual discovery logic

// Parse query parameters
const { searchParams } = new URL(request.url);
const { offset, limit } = Object.fromEntries(
searchParams.entries(),
) as ListDiscoveryResourcesRequest;

// TODO: Search by type, resource, fetching page size and page token

// For now, return mock data
const mockListDiscoveryResourcesResponse: ListDiscoveryResourcesResponse = {
x402Version: 1,
items: [],
pagination: {
limit,
offset,
total: 0,
},
};

// Validate response with schema
const validatedResponse = ListDiscoveryResourcesResponseSchema.parse(
mockListDiscoveryResourcesResponse,
);

return NextResponse.json(validatedResponse);
} catch (error) {
console.error("Error in discover/list:", error);
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
}
12 changes: 2 additions & 10 deletions typescript/site/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,10 @@ export default function RootLayout({
return (
<html lang="en">
<head>
<link rel="icon" href="/favicon.ico" sizes="any" />
<link
rel="icon"
type="image/png"
href="/favicon-96x96.png"
sizes="96x96"
/>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" href="/x402-icon-black.png" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
href="/x402-icon-black.png"
/>
<meta name="apple-mobile-web-app-title" content="x402" />
<link rel="manifest" href="/site.webmanifest" />
Expand Down
43 changes: 30 additions & 13 deletions typescript/site/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Link from 'next/link';
import Image from 'next/image';
import {
BoltIcon,
CloudIcon,
Expand All @@ -15,7 +16,6 @@ import {
import { FeatureItem } from './components/FeatureItem';
import GithubIcon from './assets/github.svg';
import DiscordIcon from './assets/discord.svg';
import WordmarkCondensed from './assets/x402_wordmark_dark.svg';
import { Section } from './components/Section';
import { BackgroundVideo } from './components/BackgroundVideo';
import NavBar from './components/NavBar';
Expand Down Expand Up @@ -92,24 +92,37 @@ export default function Home() {
{/* Hero Section */}
<section className="max-w-6xl mx-auto px-4 py-20 lg:py-28">
<div className="text-center">
<div className="w-64 mb-6 mx-auto">
<WordmarkCondensed className="mx-auto" />
<div className="mb-6">
<Image
src="/x402-logo.png"
alt="x402 logo"
width={320}
height={160}
className="mx-auto"
/>
</div>
<p className="text-xl text-gray-400 mb-8 font-mono">
An open protocol for internet-native payments
</p>
<div className="flex flex-wrap gap-4 mb-6 justify-center">
<Link
href="/ecosystem"
className="px-6 py-4 bg-blue-600 hover:bg-blue-700 rounded-lg font-mono transition-colors flex items-center gap-2 text-lg"
>
<Squares2X2Icon className="w-5 h-5 mr-1" />
View Ecosystem
</Link>
</div>
<div className="flex flex-wrap gap-4 mb-8 justify-center">
<Link
href="/x402-whitepaper.pdf"
target="_blank"
rel="noopener noreferrer"
className="px-6 py-4 bg-blue-600 hover:bg-blue-700 rounded-lg font-mono transition-colors flex items-center gap-2 text-lg"
className="px-4 py-3 border-2 border-transparent hover:border-blue-600 rounded-lg font-mono transition-colors flex items-center gap-2 text-sm"
>
<DocumentTextIcon className="w-5 h-5 mr-1" />
Read the whitepaper
</Link>
</div>
<div className="flex flex-wrap gap-4 mb-8 justify-center">
<Link
href="https://x402.gitbook.io/x402"
target="_blank"
Expand All @@ -128,13 +141,6 @@ export default function Home() {
<CodeBracketIcon className="w-5 h-5 mr-1" />
Try it out
</Link>
<Link
href="/ecosystem"
className="px-4 py-3 border-2 border-transparent hover:border-blue-600 rounded-lg font-mono transition-colors flex items-center gap-2 text-sm"
>
<Squares2X2Icon className="w-5 h-5 mr-1" />
View Ecosystem
</Link>
</div>
</div>
</section>
Expand Down Expand Up @@ -247,6 +253,17 @@ export default function Home() {
</div>
</div>
</Section>

{/* x402 Button Section */}
<div className="relative z-10 text-center py-12">
<Image
src="/x402-button-large.png"
alt="x402 button"
width={320}
height={160}
className="mx-auto"
/>
</div>
</div>
<footer className="relative z-10 py-8 text-center text-sm text-gray-400">
By using this site, you agree to be bound by the{' '}
Expand Down
58 changes: 56 additions & 2 deletions typescript/site/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import { Address } from "viem";
import { paymentMiddleware, Resource, Network } from "x402-next";
import { NextRequest, NextResponse } from "next/server";

const address = process.env.RESOURCE_WALLET_ADDRESS as Address;
const network = process.env.NETWORK as Network;
const facilitatorUrl = process.env.NEXT_PUBLIC_FACILITATOR_URL as Resource;
const cdpClientKey = process.env.NEXT_PUBLIC_ONCHAINKIT_API_KEY;

export const middleware = paymentMiddleware(
// List of blocked countries and regions
const BLOCKED_COUNTRIES = [
"KP", // North Korea
"IR", // Iran
"CU", // Cuba
"SY", // Syria
];

// List of blocked regions within specific countries
const BLOCKED_REGIONS = {
UA: ["43", "14", "09"],
};

const x402PaymentMiddleware = paymentMiddleware(
address,
{
"/protected": {
Expand All @@ -24,10 +38,50 @@ export const middleware = paymentMiddleware(
cdpClientKey,
appLogo: "/logos/x402-examples.png",
appName: "x402 Demo",
sessionTokenEndpoint: "/api/x402/session-token",
},
);

const geolocationMiddleware = async (req: NextRequest) => {
// Get the country and region from Vercel's headers
const country = req.headers.get("x-vercel-ip-country") || "US";
const region = req.headers.get("x-vercel-ip-country-region");

const isCountryBlocked = BLOCKED_COUNTRIES.includes(country);
const isRegionBlocked =
region && BLOCKED_REGIONS[country as keyof typeof BLOCKED_REGIONS]?.includes(region);

if (isCountryBlocked || isRegionBlocked) {
return new NextResponse("Access denied: This service is not available in your region", {
status: 451,
headers: {
"Content-Type": "text/plain",
},
});
}

return null;
};

export const middleware = async (req: NextRequest) => {
const geolocationResponse = await geolocationMiddleware(req);
if (geolocationResponse) {
return geolocationResponse;
}

return x402PaymentMiddleware(req);
};

// Configure which paths the middleware should run on
export const config = {
matcher: ["/protected/:path*"],
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (metadata files)
*/
"/((?!_next/static|_next/image|favicon.ico).*)",
"/", // Include the root path explicitly
],
};
1 change: 1 addition & 0 deletions typescript/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"dependencies": {
"@heroicons/react": "^2.2.0",
"@vercel/functions": "^2.2.8",
"next": "^15.2.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
Expand Down
Binary file modified typescript/site/public/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added typescript/site/public/favicon.ico
Binary file not shown.
Binary file added typescript/site/public/logos/1shot-api.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added typescript/site/public/logos/aurracloud.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added typescript/site/public/logos/bonsai.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added typescript/site/public/logos/firecrawl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added typescript/site/public/logos/questflow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added typescript/site/public/logos/tipdotmd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions typescript/site/public/logos/x402-rs.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 5 additions & 7 deletions typescript/site/public/site.webmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,17 @@
"short_name": "x402",
"icons": [
{
"src": "/web-app-manifest-192x192.png",
"src": "/x402-icon-black.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
"type": "image/png"
},
{
"src": "/web-app-manifest-512x512.png",
"src": "/x402-icon-black.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
"type": "image/png"
}
],
"theme_color": "#ffffff",
"theme_color": "#000000",
"background_color": "#ffffff",
"display": "standalone"
}
Binary file added typescript/site/public/x402-button-large.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added typescript/site/public/x402-icon-black.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added typescript/site/public/x402-icon-blue.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added typescript/site/public/x402-icon-grey.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added typescript/site/public/x402-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading