SDKsGoFacilitator

Go Facilitator SDK

This guide covers how to build payment facilitator services in Go using the t402 package.

Overview

A facilitator is a payment processing service that sits between clients and the blockchain. It:

  1. Verifies payment signatures from clients
  2. Settles payments by submitting transactions to the blockchain
  3. 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/t402-io/t402/sdks/go

Basic Facilitator Server

package main
 
import (
    "github.com/gin-gonic/gin"
    t402 "github.com/t402-io/t402/sdks/go"
    evm "github.com/t402-io/t402/sdks/go/mechanisms/evm/exact/facilitator"
)
 
func main() {
    // 1. Create facilitator
    facilitator := t402.Newt402Facilitator()
 
    // 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 (t402.X402Facilitator)

The core facilitator manages verification and settlement.

Key Methods:

facilitator := t402.Newt402Facilitator()
 
// 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

3. HTTP Endpoints

Facilitators expose three standard endpoints:

GET /supported

Returns supported networks and schemes.

Response:

{
  "kinds": [
    {
      "t402Version": 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

t402.X402Facilitator

Constructor:

func Newt402Facilitator() *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:

  1. Connect to RPC: Maintain blockchain connection (ethclient, Solana RPC)
  2. Manage Nonces: Track transaction nonces for reliability
  3. Estimate Gas: Calculate appropriate gas prices
  4. Submit Transactions: Send signed transactions to blockchain
  5. Wait for Confirmation: Poll for transaction finality
  6. Handle Errors: Retry on nonce errors, gas estimation failures

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

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 *t402.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 *t402.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"))

Testing

Unit Tests

func TestFacilitatorVerify(t *testing.T) {
    facilitator := t402.Newt402Facilitator()
    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 := t402.Newt402Facilitator()
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 duration
  • facilitator.settlement_time - Settlement duration (including blockchain confirmation)
  • facilitator.gas_used - Gas consumed per transaction
  • facilitator.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

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: t402-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