rust-x402 0.3.0

HTTP-native micropayments with x402 protocol
Documentation
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Payment Required</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 1rem;
    }

    .container {
      background: white;
      border-radius: 16px;
      box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
      padding: 2.5rem;
      max-width: 480px;
      width: 100%;
      text-align: center;
      position: relative;
      overflow: hidden;
    }

    .container::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      height: 4px;
      background: linear-gradient(90deg, #667eea, #764ba2);
    }

    .logo {
      width: 80px;
      height: 80px;
      margin: 0 auto 1.5rem;
      background: linear-gradient(135deg, #667eea, #764ba2);
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 32px;
      color: white;
      box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
    }

    h1 {
      color: #1a1a1a;
      margin-bottom: 0.5rem;
      font-size: 1.75rem;
      font-weight: 700;
    }

    .subtitle {
      color: #666;
      margin-bottom: 2rem;
      line-height: 1.6;
      font-size: 1rem;
    }

    .payment-info {
      background: #f8f9fa;
      border: 1px solid #e9ecef;
      border-radius: 12px;
      padding: 1.5rem;
      margin: 1.5rem 0;
      text-align: left;
    }

    .payment-row {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 0.75rem;
      padding: 0.5rem 0;
    }

    .payment-row:last-child {
      margin-bottom: 0;
      border-top: 1px solid #e9ecef;
      padding-top: 0.75rem;
      margin-top: 0.75rem;
    }

    .label {
      color: #666;
      font-weight: 500;
      font-size: 0.9rem;
    }

    .value {
      color: #1a1a1a;
      font-weight: 600;
      font-size: 0.9rem;
    }

    .amount {
      font-size: 1.1rem;
      color: #667eea;
    }

    .error {
      background: #fee;
      color: #dc3545;
      padding: 1rem;
      border-radius: 8px;
      margin: 1.5rem 0;
      border-left: 4px solid #dc3545;
      font-size: 0.9rem;
    }

    .instructions {
      background: #e3f2fd;
      color: #1976d2;
      padding: 1rem;
      border-radius: 8px;
      margin: 1.5rem 0;
      font-size: 0.9rem;
      line-height: 1.5;
    }

    .instructions strong {
      display: block;
      margin-bottom: 0.5rem;
    }

    .instructions a {
      color: #1976d2;
      text-decoration: none;
      font-weight: 600;
    }

    .instructions a:hover {
      text-decoration: underline;
    }

    .testnet-info {
      background: #fff3cd;
      color: #856404;
      padding: 1rem;
      border-radius: 8px;
      margin: 1rem 0;
      font-size: 0.9rem;
    }

    .testnet-info a {
      color: #856404;
      text-decoration: none;
      font-weight: 600;
    }

    .testnet-info a:hover {
      text-decoration: underline;
    }

    .status {
      margin-top: 1rem;
      padding: 0.75rem;
      border-radius: 6px;
      font-size: 0.9rem;
      font-weight: 500;
    }

    .status.loading {
      background: #e3f2fd;
      color: #1976d2;
    }

    .status.success {
      background: #d4edda;
      color: #155724;
    }

    .status.error {
      background: #f8d7da;
      color: #721c24;
    }

    @media (max-width: 480px) {
      .container {
        padding: 1.5rem;
        margin: 0.5rem;
      }

      .logo {
        width: 64px;
        height: 64px;
        font-size: 24px;
      }

      h1 {
        font-size: 1.5rem;
      }
    }
  </style>
</head>

<body>
  <div class="container">
    <div class="logo">💰</div>
    <h1>Payment Required</h1>
    <div class="subtitle">
      This resource requires payment to access. Please provide a valid X-PAYMENT header.
    </div>

    <div id="payment-details" class="payment-info" style="display: none;">
      <div class="payment-row">
        <span class="label">Amount:</span>
        <span class="value amount" id="amount">$0.00 USDC</span>
      </div>
      <div class="payment-row">
        <span class="label">Network:</span>
        <span class="value" id="network">Base Sepolia</span>
      </div>
      <div class="payment-row">
        <span class="label">Description:</span>
        <span class="value" id="description">Payment required</span>
      </div>
    </div>

    <div id="error-message" class="error" style="display: none;"></div>

    <div id="instructions" class="instructions" style="display: none;">
      <strong>How to pay:</strong><br>
      1. Connect your wallet to this network<br>
      2. Ensure you have sufficient USDC balance<br>
      3. Include the X-PAYMENT header in your request<br>
      4. Retry the request
    </div>

    <div id="testnet-info" class="testnet-info" style="display: none;">
      <strong>Need testnet USDC?</strong><br>
      Get some from the <a href="https://faucet.circle.com/" target="_blank" rel="noopener noreferrer">Circle
        faucet</a>.
    </div>

    <div id="status" class="status" style="display: none;"></div>
  </div>

  <script>
    // Initialize the paywall when the page loads
    document.addEventListener('DOMContentLoaded', function () {
      if (window.x402) {
        initializePaywall();
      } else {
        console.warn('x402 configuration not found');
        showInstructions();
      }
    });

    function initializePaywall() {
      const config = window.x402;

      // Show payment details if amount is specified
      if (config.amount > 0) {
        document.getElementById('amount').textContent = `$${config.amount} USDC`;
        document.getElementById('network').textContent = config.testnet ? 'Base Sepolia' : 'Base';
        document.getElementById('description').textContent =
          config.paymentRequirements[0]?.description || 'Payment required';
        document.getElementById('payment-details').style.display = 'block';
      }

      // Show error message
      if (config.error) {
        document.getElementById('error-message').textContent = config.error;
        document.getElementById('error-message').style.display = 'block';
      }

      // Show testnet instructions
      if (config.testnet) {
        document.getElementById('testnet-info').style.display = 'block';
      }

      // Show general instructions
      showInstructions();

      // Log configuration for debugging
      if (config.testnet) {
        console.log('Payment requirements initialized:', config);
      }
    }

    function showInstructions() {
      document.getElementById('instructions').style.display = 'block';
    }

    function showStatus(message, type) {
      const statusEl = document.getElementById('status');
      statusEl.textContent = message;
      statusEl.className = `status ${type}`;
      statusEl.style.display = 'block';
    }

    function hideStatus() {
      document.getElementById('status').style.display = 'none';
    }
  </script>
</body>

</html>