Go Facilitator Implementation Guide
Comprehensive guide to implementing x402 facilitators in Go
x402 Go Facilitator Documentation
This guide covers how to build payment facilitator services in Go using the x402 package.
Overview
A facilitator is a payment processing service that sits between clients and the blockchain. It:
- Verifies payment signatures from clients
- Settles payments by submitting transactions to the blockchain
- Returns settlement confirmation to resource servers
Facilitators enable clients to create payments without direct blockchain interaction, simplifying client implementation and improving user experience.
Architecture
Client → Resource Server → Facilitator → Network
│ │ │ │
│ │ POST /verify → │
│ │ ← IsValid │ │
│ │ │ │
│ │ POST /settle → Submit tx →
│ │ ← Settlement ← ← Confirmed
Quick Start
Installation
go get github.com/coinbase/x402/go
Basic Facilitator Server
package main
import (
"github.com/gin-gonic/gin"
x402 "github.com/coinbase/x402/go"
evm "github.com/coinbase/x402/go/mechanisms/evm/exact/facilitator"
)
func main() {
// 1. Create facilitator
facilitator := x402.Newx402Facilitator()
// 2. Register payment schemes
// Note: Requires facilitator signer with RPC integration
facilitator.Register("eip155:84532", evm.NewExactEvmScheme(evmSigner))
// 3. Create HTTP server
r := gin.Default()
// 4. Expose facilitator endpoints
r.GET("/supported", handleSupported(facilitator))
r.POST("/verify", handleVerify(facilitator))
r.POST("/settle", handleSettle(facilitator))
r.Run(":4022")
}
Core Concepts
1. Facilitator Core (x402.X402Facilitator)
The core facilitator manages verification and settlement.
Key Methods:
facilitator := x402.Newx402Facilitator()
// Register payment mechanisms
facilitator.Register(network, schemeFacilitator)
// Query supported networks/schemes
supported, _ := facilitator.Supported(ctx)
// Verify payment signature
verifyResult, _ := facilitator.Verify(ctx, payloadBytes, requirementsBytes)
// Settle payment on-chain
settleResult, _ := facilitator.Settle(ctx, payloadBytes, requirementsBytes)
2. Facilitator Signers
Facilitator signers interact with the blockchain to verify and settle payments.
Requirements:
- Verify EIP-712 signatures (EVM) or transaction signatures (SVM)
- Submit transactions to blockchain
- Read blockchain state (nonces, balances)
- Wait for transaction confirmation
Note: Facilitator signer helpers are not yet available. For now, see the reference implementation in e2e/facilitators/go/main.go.
3. HTTP Endpoints
Facilitators expose three standard endpoints:
GET /supported
Returns supported networks and schemes.
Response:
{
"kinds": [
{
"x402Version": 2,
"scheme": "exact",
"network": "eip155:84532"
}
]
}
POST /verify
Verifies a payment signature.
Request:
{
"paymentPayload": {...},
"paymentRequirements": {...}
}
Response:
{
"isValid": true,
"invalidReason": ""
}
POST /settle
Settles a payment on-chain.
Request:
{
"paymentPayload": {...},
"paymentRequirements": {...}
}
Response:
{
"success": true,
"transaction": "0x1234...",
"network": "eip155:84532",
"payer": "0xabcd..."
}
Lifecycle Hooks
Hooks allow you to run custom logic during verification and settlement.
Verify Hooks
facilitator.OnBeforeVerify(func(ctx FacilitatorVerifyContext) (*BeforeHookResult, error) {
// Called before verification starts
log.Printf("Verifying payment for %s", ctx.Requirements.GetNetwork())
// Can abort verification:
// return &BeforeHookResult{Abort: true, Reason: "..."}, nil
return nil, nil
})
facilitator.OnAfterVerify(func(ctx FacilitatorVerifyResultContext) error {
// Called after successful verification
log.Printf("Payment verified: valid=%v", ctx.Result.IsValid)
return nil
})
facilitator.OnVerifyFailure(func(ctx FacilitatorVerifyFailureContext) (*VerifyFailureHookResult, error) {
// Called when verification fails
log.Printf("Verification failed: %v", ctx.Error)
// Can recover by providing result:
// return &VerifyFailureHookResult{Recovered: true, Result: ...}, nil
return nil, nil
})
Settle Hooks
facilitator.OnBeforeSettle(func(ctx FacilitatorSettleContext) (*BeforeHookResult, error) {
// Called before settlement starts
log.Printf("Settling payment for %s", ctx.Requirements.GetNetwork())
return nil, nil
})
facilitator.OnAfterSettle(func(ctx FacilitatorSettleResultContext) error {
// Called after successful settlement
log.Printf("Transaction submitted: %s", ctx.Result.Transaction)
// Record in database, emit metrics, send notifications
db.RecordTransaction(ctx.Result.Transaction, ctx.Result.Payer)
return nil
})
facilitator.OnSettleFailure(func(ctx FacilitatorSettleFailureContext) (*SettleFailureHookResult, error) {
// Called when settlement fails
log.Printf("Settlement failed: %v", ctx.Error)
// Could implement retry with higher gas:
// if isGasError(ctx.Error) {
// result := retryWithHigherGas(ctx)
// return &SettleFailureHookResult{Recovered: true, Result: result}, nil
// }
return nil, nil
})
Hook Use Cases
Database Logging:
facilitator.OnAfterSettle(func(ctx FacilitatorSettleResultContext) error {
return db.InsertTransaction(Transaction{
Hash: ctx.Result.Transaction,
Payer: ctx.Result.Payer,
Network: ctx.Result.Network,
Timestamp: time.Now(),
})
})
Metrics Collection:
facilitator.OnAfterVerify(func(ctx FacilitatorVerifyResultContext) error {
tags := map[string]string{
"network": string(ctx.Requirements.GetNetwork()),
"valid": fmt.Sprintf("%v", ctx.Result.IsValid),
}
metrics.IncrementCounter("facilitator.verifications", tags)
return nil
})
Rate Limiting:
facilitator.OnBeforeSettle(func(ctx FacilitatorSettleContext) (*BeforeHookResult, error) {
payer := ctx.Payload.GetPayer()
if rateLimiter.IsExceeded(payer) {
return &BeforeHookResult{
Abort: true,
Reason: "Rate limit exceeded",
}, nil
}
return nil, nil
})
Fraud Detection:
facilitator.OnBeforeVerify(func(ctx FacilitatorVerifyContext) (*BeforeHookResult, error) {
if fraudDetector.IsSuspicious(ctx.Payload.GetPayer()) {
return &BeforeHookResult{
Abort: true,
Reason: "Suspicious activity detected",
}, nil
}
return nil, nil
})
API Reference
x402.X402Facilitator
Constructor:
func Newx402Facilitator() *X402Facilitator
Registration:
func (f *X402Facilitator) Register(network Network, facilitator SchemeNetworkFacilitator) *X402Facilitator
Verify Hooks:
func (f *X402Facilitator) OnBeforeVerify(hook FacilitatorBeforeVerifyHook) *X402Facilitator
func (f *X402Facilitator) OnAfterVerify(hook FacilitatorAfterVerifyHook) *X402Facilitator
func (f *X402Facilitator) OnVerifyFailure(hook FacilitatorOnVerifyFailureHook) *X402Facilitator
Settle Hooks:
func (f *X402Facilitator) OnBeforeSettle(hook FacilitatorBeforeSettleHook) *X402Facilitator
func (f *X402Facilitator) OnAfterSettle(hook FacilitatorAfterSettleHook) *X402Facilitator
func (f *X402Facilitator) OnSettleFailure(hook FacilitatorOnSettleFailureHook) *X402Facilitator
Payment Methods:
func (f *X402Facilitator) Supported(ctx context.Context) (SupportedResponse, error)
func (f *X402Facilitator) Verify(ctx context.Context, payloadBytes []byte, requirementsBytes []byte) (VerifyResponse, error)
func (f *X402Facilitator) Settle(ctx context.Context, payloadBytes []byte, requirementsBytes []byte) (SettleResponse, error)
Facilitator Signers
Facilitator signers require blockchain interaction for verification and settlement.
Required Interface (EVM)
type FacilitatorEvmSigner interface {
// Get facilitator's Ethereum address
GetAddress() string
// Get chain ID for the network
GetChainID() (*big.Int, error)
// Verify EIP-712 signature
VerifyTypedData(address string, domain TypedDataDomain, types map[string][]TypedDataField,
primaryType string, message map[string]interface{}, signature []byte) (bool, error)
// Send EIP-3009 transaction
SendReceiveWithAuthorizationTransaction(authorization ReceiveWithAuthorization) (string, error)
// Wait for transaction confirmation
WaitForTransactionConfirmation(txHash string, maxWaitTime time.Duration) error
}
Implementation Notes
Facilitator signers need to:
- Connect to RPC: Maintain blockchain connection (
ethclient, Solana RPC) - Manage Nonces: Track transaction nonces for reliability
- Estimate Gas: Calculate appropriate gas prices
- Submit Transactions: Send signed transactions to blockchain
- Wait for Confirmation: Poll for transaction finality
- Handle Errors: Retry on nonce errors, gas estimation failures
Reference Implementation: See e2e/facilitators/go/main.go for a complete facilitator signer implementation (~300 lines).
Coming Soon: Facilitator signer helpers will reduce this to ~10 lines.
Production Considerations
Gas Management
- Monitor facilitator wallet balance
- Implement gas price strategies (EIP-1559)
- Set up alerts for low balance
- Use separate wallets per network
Transaction Monitoring
- Log all submitted transactions
- Monitor for failed transactions
- Implement retry logic with higher gas
- Track transaction confirmation times
Security
- Secure private key storage (use HSM, KMS)
- Implement rate limiting per payer
- Add fraud detection hooks
- Monitor for unusual patterns
- Set transaction value limits
High Availability
- Run multiple facilitator instances
- Use load balancer with health checks
- Implement transaction queue for resilience
- Set up monitoring and alerts
Performance
- Use connection pooling for RPC
- Cache blockchain state when possible
- Batch verification requests if possible
- Optimize gas estimation
Testing
Unit Tests
func TestFacilitatorVerify(t *testing.T) {
facilitator := x402.Newx402Facilitator()
facilitator.Register(network, mockScheme)
result, err := facilitator.Verify(ctx, payloadBytes, requirementsBytes)
if err != nil {
t.Errorf("Verify failed: %v", err)
}
if !result.IsValid {
t.Errorf("Expected valid payment")
}
}
Integration Tests
Test against real blockchain networks (testnet):
// Create real signer with RPC connection
signer := newRealFacilitatorSigner(privateKey, rpcURL)
facilitator := x402.Newx402Facilitator()
facilitator.Register(network, evm.NewExactEvmScheme(signer))
// Test real verification and settlement
result, _ := facilitator.Settle(ctx, payloadBytes, requirementsBytes)
// Verify transaction on blockchain
receipt, _ := rpcClient.TransactionReceipt(ctx, result.Transaction)
Monitoring
Key Metrics
Track these metrics for production facilitators:
facilitator.OnAfterVerify(func(ctx FacilitatorVerifyResultContext) error {
metrics.IncrementCounter("facilitator.verifications", map[string]string{
"network": string(ctx.Requirements.GetNetwork()),
"valid": fmt.Sprintf("%v", ctx.Result.IsValid),
})
return nil
})
facilitator.OnAfterSettle(func(ctx FacilitatorSettleResultContext) error {
metrics.IncrementCounter("facilitator.settlements", map[string]string{
"network": ctx.Result.Network,
"success": fmt.Sprintf("%v", ctx.Result.Success),
})
metrics.RecordHistogram("facilitator.settlement_time", time.Since(startTime))
return nil
})
Recommended Metrics:
facilitator.verifications- Total verifications (by network, valid/invalid)facilitator.settlements- Total settlements (by network, success/failure)facilitator.verification_time- Verification durationfacilitator.settlement_time- Settlement duration (including blockchain confirmation)facilitator.gas_used- Gas consumed per transactionfacilitator.wallet_balance- Current wallet balance per network
Alerting
Set up alerts for:
- Low wallet balance (< 0.1 ETH)
- High verification failure rate (> 5%)
- High settlement failure rate (> 2%)
- Slow transaction confirmation (> 60s)
- Unusual traffic patterns
Error Handling
Verification Errors
result, err := facilitator.Verify(ctx, payloadBytes, requirementsBytes)
if err != nil {
// System error (network, parsing, etc.)
return http.StatusInternalServerError, err
}
if !result.IsValid {
// Payment is invalid (bad signature, wrong amount, etc.)
return http.StatusOK, result // Return invalid result to caller
}
Settlement Errors
result, err := facilitator.Settle(ctx, payloadBytes, requirementsBytes)
if err != nil {
// Settlement failed (network error, gas estimation, etc.)
log.Printf("Settlement failed: %v", err)
// Could implement retry logic here
return http.StatusInternalServerError, err
}
if !result.Success {
// Transaction failed on-chain
log.Printf("Transaction failed: %s", result.ErrorReason)
return http.StatusOK, result
}
Error Recovery
Use hooks to implement intelligent error recovery:
facilitator.OnSettleFailure(func(ctx FacilitatorSettleFailureContext) (*SettleFailureHookResult, error) {
// Classify error
if isGasTooLow(ctx.Error) {
// Retry with higher gas
result := retryWithHigherGas(ctx)
if result != nil {
return &SettleFailureHookResult{
Recovered: true,
Result: *result,
}, nil
}
}
if isNonceError(ctx.Error) {
// Reset nonce and retry
resetNonce()
// Let retry mechanism handle it
}
return nil, nil // No recovery
})
Implementation Patterns
HTTP Server Handler
func handleVerify(facilitator *x402.X402Facilitator) gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
var req struct {
PaymentPayload json.RawMessage `json:"paymentPayload"`
PaymentRequirements json.RawMessage `json:"paymentRequirements"`
}
if err := c.BindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "Invalid request"})
return
}
result, err := facilitator.Verify(ctx, req.PaymentPayload, req.PaymentRequirements)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, result)
}
}
Settlement with Timeout
func handleSettle(facilitator *x402.X402Facilitator) gin.HandlerFunc {
return func(c *gin.Context) {
// Use longer timeout for blockchain operations
ctx, cancel := context.WithTimeout(c.Request.Context(), 60*time.Second)
defer cancel()
var req struct {
PaymentPayload json.RawMessage `json:"paymentPayload"`
PaymentRequirements json.RawMessage `json:"paymentRequirements"`
}
if err := c.BindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "Invalid request"})
return
}
result, err := facilitator.Settle(ctx, req.PaymentPayload, req.PaymentRequirements)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, result)
}
}
Best Practices
1. Separate Wallets Per Network
evmMainnetSigner := newSigner(mainnetKey, mainnetRPC)
evmTestnetSigner := newSigner(testnetKey, testnetRPC)
facilitator.
Register("eip155:1", evm.NewExactEvmScheme(evmMainnetSigner)).
Register("eip155:84532", evm.NewExactEvmScheme(evmTestnetSigner))
2. Monitor Wallet Balances
facilitator.OnAfterSettle(func(ctx FacilitatorSettleResultContext) error {
balance := getWalletBalance(ctx.Result.Network)
if balance < minimumBalance {
alerts.Send("Low facilitator balance", map[string]interface{}{
"network": ctx.Result.Network,
"balance": balance,
})
}
return nil
})
3. Implement Rate Limiting
rateLimiter := newRateLimiter(100, time.Minute) // 100 per minute
facilitator.OnBeforeSettle(func(ctx FacilitatorSettleContext) (*BeforeHookResult, error) {
payer := ctx.Payload.GetPayer()
if !rateLimiter.Allow(payer) {
return &BeforeHookResult{
Abort: true,
Reason: "Rate limit exceeded",
}, nil
}
return nil, nil
})
4. Set Appropriate Timeouts
// Verification: Quick (signature check)
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
// Settlement: Longer (blockchain interaction)
ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
5. Log All Operations
facilitator.
OnBeforeVerify(logOperation("verify")).
OnAfterVerify(logSuccess("verify")).
OnBeforeSettle(logOperation("settle")).
OnAfterSettle(logSuccess("settle"))
Deployment
Environment Variables
# Required
EVM_PRIVATE_KEY=0x... # Facilitator wallet private key
RPC_URL=https://base.org # Blockchain RPC endpoint
PORT=4022 # Server port
# Optional
SOLANA_PRIVATE_KEY=5J... # For SVM support
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
MAX_GAS_PRICE=100 # Maximum gas price in gwei
Docker Deployment
FROM golang:1.21-alpine
WORKDIR /app
COPY . .
RUN go build -o facilitator
EXPOSE 4022
CMD ["./facilitator"]
Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: x402-facilitator
spec:
replicas: 3
template:
spec:
containers:
- name: facilitator
image: your-facilitator:latest
ports:
- containerPort: 4022
env:
- name: EVM_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: facilitator-secrets
key: evm-private-key
Examples
Complete facilitator examples:
- Basic Facilitator - API structure with hooks
- E2E Facilitator - Complete implementation
Related Documentation
- Main README - Package overview
- CLIENT.md - Building clients
- SERVER.md - Building servers
- Mechanisms - Payment scheme implementations
- Examples - Working facilitator examples
Related Content
Looking for more? Check out our other go examples or browse by facilitator content.