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
Create project and install dependencies
mkdir facilitator && cd facilitator
npm init -y
npm install @x402/core @x402/evm @x402r/evm express dotenv viem
Configure environment variables
Create a .env file in the project root:PRIVATE_KEY=0xYourPrivateKey
PORT=4022
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}`);
});
Start the facilitator
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