Skip to main content
A facilitator is a service that verifies payment signatures and settles escrow transactions on-chain. This guide walks you through running your own facilitator on Base Sepolia.
The full source code for this example is available on GitHub.

Prerequisites

  • Node.js 20+
  • A wallet private key with Base Sepolia ETH
Never commit private keys to source control. Use environment variables or a secrets manager.

Setup

1

Create project and install dependencies

mkdir facilitator && cd facilitator
npm init -y
npm install @x402/core @x402/evm @x402r/evm express dotenv viem
2

Configure environment variables

Create a .env file in the project root:
PRIVATE_KEY=0xYourPrivateKey
PORT=4022
3

Create the facilitator server

Create index.ts. This is identical to a standard x402 facilitator, with two additions: the @x402r/evm import and the registerEscrowScheme() call.
import "dotenv/config";
import express from "express";
import { x402Facilitator } from "@x402/core/facilitator";
import { PaymentPayload, PaymentRequirements } from "@x402/core/types";
import { toFacilitatorEvmSigner } from "@x402/evm";
import { registerEscrowScheme } from "@x402r/evm/escrow/facilitator";
import { createWalletClient, http, publicActions } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";

if (!process.env.PRIVATE_KEY) {
  console.error("PRIVATE_KEY environment variable is required");
  process.exit(1);
}

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const viemClient = createWalletClient({
  account, chain: baseSepolia, transport: http(),
}).extend(publicActions);
const evmSigner = toFacilitatorEvmSigner({
  address: account.address,
  getCode: (args) => viemClient.getCode(args),
  readContract: (args) => viemClient.readContract({ ...args, args: args.args || [] }),
  verifyTypedData: (args) => viemClient.verifyTypedData(args as any),
  writeContract: (args) => viemClient.writeContract({ ...args, args: args.args || [] }),
  sendTransaction: (args) => viemClient.sendTransaction(args),
  waitForTransactionReceipt: (args) => viemClient.waitForTransactionReceipt(args),
});

// Standard x402 facilitator setup
const facilitator = new x402Facilitator();

// x402r: Register the escrow scheme to handle refundable payments
registerEscrowScheme(facilitator, {
  signer: evmSigner,
  networks: "eip155:84532",
});

const app = express();
app.use(express.json());

app.post("/verify", async (req, res) => {
  const { paymentPayload, paymentRequirements } = req.body as {
    paymentPayload: PaymentPayload;
    paymentRequirements: PaymentRequirements;
  };
  res.json(await facilitator.verify(paymentPayload, paymentRequirements));
});

app.post("/settle", async (req, res) => {
  const { paymentPayload, paymentRequirements } = req.body;
  res.json(await facilitator.settle(
    paymentPayload as PaymentPayload,
    paymentRequirements as PaymentRequirements,
  ));
});

app.get("/supported", async (_req, res) => {
  res.json(facilitator.getSupported());
});

const PORT = process.env.PORT || "4022";
app.listen(parseInt(PORT), () => {
  console.log(`Facilitator listening on http://localhost:${PORT}`);
});
4

Start the facilitator

npx tsx index.ts
You should see:
Facilitator account: 0x...
Facilitator listening on http://localhost:4022

How it works

  • x402Facilitator is the core facilitator class from @x402/core that routes verify and settle requests to registered scheme handlers.
  • registerEscrowScheme adds x402r escrow support to the facilitator, enabling it to verify escrow payment signatures and settle them on-chain.
  • toFacilitatorEvmSigner adapts a viem wallet client into the signer interface the facilitator expects for on-chain interactions.
  • The three endpoints (/verify, /settle, /supported) match the interface that HTTPFacilitatorClient on the merchant side expects.

Next Steps