@payai/x402-next-starter 0.1.5 → 1.0.0
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/NOTICE +1 -1
- package/package.json +6 -2
- package/template/.env-local +10 -1
- package/template/.env.example +4 -0
- package/template/.prettierignore +1 -0
- package/template/README.md +204 -54
- package/template/app/api/weather/route.ts +69 -0
- package/template/app/globals.css +16 -20
- package/template/app/layout.tsx +5 -15
- package/template/app/page.tsx +21 -8
- package/template/app/protected/page.tsx +40 -3
- package/template/eslint.config.js +1 -1
- package/template/next-env.d.ts +6 -0
- package/template/next.config.ts +1 -13
- package/template/package.json +18 -16
- package/template/postcss.config.mjs +1 -1
- package/template/proxy.ts +78 -0
- package/template/public/favicon.ico +0 -0
- package/template/public/site.webmanifest +7 -9
- package/template/public/x402-icon-black.png +0 -0
- package/template/public/x402-logo-dark.png +0 -0
- package/template/tsconfig.json +9 -2
- package/template/turbo.json +9 -0
- package/template/app/assets/x402_wordmark_dark.png +0 -0
- package/template/app/assets/x402_wordmark_dark.svg +0 -4
- package/template/app/assets/x402_wordmark_light.svg +0 -1
- package/template/app/favicon.ico +0 -0
- package/template/middleware.ts +0 -31
- package/template/package-lock.json +0 -8726
- package/template/public/apple-touch-icon.png +0 -0
- package/template/public/favicon-96x96.png +0 -0
- package/template/public/favicon.svg +0 -3
- package/template/public/web-app-manifest-192x192.png +0 -0
- package/template/public/web-app-manifest-512x512.png +0 -0
- package/template/tailwind.config.ts +0 -18
- package/template/types/svg.d.ts +0 -5
package/NOTICE
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
This package includes portions derived from coinbase/x402 (examples/typescript/fullstack/next), Apache-2.0,
|
|
2
|
-
commit
|
|
2
|
+
commit 6a99ba028b0d657122db8a06328cd432bdd8c7c0. See LICENSE and upstream LICENSE notices.
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payai/x402-next-starter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public",
|
|
7
|
+
"registry": "https://registry.npmjs.org"
|
|
8
|
+
},
|
|
5
9
|
"description": "Create an x402 Next.js app in less than 2 minutes!",
|
|
6
10
|
"type": "module",
|
|
7
11
|
"bin": {
|
|
@@ -34,6 +38,6 @@
|
|
|
34
38
|
"url": "https://github.com/PayAINetwork/x402-next-starter"
|
|
35
39
|
},
|
|
36
40
|
"config": {
|
|
37
|
-
"x402NextVersion": "
|
|
41
|
+
"x402NextVersion": "2.3.0"
|
|
38
42
|
}
|
|
39
43
|
}
|
package/template/.env-local
CHANGED
package/template/.prettierignore
CHANGED
package/template/README.md
CHANGED
|
@@ -1,30 +1,36 @@
|
|
|
1
1
|
# x402-next Example App
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Next.js application demonstrating how to protect routes with a paywall using the `@x402/next` middleware.
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
7
7
|
- Node.js v20+ (install via [nvm](https://github.com/nvm-sh/nvm))
|
|
8
8
|
- pnpm v10 (install via [pnpm.io/installation](https://pnpm.io/installation))
|
|
9
|
-
-
|
|
9
|
+
- Valid EVM and SVM addresses for receiving payments
|
|
10
|
+
- URL of a facilitator supporting the desired payment network, see [facilitator list](https://www.x402.org/ecosystem?category=facilitators)
|
|
10
11
|
|
|
11
12
|
## Setup
|
|
12
13
|
|
|
13
|
-
1. Copy `.env-local` to `.env
|
|
14
|
+
1. Copy `.env-local` to `.env`:
|
|
14
15
|
|
|
15
16
|
```bash
|
|
16
17
|
cp .env-local .env
|
|
17
18
|
```
|
|
18
19
|
|
|
20
|
+
and fill required environment variables:
|
|
21
|
+
|
|
22
|
+
- `FACILITATOR_URL` - Facilitator endpoint URL
|
|
23
|
+
- `EVM_ADDRESS` - Ethereum address to receive payments
|
|
24
|
+
- `SVM_ADDRESS` - Solana address to receive payments
|
|
25
|
+
|
|
19
26
|
2. Install and build all packages from the typescript examples root:
|
|
20
27
|
```bash
|
|
21
28
|
cd ../../
|
|
22
|
-
pnpm install
|
|
23
|
-
pnpm build
|
|
29
|
+
pnpm install && pnpm build
|
|
24
30
|
cd fullstack/next
|
|
25
31
|
```
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
3. Run the server:
|
|
28
34
|
```bash
|
|
29
35
|
pnpm dev
|
|
30
36
|
```
|
|
@@ -34,94 +40,238 @@ pnpm dev
|
|
|
34
40
|
The app includes protected routes that require payment to access:
|
|
35
41
|
|
|
36
42
|
### Protected Page Route
|
|
37
|
-
|
|
43
|
+
|
|
44
|
+
The `/protected` route is protected using `paymentProxy`. Page routes are protected using this approach:
|
|
38
45
|
|
|
39
46
|
```typescript
|
|
40
|
-
//
|
|
41
|
-
import {
|
|
47
|
+
// proxy.ts
|
|
48
|
+
import { paymentProxy } from "@x402/next";
|
|
49
|
+
import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server";
|
|
50
|
+
import { registerExactEvmScheme } from "@x402/evm/exact/server";
|
|
51
|
+
import { registerExactSvmScheme } from "@x402/svm/exact/server";
|
|
52
|
+
import { createPaywall } from "@x402/paywall";
|
|
53
|
+
import { evmPaywall } from "@x402/paywall/evm";
|
|
54
|
+
import { svmPaywall } from "@x402/paywall/svm";
|
|
42
55
|
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const network = process.env.NETWORK as Network;
|
|
56
|
+
const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });
|
|
57
|
+
const server = new x402ResourceServer(facilitatorClient);
|
|
46
58
|
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
// Register schemes
|
|
60
|
+
registerExactEvmScheme(server);
|
|
61
|
+
registerExactSvmScheme(server);
|
|
62
|
+
|
|
63
|
+
// Build paywall using builder pattern
|
|
64
|
+
const paywall = createPaywall()
|
|
65
|
+
.withNetwork(evmPaywall)
|
|
66
|
+
.withNetwork(svmPaywall)
|
|
67
|
+
.withConfig({
|
|
68
|
+
appName: "Next x402 Demo",
|
|
69
|
+
appLogo: "/x402-icon-blue.png",
|
|
70
|
+
testnet: true,
|
|
71
|
+
})
|
|
72
|
+
.build();
|
|
73
|
+
|
|
74
|
+
export const proxy = paymentProxy(
|
|
49
75
|
{
|
|
50
76
|
"/protected": {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
77
|
+
accepts: [
|
|
78
|
+
{
|
|
79
|
+
scheme: "exact",
|
|
80
|
+
price: "$0.001",
|
|
81
|
+
network: "eip155:84532",
|
|
82
|
+
payTo: evmAddress,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
scheme: "exact",
|
|
86
|
+
price: "$0.001",
|
|
87
|
+
network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
88
|
+
payTo: svmAddress,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
description: "Premium music: x402 Remix",
|
|
92
|
+
mimeType: "text/html",
|
|
56
93
|
},
|
|
57
94
|
},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
95
|
+
server,
|
|
96
|
+
undefined, // paywallConfig (using custom paywall instead)
|
|
97
|
+
paywall, // custom paywall provider
|
|
61
98
|
);
|
|
62
99
|
|
|
63
|
-
// Configure which paths the middleware should run on
|
|
64
100
|
export const config = {
|
|
65
101
|
matcher: ["/protected/:path*"],
|
|
66
102
|
};
|
|
67
103
|
```
|
|
68
104
|
|
|
105
|
+
### Weather API Route (using withX402)
|
|
106
|
+
|
|
107
|
+
The `/api/weather` route demonstrates the `withX402` wrapper for individual API routes:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// app/api/weather/route.ts
|
|
111
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
112
|
+
import { withX402 } from "@x402/next";
|
|
113
|
+
import { server, paywall, evmAddress, svmAddress } from "../../../proxy";
|
|
114
|
+
|
|
115
|
+
const handler = async (_: NextRequest) => {
|
|
116
|
+
return NextResponse.json({
|
|
117
|
+
report: {
|
|
118
|
+
weather: "sunny",
|
|
119
|
+
temperature: 72,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const GET = withX402(
|
|
125
|
+
handler,
|
|
126
|
+
{
|
|
127
|
+
accepts: [
|
|
128
|
+
{
|
|
129
|
+
scheme: "exact",
|
|
130
|
+
price: "$0.001",
|
|
131
|
+
network: "eip155:84532",
|
|
132
|
+
payTo: evmAddress,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
scheme: "exact",
|
|
136
|
+
price: "$0.001",
|
|
137
|
+
network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
138
|
+
payTo: svmAddress,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
description: "Access to weather API",
|
|
142
|
+
mimeType: "application/json",
|
|
143
|
+
},
|
|
144
|
+
server,
|
|
145
|
+
undefined, // paywallConfig (using custom paywall from proxy.ts)
|
|
146
|
+
paywall,
|
|
147
|
+
);
|
|
148
|
+
```
|
|
149
|
+
|
|
69
150
|
## Response Format
|
|
70
151
|
|
|
71
152
|
### Payment Required (402)
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
HTTP/1.1 402 Payment Required
|
|
156
|
+
Content-Type: application/json; charset=utf-8
|
|
157
|
+
PAYMENT-REQUIRED: <base64-encoded JSON>
|
|
158
|
+
|
|
159
|
+
{}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The `PAYMENT-REQUIRED` header contains base64-encoded JSON with the payment requirements.
|
|
163
|
+
Note: `amount` is in atomic units (e.g., 1000 = 0.001 USDC, since USDC has 6 decimals):
|
|
164
|
+
|
|
72
165
|
```json
|
|
73
166
|
{
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
167
|
+
"x402Version": 2,
|
|
168
|
+
"error": "Payment required",
|
|
169
|
+
"resource": {
|
|
170
|
+
"url": "http://localhost:3000/api/weather",
|
|
171
|
+
"description": "Access to weather API",
|
|
172
|
+
"mimeType": "application/json"
|
|
173
|
+
},
|
|
174
|
+
"accepts": [
|
|
175
|
+
{
|
|
176
|
+
"scheme": "exact",
|
|
177
|
+
"network": "eip155:84532",
|
|
178
|
+
"amount": "1000",
|
|
179
|
+
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
180
|
+
"payTo": "0x...",
|
|
181
|
+
"maxTimeoutSeconds": 300,
|
|
182
|
+
"extra": {
|
|
183
|
+
"name": "USDC",
|
|
184
|
+
"version": "2",
|
|
185
|
+
"resourceUrl": "http://localhost:4021/weather"
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
]
|
|
88
189
|
}
|
|
89
190
|
```
|
|
90
191
|
|
|
91
192
|
### Successful Response
|
|
92
|
-
|
|
93
|
-
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
HTTP/1.1 200 OK
|
|
196
|
+
Content-Type: application/json; charset=utf-8
|
|
197
|
+
PAYMENT-RESPONSE: <base64-encoded JSON>
|
|
198
|
+
|
|
199
|
+
{"report":{"weather":"sunny","temperature":72}}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The `PAYMENT-RESPONSE` header contains base64-encoded JSON with the settlement details:
|
|
203
|
+
|
|
204
|
+
```json
|
|
94
205
|
{
|
|
95
|
-
"
|
|
206
|
+
"success": true,
|
|
207
|
+
"transaction": "0x...",
|
|
208
|
+
"network": "eip155:84532",
|
|
209
|
+
"payer": "0x...",
|
|
210
|
+
"requirements": {
|
|
211
|
+
"scheme": "exact",
|
|
212
|
+
"network": "eip155:84532",
|
|
213
|
+
"amount": "1000",
|
|
214
|
+
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
215
|
+
"payTo": "0x...",
|
|
216
|
+
"maxTimeoutSeconds": 300,
|
|
217
|
+
"extra": {
|
|
218
|
+
"name": "USDC",
|
|
219
|
+
"version": "2",
|
|
220
|
+
"resourceUrl": "http://localhost:4021/weather"
|
|
221
|
+
}
|
|
222
|
+
}
|
|
96
223
|
}
|
|
97
224
|
```
|
|
98
225
|
|
|
226
|
+
## paymentProxy vs withX402
|
|
227
|
+
|
|
228
|
+
The `paymentProxy` function is used to protect page routes. It can also protect API routes, however this will charge clients for failed API responses.
|
|
229
|
+
|
|
230
|
+
The `withX402` function wraps API route handlers. This is the recommended approach to protect API routes as it guarantees payment settlement only AFTER successful API responses (status < 400).
|
|
231
|
+
|
|
232
|
+
| Approach | Use Case |
|
|
233
|
+
|----------|----------|
|
|
234
|
+
| `paymentProxy` | Protecting page routes or multiple routes with a single configuration |
|
|
235
|
+
| `withX402` | Protecting individual API routes where you need precise control over settlement timing |
|
|
236
|
+
|
|
99
237
|
## Extending the Example
|
|
100
238
|
|
|
101
|
-
To add more protected routes, update the
|
|
239
|
+
To add more protected routes, update the proxy configuration:
|
|
102
240
|
|
|
103
241
|
```typescript
|
|
104
|
-
export const
|
|
105
|
-
payTo,
|
|
242
|
+
export const proxy = paymentProxy(
|
|
106
243
|
{
|
|
107
244
|
"/protected": {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
245
|
+
accepts: {
|
|
246
|
+
scheme: "exact",
|
|
247
|
+
price: "$0.001",
|
|
248
|
+
network: "eip155:84532",
|
|
249
|
+
payTo: evmAddress,
|
|
112
250
|
},
|
|
251
|
+
description: "Access to protected content",
|
|
113
252
|
},
|
|
114
|
-
"/
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
253
|
+
"/premium": {
|
|
254
|
+
accepts: {
|
|
255
|
+
scheme: "exact",
|
|
256
|
+
price: "$0.10",
|
|
257
|
+
network: "eip155:84532",
|
|
258
|
+
payTo: evmAddress,
|
|
119
259
|
},
|
|
260
|
+
description: "Premium content access",
|
|
120
261
|
},
|
|
121
|
-
}
|
|
262
|
+
},
|
|
263
|
+
server,
|
|
264
|
+
undefined,
|
|
265
|
+
paywall,
|
|
122
266
|
);
|
|
123
267
|
|
|
124
268
|
export const config = {
|
|
125
|
-
matcher: ["/protected/:path*", "/
|
|
269
|
+
matcher: ["/protected/:path*", "/premium/:path*"],
|
|
126
270
|
};
|
|
127
271
|
```
|
|
272
|
+
|
|
273
|
+
**Network identifiers** use [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) format, for example:
|
|
274
|
+
- `eip155:84532` — Base Sepolia
|
|
275
|
+
- `eip155:8453` — Base Mainnet
|
|
276
|
+
- `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` — Solana Devnet
|
|
277
|
+
- `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` — Solana Mainnet
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { withX402 } from "@x402/next";
|
|
3
|
+
import { declareDiscoveryExtension } from "@x402/extensions/bazaar";
|
|
4
|
+
import { server, paywall, evmAddress, svmAddress } from "../../../proxy";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Weather API endpoint handler
|
|
8
|
+
*
|
|
9
|
+
* This handler returns weather data after payment verification.
|
|
10
|
+
* Payment is only settled after a successful response (status < 400).
|
|
11
|
+
*
|
|
12
|
+
* @param _ - Incoming Next.js request
|
|
13
|
+
* @returns JSON response with weather data
|
|
14
|
+
*/
|
|
15
|
+
const handler = async (_: NextRequest) => {
|
|
16
|
+
return NextResponse.json(
|
|
17
|
+
{
|
|
18
|
+
report: {
|
|
19
|
+
weather: "sunny",
|
|
20
|
+
temperature: 72,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{ status: 200 },
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Protected weather API endpoint using withX402 wrapper
|
|
29
|
+
*
|
|
30
|
+
* This demonstrates the v2 withX402 wrapper for individual API routes.
|
|
31
|
+
* Unlike middleware, withX402 guarantees payment settlement only after
|
|
32
|
+
* the handler returns a successful response (status < 400).
|
|
33
|
+
*/
|
|
34
|
+
export const GET = withX402(
|
|
35
|
+
handler,
|
|
36
|
+
{
|
|
37
|
+
accepts: [
|
|
38
|
+
{
|
|
39
|
+
scheme: "exact",
|
|
40
|
+
price: "$0.001",
|
|
41
|
+
network: "eip155:84532", // base-sepolia
|
|
42
|
+
payTo: evmAddress,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
scheme: "exact",
|
|
46
|
+
price: "$0.001",
|
|
47
|
+
network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", // solana devnet
|
|
48
|
+
payTo: svmAddress,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
description: "Access to weather API",
|
|
52
|
+
mimeType: "application/json",
|
|
53
|
+
extensions: {
|
|
54
|
+
...declareDiscoveryExtension({
|
|
55
|
+
output: {
|
|
56
|
+
example: {
|
|
57
|
+
report: {
|
|
58
|
+
weather: "sunny",
|
|
59
|
+
temperature: 72,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
server,
|
|
67
|
+
undefined, // paywallConfig (using custom paywall from proxy.ts)
|
|
68
|
+
paywall,
|
|
69
|
+
);
|
package/template/app/globals.css
CHANGED
|
@@ -1,33 +1,29 @@
|
|
|
1
|
-
@
|
|
2
|
-
@tailwind components;
|
|
3
|
-
@tailwind utilities;
|
|
1
|
+
@import "tailwindcss";
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
--background: #ffffff;
|
|
7
|
-
--foreground: #171717;
|
|
3
|
+
@theme {
|
|
4
|
+
--color-background: #ffffff;
|
|
5
|
+
--color-foreground: #171717;
|
|
8
6
|
}
|
|
9
7
|
|
|
10
8
|
@media (prefers-color-scheme: dark) {
|
|
11
|
-
|
|
12
|
-
--background: #1a1a2e;
|
|
13
|
-
--foreground: #ededed;
|
|
9
|
+
@theme {
|
|
10
|
+
--color-background: #1a1a2e;
|
|
11
|
+
--color-foreground: #ededed;
|
|
14
12
|
}
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
body {
|
|
18
|
-
color: var(--foreground);
|
|
19
|
-
background: var(--background);
|
|
16
|
+
color: var(--color-foreground);
|
|
17
|
+
background: var(--color-background);
|
|
20
18
|
font-family: Arial, Helvetica, sans-serif;
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
@
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
21
|
+
@utility full-bleed {
|
|
22
|
+
width: 100vw;
|
|
23
|
+
margin-left: calc(50% - 50vw);
|
|
24
|
+
}
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
26
|
+
@utility unset-full-bleed {
|
|
27
|
+
width: unset;
|
|
28
|
+
margin-left: unset;
|
|
33
29
|
}
|
package/template/app/layout.tsx
CHANGED
|
@@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
export const metadata: Metadata = {
|
|
16
|
-
title:
|
|
17
|
-
description:
|
|
16
|
+
title: "x402 Next.js Demo",
|
|
17
|
+
description: "A chain-agnostic protocol for web payments",
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export default function RootLayout({
|
|
@@ -25,19 +25,8 @@ export default function RootLayout({
|
|
|
25
25
|
return (
|
|
26
26
|
<html lang="en">
|
|
27
27
|
<head>
|
|
28
|
-
<link rel="icon"
|
|
29
|
-
<link
|
|
30
|
-
rel="icon"
|
|
31
|
-
type="image/png"
|
|
32
|
-
href="/favicon-96x96.png"
|
|
33
|
-
sizes="96x96"
|
|
34
|
-
/>
|
|
35
|
-
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
36
|
-
<link
|
|
37
|
-
rel="apple-touch-icon"
|
|
38
|
-
sizes="180x180"
|
|
39
|
-
href="/apple-touch-icon.png"
|
|
40
|
-
/>
|
|
28
|
+
<link rel="icon" type="image/png" href="/x402-icon-black.png" />
|
|
29
|
+
<link rel="apple-touch-icon" href="/x402-icon-black.png" />
|
|
41
30
|
<meta name="apple-mobile-web-app-title" content="x402" />
|
|
42
31
|
<link rel="manifest" href="/site.webmanifest" />
|
|
43
32
|
</head>
|
|
@@ -49,3 +38,4 @@ export default function RootLayout({
|
|
|
49
38
|
</html>
|
|
50
39
|
);
|
|
51
40
|
}
|
|
41
|
+
|
package/template/app/page.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import Link from
|
|
2
|
-
import
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import Image from "next/image";
|
|
3
3
|
|
|
4
4
|
export default function Home() {
|
|
5
5
|
return (
|
|
@@ -8,8 +8,14 @@ export default function Home() {
|
|
|
8
8
|
{/* Hero Section */}
|
|
9
9
|
<section className="max-w-6xl mx-auto px-4 py-20 lg:py-28">
|
|
10
10
|
<div className="text-center">
|
|
11
|
-
<div className="
|
|
12
|
-
<
|
|
11
|
+
<div className="mb-6">
|
|
12
|
+
<Image
|
|
13
|
+
src="/x402-logo-dark.png"
|
|
14
|
+
alt="x402 logo"
|
|
15
|
+
width={320}
|
|
16
|
+
height={160}
|
|
17
|
+
className="mx-auto"
|
|
18
|
+
/>
|
|
13
19
|
</div>
|
|
14
20
|
<p className="text-xl text-gray-600 mb-8 font-mono">
|
|
15
21
|
Fullstack demo powered by Next.js
|
|
@@ -19,14 +25,20 @@ export default function Home() {
|
|
|
19
25
|
href="/protected"
|
|
20
26
|
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 rounded-lg font-mono transition-colors text-white"
|
|
21
27
|
>
|
|
22
|
-
|
|
28
|
+
Protected page
|
|
29
|
+
</Link>
|
|
30
|
+
<Link
|
|
31
|
+
href="/api/weather"
|
|
32
|
+
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 rounded-lg font-mono transition-colors text-white"
|
|
33
|
+
>
|
|
34
|
+
Protected API
|
|
23
35
|
</Link>
|
|
24
36
|
</div>
|
|
25
37
|
</div>
|
|
26
38
|
</section>
|
|
27
39
|
</div>
|
|
28
40
|
<footer className="py-8 text-center text-sm text-gray-500">
|
|
29
|
-
By using this site, you agree to be bound by the{
|
|
41
|
+
By using this site, you agree to be bound by the{" "}
|
|
30
42
|
<a
|
|
31
43
|
href="https://www.coinbase.com/legal/developer-platform/terms-of-service"
|
|
32
44
|
target="_blank"
|
|
@@ -34,8 +46,8 @@ export default function Home() {
|
|
|
34
46
|
className="text-blue-500"
|
|
35
47
|
>
|
|
36
48
|
CDP Terms of Service
|
|
37
|
-
</a>{
|
|
38
|
-
and{
|
|
49
|
+
</a>{" "}
|
|
50
|
+
and{" "}
|
|
39
51
|
<a
|
|
40
52
|
href="https://www.coinbase.com/legal/privacy"
|
|
41
53
|
target="_blank"
|
|
@@ -49,3 +61,4 @@ export default function Home() {
|
|
|
49
61
|
</div>
|
|
50
62
|
);
|
|
51
63
|
}
|
|
64
|
+
|
|
@@ -6,11 +6,48 @@ export default function ProtectedPage() {
|
|
|
6
6
|
<p className="text-lg">
|
|
7
7
|
Your payment was successful! Enjoy this banger song.
|
|
8
8
|
</p>
|
|
9
|
-
<iframe
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
<iframe
|
|
10
|
+
width="100%"
|
|
11
|
+
height="300"
|
|
12
|
+
scrolling="no"
|
|
13
|
+
frameBorder="no"
|
|
14
|
+
allow="autoplay"
|
|
15
|
+
src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/2044190296&color=%23ff5500&auto_play=true&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true&visual=true"
|
|
16
|
+
></iframe>
|
|
17
|
+
<div
|
|
18
|
+
style={{
|
|
19
|
+
fontSize: "10px",
|
|
20
|
+
color: "#cccccc",
|
|
21
|
+
lineBreak: "anywhere",
|
|
22
|
+
wordBreak: "normal",
|
|
23
|
+
overflow: "hidden",
|
|
24
|
+
whiteSpace: "nowrap",
|
|
25
|
+
textOverflow: "ellipsis",
|
|
26
|
+
fontFamily:
|
|
27
|
+
"Interstate,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Garuda,Verdana,Tahoma,sans-serif",
|
|
28
|
+
fontWeight: "100",
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
<a
|
|
32
|
+
href="https://soundcloud.com/dan-kim-675678711"
|
|
33
|
+
title="danXkim"
|
|
34
|
+
target="_blank"
|
|
35
|
+
style={{ color: "#cccccc", textDecoration: "none" }}
|
|
36
|
+
>
|
|
37
|
+
danXkim
|
|
38
|
+
</a>{" "}
|
|
39
|
+
·{" "}
|
|
40
|
+
<a
|
|
41
|
+
href="https://soundcloud.com/dan-kim-675678711/x402"
|
|
42
|
+
title="x402 (DJ Reppel Remix)"
|
|
43
|
+
target="_blank"
|
|
44
|
+
style={{ color: "#cccccc", textDecoration: "none" }}
|
|
45
|
+
>
|
|
46
|
+
x402 (DJ Reppel Remix)
|
|
47
|
+
</a>
|
|
12
48
|
</div>
|
|
13
49
|
</div>
|
|
14
50
|
</div>
|
|
15
51
|
);
|
|
16
52
|
}
|
|
53
|
+
|