@puga-labs/x402-mantle-sdk 0.3.9 → 0.3.11
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/README.md +478 -0
- package/dist/chunk-3DGAB7HD.js +126 -0
- package/dist/chunk-3YIQEQDT.js +96 -0
- package/dist/chunk-HVQ2RGMT.js +126 -0
- package/dist/chunk-JXMWK3BO.js +96 -0
- package/dist/chunk-NC5OU47J.js +99 -0
- package/dist/chunk-NWWXJFJ4.js +293 -0
- package/dist/chunk-P5FKQVHW.js +99 -0
- package/dist/chunk-UVYA6H32.js +293 -0
- package/dist/express-BWE0nQty.d.cts +68 -0
- package/dist/express-BvuN0Lx1.d.ts +68 -0
- package/dist/index.cjs +58 -2
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/nextjs-Bujo9Okf.d.cts +89 -0
- package/dist/nextjs-CzSejZe8.d.ts +89 -0
- package/dist/server-express.cjs +58 -2
- package/dist/server-express.d.cts +2 -2
- package/dist/server-express.d.ts +2 -2
- package/dist/server-express.js +2 -2
- package/dist/server-nextjs.cjs +58 -2
- package/dist/server-nextjs.d.cts +2 -2
- package/dist/server-nextjs.d.ts +2 -2
- package/dist/server-nextjs.js +2 -2
- package/dist/server-web.cjs +58 -2
- package/dist/server-web.d.cts +2 -2
- package/dist/server-web.d.ts +2 -2
- package/dist/server-web.js +2 -2
- package/dist/server.cjs +84 -2
- package/dist/server.d.cts +8 -7
- package/dist/server.d.ts +8 -7
- package/dist/server.js +4 -4
- package/dist/types-Ba0v9XsC.d.ts +108 -0
- package/dist/types-BkGUHT4x.d.cts +108 -0
- package/dist/types-DEpSrXCf.d.ts +112 -0
- package/dist/types-DrBw0xwj.d.cts +112 -0
- package/dist/web-standards-DsCZRJPE.d.ts +77 -0
- package/dist/web-standards-QCbyQ14G.d.cts +77 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
# @puga-labs/x402-mantle-sdk
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@puga-labs/x402-mantle-sdk)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
SDK for x402 payments on Mantle blockchain. Protect your APIs with USDC payments using a simple middleware.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @puga-labs/x402-mantle-sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- React hooks (`useMantleX402`, `useEthersWallet`)
|
|
17
|
+
- Vanilla JS client (`createMantleClient`)
|
|
18
|
+
- Server middleware for Express, Next.js, Hono, Cloudflare Workers, Deno
|
|
19
|
+
- Full TypeScript support
|
|
20
|
+
- Automatic payment flow handling
|
|
21
|
+
- Analytics & telemetry integration
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Client-Side Usage
|
|
26
|
+
|
|
27
|
+
### React (Recommended)
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { useMantleX402 } from '@puga-labs/x402-mantle-sdk/react';
|
|
31
|
+
|
|
32
|
+
function PaymentButton() {
|
|
33
|
+
const { postWithPayment } = useMantleX402({
|
|
34
|
+
facilitatorUrl: 'https://facilitator.x402mantlesdk.xyz'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const handlePay = async () => {
|
|
38
|
+
try {
|
|
39
|
+
const { response, txHash } = await postWithPayment('/api/generate', {
|
|
40
|
+
prompt: 'Hello world'
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log('Result:', response);
|
|
44
|
+
console.log('Transaction:', txHash);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error('Payment failed:', error);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return <button onClick={handlePay}>Generate ($0.01)</button>;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Wallet Hook
|
|
55
|
+
|
|
56
|
+
Separate hook for wallet management:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { useEthersWallet } from '@puga-labs/x402-mantle-sdk/react';
|
|
60
|
+
|
|
61
|
+
function WalletConnect() {
|
|
62
|
+
const {
|
|
63
|
+
address,
|
|
64
|
+
isConnected,
|
|
65
|
+
chainId,
|
|
66
|
+
connect,
|
|
67
|
+
disconnect,
|
|
68
|
+
error
|
|
69
|
+
} = useEthersWallet({ autoConnect: false });
|
|
70
|
+
|
|
71
|
+
if (isConnected) {
|
|
72
|
+
return (
|
|
73
|
+
<div>
|
|
74
|
+
<p>Connected: {address}</p>
|
|
75
|
+
<p>Chain ID: {chainId}</p>
|
|
76
|
+
<button onClick={disconnect}>Disconnect</button>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return <button onClick={connect}>Connect Wallet</button>;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Vanilla JavaScript
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { createMantleClient } from '@puga-labs/x402-mantle-sdk/client';
|
|
89
|
+
|
|
90
|
+
// Create client
|
|
91
|
+
const client = createMantleClient({
|
|
92
|
+
facilitatorUrl: 'https://facilitator.x402mantlesdk.xyz',
|
|
93
|
+
resourceUrl: 'https://your-api.com',
|
|
94
|
+
getProvider: () => window.ethereum,
|
|
95
|
+
getAccount: () => userAddress
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Make paid request
|
|
99
|
+
const { response, txHash } = await client.postWithPayment('/api/generate', {
|
|
100
|
+
prompt: 'Hello world'
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Low-Level Payment Client
|
|
105
|
+
|
|
106
|
+
For full control over the payment flow:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { createPaymentClient } from '@puga-labs/x402-mantle-sdk/client';
|
|
110
|
+
|
|
111
|
+
const paymentClient = createPaymentClient({
|
|
112
|
+
resourceUrl: 'https://your-api.com',
|
|
113
|
+
facilitatorUrl: 'https://facilitator.x402mantlesdk.xyz',
|
|
114
|
+
provider: window.ethereum,
|
|
115
|
+
userAddress: '0x...' // Optional
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const result = await paymentClient.callWithPayment('/api/endpoint', {
|
|
119
|
+
data: 'payload'
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Server-Side Usage
|
|
126
|
+
|
|
127
|
+
### Express.js
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import express from 'express';
|
|
131
|
+
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/express';
|
|
132
|
+
|
|
133
|
+
const app = express();
|
|
134
|
+
app.use(express.json());
|
|
135
|
+
|
|
136
|
+
// Create paywall middleware
|
|
137
|
+
const pay = mantlePaywall({
|
|
138
|
+
priceUsd: 0.01,
|
|
139
|
+
payTo: '0xYourWalletAddress',
|
|
140
|
+
facilitatorUrl: 'https://facilitator.x402mantlesdk.xyz'
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Protected route
|
|
144
|
+
app.post('/api/generate', pay, async (req, res) => {
|
|
145
|
+
const { prompt } = req.body;
|
|
146
|
+
// Your logic here - only runs after payment
|
|
147
|
+
res.json({ result: 'Generated content' });
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Unprotected route
|
|
151
|
+
app.get('/api/health', (req, res) => {
|
|
152
|
+
res.json({ status: 'ok' });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
app.listen(3000);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Next.js (App Router)
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// app/api/generate/route.ts
|
|
162
|
+
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/nextjs';
|
|
163
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
164
|
+
|
|
165
|
+
const pay = mantlePaywall({
|
|
166
|
+
priceUsd: 0.01,
|
|
167
|
+
payTo: '0xYourWalletAddress',
|
|
168
|
+
facilitatorUrl: 'https://facilitator.x402mantlesdk.xyz'
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
export const POST = pay(async (req: NextRequest) => {
|
|
172
|
+
const { prompt } = await req.json();
|
|
173
|
+
// Your logic here
|
|
174
|
+
return NextResponse.json({ result: 'Generated content' });
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Hono
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { Hono } from 'hono';
|
|
182
|
+
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/web';
|
|
183
|
+
|
|
184
|
+
const app = new Hono();
|
|
185
|
+
|
|
186
|
+
const pay = mantlePaywall({
|
|
187
|
+
priceUsd: 0.01,
|
|
188
|
+
payTo: '0xYourWalletAddress',
|
|
189
|
+
facilitatorUrl: 'https://facilitator.x402mantlesdk.xyz'
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
app.post('/api/generate', pay(async (c) => {
|
|
193
|
+
const { prompt } = await c.req.json();
|
|
194
|
+
return c.json({ result: 'Generated content' });
|
|
195
|
+
}));
|
|
196
|
+
|
|
197
|
+
export default app;
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Cloudflare Workers
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/web';
|
|
204
|
+
|
|
205
|
+
const pay = mantlePaywall({
|
|
206
|
+
priceUsd: 0.01,
|
|
207
|
+
payTo: '0xYourWalletAddress',
|
|
208
|
+
facilitatorUrl: 'https://facilitator.x402mantlesdk.xyz'
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
export default {
|
|
212
|
+
async fetch(request: Request): Promise<Response> {
|
|
213
|
+
if (request.method === 'POST') {
|
|
214
|
+
return pay(async (req) => {
|
|
215
|
+
const body = await req.json();
|
|
216
|
+
return new Response(JSON.stringify({ result: 'success' }), {
|
|
217
|
+
headers: { 'Content-Type': 'application/json' }
|
|
218
|
+
});
|
|
219
|
+
})(request);
|
|
220
|
+
}
|
|
221
|
+
return new Response('Method not allowed', { status: 405 });
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Deno
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/web';
|
|
230
|
+
|
|
231
|
+
const pay = mantlePaywall({
|
|
232
|
+
priceUsd: 0.01,
|
|
233
|
+
payTo: '0xYourWalletAddress',
|
|
234
|
+
facilitatorUrl: 'https://facilitator.x402mantlesdk.xyz'
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
Deno.serve(pay(async (req) => {
|
|
238
|
+
const body = await req.json();
|
|
239
|
+
return new Response(JSON.stringify({ result: 'success' }), {
|
|
240
|
+
headers: { 'Content-Type': 'application/json' }
|
|
241
|
+
});
|
|
242
|
+
}));
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Configuration
|
|
248
|
+
|
|
249
|
+
### Client Options
|
|
250
|
+
|
|
251
|
+
| Option | Type | Default | Description |
|
|
252
|
+
|--------|------|---------|-------------|
|
|
253
|
+
| `facilitatorUrl` | `string` | `http://localhost:8080` | Facilitator service URL |
|
|
254
|
+
| `resourceUrl` | `string` | `window.location.origin` | Your API base URL |
|
|
255
|
+
| `projectKey` | `string` | - | Project key for hosted facilitator billing |
|
|
256
|
+
| `getProvider` | `() => EIP1193Provider` | - | Function returning wallet provider |
|
|
257
|
+
| `getAccount` | `() => string` | - | Function returning user address |
|
|
258
|
+
|
|
259
|
+
### Server Options (mantlePaywall)
|
|
260
|
+
|
|
261
|
+
| Option | Type | Required | Description |
|
|
262
|
+
|--------|------|----------|-------------|
|
|
263
|
+
| `priceUsd` | `number` | Yes | Price in USD (e.g., `0.01` for 1 cent) |
|
|
264
|
+
| `payTo` | `string` | Yes | Your wallet address to receive payments |
|
|
265
|
+
| `facilitatorUrl` | `string` | No | Facilitator URL (default: localhost:8080) |
|
|
266
|
+
| `apiKey` | `string` | No | API key for hosted facilitator |
|
|
267
|
+
| `network` | `string` | No | Network ID (default: `mantle-mainnet`) |
|
|
268
|
+
| `onPaymentSettled` | `function` | No | Callback when payment is verified |
|
|
269
|
+
| `telemetry` | `object` | No | Analytics configuration |
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Hosted vs Self-Hosted Facilitator
|
|
274
|
+
|
|
275
|
+
### Hosted Facilitator (Recommended)
|
|
276
|
+
|
|
277
|
+
Use our managed facilitator service:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const pay = mantlePaywall({
|
|
281
|
+
priceUsd: 0.01,
|
|
282
|
+
payTo: '0xYourWallet',
|
|
283
|
+
facilitatorUrl: 'https://facilitator.x402mantlesdk.xyz',
|
|
284
|
+
apiKey: 'your-api-key' // Get from dashboard
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Get your API key from [Dashboard](https://x402mantlesdk.xyz/dashboard).
|
|
289
|
+
|
|
290
|
+
### Self-Hosted Facilitator
|
|
291
|
+
|
|
292
|
+
Run your own facilitator:
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
const pay = mantlePaywall({
|
|
296
|
+
priceUsd: 0.01,
|
|
297
|
+
payTo: '0xYourWallet',
|
|
298
|
+
facilitatorUrl: 'https://your-facilitator.com'
|
|
299
|
+
// No apiKey needed for self-hosted
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Create a facilitator with:
|
|
304
|
+
```bash
|
|
305
|
+
npx create-mantle-facilitator my-facilitator
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Analytics & Telemetry
|
|
311
|
+
|
|
312
|
+
Track payments with built-in analytics:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
const pay = mantlePaywall({
|
|
316
|
+
priceUsd: 0.01,
|
|
317
|
+
payTo: '0x...',
|
|
318
|
+
facilitatorUrl: 'https://facilitator.x402mantlesdk.xyz',
|
|
319
|
+
|
|
320
|
+
// Analytics configuration
|
|
321
|
+
telemetry: {
|
|
322
|
+
projectKey: 'pk_xxx', // Get from dashboard
|
|
323
|
+
debug: true // Enable console logging
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
// Callback on each successful payment
|
|
327
|
+
onPaymentSettled: (entry) => {
|
|
328
|
+
console.log('Payment received!');
|
|
329
|
+
console.log('Amount:', entry.valueAtomic);
|
|
330
|
+
console.log('From:', entry.from);
|
|
331
|
+
console.log('To:', entry.to);
|
|
332
|
+
console.log('TxHash:', entry.txHash);
|
|
333
|
+
console.log('Route:', entry.route);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### PaymentLogEntry Fields
|
|
339
|
+
|
|
340
|
+
| Field | Type | Description |
|
|
341
|
+
|-------|------|-------------|
|
|
342
|
+
| `id` | `string` | Unique payment identifier (nonce) |
|
|
343
|
+
| `from` | `string` | Payer's wallet address |
|
|
344
|
+
| `to` | `string` | Recipient's wallet address |
|
|
345
|
+
| `valueAtomic` | `string` | Amount in atomic units |
|
|
346
|
+
| `network` | `string` | Network ID |
|
|
347
|
+
| `asset` | `string` | Token contract address |
|
|
348
|
+
| `route` | `string` | API route (e.g., `POST /api/generate`) |
|
|
349
|
+
| `txHash` | `string` | Blockchain transaction hash |
|
|
350
|
+
| `timestamp` | `number` | Unix timestamp (ms) |
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## TypeScript Types
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import type {
|
|
358
|
+
// Shared types
|
|
359
|
+
PaymentRequirements,
|
|
360
|
+
Authorization,
|
|
361
|
+
PaymentHeaderObject,
|
|
362
|
+
NetworkId,
|
|
363
|
+
|
|
364
|
+
// Client types
|
|
365
|
+
MantleClient,
|
|
366
|
+
MantleClientConfig,
|
|
367
|
+
PaymentClient,
|
|
368
|
+
PaymentClientConfig,
|
|
369
|
+
CallWithPaymentResult,
|
|
370
|
+
|
|
371
|
+
// Server types
|
|
372
|
+
PaymentLogEntry,
|
|
373
|
+
TelemetryConfig,
|
|
374
|
+
MinimalPaywallOptions,
|
|
375
|
+
|
|
376
|
+
// React types
|
|
377
|
+
UseMantleX402Options,
|
|
378
|
+
UseEthersWalletOptions,
|
|
379
|
+
UseEthersWalletReturn
|
|
380
|
+
} from '@puga-labs/x402-mantle-sdk';
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Import Paths
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
// Main exports (types + constants)
|
|
389
|
+
import { MANTLE_DEFAULTS } from '@puga-labs/x402-mantle-sdk';
|
|
390
|
+
|
|
391
|
+
// Client
|
|
392
|
+
import { createMantleClient, createPaymentClient } from '@puga-labs/x402-mantle-sdk/client';
|
|
393
|
+
|
|
394
|
+
// React hooks
|
|
395
|
+
import { useMantleX402, useEthersWallet } from '@puga-labs/x402-mantle-sdk/react';
|
|
396
|
+
|
|
397
|
+
// Server - Express
|
|
398
|
+
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/express';
|
|
399
|
+
|
|
400
|
+
// Server - Next.js
|
|
401
|
+
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/nextjs';
|
|
402
|
+
|
|
403
|
+
// Server - Web Standards (Hono, CF Workers, Deno)
|
|
404
|
+
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/web';
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Network Configuration
|
|
410
|
+
|
|
411
|
+
| Network | Chain ID | USDC Address | Decimals |
|
|
412
|
+
|---------|----------|--------------|----------|
|
|
413
|
+
| Mantle Mainnet | 5000 | `0x09Bc4E0D864854c6aFB6eB9A9cdF58aC190D0dF9` | 6 |
|
|
414
|
+
|
|
415
|
+
### Price Conversion
|
|
416
|
+
|
|
417
|
+
- `priceUsd: 0.01` = 1 cent = 10,000 atomic units
|
|
418
|
+
- Formula: `atomicUnits = priceUsd * 10^6`
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## Error Handling
|
|
423
|
+
|
|
424
|
+
### Client-Side Errors
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
try {
|
|
428
|
+
const { response, txHash } = await postWithPayment('/api/generate', data);
|
|
429
|
+
} catch (error) {
|
|
430
|
+
if (error.code === 4001) {
|
|
431
|
+
// User rejected the transaction
|
|
432
|
+
console.log('User cancelled');
|
|
433
|
+
} else if (error.code === -32603) {
|
|
434
|
+
// Internal error (insufficient funds, etc.)
|
|
435
|
+
console.log('Transaction failed:', error.message);
|
|
436
|
+
} else {
|
|
437
|
+
// Network or facilitator error
|
|
438
|
+
console.log('Error:', error.message);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Server-Side Error Responses
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
// 402 Payment Required
|
|
447
|
+
{
|
|
448
|
+
"error": "Payment Required",
|
|
449
|
+
"paymentRequirements": {
|
|
450
|
+
"scheme": "exact",
|
|
451
|
+
"network": "mantle-mainnet",
|
|
452
|
+
"asset": "0x09Bc4E0D...",
|
|
453
|
+
"maxAmountRequired": "10000",
|
|
454
|
+
"payTo": "0x...",
|
|
455
|
+
"price": "$0.01",
|
|
456
|
+
"currency": "USD"
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// 400 Invalid Payment
|
|
461
|
+
{
|
|
462
|
+
"error": "Invalid payment",
|
|
463
|
+
"invalidReason": "Payment amount too low"
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## Links
|
|
470
|
+
|
|
471
|
+
- [Full Documentation](https://x402mantlesdk.xyz/docs)
|
|
472
|
+
- [Dashboard](https://x402mantlesdk.xyz/dashboard)
|
|
473
|
+
- [GitHub](https://github.com/puga-labs/x402-mantle-sdk)
|
|
474
|
+
- [Facilitator Kit](../create-mantle-facilitator)
|
|
475
|
+
|
|
476
|
+
## License
|
|
477
|
+
|
|
478
|
+
MIT
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildRouteKey,
|
|
3
|
+
checkPayment,
|
|
4
|
+
validateAddress
|
|
5
|
+
} from "./chunk-UVYA6H32.js";
|
|
6
|
+
import {
|
|
7
|
+
MANTLE_DEFAULTS,
|
|
8
|
+
__export,
|
|
9
|
+
getDefaultAssetForNetwork,
|
|
10
|
+
usdCentsToAtomic
|
|
11
|
+
} from "./chunk-HEZZ74SI.js";
|
|
12
|
+
|
|
13
|
+
// src/server/adapters/express.ts
|
|
14
|
+
var express_exports = {};
|
|
15
|
+
__export(express_exports, {
|
|
16
|
+
createPaymentMiddleware: () => createPaymentMiddleware,
|
|
17
|
+
mantlePaywall: () => mantlePaywall
|
|
18
|
+
});
|
|
19
|
+
function debugLog(config, prefix, message, data) {
|
|
20
|
+
if (config?.debug) {
|
|
21
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
22
|
+
console.log(`[${timestamp}] [x402-debug:${prefix}]`, message, data || "");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function createPaymentMiddleware(config) {
|
|
26
|
+
const { facilitatorUrl, receiverAddress, routes, apiKey, onPaymentSettled, telemetry } = config;
|
|
27
|
+
if (!facilitatorUrl) {
|
|
28
|
+
throw new Error("facilitatorUrl is required");
|
|
29
|
+
}
|
|
30
|
+
if (!receiverAddress) {
|
|
31
|
+
throw new Error("receiverAddress is required");
|
|
32
|
+
}
|
|
33
|
+
validateAddress(receiverAddress, "receiverAddress");
|
|
34
|
+
if (!routes || Object.keys(routes).length === 0) {
|
|
35
|
+
throw new Error("routes config must not be empty");
|
|
36
|
+
}
|
|
37
|
+
return async function paymentMiddleware(req, res, next) {
|
|
38
|
+
const routeKey = buildRouteKey(req.method, req.path);
|
|
39
|
+
const routeConfig = routes[routeKey];
|
|
40
|
+
if (!routeConfig) {
|
|
41
|
+
next();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const { priceUsdCents, network } = routeConfig;
|
|
45
|
+
const assetConfig = getDefaultAssetForNetwork(network);
|
|
46
|
+
const maxAmountRequiredBigInt = usdCentsToAtomic(
|
|
47
|
+
priceUsdCents,
|
|
48
|
+
assetConfig.decimals
|
|
49
|
+
);
|
|
50
|
+
const paymentRequirements = {
|
|
51
|
+
scheme: "exact",
|
|
52
|
+
network,
|
|
53
|
+
asset: assetConfig.address,
|
|
54
|
+
maxAmountRequired: maxAmountRequiredBigInt.toString(),
|
|
55
|
+
payTo: receiverAddress,
|
|
56
|
+
price: `$${(priceUsdCents / 100).toFixed(2)}`,
|
|
57
|
+
currency: "USD"
|
|
58
|
+
};
|
|
59
|
+
const paymentHeader = req.header("X-PAYMENT") || req.header("x-payment") || null;
|
|
60
|
+
const result = await checkPayment({
|
|
61
|
+
paymentHeader,
|
|
62
|
+
paymentRequirements,
|
|
63
|
+
facilitatorUrl,
|
|
64
|
+
apiKey,
|
|
65
|
+
routeKey,
|
|
66
|
+
network,
|
|
67
|
+
asset: assetConfig.address,
|
|
68
|
+
telemetry,
|
|
69
|
+
onPaymentSettled
|
|
70
|
+
});
|
|
71
|
+
if (!result.isValid) {
|
|
72
|
+
res.status(result.statusCode).json(result.responseBody);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
debugLog(telemetry, "handler", "\u25B6\uFE0F Handler execution started (Express middleware)");
|
|
76
|
+
if (result.sendTelemetryAfterResponse) {
|
|
77
|
+
res.on("finish", () => {
|
|
78
|
+
const statusCode = res.statusCode;
|
|
79
|
+
debugLog(telemetry, "handler", `\u2705 Handler completed: ${statusCode}`);
|
|
80
|
+
const errorMessage = statusCode >= 400 ? `Handler returned ${statusCode}` : void 0;
|
|
81
|
+
debugLog(telemetry, "callback", `\u{1F4E4} Calling telemetry callback with status ${statusCode}`);
|
|
82
|
+
result.sendTelemetryAfterResponse(statusCode, errorMessage);
|
|
83
|
+
});
|
|
84
|
+
res.on("close", () => {
|
|
85
|
+
if (!res.writableEnded) {
|
|
86
|
+
debugLog(telemetry, "handler", "\u274C Response closed without finishing");
|
|
87
|
+
debugLog(telemetry, "callback", "\u{1F4E4} Calling telemetry callback with error");
|
|
88
|
+
result.sendTelemetryAfterResponse(500, "Response closed without finishing");
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
debugLog(telemetry, "callback", "\u26A0\uFE0F No telemetry callback to call");
|
|
93
|
+
}
|
|
94
|
+
next();
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function mantlePaywall(opts) {
|
|
98
|
+
const { priceUsd, payTo, facilitatorUrl, apiKey, telemetry, onPaymentSettled } = opts;
|
|
99
|
+
validateAddress(payTo, "payTo");
|
|
100
|
+
const priceUsdCents = Math.round(priceUsd * 100);
|
|
101
|
+
return async (req, res, next) => {
|
|
102
|
+
const method = (req.method || "GET").toUpperCase();
|
|
103
|
+
const path = req.path || "/";
|
|
104
|
+
const routeKey = `${method} ${path}`;
|
|
105
|
+
const middleware = createPaymentMiddleware({
|
|
106
|
+
facilitatorUrl: facilitatorUrl || MANTLE_DEFAULTS.FACILITATOR_URL,
|
|
107
|
+
receiverAddress: payTo,
|
|
108
|
+
routes: {
|
|
109
|
+
[routeKey]: {
|
|
110
|
+
priceUsdCents,
|
|
111
|
+
network: MANTLE_DEFAULTS.NETWORK
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
apiKey,
|
|
115
|
+
telemetry,
|
|
116
|
+
onPaymentSettled
|
|
117
|
+
});
|
|
118
|
+
return middleware(req, res, next);
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
createPaymentMiddleware,
|
|
124
|
+
mantlePaywall,
|
|
125
|
+
express_exports
|
|
126
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildRouteKey,
|
|
3
|
+
checkPayment,
|
|
4
|
+
validateAddress
|
|
5
|
+
} from "./chunk-NWWXJFJ4.js";
|
|
6
|
+
import {
|
|
7
|
+
MANTLE_DEFAULTS,
|
|
8
|
+
__export,
|
|
9
|
+
getDefaultAssetForNetwork,
|
|
10
|
+
usdCentsToAtomic
|
|
11
|
+
} from "./chunk-HEZZ74SI.js";
|
|
12
|
+
|
|
13
|
+
// src/server/adapters/web-standards.ts
|
|
14
|
+
var web_standards_exports = {};
|
|
15
|
+
__export(web_standards_exports, {
|
|
16
|
+
mantlePaywall: () => mantlePaywall
|
|
17
|
+
});
|
|
18
|
+
function debugLog(config, prefix, message, data) {
|
|
19
|
+
if (config?.debug) {
|
|
20
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
21
|
+
console.log(`[${timestamp}] [x402-debug:${prefix}]`, message, data || "");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function mantlePaywall(opts) {
|
|
25
|
+
const { priceUsd, payTo, facilitatorUrl, apiKey, telemetry, onPaymentSettled } = opts;
|
|
26
|
+
validateAddress(payTo, "payTo");
|
|
27
|
+
const priceUsdCents = Math.round(priceUsd * 100);
|
|
28
|
+
return function(handler) {
|
|
29
|
+
return async (request) => {
|
|
30
|
+
const url = new URL(request.url);
|
|
31
|
+
const method = request.method;
|
|
32
|
+
const path = url.pathname;
|
|
33
|
+
const routeKey = buildRouteKey(method, path);
|
|
34
|
+
const network = MANTLE_DEFAULTS.NETWORK;
|
|
35
|
+
const assetConfig = getDefaultAssetForNetwork(network);
|
|
36
|
+
const maxAmountRequiredBigInt = usdCentsToAtomic(
|
|
37
|
+
priceUsdCents,
|
|
38
|
+
assetConfig.decimals
|
|
39
|
+
);
|
|
40
|
+
const paymentRequirements = {
|
|
41
|
+
scheme: "exact",
|
|
42
|
+
network,
|
|
43
|
+
asset: assetConfig.address,
|
|
44
|
+
maxAmountRequired: maxAmountRequiredBigInt.toString(),
|
|
45
|
+
payTo,
|
|
46
|
+
price: `$${(priceUsdCents / 100).toFixed(2)}`,
|
|
47
|
+
currency: "USD"
|
|
48
|
+
};
|
|
49
|
+
const paymentHeader = request.headers.get("X-PAYMENT") || request.headers.get("x-payment") || null;
|
|
50
|
+
const result = await checkPayment({
|
|
51
|
+
paymentHeader,
|
|
52
|
+
paymentRequirements,
|
|
53
|
+
facilitatorUrl: facilitatorUrl || MANTLE_DEFAULTS.FACILITATOR_URL,
|
|
54
|
+
apiKey,
|
|
55
|
+
routeKey,
|
|
56
|
+
network,
|
|
57
|
+
asset: assetConfig.address,
|
|
58
|
+
telemetry,
|
|
59
|
+
onPaymentSettled
|
|
60
|
+
});
|
|
61
|
+
if (!result.isValid) {
|
|
62
|
+
return new Response(JSON.stringify(result.responseBody), {
|
|
63
|
+
status: result.statusCode,
|
|
64
|
+
headers: {
|
|
65
|
+
"Content-Type": "application/json"
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
debugLog(telemetry, "handler", "Handler execution started");
|
|
71
|
+
const response = await handler(request);
|
|
72
|
+
debugLog(telemetry, "handler", `Handler completed: ${response.status}`);
|
|
73
|
+
if (result.sendTelemetryAfterResponse) {
|
|
74
|
+
debugLog(telemetry, "callback", `Calling telemetry callback with status ${response.status}`);
|
|
75
|
+
result.sendTelemetryAfterResponse(response.status);
|
|
76
|
+
} else {
|
|
77
|
+
debugLog(telemetry, "callback", "WARNING: No telemetry callback to call");
|
|
78
|
+
}
|
|
79
|
+
return response;
|
|
80
|
+
} catch (err) {
|
|
81
|
+
const errorMessage = err instanceof Error ? err.message : "Unknown error";
|
|
82
|
+
debugLog(telemetry, "handler", `ERROR: Handler error: ${errorMessage}`);
|
|
83
|
+
if (result.sendTelemetryAfterResponse) {
|
|
84
|
+
debugLog(telemetry, "callback", "Calling telemetry callback with error");
|
|
85
|
+
result.sendTelemetryAfterResponse(500, errorMessage);
|
|
86
|
+
}
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export {
|
|
94
|
+
mantlePaywall,
|
|
95
|
+
web_standards_exports
|
|
96
|
+
};
|