Integrating x402 Payments with Rootstock
The x402 protocol (deriving from HTTP Status 402 Payment Required) is emerging as the standard for Agentic Commerce. It allows AI agents, automated scripts, and browsers to autonomously negotiate and pay for resources, such as premium APIs, gated content, or computational tasks, without human intervention.
While some chains rely on centralized facilitators, Rootstock is uniquely positioned for Sovereign Mode integration. As the EVM-compatible Bitcoin sidechain, Rootstock allows you to verify payments with Bitcoin-level security directly on your server.
In this guide, we will build a Sovereign x402 Server that:
- Intercepts requests to premium endpoints.
- Challenges unpaid requests with a 402 status and payment metadata.
- Verifies on-chain tRBTC transaction proofs directly against the Rootstock ledger.
- Enforces idempotency via Redis to prevent replay attacks.
The Protocol Flow
Unlike hosted solutions, "Sovereign Mode" means your API acts as its own payment processor. This eliminates middleman fees and reliance on third-party gateways.
- Challenge (Handshake): The client requests a resource (e.g., /api/premium). The server detects a missing payment header and responds with 402 Payment Required. Crucially, it returns a WWW-Authenticate header containing the Price, Asset (tRBTC), and Target Address.
- Execution: The client (or AI Agent) parses these details, signs a transaction, and broadcasts it to the Rootstock network.
- Proof: The client retries the original request, this time including the Transaction Hash in the X-Payment (or Payment-Signature) header.
- Settlement: The server validates the transaction on-chain, ensures it hasn't been used before (via Redis), and serves the content.
Prerequisites
- Node.js (v18.x or higher)
- Redis (Required for replay protection/idempotency)
- Rootstock Testnet Wallet funded with tRBTC.
- RPC Endpoint: * Testnet:
https://public-node.testnet.rsk.co- Recommendation: For production, use a dedicated RPC key from providers like pure RPC or QuickNode (see RPC Nodes tools) to avoid rate limits.
1. Project Setup
Initialize a strictly typed Node.js environment. We will use web3.js for blockchain interaction and Redis for state management.
mkdir rootstock-x402
cd rootstock-x402
npm init -y
# Core dependencies:
# express: Web server
# web3: Interface for the Rootstock Blockchain
# redis: In-memory store for idempotency (anti-replay)
# dotenv: Environment variable management
npm install express redis dotenv web3
Ensure your package.json supports ES6 modules:
"type": "module"
2. Configuration
Create a .env file in your project root. This effectively acts as your "Pricing Table."
PORT=4000
# Local Redis or hosted instance string
REDIS_URL=redis://127.0.0.1:6379
# Rootstock Testnet RPC
RSK_NODE_RPC=https://rpc.testnet.rootstock.io/<YOUR_API_KEY>
# The address that receives the funds
RECEIVER_ADDRESS=0xYourWalletAddressHere
# Security settings
REQUIRED_CONFIRMATIONS=1
MIN_PRICE_TRBTC=0.00001
3. Server Implementation
In this section, we will build server.js step-by-step. Instead of a single block of code, we break it down into four logical stages: Setup, The Challenge, The Verification, and The Route.
Step 3.1: Imports and Initialization
First, we set up our environment. We use express for the API, web3 to communicate to the Rootstock blockchain, and redis to remember which payments have already been spent.
// server.js
// 1. Imports
import express from 'express';
import { Web3 } from 'web3'; // Rootstock interaction
import dotenv from 'dotenv';
import { createClient } from 'redis'; // Anti-replay database
// 2. Load Configuration
dotenv.config();
const app = express();
app.use(express.json()); // Allows us to parse JSON bodies
// 3. Database Connection (Redis)
// We use Redis to store "spent" transaction hashes so they can't be reused.
const redis = createClient({
url: process.env.REDIS_URL || 'redis://localhost:6379'
});
redis.on('error', (err) => console.error('Redis Client Error', err));
await redis.connect();
// 4. Blockchain Connection
// We connect to a Rootstock Node (Testnet or Mainnet) in Read-Only mode.
// No private keys are needed here because we are only verifying data.
const web3 = new Web3(process.env.RSK_NODE_RPC);
Step 3.2: Defining the "Price Tag"
We define our constants here. In blockchain payments, precision is key. We convert our human-readable price (0.00001 BTC) into Wei (the smallest unit) to ensure mathematically perfect comparisons.
// Normalize the receiver address to lowercase to avoid case-sensitivity bugs
const RECEIVER = process.env.RECEIVER_ADDRESS.toLowerCase();
// Convert 0.00001 tRBTC to Wei (10000000000000 Wei)
const MIN_PRICE_WEI = web3.utils.toWei(process.env.MIN_PRICE_TRBTC || '0.00001', 'ether');
// Security: How many blocks must pass before we trust the payment?
// 1 for speed, 12 for high security.
const REQUIRED_CONFIRMATIONS = Number(process.env.REQUIRED_CONFIRMATIONS) || 1;
Step 3.3: The Middleware (The "Guard")
This is the core of the x402 protocol. This middleware function runs before the user gets access to the content. It acts as a bouncer.
Phase A: The Challenge (402 Response)
If the user hasn't sent a payment proof header (X-Payment), we stop them right here and tell them how to pay.
const verifyPayment = async (req, res, next) => {
// Check for the payment proof header
const txHash = req.headers['x-payment'] || req.headers['payment-signature'];
// If missing or invalid format (not a 64-char hex string), reject it.
if (!txHash || !/^0x([A-Fa-f0-9]{64})$/.test(txHash)) {
return res.status(402).json({
error: 'Payment Required',
details: {
payTo: RECEIVER,
amount: process.env.MIN_PRICE_TRBTC,
asset: 'tRBTC',
network: 'rootstock-testnet',
chainId: 31
},
instructions: 'Send tRBTC to the address above. Retry request with transaction hash in "X-Payment" header.',
});
}
// If we get here, the user claims they have paid. Let's verify it.
try {
// ... verification logic continues below ...