Skip to content

aibtcdev/x402-sponsor-relay

Repository files navigation

x402 Stacks Sponsor Relay

A Cloudflare Worker that enables gasless transactions for AI agents on the Stacks blockchain by sponsoring transactions and verifying payment settlement.

Overview

The x402 protocol is an HTTP-native payment standard that uses the HTTP 402 "Payment Required" status code to enable instant, autonomous stablecoin payments. This relay service brings gasless transactions to Stacks by:

  1. Accepting pre-signed sponsored transactions from agents
  2. Validating the transaction format (must be sponsored type)
  3. Sponsoring the transaction (covers gas fees)
  4. Calling the x402 facilitator for settlement verification
  5. Storing payment receipts for verification and resource access
  6. Returning the settlement status, sponsored tx hex, and receipt token to the agent

API

POST /sponsor

Sponsor and broadcast a transaction directly (requires API key authentication).

Headers:

Authorization: Bearer x402_sk_test_...
Content-Type: application/json

Request:

{
  "transaction": "<hex-encoded-sponsored-stacks-transaction>"
}

Response (success):

{
  "success": true,
  "requestId": "550e8400-e29b-41d4-a716-446655440000",
  "txid": "0x...",
  "explorerUrl": "https://explorer.hiro.so/txid/0x...?chain=testnet",
  "fee": "1000"
}

Response (error):

{
  "success": false,
  "requestId": "550e8400-e29b-41d4-a716-446655440000",
  "error": "Daily spending cap exceeded",
  "code": "SPENDING_CAP_EXCEEDED",
  "details": "Your API key has exceeded its daily spending limit.",
  "retryable": true,
  "retryAfter": 3600
}
Error Code HTTP Status Description
MISSING_API_KEY 401 No API key provided
INVALID_API_KEY 401 API key not found or revoked
EXPIRED_API_KEY 401 API key has expired
MISSING_TRANSACTION 400 Transaction field is missing
INVALID_TRANSACTION 400 Transaction is malformed
NOT_SPONSORED 400 Transaction must be built with sponsored: true
SPENDING_CAP_EXCEEDED 429 Daily fee cap exceeded for this API key tier
BROADCAST_FAILED 502 Transaction rejected by network

POST /relay

Submit a sponsored transaction for relay and settlement.

Request:

{
  "transaction": "<hex-encoded-sponsored-stacks-transaction>",
  "settle": {
    "expectedRecipient": "SP...",
    "minAmount": "1000000",
    "tokenType": "STX",
    "expectedSender": "SP...",
    "resource": "/api/endpoint",
    "method": "GET"
  }
}
Field Required Description
transaction Yes Hex-encoded sponsored Stacks transaction
settle.expectedRecipient Yes Expected payment recipient address
settle.minAmount Yes Minimum payment amount (in smallest unit)
settle.tokenType No Token type: STX, sBTC, USDCx (default: STX)
settle.expectedSender No Expected sender address for validation
settle.resource No API resource being accessed (for tracking)
settle.method No HTTP method being used (for tracking)

Response (success):

{
  "success": true,
  "requestId": "550e8400-e29b-41d4-a716-446655440000",
  "txid": "0x...",
  "explorerUrl": "https://explorer.hiro.so/txid/0x...?chain=testnet",
  "settlement": {
    "success": true,
    "status": "confirmed",
    "sender": "SP...",
    "recipient": "SP...",
    "amount": "1000000",
    "blockHeight": 12345
  },
  "sponsoredTx": "0x00000001...",
  "receiptId": "550e8400-e29b-41d4-a716-446655440000"
}

Note: sponsoredTx contains the fully-sponsored transaction hex (usable as X-PAYMENT header). receiptId is only returned when receipt storage succeeds (requires RELAY_KV binding).

Response (error):

{
  "success": false,
  "requestId": "550e8400-e29b-41d4-a716-446655440000",
  "error": "Transaction must be sponsored",
  "code": "NOT_SPONSORED",
  "details": "Build transaction with sponsored: true",
  "retryable": false
}

GET /verify/:receiptId

Look up a payment receipt by ID and return its status.

Response (success):

{
  "success": true,
  "requestId": "...",
  "receipt": {
    "receiptId": "550e8400-...",
    "status": "valid",
    "createdAt": "2025-01-01T00:00:00.000Z",
    "expiresAt": "2025-01-01T01:00:00.000Z",
    "senderAddress": "SP...",
    "txid": "0x...",
    "explorerUrl": "https://explorer.hiro.so/txid/0x...?chain=testnet",
    "settlement": {
      "success": true,
      "status": "confirmed",
      "recipient": "SP...",
      "amount": "1000000"
    },
    "resource": "/api/endpoint",
    "method": "GET",
    "accessCount": 0
  }
}

Response (not found): 404 for unknown or expired receipts.

POST /access

Access a protected resource using a payment receipt. Validates the receipt (exists, not expired, not consumed, resource matches) and either returns relay-hosted data or proxies to a downstream service.

Request:

{
  "receiptId": "550e8400-...",
  "resource": "/api/endpoint",
  "targetUrl": "https://downstream-service.com/api/endpoint"
}
Field Required Description
receiptId Yes Receipt ID from a successful relay transaction
resource No Resource path (validated against receipt)
targetUrl No Downstream URL for proxying (HTTPS only, no internal hosts)

Response (success):

{
  "success": true,
  "requestId": "...",
  "granted": true,
  "receipt": {
    "receiptId": "550e8400-...",
    "senderAddress": "SP...",
    "resource": "/api/endpoint",
    "accessCount": 1
  },
  "data": { "..." }
}

