Join our community of builders on

Telegram!Telegram

x402 Facilitator

Overview

x402 Facilitator is a plugin for OpenZeppelin Relayer that enables payment verification and settlement for x402 payments on Stellar. The plugin implements the X402 payment facilitation protocol, allowing applications to accept payments in Stellar assets (like USDC) for API access or content.

The plugin features:

  • Payment verification: Validates payment payloads against payment requirements
  • Transaction settlement: Submits verified payments on-chain via the relayer or an optional channel service
  • Auth entry validation: Verifies authorization entries are properly signed
  • Multi-network support: Configure multiple Stellar networks and assets
  • Channel service integration: Optional integration with the Channels plugin for high-throughput settlement

Prerequisites

  • Node.js >= 18
  • pnpm >= 10
  • OpenZeppelin Relayer (installed and configured)
  • Stellar relayer account (for transaction submission)

Example Setup

For a complete working example with Docker Compose, refer to the x402 Facilitator plugin example in the OpenZeppelin Relayer repository:

Installation

x402 Facilitator can be added to any OpenZeppelin Relayer installation.

Resources:

Install from npm

# From the root of your Relayer repository
cd plugins
pnpm add @openzeppelin/relayer-plugin-x402-facilitator

Create the plugin wrapper

Inside your Relayer, create a directory for the plugin and expose its handler:

mkdir -p plugins/x402-facilitator

Create plugins/x402-facilitator/index.ts:

export { handler } from "@openzeppelin/relayer-plugin-x402-facilitator";

Configuration

Plugin Registration

Register the plugin in your config/config.json file:

{
  "plugins": [
    {
      "id": "x402-facilitator",
      "path": "x402-facilitator/index.ts",
      "timeout": 30,
      "emit_logs": false,
      "emit_traces": false,
      "forward_logs": true,
      "raw_response": true,
      "allow_get_invocation": true,
      "config": {
        "networks": [
          {
            "network": "stellar:testnet",
            "type": "stellar",
            "relayer_id": "stellar-example",
            "assets": [
              "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA"
            ]
          }
        ]
      }
    }
  ]
}

Note on path: this is the plugin entrypoint path as seen by the running Relayer process.

  • If you run Relayer locally, ensure the file exists at that path under your plugins/ directory.
  • If you run Relayer in Docker, ensure the file exists at that path inside the container (for example by mounting your plugin folder into /app/plugins/x402-facilitator in the container).

Configuration Options:

  • id: Unique identifier for the plugin
  • path: Path to the plugin file (relative to plugins/ directory)
  • timeout: Maximum execution time in seconds (default: 30)
  • emit_logs: Whether to include plugin logs in API responses (default: false)
  • emit_traces: Whether to include plugin traces in API responses (default: false)
  • raw_response: Whether to return raw plugin response without ApiResponse wrapper (default: false, recommended for x402 compatibility)
  • config.networks: Array of network configurations
    • network: Network identifier (e.g., "stellar:testnet", "stellar:pubnet")
    • type: Network type (must be "stellar")
    • relayer_id: ID of the relayer to use for this network
    • assets: Array of supported asset contract addresses
    • channel_service_api_url (optional): Channel service API URL for settlement
    • channel_service_api_key (optional): Channel service API key

Relayer Configuration

The plugin requires at least one Stellar relayer configured for transaction submission:

{
  "relayers": [
    {
      "id": "stellar-example",
      "name": "Stellar Example",
      "network": "testnet",
      "paused": false,
      "network_type": "stellar",
      "signer_id": "local-signer",
      "policies": {
        "fee_payment_strategy": "relayer",
        "min_balance": 0
      }
    }
  ],
  "signers": [
    {
      "id": "local-signer",
      "type": "local",
      "config": {
        "path": "config/keys/local-signer.json",
        "passphrase": {
          "type": "env",
          "value": "KEYSTORE_PASSPHRASE"
        }
      }
    }
  ]
}

Important configuration notes:

  • Relayer account: Must be configured with network_type: "stellar" and a valid signer
  • Network: Use testnet for testing or mainnet for production
  • Signers: Each relayer references a signer by signer_id; signers are defined separately with keystore paths
  • Keystore files: Create keystore files for each account; see the OpenZeppelin Relayer documentation for key management
  • Assets: Configure the contract addresses of assets you want to accept payments in (for example, a USDC contract address)

Channel Service Integration (Optional)

For high-throughput settlement, you can configure the plugin to use the Channels plugin:

