npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@puga-labs/x402-mantle-sdk

v0.4.2

Published

x402 payments SDK for Mantle (USDC, gasless, facilitator-based)

Readme

@puga-labs/x402-mantle-sdk

npm version License: 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

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)

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:

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

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:

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

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)

// 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

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

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

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:

// 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.

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:

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:

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:

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

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

// 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

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

// 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

License

MIT