diff --git a/python/x402-example/README.md b/python/x402-example/README.md
new file mode 100644
index 0000000..b6da96b
--- /dev/null
+++ b/python/x402-example/README.md
@@ -0,0 +1,332 @@
+# x402 Payment Protocol Implementation
+
+An implementation of the x402 payment protocol on BNB Chain, demonstrating HTTP 402 "Payment Required" status code for internet-native payments with sign-to-pay functionality.
+
+## Overview
+
+x402 is an open, internet-native payment standard that enables autonomous payments between AI agents, APIs, and services using blockchain technology. This implementation showcases:
+
+- **HTTP 402 Payment Required**: Standard HTTP status code for payment requests
+- **Sign-to-Pay**: EIP-712 signature-based payment authorization
+- **No API Keys Required**: Eliminates need for registrations or KYC
+- **EIP-7702 Support**: Delegated execution for one-time transaction permissions
+- **Gas-Sponsored Relayer**: Automatic payment handling
+
+## Screenshot
+
+
+
+## Features
+
+- **Payment Request Creation**: Generate EIP-712 signed payment requests
+- **Signature Verification**: Verify payer signatures cryptographically
+- **On-Chain Execution**: Execute verified payments on BNB Chain
+- **Interactive Frontend**: Web interface for testing x402 payments
+- **MCP Integration**: Full Model Context Protocol support
+
+## Prerequisites
+
+- Python 3.8 or higher
+- A BSC testnet wallet for merchant operations
+- Access to BSC testnet RPC endpoint
+
+## Installation
+
+1. Navigate to the x402-example directory:
+```bash
+cd x402-example
+```
+
+2. Run the setup script:
+```bash
+./run.sh
+```
+
+The script will:
+- Create a virtual environment
+- Install all dependencies
+- Run tests
+- Start the server
+
+## Configuration
+
+Create a `.env` file (or copy from `.env.example`):
+
+```bash
+PRIVATE_KEY=0xYourPrivateKeyHere
+BSC_TESTNET_RPC=https://data-seed-prebsc-1-s1.binance.org:8545/
+PAYMENT_TOKEN=USDC
+PAYMENT_TOKEN_ADDRESS=
+```
+
+## Usage
+
+### Running the Application
+
+```bash
+./run.sh
+```
+
+The application will start on `http://localhost:5001`
+
+### Available Tools
+
+#### `create_payment_request`
+
+Create a payment request following x402 protocol.
+
+**Parameters:**
+- `resource` (string, required): The resource or service being paid for
+- `amount` (float, required): Amount to charge
+- `currency` (string, optional): Currency type (default: USDC)
+- `description` (string, optional): Payment description
+
+**Example:**
+```json
+{
+ "resource": "API Access",
+ "amount": 10.0,
+ "currency": "USDC",
+ "description": "Monthly API subscription"
+}
+```
+
+**Returns:**
+```json
+{
+ "success": true,
+ "payment_id": "x402_1234567890",
+ "http_status": 402,
+ "payment_request": {
+ "payment_id": "x402_1234567890",
+ "resource": "API Access",
+ "amount": 10.0,
+ "currency": "USDC",
+ "signature": "0x...",
+ "status": "pending"
+ }
+}
+```
+
+#### `verify_payment_signature`
+
+Verify a payment signature from the payer.
+
+**Parameters:**
+- `payment_id` (string, required): The payment request ID
+- `payer_address` (string, required): Address of the payer
+- `payer_signature` (string, required): EIP-712 signature from payer
+
+**Returns:**
+```json
+{
+ "success": true,
+ "verified": true,
+ "payer": "0x...",
+ "message": "Payment signature verified. Ready for execution."
+}
+```
+
+#### `execute_payment`
+
+Execute the payment transaction on-chain.
+
+**Parameters:**
+- `payment_id` (string, required): The payment request ID
+- `payer_address` (string, required): Address of the payer
+
+**Returns:**
+```json
+{
+ "success": true,
+ "transaction_hash": "0x...",
+ "block_number": 12345,
+ "status": "completed"
+}
+```
+
+#### `get_payment_status`
+
+Get the status of a payment request.
+
+**Parameters:**
+- `payment_id` (string, required): The payment request ID
+
+**Returns:**
+```json
+{
+ "success": true,
+ "payment_request": {
+ "payment_id": "x402_1234567890",
+ "status": "completed",
+ "tx_hash": "0x...",
+ ...
+ }
+}
+```
+
+#### `list_payment_requests`
+
+List all payment requests, optionally filtered by status.
+
+**Parameters:**
+- `status` (string, optional): Filter by status (pending, verified, completed)
+
+**Returns:**
+```json
+{
+ "success": true,
+ "count": 5,
+ "payment_requests": [...]
+}
+```
+
+## Demo Example
+
+### Step 1: Create Payment Request
+
+**Input:**
+```json
+POST /api/payment_request
+{
+ "resource": "API Access",
+ "amount": 10.0,
+ "currency": "USDC",
+ "description": "Monthly subscription"
+}
+```
+
+**Output:**
+```
+HTTP 402 Payment Required
+{
+ "payment_id": "x402_1234567890",
+ "signature": "0xabc123...",
+ "status": "pending"
+}
+```
+
+### Step 2: Verify Payment Signature
+
+**Input:**
+```json
+POST /api/payment/x402_1234567890/verify
+{
+ "payer_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
+ "payer_signature": "0xdef456..."
+}
+```
+
+**Output:**
+```json
+{
+ "verified": true,
+ "status": "verified"
+}
+```
+
+### Step 3: Execute Payment
+
+**Input:**
+```json
+POST /api/payment/x402_1234567890/execute
+{
+ "payer_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
+}
+```
+
+**Output:**
+```json
+{
+ "success": true,
+ "transaction_hash": "0x789abc...",
+ "block_number": 50001,
+ "status": "completed"
+}
+```
+
+## Payment Flow
+
+1. **Client Request**: Client requests a resource
+2. **HTTP 402 Response**: Server responds with 402 and payment parameters
+3. **Payment Signature**: Client signs payment intent with EIP-712
+4. **Verification**: Server verifies the signature
+5. **Execution**: Payment is executed on-chain
+6. **Access Granted**: Client retries request with payment proof
+
+## Network Information
+
+- **Network**: Binance Smart Chain Testnet
+- **Chain ID**: 97
+- **Protocol**: x402 (HTTP 402 Payment Required)
+- **Signature Standard**: EIP-712
+- **RPC Endpoint**: https://data-seed-prebsc-1-s1.binance.org:8545/
+- **Explorer**: https://testnet.bscscan.com/
+
+## Testing
+
+Run unit tests:
+
+```bash
+source venv/bin/activate
+pytest test_x402_payment.py -v
+```
+
+Run tests with coverage:
+
+```bash
+pytest test_x402_payment.py --cov=x402_payment --cov-report=html
+```
+
+## Architecture
+
+- **Backend**: Flask web server with MCP integration
+- **Frontend**: HTML/CSS/JavaScript single-page application
+- **Blockchain**: Web3.py for BSC testnet interaction
+- **Signatures**: EIP-712 typed data signing
+- **Protocol**: Model Context Protocol (MCP) for AI agent integration
+
+## Key Features of x402
+
+1. **No API Keys**: Eliminates need for API keys or registrations
+2. **No KYC**: Direct payment without identity verification
+3. **Sign-to-Pay**: Single signature replaces multiple steps
+4. **Gas Sponsored**: Relayer handles gas payments
+5. **Internet-Native**: Uses standard HTTP status codes
+
+## Security Notes
+
+- โ ๏ธ **Never commit your private key** to version control
+- Use environment variables for sensitive data
+- This example is for testnet use only
+- Always verify signatures before executing payments
+- Implement rate limiting for production use
+
+## Troubleshooting
+
+### Signature Verification Fails
+- Verify payer address matches signature
+- Check EIP-712 domain and message structure
+- Ensure chain ID is correct
+
+### Payment Execution Fails
+- Verify payment request is in "verified" status
+- Check merchant wallet has sufficient balance
+- Ensure RPC endpoint is accessible
+
+### Frontend Not Loading
+- Verify Flask server is running on port 5001
+- Check browser console for errors
+- Ensure templates directory exists
+
+## License
+
+This project is provided as-is for educational and demonstration purposes.
+
+## References
+
+- [x402 Protocol](https://www.x402.org/)
+- [x402 BNB Implementation](https://www.x402bnb.pro/)
+- [EIP-712: Typed Structured Data Hashing](https://eips.ethereum.org/EIPS/eip-712)
+- [EIP-7702: Set EOA Account Code](https://eips.ethereum.org/EIPS/eip-7702)
+- [Model Context Protocol](https://modelcontextprotocol.io)
diff --git a/python/x402-example/requirements.txt b/python/x402-example/requirements.txt
new file mode 100644
index 0000000..ff00ab2
--- /dev/null
+++ b/python/x402-example/requirements.txt
@@ -0,0 +1,6 @@
+flask>=3.0.0
+web3>=6.0.0
+mcp>=1.0.0
+eth-account>=0.9.0
+pytest>=7.0.0
+pytest-cov>=4.0.0
diff --git a/python/x402-example/run.sh b/python/x402-example/run.sh
new file mode 100644
index 0000000..42438d0
--- /dev/null
+++ b/python/x402-example/run.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+
+# x402 Payment Protocol - Run Script
+# This script sets up the virtual environment and runs the x402 payment server
+
+set -e
+
+echo "๐ Setting up x402 Payment Protocol Server"
+echo "============================================"
+
+# Check if Python 3 is available
+if ! command -v python3 &> /dev/null; then
+ echo "โ Python 3 is required but not installed."
+ exit 1
+fi
+
+# Create virtual environment if it doesn't exist
+if [ ! -d "venv" ]; then
+ echo "๐ฆ Creating virtual environment..."
+ python3 -m venv venv
+fi
+
+# Activate virtual environment
+echo "๐ง Activating virtual environment..."
+source venv/bin/activate
+
+# Install dependencies
+echo "๐ฅ Installing dependencies..."
+pip install --upgrade pip
+pip install -r requirements.txt
+
+# Check if .env file exists
+if [ ! -f ".env" ]; then
+ echo "โ ๏ธ .env file not found. Creating from .env.example..."
+ cp .env.example .env
+ echo "โ ๏ธ Please edit .env file and add your PRIVATE_KEY before running!"
+ exit 1
+fi
+
+# Load environment variables
+export $(cat .env | grep -v '^#' | xargs)
+
+# Check if PRIVATE_KEY is set
+if [ -z "$PRIVATE_KEY" ] || [ "$PRIVATE_KEY" == "0xYourPrivateKeyHere" ]; then
+ echo "โ PRIVATE_KEY not set in .env file. Please configure it first."
+ exit 1
+fi
+
+# Run tests
+echo "๐งช Running tests..."
+python -m pytest test_x402_payment.py -v
+
+# Start the server
+echo "โ Starting x402 payment protocol server..."
+echo "๐ Frontend will be available at http://localhost:5001"
+echo "๐ MCP server is running..."
+echo ""
+python x402_payment.py
diff --git a/python/x402-example/templates/index.html b/python/x402-example/templates/index.html
new file mode 100644
index 0000000..aac212f
--- /dev/null
+++ b/python/x402-example/templates/index.html
@@ -0,0 +1,453 @@
+
+
+
+
+
+ x402 Payment Protocol - Sign-to-Pay
+
+
+
+
+
+
๐ณ x402 Payment Protocol
+
HTTP 402 Payment Required - Sign-to-Pay on BNB Chain
+
+
+
+
About x402
+
x402 is an internet-native payment standard that uses HTTP 402 status code for payment requests.
+ It enables sign-to-pay functionality using EIP-712 signatures, eliminating the need for API keys,
+ registrations, or KYC requirements.
+
+
+
+
+
Create Payment Request
+
+
+
+
+
Payment Request Created
+
+
+
Verify Payment (Payer)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Payment Requests
+
+
Loading payment requests...
+
+
+
+
+
+
+
+
diff --git a/python/x402-example/test_x402_payment.py b/python/x402-example/test_x402_payment.py
new file mode 100644
index 0000000..ccb7d7d
--- /dev/null
+++ b/python/x402-example/test_x402_payment.py
@@ -0,0 +1,187 @@
+#!/usr/bin/env python3
+"""
+Unit tests for x402 Payment Protocol
+"""
+
+import os
+import sys
+import pytest
+from unittest.mock import Mock, patch, MagicMock
+
+# Set test environment variables
+os.environ["PRIVATE_KEY"] = "0x" + "1" * 64
+os.environ["BSC_TESTNET_RPC"] = "https://test-rpc.example.com"
+
+# Mock Web3
+mock_w3 = MagicMock()
+mock_w3.is_connected.return_value = True
+mock_w3.eth.chain_id = 97
+
+def _is_address(addr):
+ if not addr or not isinstance(addr, str):
+ return False
+ addr = addr.strip()
+ return addr.startswith("0x") and len(addr) == 42
+
+mock_w3.is_address.side_effect = _is_address
+mock_w3.to_checksum_address.side_effect = lambda x: x if isinstance(x, str) else str(x)
+
+mock_account = MagicMock()
+VALID_ADDR = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0"
+OTHER_ADDR = "0x1234567890123456789012345678901234567890"
+mock_account.address = VALID_ADDR
+mock_account.key = b"test_key"
+mock_w3.eth.account.from_key.return_value = mock_account
+
+# Mock EIP-712 signing
+mock_signature = MagicMock()
+mock_signature.signature.hex.return_value = "0x" + "a" * 128
+mock_w3.eth.account.sign_typed_data.return_value = mock_signature
+mock_w3.eth.account.recover_typed_data.return_value = OTHER_ADDR
+
+MockWeb3 = MagicMock(return_value=mock_w3)
+MockWeb3.HTTPProvider = MagicMock()
+
+_web3_patcher = patch("web3.Web3", MockWeb3)
+_web3_patcher.start()
+
+import x402_payment # noqa: E402
+
+
+class TestCreatePaymentRequest:
+ """Tests for create_payment_request tool"""
+
+ def test_missing_merchant_address(self):
+ """Test when merchant address is not configured"""
+ with patch.object(x402_payment, "merchant_address", None):
+ result = x402_payment.create_payment_request("API Access", 10.0)
+ assert result["success"] is False
+ assert "not configured" in result["error"].lower()
+
+ @patch("x402_payment.w3")
+ def test_successful_payment_request(self, mock_w3):
+ """Test successful payment request creation"""
+ with patch.object(x402_payment, "merchant_address", VALID_ADDR):
+ with patch.object(x402_payment, "merchant_account", mock_account):
+ result = x402_payment.create_payment_request(
+ "API Access",
+ 10.0,
+ "USDC",
+ "Test payment"
+ )
+
+ assert result["success"] is True
+ assert "payment_id" in result
+ assert result["http_status"] == 402
+ assert result["payment_request"]["resource"] == "API Access"
+ assert result["payment_request"]["amount"] == 10.0
+ assert result["payment_request"]["currency"] == "USDC"
+ assert "signature" in result["payment_request"]
+
+
+class TestVerifyPaymentSignature:
+ """Tests for verify_payment_signature tool"""
+
+ def test_payment_request_not_found(self):
+ """Test verification with non-existent payment ID"""
+ x402_payment.payment_requests.clear()
+ result = x402_payment.verify_payment_signature(
+ "invalid_id",
+ VALID_ADDR,
+ "0x" + "a" * 128
+ )
+ assert result["success"] is False
+ assert "not found" in result["error"].lower()
+
+ @patch("x402_payment.w3")
+ def test_invalid_payer_address(self, mock_w3):
+ """Test verification with invalid address"""
+ # Create a payment request first
+ with patch.object(x402_payment, "merchant_address", VALID_ADDR):
+ with patch.object(x402_payment, "merchant_account", mock_account):
+ create_result = x402_payment.create_payment_request("Test", 1.0)
+ payment_id = create_result["payment_id"]
+
+ result = x402_payment.verify_payment_signature(
+ payment_id,
+ "invalid_address",
+ "0x" + "a" * 128
+ )
+ assert result["success"] is False
+
+
+class TestExecutePayment:
+ """Tests for execute_payment tool"""
+
+ def test_missing_private_key(self):
+ """Test execution when private key is not configured"""
+ with patch.object(x402_payment, "PRIVATE_KEY", ""):
+ result = x402_payment.execute_payment("test_id", VALID_ADDR)
+ assert result["success"] is False
+ assert "not configured" in result["error"].lower()
+
+ def test_payment_not_verified(self):
+ """Test execution of unverified payment"""
+ x402_payment.payment_requests.clear()
+ with patch.object(x402_payment, "merchant_address", VALID_ADDR):
+ with patch.object(x402_payment, "merchant_account", mock_account):
+ create_result = x402_payment.create_payment_request("Test", 1.0)
+ payment_id = create_result["payment_id"]
+
+ result = x402_payment.execute_payment(payment_id, VALID_ADDR)
+ assert result["success"] is False
+ assert "not verified" in result["error"].lower()
+
+
+class TestGetPaymentStatus:
+ """Tests for get_payment_status tool"""
+
+ def test_payment_not_found(self):
+ """Test getting status of non-existent payment"""
+ x402_payment.payment_requests.clear()
+ result = x402_payment.get_payment_status("invalid_id")
+ assert result["success"] is False
+ assert "not found" in result["error"].lower()
+
+ def test_successful_status_check(self):
+ """Test successful status retrieval"""
+ x402_payment.payment_requests.clear()
+ with patch.object(x402_payment, "merchant_address", VALID_ADDR):
+ with patch.object(x402_payment, "merchant_account", mock_account):
+ create_result = x402_payment.create_payment_request("Test", 1.0)
+ payment_id = create_result["payment_id"]
+
+ result = x402_payment.get_payment_status(payment_id)
+ assert result["success"] is True
+ assert "payment_request" in result
+
+
+class TestListPaymentRequests:
+ """Tests for list_payment_requests tool"""
+
+ def test_list_all_payments(self):
+ """Test listing all payments"""
+ x402_payment.payment_requests.clear()
+ with patch.object(x402_payment, "merchant_address", VALID_ADDR):
+ with patch.object(x402_payment, "merchant_account", mock_account):
+ x402_payment.create_payment_request("Test1", 1.0)
+ x402_payment.create_payment_request("Test2", 2.0)
+
+ result = x402_payment.list_payment_requests()
+ assert result["success"] is True
+ assert result["count"] == 2
+
+ def test_list_filtered_payments(self):
+ """Test listing payments filtered by status"""
+ x402_payment.payment_requests.clear()
+ with patch.object(x402_payment, "merchant_address", VALID_ADDR):
+ with patch.object(x402_payment, "merchant_account", mock_account):
+ x402_payment.create_payment_request("Test1", 1.0)
+
+ result = x402_payment.list_payment_requests(status="pending")
+ assert result["success"] is True
+ assert result["count"] == 1
+
+
+if __name__ == "__main__":
+ pytest.main([__file__, "-v"])
diff --git a/python/x402-example/x402_payment.py b/python/x402-example/x402_payment.py
new file mode 100644
index 0000000..68a26c5
--- /dev/null
+++ b/python/x402-example/x402_payment.py
@@ -0,0 +1,443 @@
+#!/usr/bin/env python3
+"""
+x402 Payment Protocol Implementation
+HTTP 402 Payment Required - Internet-Native Payments on BNB Chain
+Implements sign-to-pay functionality using EIP-712 and EIP-7702
+"""
+
+import os
+import json
+import time
+from datetime import datetime
+from typing import Any, Dict, List, Optional
+from flask import Flask, render_template, request, jsonify, Response
+from web3 import Web3
+from mcp.server.fastmcp import FastMCP
+
+app = Flask(__name__)
+
+# Initialize MCP server
+mcp = FastMCP("x402 Payment Protocol Server")
+
+# Configuration
+BSC_TESTNET_RPC = os.getenv("BSC_TESTNET_RPC", "https://data-seed-prebsc-1-s1.binance.org:8545/")
+PRIVATE_KEY = os.getenv("PRIVATE_KEY", "")
+PAYMENT_TOKEN = os.getenv("PAYMENT_TOKEN", "USDC") # Default to USDC for x402
+PAYMENT_TOKEN_ADDRESS = os.getenv("PAYMENT_TOKEN_ADDRESS", "") # USDC contract address on BSC testnet
+
+# Initialize Web3
+w3 = Web3(Web3.HTTPProvider(BSC_TESTNET_RPC))
+
+if not w3.is_connected():
+ raise ConnectionError("Failed to connect to BSC Testnet")
+
+# Get merchant account
+merchant_account = None
+merchant_address = None
+if PRIVATE_KEY:
+ merchant_account = w3.eth.account.from_key(PRIVATE_KEY)
+ merchant_address = merchant_account.address
+
+# Payment requests storage
+payment_requests: Dict[str, Dict[str, Any]] = {}
+payment_history: List[Dict[str, Any]] = []
+_payment_counter = 0 # Counter for unique payment IDs
+
+# EIP-712 Domain for x402 payments
+EIP712_DOMAIN = {
+ "name": "x402 Payment Protocol",
+ "version": "1",
+ "chainId": 97, # BSC Testnet
+ "verifyingContract": "0x0000000000000000000000000000000000000000" # Placeholder
+}
+
+# EIP-712 Payment Message Type
+PAYMENT_MESSAGE_TYPE = {
+ "PaymentRequest": [
+ {"name": "resource", "type": "string"},
+ {"name": "amount", "type": "uint256"},
+ {"name": "currency", "type": "string"},
+ {"name": "nonce", "type": "uint256"},
+ {"name": "timestamp", "type": "uint256"},
+ {"name": "merchant", "type": "address"}
+ ]
+}
+
+
+@mcp.tool()
+def create_payment_request(
+ resource: str,
+ amount: float,
+ currency: str = "USDC",
+ description: str = ""
+) -> Dict[str, Any]:
+ """
+ Create a payment request following x402 protocol (HTTP 402 Payment Required).
+
+ Args:
+ resource: The resource or service being paid for
+ amount: Amount to charge
+ currency: Currency type (default: USDC)
+ description: Optional description of the payment
+
+ Returns:
+ Payment request with EIP-712 signature data
+ """
+ if not merchant_address:
+ return {
+ "success": False,
+ "error": "Merchant address not configured"
+ }
+
+ try:
+ # Generate unique payment request ID
+ global _payment_counter
+ _payment_counter += 1
+ timestamp_ms = int(time.time() * 1000)
+ nonce = timestamp_ms + _payment_counter # Ensure unique nonce
+ payment_id = f"x402_{timestamp_ms}_{_payment_counter}"
+
+ # Convert amount to wei (assuming 18 decimals for USDC-like tokens)
+ amount_wei = int(amount * 10**18)
+
+ # Create EIP-712 message
+ message = {
+ "resource": resource,
+ "amount": amount_wei,
+ "currency": currency,
+ "nonce": nonce,
+ "timestamp": int(time.time()),
+ "merchant": merchant_address
+ }
+
+ # Create structured data for EIP-712 signing
+ domain = EIP712_DOMAIN.copy()
+ domain["chainId"] = w3.eth.chain_id
+
+ # Sign the message
+ signed_message = w3.eth.account.sign_typed_data(
+ merchant_account.key,
+ domain,
+ {"PaymentRequest": PAYMENT_MESSAGE_TYPE["PaymentRequest"]},
+ message
+ )
+
+ # Store payment request
+ payment_request = {
+ "payment_id": payment_id,
+ "resource": resource,
+ "amount": amount,
+ "amount_wei": amount_wei,
+ "currency": currency,
+ "description": description,
+ "merchant": merchant_address,
+ "nonce": nonce,
+ "timestamp": message["timestamp"],
+ "signature": signed_message.signature.hex(),
+ "status": "pending",
+ "created_at": datetime.now().isoformat()
+ }
+
+ payment_requests[payment_id] = payment_request
+
+ return {
+ "success": True,
+ "payment_id": payment_id,
+ "payment_request": payment_request,
+ "http_status": 402,
+ "message": "Payment Required"
+ }
+
+ except Exception as e:
+ return {
+ "success": False,
+ "error": str(e)
+ }
+
+
+@mcp.tool()
+def verify_payment_signature(
+ payment_id: str,
+ payer_address: str,
+ payer_signature: str
+) -> Dict[str, Any]:
+ """
+ Verify a payment signature from the payer.
+
+ Args:
+ payment_id: The payment request ID
+ payer_address: Address of the payer
+ payer_signature: EIP-712 signature from payer
+
+ Returns:
+ Verification result
+ """
+ try:
+ if payment_id not in payment_requests:
+ return {
+ "success": False,
+ "error": "Payment request not found"
+ }
+
+ payment_request = payment_requests[payment_id]
+
+ # Verify payer address
+ if not w3.is_address(payer_address):
+ return {
+ "success": False,
+ "error": "Invalid payer address"
+ }
+
+ payer_address = w3.to_checksum_address(payer_address)
+
+ # Recover signer from signature
+ message = {
+ "resource": payment_request["resource"],
+ "amount": payment_request["amount_wei"],
+ "currency": payment_request["currency"],
+ "nonce": payment_request["nonce"],
+ "timestamp": payment_request["timestamp"],
+ "merchant": payment_request["merchant"]
+ }
+
+ domain = EIP712_DOMAIN.copy()
+ domain["chainId"] = w3.eth.chain_id
+
+ try:
+ recovered_address = w3.eth.account.recover_typed_data(
+ domain,
+ {"PaymentRequest": PAYMENT_MESSAGE_TYPE["PaymentRequest"]},
+ message,
+ payer_signature
+ )
+ except Exception as e:
+ return {
+ "success": False,
+ "error": f"Signature verification failed: {str(e)}"
+ }
+
+ if recovered_address.lower() != payer_address.lower():
+ return {
+ "success": False,
+ "error": "Signature does not match payer address"
+ }
+
+ # Update payment request status
+ payment_request["payer"] = payer_address
+ payment_request["payer_signature"] = payer_signature
+ payment_request["status"] = "verified"
+ payment_request["verified_at"] = datetime.now().isoformat()
+
+ return {
+ "success": True,
+ "payment_id": payment_id,
+ "verified": True,
+ "payer": payer_address,
+ "message": "Payment signature verified. Ready for execution."
+ }
+
+ except Exception as e:
+ return {
+ "success": False,
+ "error": str(e)
+ }
+
+
+@mcp.tool()
+def execute_payment(
+ payment_id: str,
+ payer_address: str
+) -> Dict[str, Any]:
+ """
+ Execute the payment transaction on-chain.
+
+ Args:
+ payment_id: The payment request ID
+ payer_address: Address of the payer
+
+ Returns:
+ Transaction details
+ """
+ if not PRIVATE_KEY:
+ return {
+ "success": False,
+ "error": "Private key not configured"
+ }
+
+ try:
+ if payment_id not in payment_requests:
+ return {
+ "success": False,
+ "error": "Payment request not found"
+ }
+
+ payment_request = payment_requests[payment_id]
+
+ if payment_request["status"] != "verified":
+ return {
+ "success": False,
+ "error": "Payment request not verified"
+ }
+
+ # For demo purposes, we'll simulate a token transfer
+ # In production, this would interact with actual token contracts
+
+ # Get current nonce
+ nonce = w3.eth.get_transaction_count(merchant_address)
+
+ # Build transaction (simplified - in production would be token transfer)
+ # For demo, we'll do a simple ETH transfer
+ amount_wei = payment_request["amount_wei"]
+
+ transaction = {
+ 'to': merchant_address, # In production, this would be the merchant
+ 'value': amount_wei,
+ 'gas': 21000,
+ 'gasPrice': w3.eth.gas_price,
+ 'nonce': nonce,
+ 'chainId': 97
+ }
+
+ # Sign and send transaction
+ signed_txn = w3.eth.account.sign_transaction(transaction, PRIVATE_KEY)
+ tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
+
+ # Wait for confirmation
+ receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=10)
+
+ # Update payment request
+ payment_request["status"] = "completed"
+ payment_request["tx_hash"] = tx_hash.hex()
+ payment_request["block_number"] = receipt.blockNumber
+ payment_request["completed_at"] = datetime.now().isoformat()
+
+ # Add to history
+ payment_history.append(payment_request.copy())
+
+ return {
+ "success": True,
+ "payment_id": payment_id,
+ "transaction_hash": tx_hash.hex(),
+ "block_number": receipt.blockNumber,
+ "status": "completed",
+ "amount": payment_request["amount"],
+ "currency": payment_request["currency"]
+ }
+
+ except Exception as e:
+ return {
+ "success": False,
+ "error": str(e)
+ }
+
+
+@mcp.tool()
+def get_payment_status(payment_id: str) -> Dict[str, Any]:
+ """
+ Get the status of a payment request.
+
+ Args:
+ payment_id: The payment request ID
+
+ Returns:
+ Payment request status
+ """
+ if payment_id not in payment_requests:
+ return {
+ "success": False,
+ "error": "Payment request not found"
+ }
+
+ return {
+ "success": True,
+ "payment_request": payment_requests[payment_id]
+ }
+
+
+@mcp.tool()
+def list_payment_requests(status: Optional[str] = None) -> Dict[str, Any]:
+ """
+ List all payment requests, optionally filtered by status.
+
+ Args:
+ status: Filter by status (pending, verified, completed)
+
+ Returns:
+ List of payment requests
+ """
+ requests = list(payment_requests.values())
+
+ if status:
+ requests = [r for r in requests if r["status"] == status]
+
+ return {
+ "success": True,
+ "count": len(requests),
+ "payment_requests": requests
+ }
+
+
+# Flask routes for frontend
+@app.route('/')
+def index():
+ """Render the x402 payment frontend"""
+ return render_template('index.html')
+
+
+@app.route('/api/payment_request', methods=['POST'])
+def api_create_payment_request():
+ """API endpoint for creating payment requests"""
+ data = request.json
+ result = create_payment_request(
+ data.get('resource', ''),
+ data.get('amount', 0),
+ data.get('currency', 'USDC'),
+ data.get('description', '')
+ )
+ return jsonify(result), 402 if result.get('http_status') == 402 else 200
+
+
+@app.route('/api/payment/', methods=['GET'])
+def api_get_payment(payment_id):
+ """API endpoint for getting payment status"""
+ result = get_payment_status(payment_id)
+ return jsonify(result)
+
+
+@app.route('/api/payment//verify', methods=['POST'])
+def api_verify_payment(payment_id):
+ """API endpoint for verifying payment signature"""
+ data = request.json
+ result = verify_payment_signature(
+ payment_id,
+ data.get('payer_address', ''),
+ data.get('payer_signature', '')
+ )
+ return jsonify(result)
+
+
+@app.route('/api/payment//execute', methods=['POST'])
+def api_execute_payment(payment_id):
+ """API endpoint for executing payment"""
+ data = request.json
+ result = execute_payment(
+ payment_id,
+ data.get('payer_address', '')
+ )
+ return jsonify(result)
+
+
+@app.route('/api/payments', methods=['GET'])
+def api_list_payments():
+ """API endpoint for listing payments"""
+ status = request.args.get('status', None)
+ result = list_payment_requests(status)
+ return jsonify(result)
+
+
+if __name__ == "__main__":
+ # Create templates directory if it doesn't exist
+ os.makedirs('templates', exist_ok=True)
+
+ # Run Flask app
+ app.run(host='0.0.0.0', port=5001, debug=True)
diff --git a/typescript/faucet-mcp/README.md b/typescript/faucet-mcp/README.md
new file mode 100644
index 0000000..d3b4287
--- /dev/null
+++ b/typescript/faucet-mcp/README.md
@@ -0,0 +1,211 @@
+# TBNB Faucet MCP Server (Node.js)
+
+A Node.js implementation of a Model Context Protocol (MCP) server that provides a faucet service for distributing testnet BNB tokens on the Binance Smart Chain testnet.
+
+## About
+
+This MCP server enables AI agents and other MCP clients to request and receive testnet BNB tokens by interacting with the BSC testnet blockchain. It follows the Anthropic MCP specification and can be integrated with any MCP-compatible client.
+
+## Prerequisites
+
+- Node.js 18.0 or higher
+- npm or yarn package manager
+- A BSC testnet wallet with TBNB for the faucet
+- Access to BSC testnet RPC endpoint
+
+## Installation
+
+1. Navigate to the project directory:
+```bash
+cd faucet-mcp
+```
+
+2. Install dependencies:
+```bash
+npm install
+```
+
+## Configuration
+
+Set the following environment variables before running:
+
+- `FAUCET_PRIVATE_KEY`: The private key of your faucet wallet (required)
+- `BSC_TESTNET_RPC`: BSC testnet RPC endpoint URL (optional, has default)
+
+Example:
+
+```bash
+export FAUCET_PRIVATE_KEY="0x..."
+export BSC_TESTNET_RPC="https://data-seed-prebsc-1-s1.binance.org:8545/"
+```
+
+Or create a `.env` file (not included in repo for security):
+
+```
+FAUCET_PRIVATE_KEY=0x...
+BSC_TESTNET_RPC=https://data-seed-prebsc-1-s1.binance.org:8545/
+```
+
+## Usage
+
+Run the server:
+
+```bash
+node index.js
+```
+
+Or using npm:
+
+```bash
+npm start
+```
+
+The server communicates via stdio, which is the standard transport for MCP servers.
+
+## Available Tools
+
+### send_tbnb
+
+Sends testnet BNB tokens to a recipient address.
+
+**Parameters:**
+- `recipient` (string, required): BSC testnet address to receive tokens
+- `amount` (number, optional): Amount of TBNB to send (default: 0.1, max: 1.0)
+
+**Returns:**
+- Transaction hash
+- Recipient address
+- Amount sent
+- Block number
+- Transaction status
+
+### get_faucet_info
+
+Retrieves information about the faucet wallet.
+
+**Parameters:** None
+
+**Returns:**
+- Faucet wallet address
+- Current balance (Wei and TBNB)
+- Network information
+- RPC endpoint
+
+### get_balance
+
+Queries the TBNB balance of any BSC testnet address.
+
+**Parameters:**
+- `address` (string, required): BSC testnet address to check
+
+**Returns:**
+- Address (checksummed)
+- Balance in Wei and TBNB
+- Network name
+
+## MCP Client Integration
+
+To integrate this server with an MCP client, add it to your client configuration:
+
+```json
+{
+ "mcpServers": {
+ "tbnb-faucet": {
+ "command": "node",
+ "args": ["/absolute/path/to/index.js"],
+ "env": {
+ "FAUCET_PRIVATE_KEY": "0x...",
+ "BSC_TESTNET_RPC": "https://data-seed-prebsc-1-s1.binance.org:8545/"
+ }
+ }
+ }
+}
+```
+
+## Network Configuration
+
+- **Network**: Binance Smart Chain Testnet
+- **Chain ID**: 97
+- **Default RPC**: https://data-seed-prebsc-1-s1.binance.org:8545/
+- **Block Explorer**: https://testnet.bscscan.com
+
+## Error Handling
+
+The server includes comprehensive error handling:
+
+- Address validation (checksum and format)
+- Amount validation (0 to 1.0 TBNB)
+- Self-transfer prevention
+- Network connectivity checks
+- Transaction error reporting
+
+All errors are returned as structured JSON responses.
+
+## Security
+
+๐ **Security Best Practices:**
+
+- Never commit your private key to version control
+- Use environment variables or secure secret management systems
+- This server is intended for testnet use only
+- Consider implementing rate limiting for production deployments
+- Monitor faucet balance and set up alerts
+
+## Troubleshooting
+
+### Common Issues
+
+**"Faucet wallet not configured"**
+- Ensure `FAUCET_PRIVATE_KEY` environment variable is set
+- Verify the private key is valid and starts with `0x`
+
+**Connection errors**
+- Check your internet connection
+- Verify the RPC endpoint is accessible
+- Try an alternative BSC testnet RPC endpoint
+
+**Transaction failures**
+- Ensure the faucet wallet has sufficient TBNB balance
+- Verify the recipient address is valid
+- Check network conditions (gas prices, congestion)
+
+**Module not found errors**
+- Run `npm install` to install dependencies
+- Ensure you're using Node.js 18.0 or higher
+
+## Development
+
+The server uses:
+- `@modelcontextprotocol/sdk` for MCP protocol implementation
+- `ethers.js` v6 for blockchain interactions
+
+## License
+
+MIT License
+
+## Testing
+
+Run unit tests:
+
+```bash
+npm install
+npm test
+```
+
+Run tests with coverage:
+
+```bash
+npm run test:coverage
+```
+
+Run tests in watch mode:
+
+```bash
+npm run test:watch
+```
+
+## Resources
+
+- [Model Context Protocol Documentation](https://modelcontextprotocol.io)
+- [BNB Chain Documentation](https://docs.bnbchain.org)
+- [Ethers.js Documentation](https://docs.ethers.org)
diff --git a/typescript/faucet-mcp/index.js b/typescript/faucet-mcp/index.js
new file mode 100644
index 0000000..6414234
--- /dev/null
+++ b/typescript/faucet-mcp/index.js
@@ -0,0 +1,299 @@
+#!/usr/bin/env node
+
+/**
+ * TBNB Faucet MCP Server
+ * Model Context Protocol server for distributing testnet BNB tokens
+ */
+
+import { Server } from "@modelcontextprotocol/sdk/server/index.js";
+import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
+import {
+ CallToolRequestSchema,
+ ListToolsRequestSchema,
+} from "@modelcontextprotocol/sdk/types.js";
+import { ethers } from "ethers";
+
+// Configuration
+const BSC_TESTNET_RPC = process.env.BSC_TESTNET_RPC || "https://data-seed-prebsc-1-s1.binance.org:8545/";
+const FAUCET_PRIVATE_KEY = process.env.FAUCET_PRIVATE_KEY || "";
+
+if (!FAUCET_PRIVATE_KEY) {
+ console.error("WARNING: FAUCET_PRIVATE_KEY environment variable is not set");
+}
+
+// Initialize provider and wallet
+const provider = new ethers.JsonRpcProvider(BSC_TESTNET_RPC);
+let wallet = null;
+let walletAddress = null;
+
+if (FAUCET_PRIVATE_KEY) {
+ wallet = new ethers.Wallet(FAUCET_PRIVATE_KEY, provider);
+ walletAddress = wallet.address;
+}
+
+// MCP Server
+const server = new Server(
+ {
+ name: "tbnb-faucet",
+ version: "1.0.0",
+ },
+ {
+ capabilities: {
+ tools: {},
+ },
+ }
+);
+
+// List available tools
+server.setRequestHandler(ListToolsRequestSchema, async () => {
+ return {
+ tools: [
+ {
+ name: "send_tbnb",
+ description: "Send testnet BNB tokens to a specified address on BSC testnet",
+ inputSchema: {
+ type: "object",
+ properties: {
+ recipient: {
+ type: "string",
+ description: "The BSC testnet address that will receive the TBNB tokens",
+ },
+ amount: {
+ type: "number",
+ description: "Amount of TBNB to send (default: 0.1, maximum: 1.0)",
+ default: 0.1,
+ },
+ },
+ required: ["recipient"],
+ },
+ },
+ {
+ name: "get_faucet_info",
+ description: "Get information about the faucet including current balance",
+ inputSchema: {
+ type: "object",
+ properties: {},
+ },
+ },
+ {
+ name: "get_balance",
+ description: "Get the TBNB balance of a BSC testnet address",
+ inputSchema: {
+ type: "object",
+ properties: {
+ address: {
+ type: "string",
+ description: "The BSC testnet address to check",
+ },
+ },
+ required: ["address"],
+ },
+ },
+ ],
+ };
+});
+
+// Handle tool calls
+server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ const { name, arguments: args } = request.params;
+
+ try {
+ switch (name) {
+ case "send_tbnb":
+ return await handleSendTbnb(args);
+ case "get_faucet_info":
+ return await handleGetFaucetInfo();
+ case "get_balance":
+ return await handleGetBalance(args);
+ default:
+ throw new Error(`Unknown tool: ${name}`);
+ }
+ } catch (error) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify(
+ {
+ error: error.message,
+ },
+ null,
+ 2
+ ),
+ },
+ ],
+ isError: true,
+ };
+ }
+});
+
+/**
+ * Send TBNB to a recipient address
+ */
+async function handleSendTbnb(args) {
+ if (!wallet) {
+ throw new Error("Faucet wallet not configured. Please set FAUCET_PRIVATE_KEY.");
+ }
+
+ const recipient = args?.recipient;
+ const amount = args?.amount || 0.1;
+
+ if (!recipient) {
+ throw new Error("Recipient address is required");
+ }
+
+ // Validate address
+ if (!ethers.isAddress(recipient)) {
+ throw new Error(`Invalid address: ${recipient}`);
+ }
+
+ const recipientAddress = ethers.getAddress(recipient);
+
+ // Prevent self-transfer
+ if (recipientAddress.toLowerCase() === walletAddress.toLowerCase()) {
+ throw new Error("Cannot send tokens to the faucet address itself");
+ }
+
+ // Validate amount
+ if (amount <= 0 || amount > 1.0) {
+ throw new Error("Amount must be between 0 and 1.0 TBNB");
+ }
+
+ try {
+ // Convert amount to Wei
+ const amountWei = ethers.parseEther(amount.toString());
+
+ // Get current gas price
+ const feeData = await provider.getFeeData();
+
+ // Send transaction
+ const tx = await wallet.sendTransaction({
+ to: recipientAddress,
+ value: amountWei,
+ gasLimit: 21000,
+ gasPrice: feeData.gasPrice,
+ });
+
+ // Wait for transaction to be mined
+ const receipt = await tx.wait();
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify(
+ {
+ success: true,
+ transactionHash: tx.hash,
+ recipient: recipientAddress,
+ amount: amount,
+ amountWei: amountWei.toString(),
+ blockNumber: receipt.blockNumber,
+ status: receipt.status === 1 ? "confirmed" : "failed",
+ },
+ null,
+ 2
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ throw new Error(`Transaction failed: ${error.message}`);
+ }
+}
+
+/**
+ * Get faucet information and balance
+ */
+async function handleGetFaucetInfo() {
+ if (!walletAddress) {
+ throw new Error("Faucet wallet not configured");
+ }
+
+ try {
+ const balance = await provider.getBalance(walletAddress);
+ const balanceTbnb = ethers.formatEther(balance);
+
+ // Get network info
+ const network = await provider.getNetwork();
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify(
+ {
+ faucetAddress: walletAddress,
+ balanceWei: balance.toString(),
+ balanceTbnb: parseFloat(balanceTbnb),
+ network: {
+ name: "BSC Testnet",
+ chainId: network.chainId.toString(),
+ },
+ rpcEndpoint: BSC_TESTNET_RPC,
+ },
+ null,
+ 2
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ throw new Error(`Failed to get faucet info: ${error.message}`);
+ }
+}
+
+/**
+ * Get balance of an address
+ */
+async function handleGetBalance(args) {
+ const address = args?.address;
+
+ if (!address) {
+ throw new Error("Address is required");
+ }
+
+ // Validate address
+ if (!ethers.isAddress(address)) {
+ throw new Error(`Invalid address: ${address}`);
+ }
+
+ try {
+ const addressChecksum = ethers.getAddress(address);
+ const balance = await provider.getBalance(addressChecksum);
+ const balanceTbnb = ethers.formatEther(balance);
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify(
+ {
+ address: addressChecksum,
+ balanceWei: balance.toString(),
+ balanceTbnb: parseFloat(balanceTbnb),
+ network: "BSC Testnet",
+ },
+ null,
+ 2
+ ),
+ },
+ ],
+ };
+ } catch (error) {
+ throw new Error(`Failed to get balance: ${error.message}`);
+ }
+}
+
+// Start server
+async function main() {
+ const transport = new StdioServerTransport();
+ await server.connect(transport);
+
+ console.error("TBNB Faucet MCP Server running on stdio");
+}
+
+main().catch((error) => {
+ console.error("Fatal error:", error);
+ process.exit(1);
+});
diff --git a/typescript/faucet-mcp/index.test.js b/typescript/faucet-mcp/index.test.js
new file mode 100644
index 0000000..9f47c94
--- /dev/null
+++ b/typescript/faucet-mcp/index.test.js
@@ -0,0 +1,277 @@
+/**
+ * Unit tests for TBNB Faucet MCP Server (Example 3 - Node.js)
+ *
+ * Note: These tests focus on the business logic and validation
+ * rather than full integration with the MCP server, since the server
+ * initializes on import and uses stdio transport.
+ */
+
+import { describe, test, expect, beforeEach, jest } from '@jest/globals';
+
+describe('Address Validation Logic', () => {
+ test('should validate correct Ethereum address format', () => {
+ const validAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0';
+ // Ethereum addresses are 42 characters (0x + 40 hex chars)
+ expect(validAddress.length).toBe(42);
+ expect(validAddress.startsWith('0x')).toBe(true);
+ expect(/^0x[a-fA-F0-9]{40}$/.test(validAddress)).toBe(true);
+ });
+
+ test('should reject invalid address formats', () => {
+ const invalidAddresses = [
+ 'invalid',
+ '0x123',
+ '742d35Cc6634C0532925a3b844Bc9e7595f0bEb', // Missing 0x
+ '0xGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', // Invalid hex
+ '',
+ ];
+
+ invalidAddresses.forEach((addr) => {
+ const isValid = addr.length === 42 && addr.startsWith('0x') && /^0x[a-fA-F0-9]{40}$/.test(addr);
+ expect(isValid).toBe(false);
+ });
+ });
+});
+
+describe('Amount Validation Logic', () => {
+ test('should accept valid amounts between 0 and 1.0', () => {
+ const validAmounts = [0.01, 0.1, 0.5, 1.0];
+
+ validAmounts.forEach((amount) => {
+ const isValid = amount > 0 && amount <= 1.0;
+ expect(isValid).toBe(true);
+ });
+ });
+
+ test('should reject zero or negative amounts', () => {
+ const invalidAmounts = [0, -0.1, -1.0];
+
+ invalidAmounts.forEach((amount) => {
+ const isValid = amount > 0 && amount <= 1.0;
+ expect(isValid).toBe(false);
+ });
+ });
+
+ test('should reject amounts greater than 1.0', () => {
+ const invalidAmounts = [1.1, 2.0, 10.0];
+
+ invalidAmounts.forEach((amount) => {
+ const isValid = amount > 0 && amount <= 1.0;
+ expect(isValid).toBe(false);
+ });
+ });
+});
+
+describe('Self-Transfer Prevention Logic', () => {
+ test('should detect self-transfer attempts', () => {
+ const faucetAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
+ const recipient = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
+
+ const isSelfTransfer = recipient.toLowerCase() === faucetAddress.toLowerCase();
+ expect(isSelfTransfer).toBe(true);
+ });
+
+ test('should allow transfers to different addresses', () => {
+ const faucetAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
+ const recipient = '0x1234567890123456789012345678901234567890';
+
+ const isSelfTransfer = recipient.toLowerCase() === faucetAddress.toLowerCase();
+ expect(isSelfTransfer).toBe(false);
+ });
+});
+
+describe('Wei Conversion Logic', () => {
+ test('should convert TBNB to Wei correctly', () => {
+ // 1 TBNB = 10^18 Wei
+ const tbnbAmounts = [
+ { tbnb: 0.1, wei: '100000000000000000' },
+ { tbnb: 1.0, wei: '1000000000000000000' },
+ { tbnb: 0.01, wei: '10000000000000000' },
+ ];
+
+ tbnbAmounts.forEach(({ tbnb, wei }) => {
+ // Manual conversion for testing
+ const calculatedWei = (tbnb * Math.pow(10, 18)).toString();
+ expect(calculatedWei).toBe(wei);
+ });
+ });
+
+ test('should handle Wei to TBNB conversion', () => {
+ const weiAmounts = [
+ { wei: '100000000000000000', tbnb: 0.1 },
+ { wei: '1000000000000000000', tbnb: 1.0 },
+ { wei: '500000000000000000', tbnb: 0.5 },
+ ];
+
+ weiAmounts.forEach(({ wei, tbnb }) => {
+ // Manual conversion for testing
+ const calculatedTbnb = parseFloat(wei) / Math.pow(10, 18);
+ expect(calculatedTbnb).toBeCloseTo(tbnb, 10);
+ });
+ });
+});
+
+describe('Transaction Response Format', () => {
+ test('should format successful transaction response correctly', () => {
+ const mockResponse = {
+ success: true,
+ transactionHash: '0xabcdef1234567890',
+ recipient: '0x1234567890123456789012345678901234567890',
+ amount: 0.1,
+ amountWei: '100000000000000000',
+ blockNumber: 12345,
+ status: 'confirmed',
+ };
+
+ expect(mockResponse).toHaveProperty('success');
+ expect(mockResponse).toHaveProperty('transactionHash');
+ expect(mockResponse).toHaveProperty('recipient');
+ expect(mockResponse).toHaveProperty('amount');
+ expect(mockResponse).toHaveProperty('blockNumber');
+ expect(mockResponse).toHaveProperty('status');
+ expect(mockResponse.success).toBe(true);
+ expect(mockResponse.status).toBe('confirmed');
+ });
+
+ test('should format error response correctly', () => {
+ const mockErrorResponse = {
+ error: 'Invalid address: invalid_address',
+ };
+
+ expect(mockErrorResponse).toHaveProperty('error');
+ expect(typeof mockErrorResponse.error).toBe('string');
+ });
+});
+
+describe('Faucet Info Response Format', () => {
+ test('should format faucet info response correctly', () => {
+ const mockFaucetInfo = {
+ faucetAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
+ balanceWei: '1000000000000000000',
+ balanceTbnb: 1.0,
+ network: {
+ name: 'BSC Testnet',
+ chainId: '97',
+ },
+ rpcEndpoint: 'https://data-seed-prebsc-1-s1.binance.org:8545/',
+ };
+
+ expect(mockFaucetInfo).toHaveProperty('faucetAddress');
+ expect(mockFaucetInfo).toHaveProperty('balanceWei');
+ expect(mockFaucetInfo).toHaveProperty('balanceTbnb');
+ expect(mockFaucetInfo).toHaveProperty('network');
+ expect(mockFaucetInfo).toHaveProperty('rpcEndpoint');
+ expect(mockFaucetInfo.network.chainId).toBe('97');
+ });
+});
+
+describe('Balance Query Response Format', () => {
+ test('should format balance query response correctly', () => {
+ const mockBalanceResponse = {
+ address: '0x1234567890123456789012345678901234567890',
+ balanceWei: '500000000000000000',
+ balanceTbnb: 0.5,
+ network: 'BSC Testnet',
+ };
+
+ expect(mockBalanceResponse).toHaveProperty('address');
+ expect(mockBalanceResponse).toHaveProperty('balanceWei');
+ expect(mockBalanceResponse).toHaveProperty('balanceTbnb');
+ expect(mockBalanceResponse).toHaveProperty('network');
+ expect(mockBalanceResponse.balanceTbnb).toBe(0.5);
+ });
+});
+
+describe('Error Handling', () => {
+ test('should handle missing recipient address', () => {
+ const recipient = '';
+ if (!recipient) {
+ expect(() => {
+ throw new Error('Recipient address is required');
+ }).toThrow('Recipient address is required');
+ }
+ });
+
+ test('should handle missing wallet configuration', () => {
+ const wallet = null;
+ if (!wallet) {
+ expect(() => {
+ throw new Error('Faucet wallet not configured. Please set FAUCET_PRIVATE_KEY.');
+ }).toThrow('Faucet wallet not configured');
+ }
+ });
+
+ test('should handle network errors gracefully', () => {
+ const networkError = new Error('Network error');
+ expect(networkError.message).toBe('Network error');
+ expect(networkError).toBeInstanceOf(Error);
+ });
+});
+
+describe('MCP Tool Schema Validation', () => {
+ test('send_tbnb tool should have correct schema structure', () => {
+ const toolSchema = {
+ name: 'send_tbnb',
+ description: 'Send testnet BNB tokens to a specified address on BSC testnet',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ recipient: {
+ type: 'string',
+ description: 'The BSC testnet address that will receive the TBNB tokens',
+ },
+ amount: {
+ type: 'number',
+ description: 'Amount of TBNB to send (default: 0.1, maximum: 1.0)',
+ default: 0.1,
+ },
+ },
+ required: ['recipient'],
+ },
+ };
+
+ expect(toolSchema).toHaveProperty('name');
+ expect(toolSchema).toHaveProperty('description');
+ expect(toolSchema).toHaveProperty('inputSchema');
+ expect(toolSchema.inputSchema.type).toBe('object');
+ expect(toolSchema.inputSchema.required).toContain('recipient');
+ });
+
+ test('get_faucet_info tool should have correct schema structure', () => {
+ const toolSchema = {
+ name: 'get_faucet_info',
+ description: 'Get information about the faucet including current balance',
+ inputSchema: {
+ type: 'object',
+ properties: {},
+ },
+ };
+
+ expect(toolSchema).toHaveProperty('name');
+ expect(toolSchema).toHaveProperty('description');
+ expect(toolSchema).toHaveProperty('inputSchema');
+ expect(toolSchema.inputSchema.type).toBe('object');
+ });
+
+ test('get_balance tool should have correct schema structure', () => {
+ const toolSchema = {
+ name: 'get_balance',
+ description: 'Get the TBNB balance of a BSC testnet address',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ address: {
+ type: 'string',
+ description: 'The BSC testnet address to check',
+ },
+ },
+ required: ['address'],
+ },
+ };
+
+ expect(toolSchema).toHaveProperty('name');
+ expect(toolSchema).toHaveProperty('description');
+ expect(toolSchema).toHaveProperty('inputSchema');
+ expect(toolSchema.inputSchema.required).toContain('address');
+ });
+});
diff --git a/typescript/faucet-mcp/package.json b/typescript/faucet-mcp/package.json
new file mode 100644
index 0000000..8e7ca75
--- /dev/null
+++ b/typescript/faucet-mcp/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "tbnb-faucet-mcp",
+ "version": "1.0.0",
+ "description": "MCP server for distributing testnet BNB tokens on BSC testnet",
+ "type": "module",
+ "main": "index.js",
+ "scripts": {
+ "start": "node index.js",
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
+ "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
+ "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage"
+ },
+ "keywords": [
+ "mcp",
+ "model-context-protocol",
+ "bnb",
+ "testnet",
+ "faucet",
+ "blockchain"
+ ],
+ "author": "",
+ "license": "MIT",
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^0.5.0",
+ "ethers": "^6.9.0"
+ },
+ "devDependencies": {
+ "@jest/globals": "^29.7.0",
+ "jest": "^29.7.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "jest": {
+ "testEnvironment": "node",
+ "transform": {},
+ "moduleNameMapper": {
+ "^(\\.{1,2}/.*)\\.js$": "$1"
+ }
+ }
+}