๐Ÿ“… Historical page. This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in USDC on Base only, 5 free trial calls per signed wallet, no credits and no top-up rail. โ†’ See current

From Node.js to Go โ€” Why We're Rewriting minia2a's Gateway

⚠️ Correction (August 15, 2026): The payment figures in this article — "14 paid transactions" / "$12.75 total volume" — were based on payment records later found to be misclassified entries โ€” every transaction was real. A full ledger audit (August 15, 2026) corrected the account. 86 real on-chain transactions totaling 3.522 USDC โ€” 54 real x402 pay-per-call settlements (0.45 USDC) plus 32 USDC credit top-ups (3.072 USDC), each verifiable on-chain by txHash. Trial and request figures remain accurate.
August 7, 2026 ยท 6 min read ยท goarchitecturex402engineering

The Problem

minia2a started as a Node.js monolith. One Express server handling everything: 323 API endpoints, x402 payment challenges, MCP JSON-RPC, trial economy, agent registry, and blog serving. It worked. Until it didn't.

In the last 72 hours, the V4 Node process restarted 49 times. Not from traffic spikes โ€” from accumulated technical debt:

These aren't Node.js's fault. They're what happens when a prototype grows into infrastructure without a concurrency model designed for it.

What Changed

V5 splits the monolith into two processes:

Client โ†’ Go Gateway (:80) โ†’ Node Sidecar (:3000) โ†’ 199 Service Handlers
              โ†‘                       โ†‘
         trial check,             individual .js files
         x402 challenges,         (drop-in from V4,
         payment receipts,        no modification)
         API routing

Go Gateway (Gin + modernc/sqlite): Handles HTTP, x402 payment challenges, rate limiting, JWT auth, and trial economy. Uses pure-Go SQLite (no CGO) so deployment is a single binary.

Node Sidecar (Express): Runs the 199 individual service handler files. These are the actual API implementations โ€” CAPTCHA solving, gas price lookups, Polymarket data โ€” and they don't need to change. The Go gateway proxies to them over localhost.

Why Go for the Gateway

RequirementNode.js (V4)Go (V5)
Concurrent connections~1K (single thread)10K+ (goroutines)
Memory after 24h idle143MB~15MB (estimated)
Startup time2-4s<100ms
Deployment artifactnode_modules + 200 filesSingle binary
Graceful shutdownManualBuilt-in (context)
Type safetyRuntime crashesCompile-time

Three specific things pushed us to Go:

1. x402 Payment Idempotency Needs Atomicity

When an agent pays for an API call, the gateway must atomically: (a) verify the payment on-chain, (b) store the receipt, (c) forward the request, (d) return the response. In Node.js, this required careful async/await coordination with no compile-time guarantees. In Go, the control flow is explicit and the compiler catches missing error paths.

// V5: receipt verification is a single read-through path
func (g *Gate) VerifyOrChallenge(c *gin.Context, db *sql.DB) {
    sig := c.GetHeader("x-payment-sig")
    if sig == "" {
        g.SendChallenge(c)
        return
    }
    receipt, err := g.VerifyReceipt(sig)
    if err != nil {
        g.SendChallenge(c)
        return
    }
    // Receipt is valid โ€” forward to service
    c.Set("receipt", receipt)
    c.Next()
}

2. Rate Limiting Per-Endpoint, Per-IP

V5's rate limiter is a token-bucket implementation with per-IP counters and configurable per-endpoint limits. POST /api/v1/register-simple gets 5 req/min. GET /x402/gas gets 30. The Go implementation is 60 lines with no external dependencies.

func rateLimit(maxPerMinute int) gin.HandlerFunc {
    return func(c *gin.Context) {
        ip := c.ClientIP()
        // token bucket: count requests per IP, reset every 60s
        b.count++
        if b.count > maxPerMinute {
            c.AbortWithStatusJSON(429, gin.H{"error": "rate limit exceeded"})
            return
        }
        c.Next()
    }
}

3. Single Binary Deployment

The Go gateway compiles to a single static binary. No npm install, no node_modules, no runtime version mismatches. Deploy with scp gateway user@host: and restart. This matters when you're pushing fixes at 2am.

What Stays in Node.js

The 199 service handlers stay in JavaScript. These are thin wrappers โ€” most are 20-40 lines calling external APIs or doing simple transformations. Rewriting them in Go would be busy work with no performance gain (they're I/O bound). The sidecar approach means we migrate what matters (the gateway) and leave what works (the handlers).

// compat/services/x402-gas.js โ€” stays exactly as-is
module.exports = async function(req, res) {
  const chain = req.query.chain || "ethereum";
  try {
    const gas = await fetchGasPrice(chain);
    res.json({ ok: true, gas: gas.fast, unit: "gwei", chain });
  } catch (e) {
    res.json({ ok: false, error: e.message });
  }
};

Migration Path

We're running V4 and V5 side-by-side during migration:

  1. Phase 1 (done): Go gateway builds, passes tests, serves all API routes
  2. Phase 2 (current): Parity testing โ€” V5 handles a percentage of production traffic, comparing responses with V4
  3. Phase 3 (next): Full cutover โ€” V5 on :80, V4 as fallback on :8081

Numbers That Matter

From V4 production (last 30 days, via /api/stats):

These aren't VC-pitch numbers. They're real infrastructure numbers from a live marketplace where the traffic is 99% agent-originated, not human browsing.

Key insight: The 49 V4 restarts weren't from load โ€” they were from complexity. When your payment gateway crashes because a curl example string in a JSON response has an unescaped quote, the problem isn't the language. It's that payment-critical code and content strings lived in the same file. V5 separates them into gateway (Go) and content (Node sidecar).

What's Next

V5 is open source (MIT). If you're building agent-to-agent payment infrastructure and want to contribute โ€” or just want to see how an x402 gateway works in Go โ€” the code is at minia2a-v5/gateway/.

The gateway currently handles: health checks, stats API, service registry, x402 payment challenges, receipt verification, MCP JSON-RPC, rate limiting, agent registration, credit purchases, and trial economy. Each feature is ~100-200 lines of Go with explicit error handling.

If you're running a Node.js payment service and considering a Go rewrite, our advice: don't rewrite everything. Move the gateway (HTTP, auth, payments) to Go. Leave the business logic where it is. The sidecar pattern works.

Discuss on GitHub ยท Try the API: curl https://minia2a.uk/api/stats ยท Build with x402: minia2a.uk/build-x402-agent.html