Desktop Only

This application is optimized for desktop browsers. Please open it on a device with a screen width of at least 1024px.

Client SDK

The client SDK handles the payment flow automatically:

  1. Detects 402 responses
  2. Prompts user to sign payment
  3. Submits payment to facilitator
  4. Retries original request with payment proof

Supported Platforms

PlatformPackage
React / Next.js@puga-labs/x402-mantle-sdk/react
Vanilla JS@puga-labs/x402-mantle-sdk/client

Auto-Detection

Both facilitatorUrl and projectKey are automatically detected from the backend's 402 response. You typically don't need to configure them on the client side.

React Hooks

useMantleX402

Main hook for making paid requests:

import { useMantleX402 } from '@puga-labs/x402-mantle-sdk/react';

function MyComponent() {
  // facilitatorUrl auto-detected from backend 402 response
  const { postWithPayment } = useMantleX402();

  const handleClick = async () => {
    try {
      const result = await postWithPayment('/api/endpoint', {
        // Your request body
        prompt: 'Hello'
      });

      console.log('Response:', result.response);
      console.log('Transaction hash:', result.txHash);
    } catch (error) {
      if (error.code === 4001) {
        console.log('User rejected the transaction');
      } else {
        console.error('Error:', error.message);
      }
    }
  };

  return <button onClick={handleClick}>Make Paid Request</button>;
}

With Custom Configuration

const { postWithPayment } = useMantleX402({
  resourceUrl: 'https://your-api.com',  // Optional, defaults to window.location.origin
});

useEthersWallet

Separate hook for wallet management:

import { useEthersWallet } from '@puga-labs/x402-mantle-sdk/react';

function WalletButton() {
  const {
    address,      // User's wallet address (undefined if not connected)
    isConnected,  // Boolean connection status
    chainId,      // Current chain ID (5000 for Mantle)
    provider,     // ethers.BrowserProvider instance
    connect,      // Function to connect wallet
    disconnect,   // Function to disconnect (clears state)
    error         // Error object if connection failed
  } = useEthersWallet({
    autoConnect: false  // Set true to auto-connect on mount
  });

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  if (isConnected) {
    return (
      <div>
        <p>Connected: {address}</p>
        <p>Chain: {chainId}</p>
        <button onClick={disconnect}>Disconnect</button>
      </div>
    );
  }

  return <button onClick={connect}>Connect Wallet</button>;
}

Combining Both Hooks

import { useMantleX402, useEthersWallet } from '@puga-labs/x402-mantle-sdk/react';

function PaymentApp() {
  const wallet = useEthersWallet();
  const { postWithPayment } = useMantleX402();

  const handlePayment = async () => {
    if (!wallet.isConnected) {
      await wallet.connect();
    }

    // Check network
    if (wallet.chainId !== 5000) {
      alert('Please switch to Mantle network');
      return;
    }

    const { response, txHash } = await postWithPayment('/api/generate', {
      prompt: 'Hello'
    });

    console.log('Done!', response, txHash);
  };

  return (
    <div>
      {wallet.isConnected ? (
        <p>Wallet: {wallet.address}</p>
      ) : (
        <button onClick={wallet.connect}>Connect</button>
      )}
      <button onClick={handlePayment} disabled={!wallet.isConnected}>
        Pay $0.01
      </button>
    </div>
  );
}

Vanilla JavaScript

createMantleClient (High-Level)

Simplified API for most use cases:

import { createMantleClient } from '@puga-labs/x402-mantle-sdk/client';

// You need to manage wallet connection yourself
let userAddress: string | undefined;

// Listen for account changes
window.ethereum?.on('accountsChanged', (accounts: string[]) => {
  userAddress = accounts[0];
});

// Create client (facilitatorUrl auto-detected from backend)
const client = createMantleClient({
  resourceUrl: 'https://your-api.com',
  getProvider: () => window.ethereum,
  getAccount: () => userAddress
});

// Make paid request
async function makeRequest() {
  try {
    const { response, txHash } = await client.postWithPayment('/api/generate', {
      prompt: 'Hello world'
    });
    console.log('Response:', response);
    console.log('TxHash:', txHash);
  } catch (error) {
    console.error('Failed:', error);
  }
}

createPaymentClient (Low-Level)

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, will be fetched if not provided
});

// callWithPayment returns more details
const result = await paymentClient.callWithPayment('/api/endpoint', {
  data: 'payload'
});

console.log('Response:', result.response);
console.log('TxHash:', result.txHash);
console.log('Payment Header:', result.paymentHeader);
console.log('Requirements:', result.paymentRequirements);

Configuration Options

MantleClientConfig

OptionTypeDefaultDescription
facilitatorUrlstringFrom 402 responseAuto-detected from backend (rarely needed)
resourceUrlstringwindow.location.originBase URL of your API
projectKeystringFrom 402 responseAuto-detected from backend (rarely needed)
getProviderfunctionundefinedFunction returning wallet provider
getAccountfunctionundefinedFunction returning user's address

UseMantleX402Options (React)

OptionTypeDefaultDescription
facilitatorUrlstringFrom 402 responseAuto-detected from backend
resourceUrlstringwindow.location.originYour API URL
projectKeystringFrom 402 responseAuto-detected from backend

UseEthersWalletOptions (React)

OptionTypeDefaultDescription
autoConnectbooleanfalseAuto-connect on component mount

CallWithPaymentResult

interface CallWithPaymentResult<T> {
  response: T;                              // Your API response
  txHash?: string;                          // Blockchain transaction hash
  paymentHeader?: string;                   // Base64-encoded payment proof
  paymentRequirements?: PaymentRequirements; // Payment requirements used
}

Error Handling

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);
  }
}

Next Steps