npm package: @puga-labs/x402-mantle-sdk
Description: x402 payments SDK for Mantle (USDC, gasless, facilitator-based)
Author: Evgenii Pugachev
Latest version: 0.4.2

# @puga-labs/x402-mantle-sdk

[![npm version](https://img.shields.io/npm/v/@puga-labs/x402-mantle-sdk.svg)](https://www.npmjs.com/package/@puga-labs/x402-mantle-sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

SDK for gasless x402 payments on Mantle blockchain using USDC (EIP-3009). Protect your APIs with crypto payments — users sign, facilitator pays gas.

## Installation

```bash
npm install @puga-labs/x402-mantle-sdk
```

## Features

- React hooks (`useMantleX402`, `useEthersWallet`)
- Vanilla JS client (`createMantleClient`)
- Server middleware for Express, Next.js, Hono, Cloudflare Workers, Deno
- Full TypeScript support
- Automatic payment flow handling
- Analytics & telemetry integration

---

## Client-Side Usage

### React (Recommended)

```tsx
import { useMantleX402 } from '@puga-labs/x402-mantle-sdk/react';

function PaymentButton() {
  // facilitatorUrl auto-detected from backend 402 response
  const { postWithPayment } = useMantleX402();

  const handlePay = async () => {
    try {
      const { response, txHash } = await postWithPayment('/api/generate', {
        prompt: 'Hello world'
      });

      console.log('Result:', response);
      console.log('Transaction:', txHash);
    } catch (error) {
      console.error('Payment failed:', error);
    }
  };

  return <button onClick={handlePay}>Generate ($0.01)</button>;
}
```

### Wallet Hook

Separate hook for wallet management:

```tsx
import { useEthersWallet } from '@puga-labs/x402-mantle-sdk/react';

function WalletConnect() {
  const {
    address,
    isConnected,
    chainId,
    connect,
    disconnect,
    error
  } = useEthersWallet({ autoConnect: false });

  if (isConnected) {
    return (
      <div>
        <p>Connected: {address}</p>
        <p>Chain ID: {chainId}</p>
        <button onClick={disconnect}>Disconnect</button>
      </div>
    );
  }

  return <button onClick={connect}>Connect Wallet</button>;
}
```

### Vanilla JavaScript

```typescript
import { createMantleClient } from '@puga-labs/x402-mantle-sdk/client';

// Create client (facilitatorUrl auto-detected from backend)
const client = createMantleClient({
  resourceUrl: 'https://your-api.com',
  getProvider: () => window.ethereum,
  getAccount: () => userAddress
});

// Make paid request
const { response, txHash } = await client.postWithPayment('/api/generate', {
  prompt: 'Hello world'
});
```

### Low-Level Payment Client

For full control over the payment flow:

```typescript
import { createPaymentClient } from '@puga-labs/x402-mantle-sdk/client';

// Low-level client (facilitatorUrl auto-detected from backend)
const paymentClient = createPaymentClient({
  resourceUrl: 'https://your-api.com',
  provider: window.ethereum,
  userAddress: '0x...' // Optional
});

const result = await paymentClient.callWithPayment('/api/endpoint', {
  data: 'payload'
});
```

---

## Server-Side Usage

### Express.js

```typescript
import express from 'express';
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/express';

const app = express();
app.use(express.json());

// Create paywall middleware (hosted mode - facilitatorUrl auto-detected)
const pay = mantlePaywall({
  priceUsd: 0.01,
  payTo: '0xYourWalletAddress',
  projectKey: 'pk_xxx' // Get from https://x402mantlesdk.xyz/dashboard
});

// Protected route
app.post('/api/generate', pay, async (req, res) => {
  const { prompt } = req.body;
  // Your logic here - only runs after payment
  res.json({ result: 'Generated content' });
});

// Unprotected route
app.get('/api/health', (req, res) => {
  res.json({ status: 'ok' });
});

app.listen(3000);
```

### Next.js (App Router)

```typescript
// app/api/generate/route.ts
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/nextjs';
import { NextRequest, NextResponse } from 'next/server';

const pay = mantlePaywall({
  priceUsd: 0.01,
  payTo: '0xYourWalletAddress',
  projectKey: 'pk_xxx' // Get from dashboard
});

export const POST = pay(async (req: NextRequest) => {
  const { prompt } = await req.json();
  // Your logic here
  return NextResponse.json({ result: 'Generated content' });
});
```

### Hono

```typescript
import { Hono } from 'hono';
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/web';

const app = new Hono();

const pay = mantlePaywall({
  priceUsd: 0.01,
  payTo: '0xYourWalletAddress',
  projectKey: 'pk_xxx' // Get from dashboard
});

app.post('/api/generate', pay(async (c) => {
  const { prompt } = await c.req.json();
  return c.json({ result: 'Generated content' });
}));

export default app;
```

### Cloudflare Workers

```typescript
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/web';

const pay = mantlePaywall({
  priceUsd: 0.01,
  payTo: '0xYourWalletAddress',
  projectKey: 'pk_xxx' // Get from dashboard
});

export default {
  async fetch(request: Request): Promise<Response> {
    if (request.method === 'POST') {
      return pay(async (req) => {
        const body = await req.json();
        return new Response(JSON.stringify({ result: 'success' }), {
          headers: { 'Content-Type': 'application/json' }
        });
      })(request);
    }
    return new Response('Method not allowed', { status: 405 });
  }
};
```

### Deno

```typescript
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/web';

const pay = mantlePaywall({
  priceUsd: 0.01,
  payTo: '0xYourWalletAddress',
  projectKey: 'pk_xxx' // Get from dashboard
});

Deno.serve(pay(async (req) => {
  const body = await req.json();
  return new Response(JSON.stringify({ result: 'success' }), {
    headers: { 'Content-Type': 'application/json' }
  });
}));
```

---

## Configuration

### Client Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `facilitatorUrl` | `string` | From 402 response | Facilitator service URL (auto-detected from backend) |
| `resourceUrl` | `string` | `window.location.origin` | Your API base URL |
| `projectKey` | `string` | From 402 response | Project key (auto-detected from backend) |
| `getProvider` | `() => EIP1193Provider` | - | Function returning wallet provider |
| `getAccount` | `() => string` | - | Function returning user address |

**Note:** Both `facilitatorUrl` and `projectKey` are automatically passed from your backend via 402 responses, so clients typically don't need to configure them.

### Server Options (mantlePaywall)

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `priceUsd` | `number` | Yes | Price in USD (e.g., `0.01` for 1 cent) |
| `payTo` | `string` | Yes | Your wallet address to receive payments |
| `projectKey` | `string` | For hosted | Project key from dashboard (for billing + analytics) |
| `facilitatorUrl` | `string` | For self-hosted | Your facilitator URL (defaults to hosted if not set) |
| `facilitatorSecret` | `string` | For self-hosted | Shared secret (when set, requires facilitatorUrl) |
| `onPaymentSettled` | `function` | No | Callback when payment is verified |
| `telemetry` | `object` | No | Analytics configuration (auto-uses projectKey if not set) |

---

## Hosted vs Self-Hosted Facilitator

### Hosted Facilitator (Recommended)

Use our managed facilitator service - **zero URL config needed**:

```typescript
// Minimal setup - facilitatorUrl defaults to hosted service!
const pay = mantlePaywall({
  priceUsd: 0.01,
  payTo: '0xYourWallet',
  projectKey: 'pk_xxx' // Get from dashboard (for billing + analytics)
});
```

Get your project key from [Dashboard](https://x402mantlesdk.xyz/dashboard).

**Note:** `projectKey` is automatically passed to clients via 402 responses, so they don't need to configure it separately.

### Self-Hosted Facilitator

Run your own facilitator for full control and cost savings:

```typescript
const pay = mantlePaywall({
  priceUsd: 0.01,
  payTo: '0xYourWallet',
  facilitatorUrl: 'https://your-facilitator.com', // Required for self-hosted
  facilitatorSecret: process.env.FACILITATOR_SECRET! // Required for self-hosted
});

// Optional: add projectKey for telemetry
const payWithTelemetry = mantlePaywall({
  priceUsd: 0.01,
  payTo: '0xYourWallet',
  facilitatorUrl: 'https://your-facilitator.com',
  facilitatorSecret: process.env.FACILITATOR_SECRET!,
  projectKey: 'pk_xxx' // Optional: for analytics
});
```

The `facilitatorSecret` is **required** for self-hosted facilitators to prevent unauthorized usage. When `facilitatorSecret` is provided, `facilitatorUrl` is also required.

Create a facilitator with:
```bash
npx create-mantle-facilitator
```

After setup, copy `FACILITATOR_SECRET` from the generated `.env` file to your backend's environment variables.

---

## Analytics & Telemetry

Track payments with built-in analytics:

```typescript
const pay = mantlePaywall({
  priceUsd: 0.01,
  payTo: '0x...',
  projectKey: 'pk_xxx',  // Get from dashboard - used for both billing AND analytics

  // Optional: Advanced telemetry config
  telemetry: {
    debug: true  // Enable console logging (projectKey auto-derived from above)
  },

  // Callback on each successful payment
  onPaymentSettled: (entry) => {
    console.log('Payment received!');
    console.log('Amount:', entry.valueAtomic);
    console.log('From:', entry.from);
    console.log('To:', entry.to);
    console.log('TxHash:', entry.txHash);
    console.log('Route:', entry.route);
  }
});
```

### PaymentLogEntry Fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | `string` | Unique payment identifier (nonce) |
| `from` | `string` | Payer's wallet address |
| `to` | `string` | Recipient's wallet address |
| `valueAtomic` | `string` | Amount in atomic units |
| `network` | `string` | Network ID |
| `asset` | `string` | Token contract address |
| `route` | `string` | API route (e.g., `POST /api/generate`) |
| `txHash` | `string` | Blockchain transaction hash |
| `timestamp` | `number` | Unix timestamp (ms) |

---

## TypeScript Types

```typescript
import type {
  // Shared types
  PaymentRequirements,
  Authorization,
  PaymentHeaderObject,
  NetworkId,

  // Client types
  MantleClient,
  MantleClientConfig,
  PaymentClient,
  PaymentClientConfig,
  CallWithPaymentResult,

  // Server types
  PaymentLogEntry,
  TelemetryConfig,
  MinimalPaywallOptions,

  // React types
  UseMantleX402Options,
  UseEthersWalletOptions,
  UseEthersWalletReturn
} from '@puga-labs/x402-mantle-sdk';
```

---

## Import Paths

```typescript
// Main exports (types + constants)
import { MANTLE_DEFAULTS } from '@puga-labs/x402-mantle-sdk';

// Client
import { createMantleClient, createPaymentClient } from '@puga-labs/x402-mantle-sdk/client';

// React hooks
import { useMantleX402, useEthersWallet } from '@puga-labs/x402-mantle-sdk/react';

// Server - Express
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/express';

// Server - Next.js
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/nextjs';

// Server - Web Standards (Hono, CF Workers, Deno)
import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/web';
```

---

## Network Configuration

| Network | Chain ID | USDC Address | Decimals |
|---------|----------|--------------|----------|
| Mantle Mainnet | 5000 | `0x09Bc4E0D864854c6aFB6eB9A9cdF58aC190D0dF9` | 6 |

## Supported Tokens

Currently, the SDK supports **USDC only** for gasless payments.

### Why only USDC?

Gasless payments via facilitator require tokens that implement the **EIP-3009** standard (`transferWithAuthorization`). This standard allows users to sign a payment authorization off-chain, while the facilitator submits the transaction and pays gas fees.

On Mantle mainnet, **USDC is currently the only token** that implements EIP-3009. As other stablecoins and tokens adopt this standard, we will add support for them in the SDK and facilitator.

| Token | EIP-3009 Support | Status |
|-------|------------------|--------|
| USDC | Yes | Supported |
| USDT | No | Not available |
| DAI | No | Not available |
| Other tokens | — | Pending EIP-3009 adoption |

### Price Conversion

- `priceUsd: 0.01` = 1 cent = 10,000 atomic units
- Formula: `atomicUnits = priceUsd * 10^6`

---

## Error Handling

### Client-Side Errors

```typescript
try {
  const { response, txHash } = await postWithPayment('/api/generate', data);
} catch (error) {
  if (error.code === 4001) {
    // User rejected the transaction
    console.log('User cancelled');
  } else if (error.code === -32603) {
    // Internal error (insufficient funds, etc.)
    console.log('Transaction failed:', error.message);
  } else {
    // Network or facilitator error
    console.log('Error:', error.message);
  }
}
```

### Server-Side Error Responses

```typescript
// 402 Payment Required
{
  "error": "Payment Required",
  "paymentRequirements": {
    "scheme": "exact",
    "network": "mantle-mainnet",
    "asset": "0x09Bc4E0D...",
    "maxAmountRequired": "10000",
    "payTo": "0x...",
    "price": "$0.01",
    "currency": "USD"
  }
}

// 400 Invalid Payment
{
  "error": "Invalid payment",
  "invalidReason": "Payment amount too low"
}
```

---

## Links

- [Full Documentation](https://x402mantlesdk.xyz/docs)
- [Dashboard](https://x402mantlesdk.xyz/dashboard)
- [GitHub](https://github.com/puga-labs/x402-mantle-sdk)
- [Facilitator Kit](../create-mantle-facilitator)

## License

MIT