Note: Receipts are one-time-use — consumed after successful access. If proxying, the receipt is only consumed on a 2xx downstream response. The targetUrl must be HTTPS and cannot point to internal hosts.

GET /health

Health check endpoint.

Response:

{
  "status": "ok",
  "network": "testnet",
  "version": "0.3.0"
}

GET /docs

Interactive API documentation (Swagger UI).

GET /openapi.json

OpenAPI 3.1 specification for programmatic access.

Usage

Building a Sponsored Transaction

Transactions must be built with sponsored: true and fee: 0n:

import { makeSTXTokenTransfer, getAddressFromPrivateKey, TransactionVersion } from "@stacks/transactions";

const senderAddress = getAddressFromPrivateKey(privateKey, TransactionVersion.Testnet);
const recipient = "SP..."; // Payment recipient

const transaction = await makeSTXTokenTransfer({
  recipient,
  amount: 1000000n,
  senderKey: privateKey,
  network: "testnet",
  sponsored: true,  // Required
  fee: 0n,          // Sponsor pays
});

const txHex = Buffer.from(transaction.serialize()).toString("hex");

Submitting to the Relay

const response = await fetch("https://x402-relay.aibtc.dev/relay", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    transaction: txHex,
    settle: {
      expectedRecipient: recipient,
      minAmount: "1000000",
      tokenType: "STX",
      expectedSender: senderAddress,
    },
  }),
});

const { txid, settlement, sponsoredTx, receiptId } = await response.json();
console.log(`Transaction: https://explorer.hiro.so/txid/${txid}?chain=testnet`);
console.log(`Settlement status: ${settlement.status}`);

// Use receiptId to verify payment or access protected resources
if (receiptId) {
  const verifyResponse = await fetch(`https://x402-relay.aibtc.dev/verify/${receiptId}`);
  const { receipt } = await verifyResponse.json();
  console.log(`Receipt status: ${receipt.status}`);
}

Deployments

Environment URL Network
Staging https://x402-relay.aibtc.dev Testnet
Production https://x402-relay.aibtc.com Mainnet

All Endpoints

Method Path Auth Description
GET / None Service info
GET /health None Health check with version and network
GET /docs None Swagger UI documentation
GET /openapi.json None OpenAPI specification
POST /relay None Submit transaction via x402 facilitator
POST /sponsor API Key Sponsor and broadcast transaction directly
GET /verify/:receiptId None Verify a payment receipt
POST /access None Access protected resource with receipt
GET /stats None Relay statistics (JSON)
GET /dashboard None Public dashboard (HTML)

Rate Limits

/relay Endpoint

  • 10 requests per minute per sender address
  • Rate limiting is based on the transaction sender, not IP

/sponsor Endpoint (API Key)

Rate limits and spending caps are based on API key tier:

Tier Requests/min Requests/day Daily Fee Cap
free 10 100 100 STX
standard 60 10,000 1,000 STX
unlimited Unlimited Unlimited No cap

API Key Authentication

The /sponsor endpoint requires API key authentication.

Obtaining an API Key

API keys are provisioned via the CLI:

# Set your environment (staging = testnet, production = mainnet)
export WRANGLER_ENV=staging

# Create a new API key
npm run keys -- create --app "My App" --email "dev@example.com"

# Create with specific tier (default: free)
npm run keys -- create --app "My App" --email "dev@example.com" --tier standard

Managing API Keys

# List all API keys
WRANGLER_ENV=staging npm run keys -- list

# Get info about a specific key
WRANGLER_ENV=staging npm run keys -- info x402_sk_test_...

# View usage statistics (last 7 days)
WRANGLER_ENV=staging npm run keys -- usage x402_sk_test_... --days 7

# Renew an expiring key (extends by 30 days)
WRANGLER_ENV=staging npm run keys -- renew x402_sk_test_...

# Revoke a key
WRANGLER_ENV=staging npm run keys -- revoke x402_sk_test_...

Using API Keys

Include the API key in the Authorization header:

const response = await fetch("https://x402-relay.aibtc.dev/sponsor", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer x402_sk_test_...",
  },
  body: JSON.stringify({ transaction: txHex }),
});

Stack

  • Cloudflare Workers - Serverless deployment
  • Hono - Lightweight web framework
  • Chanfana - OpenAPI documentation generator
  • @stacks/transactions - Stacks transaction handling
  • x402-stacks - x402 protocol implementation for Stacks

Development

# Install dependencies
npm install

# Create .env and configure credentials (see Environment Variables below)
# Required: AGENT_MNEMONIC or AGENT_PRIVATE_KEY for test scripts

# Start local dev server
npm run dev

# Test /relay endpoint (no auth required)
npm run test:relay                              # Uses RELAY_URL from .env or localhost
npm run test:relay -- http://localhost:8787    # Override relay URL

# Test /sponsor endpoint (requires API key)
npm run test:sponsor                            # Uses TEST_API_KEY from .env
npm run test:sponsor -- http://localhost:8787  # Override relay URL

# Type check
npm run check

Environment Variables

The test scripts support these environment variables (set in .env):

Variable Description
AGENT_MNEMONIC 24-word mnemonic phrase (recommended)
AGENT_PRIVATE_KEY Hex-encoded private key (alternative)
AGENT_ACCOUNT_INDEX Account index to derive from mnemonic (default: 0)
RELAY_URL Relay endpoint URL (default: http://localhost:8787)
TEST_API_KEY API key for /sponsor endpoint (required for test:sponsor)

Related Projects

License

MIT

About

x402 sponsor relay for gasless Stacks transactions

Resources

Stars

Watchers

Forks

Contributors 4

  •  
  •  
  •  
  •