{
  "plugins": [
    {
      "id": "x402-facilitator",
      "path": "x402-facilitator/index.ts",
      "timeout": 30,
      "raw_response": true,
      "config": {
        "networks": [
          {
            "network": "stellar:testnet",
            "type": "stellar",
            "relayer_id": "stellar-example",
            "assets": [
              "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA"
            ],
            "channel_service_api_url": "http://localhost:8080/api/v1/plugins/channels-plugin/call",
            "channel_service_api_key": "YOUR_CHANNELS_API_KEY"
          }
        ]
      }
    }
  ]
}

When channel_service_api_url and channel_service_api_key are configured, the plugin will use the Channels service for settlement instead of the relayer API, enabling parallel transaction processing.

API Usage

The x402 Facilitator plugin implements the X402 v2 specification API, providing three main endpoints:

  • /verify: Verifies a payment payload against payment requirements
  • /settle: Settles a verified payment by submitting the transaction on-chain
  • /supported: Discovery of supported payment kinds

Invocation

The plugin is invoked via the standard Relayer plugin endpoint with route-based routing:

POST /api/v1/plugins/{plugin-id}/call/{route}

Where {route} is one of:

  • verify - Payment verification
  • settle - Payment settlement
  • supported - Supported payment kinds

Integration with x402 Ecosystem

The plugin implements the API defined by the x402 v2 spec, making it compatible with any packages in the x402 ecosystem. For more information on available packages, see https://github.com/coinbase/x402.

x402-express Example

To use OpenZeppelin Relayer and its x402-facilitator plugin with x402-express (and similar packages), point the facilitator to your Relayer plugin URL and pass the Relayer API key via createAuthHeaders:

.env

STELLAR_ADDRESS=
FACILITATOR_URL=http://localhost:8080/api/v1/plugins/x402-facilitator/call
import { config } from "dotenv";
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactStellarScheme } from "@x402/stellar/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
config();

const stellarAddress = process.env.STELLAR_ADDRESS as string | undefined;

// Validate stellar address is provided
if (!stellarAddress) {
  console.error("❌ STELLAR_ADDRESS is required");
  process.exit(1);
}

const facilitatorUrl = process.env.FACILITATOR_URL;
if (!facilitatorUrl) {
  console.error("❌ FACILITATOR_URL environment variable is required");
  process.exit(1);
}
const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl, createAuthHeaders: async () => ({
  // Use your Relayer API key for the plugin
  verify: { Authorization: "Bearer RELAYER_API_KEY" },
  settle: { Authorization: "Bearer RELAYER_API_KEY" },
  supported: { Authorization: "Bearer RELAYER_API_KEY" },
})});

const app = express();

app.use(
  paymentMiddleware(
    {
      "GET /weather": {
        accepts: [
          {
            scheme: "exact",
            price: "$0.001",
            network: "stellar:testnet",
            payTo: stellarAddress,
          },
        ],
        description: "Weather data",
        mimeType: "application/json",
      },
    },
    new x402ResourceServer(facilitatorClient)
      .register("stellar:testnet", new ExactStellarScheme())

  ),
);

app.get("/weather", (req, res) => {
  res.send({
    report: {
      weather: "sunny",
      temperature: 70,
    },
  });
});

app.listen(4021, () => {
  console.log(`Server listening at http://localhost:${4021}`);
});

Server Setup (Express)

Set up an Express server with x402 payment middleware. For detailed instructions, see the x402 Express server guide.

Client Setup (Fetch)

Make requests to x402-protected endpoints using the fetch client. For detailed instructions, see the x402 Fetch client guide.

How It Works

Verification Flow

  1. Protocol Validation: Validates X402 version, scheme, and network
  2. Transaction Parsing: Decodes transaction XDR and extracts operation details
  3. Operation Validation: Ensures it's an invokeHostFunction calling transfer
  4. Amount & Recipient Validation: Validates transfer amount and recipient match requirements
  5. Auth Entry Validation: Verifies auth entries are present and signed by the payer
  6. Envelope Signature Check: Ensures transaction envelope has no signatures (for relayer rebuild)
  7. Simulation: Simulates transaction to ensure it will succeed
  8. Security Checks: Validates transaction source is not the relayer

Settlement Flow

  1. Verification: Verifies payment before settlement
  2. Operation Extraction: Extracts operation details and signed auth entries from transaction
  3. Submission:
    • If channel service configured: Submits via channel service API with func and auth XDRs
    • Otherwise: Submits via relayer API with operations format
  4. Confirmation: Waits for transaction confirmation

Channel Service vs Relayer API

The plugin supports two settlement methods:

  • Relayer API (default): Uses the relayer's sendTransaction with operations format
  • Channel Service API (optional): Uses external channel service when channel_service_api_url and channel_service_api_key are configured

Additional Resources