create-x402-app 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +54 -4
- package/package.json +2 -2
- package/templates/backend-express/README.md +56 -0
- package/templates/backend-express/env.example +8 -0
- package/templates/backend-express/gitignore +4 -0
- package/templates/backend-express/package.json +23 -0
- package/templates/backend-express/src/index.ts +79 -0
- package/templates/backend-express/tsconfig.json +18 -0
- package/templates/backend-hono/README.md +57 -0
- package/templates/backend-hono/env.example +8 -0
- package/templates/backend-hono/gitignore +4 -0
- package/templates/backend-hono/package.json +21 -0
- package/templates/backend-hono/src/index.ts +75 -0
- package/templates/backend-hono/tsconfig.json +18 -0
- package/templates/fullstack-express/README.md +308 -0
- package/templates/fullstack-express/_env.example +11 -0
- package/templates/fullstack-express/_gitignore +46 -0
- package/templates/fullstack-express/eslint.config.mjs +18 -0
- package/templates/fullstack-express/next-env.d.ts +6 -0
- package/templates/fullstack-express/next.config.ts +7 -0
- package/templates/fullstack-express/package.json +33 -0
- package/templates/fullstack-express/postcss.config.mjs +7 -0
- package/templates/fullstack-express/public/X402.png +0 -0
- package/templates/fullstack-express/src/app/api/info/route.ts +14 -0
- package/templates/fullstack-express/src/app/api/premium/route.ts +52 -0
- package/templates/fullstack-express/src/app/api/weather/route.ts +103 -0
- package/templates/fullstack-express/src/app/favicon.ico +0 -0
- package/templates/fullstack-express/src/app/globals.css +82 -0
- package/templates/fullstack-express/src/app/layout.tsx +42 -0
- package/templates/fullstack-express/src/app/page.tsx +511 -0
- package/templates/fullstack-express/src/components/grain-overlay.tsx +11 -0
- package/templates/fullstack-express/src/components/magnetic-button.tsx +90 -0
- package/templates/fullstack-express/src/components/ui/blur-fade.tsx +81 -0
- package/templates/fullstack-express/src/components/ui/magic-card.tsx +103 -0
- package/templates/fullstack-express/src/lib/utils.ts +6 -0
- package/templates/fullstack-express/src/types/ethereum.d.ts +11 -0
- package/templates/fullstack-express/tsconfig.json +34 -0
- package/templates/fullstack-hono/README.md +308 -0
- package/templates/fullstack-hono/_env.example +11 -0
- package/templates/fullstack-hono/_gitignore +46 -0
- package/templates/fullstack-hono/eslint.config.mjs +18 -0
- package/templates/fullstack-hono/next-env.d.ts +6 -0
- package/templates/fullstack-hono/next.config.ts +7 -0
- package/templates/fullstack-hono/package.json +33 -0
- package/templates/fullstack-hono/postcss.config.mjs +7 -0
- package/templates/fullstack-hono/public/X402.png +0 -0
- package/templates/fullstack-hono/src/app/api/info/route.ts +14 -0
- package/templates/fullstack-hono/src/app/api/premium/route.ts +52 -0
- package/templates/fullstack-hono/src/app/api/weather/route.ts +103 -0
- package/templates/fullstack-hono/src/app/favicon.ico +0 -0
- package/templates/fullstack-hono/src/app/globals.css +82 -0
- package/templates/fullstack-hono/src/app/layout.tsx +42 -0
- package/templates/fullstack-hono/src/app/page.tsx +511 -0
- package/templates/fullstack-hono/src/components/grain-overlay.tsx +11 -0
- package/templates/fullstack-hono/src/components/magnetic-button.tsx +90 -0
- package/templates/fullstack-hono/src/components/ui/blur-fade.tsx +81 -0
- package/templates/fullstack-hono/src/components/ui/magic-card.tsx +103 -0
- package/templates/fullstack-hono/src/lib/utils.ts +6 -0
- package/templates/fullstack-hono/src/types/ethereum.d.ts +11 -0
- package/templates/fullstack-hono/tsconfig.json +34 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { processPaymentMiddleware, initializePlatform } from 'x402-mantle-sdk/server'
|
|
3
|
+
|
|
4
|
+
let initialized = false
|
|
5
|
+
|
|
6
|
+
export async function GET(request: NextRequest) {
|
|
7
|
+
// Initialize platform once
|
|
8
|
+
if (!initialized) {
|
|
9
|
+
await initializePlatform()
|
|
10
|
+
initialized = true
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Convert headers to plain object
|
|
14
|
+
const headers: Record<string, string> = {}
|
|
15
|
+
request.headers.forEach((value, key) => {
|
|
16
|
+
headers[key] = value
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
// Process payment - returns 402 if payment required
|
|
20
|
+
const result = await processPaymentMiddleware(
|
|
21
|
+
{ price: '0.001', token: 'MNT', testnet: true },
|
|
22
|
+
headers
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
// Payment required - return 402 with payment details
|
|
26
|
+
if (result.paymentRequired) {
|
|
27
|
+
const response = NextResponse.json(result.paymentRequired.body, { status: 402 })
|
|
28
|
+
Object.entries(result.paymentRequired.headers).forEach(([key, value]) => {
|
|
29
|
+
response.headers.set(key, value)
|
|
30
|
+
})
|
|
31
|
+
return response
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Payment verified - return premium content
|
|
35
|
+
if (result.allowed) {
|
|
36
|
+
return NextResponse.json({
|
|
37
|
+
success: true,
|
|
38
|
+
message: 'Premium content unlocked!',
|
|
39
|
+
data: {
|
|
40
|
+
secret: 'This is premium data that required payment.',
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Error handling
|
|
47
|
+
if (result.error) {
|
|
48
|
+
return NextResponse.json({ error: result.error.message }, { status: result.error.status })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return NextResponse.json({ error: 'Unexpected error' }, { status: 500 })
|
|
52
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { processPaymentMiddleware, initializePlatform, clearCache } from 'x402-mantle-sdk/server'
|
|
3
|
+
|
|
4
|
+
// Configuration - Set these in your .env file
|
|
5
|
+
const appId = process.env.X402_APP_ID || ''
|
|
6
|
+
const platformUrl = process.env.X402_PLATFORM_URL || 'https://mantle-x402.vercel.app'
|
|
7
|
+
|
|
8
|
+
if (!process.env.X402_APP_ID) {
|
|
9
|
+
process.env.X402_APP_ID = appId
|
|
10
|
+
}
|
|
11
|
+
if (!process.env.X402_PLATFORM_URL) {
|
|
12
|
+
process.env.X402_PLATFORM_URL = platformUrl
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let initialized = false
|
|
16
|
+
let initError: Error | null = null
|
|
17
|
+
|
|
18
|
+
const initPlatform = async () => {
|
|
19
|
+
try {
|
|
20
|
+
await initializePlatform()
|
|
21
|
+
initialized = true
|
|
22
|
+
initError = null
|
|
23
|
+
} catch (error) {
|
|
24
|
+
initError = error as Error
|
|
25
|
+
console.error('Platform initialization failed:', error)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function GET(request: NextRequest) {
|
|
30
|
+
try {
|
|
31
|
+
// Initialize platform on first request
|
|
32
|
+
if (!initialized && !initError) {
|
|
33
|
+
await initPlatform()
|
|
34
|
+
} else if (initError) {
|
|
35
|
+
try { clearCache() } catch {}
|
|
36
|
+
initError = null
|
|
37
|
+
await initPlatform()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (initError) {
|
|
41
|
+
return NextResponse.json(
|
|
42
|
+
{ error: 'Platform initialization failed', details: (initError as Error).message },
|
|
43
|
+
{ status: 500 }
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Payment options - cheaper for weather
|
|
48
|
+
const paymentOptions = {
|
|
49
|
+
price: '0.0005',
|
|
50
|
+
token: 'MNT',
|
|
51
|
+
testnet: true,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Convert headers
|
|
55
|
+
const headers: Record<string, string> = {}
|
|
56
|
+
request.headers.forEach((value, key) => {
|
|
57
|
+
headers[key] = value
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// Process payment middleware
|
|
61
|
+
const result = await processPaymentMiddleware(paymentOptions, headers)
|
|
62
|
+
|
|
63
|
+
// Payment required - return 402
|
|
64
|
+
if (result.paymentRequired) {
|
|
65
|
+
const response = NextResponse.json(result.paymentRequired.body, { status: 402 })
|
|
66
|
+
Object.entries(result.paymentRequired.headers).forEach(([key, value]) => {
|
|
67
|
+
response.headers.set(key, value)
|
|
68
|
+
})
|
|
69
|
+
return response
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Error
|
|
73
|
+
if (result.error) {
|
|
74
|
+
return NextResponse.json(
|
|
75
|
+
{ error: result.error.message },
|
|
76
|
+
{ status: result.error.status }
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Payment verified - return weather data
|
|
81
|
+
if (result.allowed) {
|
|
82
|
+
return NextResponse.json({
|
|
83
|
+
success: true,
|
|
84
|
+
data: {
|
|
85
|
+
location: 'New York',
|
|
86
|
+
temperature: 72,
|
|
87
|
+
unit: 'F',
|
|
88
|
+
condition: 'Sunny',
|
|
89
|
+
humidity: 45,
|
|
90
|
+
forecast: ['Sunny', 'Partly Cloudy', 'Rain'],
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return NextResponse.json({ error: 'Unexpected error' }, { status: 500 })
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('Error in weather route:', error)
|
|
98
|
+
return NextResponse.json(
|
|
99
|
+
{ error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error' },
|
|
100
|
+
{ status: 500 }
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
|
|
4
|
+
@custom-variant dark (&:is(.dark *));
|
|
5
|
+
|
|
6
|
+
:root {
|
|
7
|
+
/* Light blue background with dark blue text palette */
|
|
8
|
+
--background: oklch(0.95 0.05 240); /* Light blue background */
|
|
9
|
+
--foreground: oklch(0.25 0.12 240); /* Dark blue (navy) text */
|
|
10
|
+
--card: oklch(0.92 0.04 240); /* Slightly darker light blue */
|
|
11
|
+
--card-foreground: oklch(0.25 0.12 240); /* Dark blue */
|
|
12
|
+
--popover: oklch(0.92 0.04 240);
|
|
13
|
+
--popover-foreground: oklch(0.25 0.12 240);
|
|
14
|
+
--primary: oklch(0.35 0.15 240); /* Medium dark blue */
|
|
15
|
+
--primary-foreground: oklch(0.95 0.05 240); /* Light blue */
|
|
16
|
+
--secondary: oklch(0.88 0.04 240); /* Light blue gray */
|
|
17
|
+
--secondary-foreground: oklch(0.25 0.12 240); /* Dark blue */
|
|
18
|
+
--muted: oklch(0.85 0.03 240);
|
|
19
|
+
--muted-foreground: oklch(0.4 0.1 240);
|
|
20
|
+
--accent: oklch(0.3 0.13 240); /* Dark blue accent */
|
|
21
|
+
--accent-foreground: oklch(0.95 0.05 240); /* Light blue */
|
|
22
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
23
|
+
--destructive-foreground: oklch(0.95 0.05 240);
|
|
24
|
+
--border: oklch(0.8 0.05 240); /* Light blue border */
|
|
25
|
+
--input: oklch(0.8 0.05 240);
|
|
26
|
+
--ring: oklch(0.35 0.15 240); /* Dark blue ring */
|
|
27
|
+
--radius: 0.75rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@theme inline {
|
|
31
|
+
--font-sans: "Geist", "Geist Fallback", system-ui, sans-serif;
|
|
32
|
+
--font-mono: "Geist Mono", "Geist Mono Fallback", monospace;
|
|
33
|
+
--color-background: var(--background);
|
|
34
|
+
--color-foreground: var(--foreground);
|
|
35
|
+
--color-card: var(--card);
|
|
36
|
+
--color-card-foreground: var(--card-foreground);
|
|
37
|
+
--color-popover: var(--popover);
|
|
38
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
39
|
+
--color-primary: var(--primary);
|
|
40
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
41
|
+
--color-secondary: var(--secondary);
|
|
42
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
43
|
+
--color-muted: var(--muted);
|
|
44
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
45
|
+
--color-accent: var(--accent);
|
|
46
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
47
|
+
--color-destructive: var(--destructive);
|
|
48
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
49
|
+
--color-border: var(--border);
|
|
50
|
+
--color-input: var(--input);
|
|
51
|
+
--color-ring: var(--ring);
|
|
52
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
53
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
54
|
+
--radius-lg: var(--radius);
|
|
55
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@layer base {
|
|
59
|
+
* {
|
|
60
|
+
@apply border-border outline-ring/50;
|
|
61
|
+
cursor: default;
|
|
62
|
+
}
|
|
63
|
+
body {
|
|
64
|
+
@apply bg-background text-foreground;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
main {
|
|
68
|
+
/* Light blue textured background */
|
|
69
|
+
background-color: oklch(0.95 0.05 240);
|
|
70
|
+
background-image:
|
|
71
|
+
url("data:image/svg+xml,%3Csvg viewBox='0 0 400 400' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.4' fill='%23e0f2fe'/%3E%3C/svg%3E");
|
|
72
|
+
background-size: 200px 200px;
|
|
73
|
+
background-repeat: repeat;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
button,
|
|
77
|
+
a {
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
transform: translateZ(0);
|
|
80
|
+
backface-visibility: hidden;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type React from "react"
|
|
2
|
+
import type { Metadata } from "next"
|
|
3
|
+
import { Geist, Geist_Mono } from "next/font/google"
|
|
4
|
+
import "./globals.css"
|
|
5
|
+
|
|
6
|
+
const geist = Geist({ subsets: ["latin"], variable: "--font-geist" })
|
|
7
|
+
const geistMono = Geist_Mono({ subsets: ["latin"], variable: "--font-geist-mono" })
|
|
8
|
+
|
|
9
|
+
export const metadata: Metadata = {
|
|
10
|
+
title: "x402 Demo - HTTP 402 Payments on Mantle",
|
|
11
|
+
description:
|
|
12
|
+
"Experience HTTP 402 payments in action. A demo app built with x402-mantle-sdk for pay-per-request APIs on Mantle Network.",
|
|
13
|
+
keywords: [
|
|
14
|
+
"x402",
|
|
15
|
+
"HTTP 402",
|
|
16
|
+
"Mantle",
|
|
17
|
+
"Mantle Network",
|
|
18
|
+
"API monetization",
|
|
19
|
+
"blockchain payments",
|
|
20
|
+
"web3 payments",
|
|
21
|
+
"MNT",
|
|
22
|
+
],
|
|
23
|
+
icons: {
|
|
24
|
+
icon: "/X402.png",
|
|
25
|
+
shortcut: "/X402.png",
|
|
26
|
+
apple: "/X402.png",
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function RootLayout({
|
|
31
|
+
children,
|
|
32
|
+
}: Readonly<{
|
|
33
|
+
children: React.ReactNode
|
|
34
|
+
}>) {
|
|
35
|
+
return (
|
|
36
|
+
<html lang="en">
|
|
37
|
+
<body className={`${geist.variable} ${geistMono.variable} font-sans antialiased`}>
|
|
38
|
+
{children}
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
|
41
|
+
)
|
|
42
|
+
}
|