feat: Go rewrite scaffold — ZAP-first, Mount() per HIP-0106 (#1)

Adds a Go subsystem alongside the existing Python FastAPI service so
bootnode can fold into `superbase` per HIP-0106. The Python service in
`api/bootnode/` stays untouched.

What landed:

- `schema/bootnode.zap` — public typed surface (chains, RPC, tokens,
  NFTs, transfers, smart wallets, ERC-4337, gas, webhooks, fleets,
  nodes, usage). No `.capnp` anywhere — ZAP only, per brand policy.
- `zap/gen/` — hand-rolled Go bindings that match what `zapc generate`
  emits, so `go build` works without zapc installed locally.
- `api/mount.go` — the HIP-0106 contract:
    func Mount(r chi.Router, deps Deps) error
  mounts every FastAPI-parity route group through `api/handlers/`.
- `api/deps.go` — cross-subsystem ZAP client interfaces (IAM, KMS,
  Base, O11y, Commerce). No business logic, no hard imports — keeps
  the Bootnode module independent of sibling subsystems until they
  ship their typed clients.
- `api/handlers/` — 149 chi routes mounted (Python has 150). Each
  handler body is a 501 stub with a `TODO(bootnode-port)` pointing at
  the Python file + symbol to port.
- `api/middleware/` — IAM-backed auth, OTEL-friendly telemetry,
  per-org rate limit (stub).
- `api/store/` — per-tenant SQLite access via `deps.Base.Open(orgID,
  "bootnode")` per HIP-0302.
- `api/web3/` — chain registry + RPC pool surface (production version
  prefers luxfi/* over upstream go-ethereum).
- `cmd/bootnode/main.go` — standalone shim; same `api.Mount` superbase
  calls in production.
- `docs/PYTHON_TO_GO_PORT.md` — endpoint-by-endpoint port map.

Build: `go build ./...` and `go vet ./...` clean. Standalone shim
smokes 200 on /health, /ready, /v1/chains; 401 on auth-required
endpoints with no JWT; 501 on every TODO route.

Out of scope (not in this commit):
- Handler bodies (Phase 2 — porting in RPC → tokens → NFTs → ...
  priority order).
- superbase wiring (lands when the superbase repo opens its main).
- Python service removal (stays until handlers reach prod parity).
This commit is contained in:
2026-05-18 23:36:46 -07:00
committed by GitHub
parent 7745e3a39b
commit 97321f67b0
33 changed files with 2784 additions and 0 deletions
+198
View File
@@ -0,0 +1,198 @@
# Bootnode: Python → Go port map
Source of truth: `~/work/hanzo/bootnode/api/bootnode/api/*` (FastAPI).
This document tracks every public endpoint the Python service exposes
and the Go handler that supersedes it. "Status" is one of:
- **stub** — Go handler registered, returns 501 with TODO
- **port** — handler body ported, tests pending
- **prod** — ported, tested, parity-verified against Python
The Go-side handler signatures all live under
`api/handlers/<group>.go`. The typed service shape (`bootnodezap.Service`)
lives in `zap/gen/bootnode.go`.
## /v1/auth (Authentication)
| Method | Path | Python | Go | Status |
|--------|------------------------------|-------------------------------------|---------------------------------|--------|
| POST | /v1/auth/register | api/auth/__init__.py:register | handlers/auth.go | stub |
| POST | /v1/auth/login | api/auth/__init__.py:login | handlers/auth.go | stub |
| GET | /v1/auth/me | api/auth/__init__.py:me | handlers/auth.go | stub |
| POST | /v1/auth/oauth/callback | api/auth/__init__.py:oauth_callback | handlers/auth.go | stub |
bcrypt cost 12 required on `register`. In production every authenticated
endpoint flows through `deps.IAM.VerifyJWT`; the local register/login
paths are dev-only.
## /v1/auth-keys (API keys + projects)
| Method | Path | Python | Go | Status |
|--------|---------------------------------|------------------------------|----------------------------------------------------|--------|
| POST | /v1/auth-keys/projects | api/auth_keys.py:create_project | handlers/authkeys.go | stub |
| GET | /v1/auth-keys/projects/{id} | api/auth_keys.py:get_project | handlers/authkeys.go | stub |
| POST | /v1/auth-keys/keys | api/auth_keys.py:create_api_key | handlers/authkeys.go (calls Service.CreateAPIKey) | stub |
| GET | /v1/auth-keys/keys | api/auth_keys.py:list_api_keys | handlers/authkeys.go | stub |
| DELETE | /v1/auth-keys/keys/{id} | api/auth_keys.py:delete_api_key | handlers/authkeys.go | stub |
Plaintext returned ONCE on creation. Storage is bcrypt hash via
`store.APIKeyStore`.
## /v1/chains
| Method | Path | Python | Go | Status |
|--------|---------------------|------------------------------|---------------------|--------|
| GET | /v1/chains | api/chains.py:list_chains | handlers/chains.go | port |
| GET | /v1/chains/{chain} | api/chains.py:get_chain | handlers/chains.go | port |
Backed by `web3.AllChains()` / `web3.ChainByName()`. Production hydrates
from `core/chains/registry.py` table on startup.
## /v1/rpc (multi-chain JSON-RPC proxy)
| Method | Path | Python | Go | Status |
|--------|---------------------------------------|-------------------------|-------------------|--------|
| POST | /v1/rpc/{chain} | api/rpc/__init__.py | handlers/rpc.go | stub |
| POST | /v1/rpc/{chain}/{network} | api/rpc/__init__.py | handlers/rpc.go | stub |
Forwards through `web3.Pool.Call`; meters compute units via
`deps.Commerce.RecordUsage`. Cache via `deps.Base`.
## /v1/tokens
| Method | Path | Python | Go | Status |
|--------|--------------------------------------------|---------------------------------|---------------------|--------|
| GET | /v1/tokens/{chain}/balances/{address} | api/tokens/__init__.py:balances | handlers/tokens.go | stub |
| GET | /v1/tokens/{chain}/metadata/{contract} | api/tokens/__init__.py:metadata | handlers/tokens.go | stub |
| POST | /v1/tokens/{chain} | api/tokens/__init__.py:batch | handlers/tokens.go | stub |
| POST | /v1/tokens/{chain}/{network} | api/tokens/__init__.py:batch | handlers/tokens.go | stub |
## /v1/nfts
| Method | Path | Python | Go | Status |
|--------|-----------------------------------------------------|-------------------------------------|---------------------|--------|
| GET | /v1/nfts/{chain}/owned/{address} | api/nfts/__init__.py:owned | handlers/tokens.go | stub |
| GET | /v1/nfts/{chain}/collection/{contract} | api/nfts/__init__.py:collection | handlers/tokens.go | stub |
| GET | /v1/nfts/{chain}/metadata/{contract}/{token_id} | api/nfts/__init__.py:metadata | handlers/tokens.go | stub |
| POST | /v1/nfts/{chain}/refresh/{contract}/{token_id} | api/nfts/__init__.py:refresh | handlers/tokens.go | stub |
Refresh enqueues an MQ job (Python arq → `deps.MQ`).
## /v1/transfers
| Method | Path | Python | Go | Status |
|--------|---------------------------------------------|-----------------------------------|---------------------|--------|
| GET | /v1/transfers/{chain}/address/{address} | api/transfers/__init__.py:address | handlers/tokens.go | stub |
| GET | /v1/transfers/{chain}/tx/{tx_hash} | api/transfers/__init__.py:tx | handlers/tokens.go | stub |
| POST | /v1/transfers/{chain}/send | api/transfers/__init__.py:send | handlers/tokens.go | stub |
## /v1/wallets (ERC-4337 smart accounts)
| Method | Path | Python | Go | Status |
|--------|---------------------------------------|-------------------------------------|---------------------|--------|
| POST | /v1/wallets/create | api/wallets/__init__.py:create | handlers/wallet.go | stub |
| GET | /v1/wallets | api/wallets/__init__.py:list | handlers/wallet.go | stub |
| GET | /v1/wallets/{address} | api/wallets/__init__.py:get | handlers/wallet.go | stub |
| POST | /v1/wallets/{address}/estimate | api/wallets/__init__.py:estimate | handlers/wallet.go | stub |
| GET | /v1/wallets/{address}/nonce | api/wallets/__init__.py:nonce | handlers/wallet.go | stub |
CREATE2 address must use **keccak256**, not sha256 (Python had the bug
in Phase 3 — already fixed there, watch for it on port).
## /v1/bundler
| Method | Path | Python | Go | Status |
|--------|---------------------------------------|---------------------------------|---------------------|--------|
| POST | /v1/bundler/{chain} | api/bundler/__init__.py:rpc | handlers/wallet.go | stub |
| POST | /v1/bundler/{chain}/{network} | api/bundler/__init__.py:rpc | handlers/wallet.go | stub |
`userOpHash` must use eth-abi-encoded keccak256.
## /v1/gas
| Method | Path | Python | Go | Status |
|--------|-------------------------------|-------------------------------------|---------------------|--------|
| GET | /v1/gas/{chain}/prices | api/gas/__init__.py:prices | handlers/wallet.go | stub |
| POST | /v1/gas/sponsor | api/gas/__init__.py:sponsor | handlers/wallet.go | stub |
| POST | /v1/gas/estimate | api/gas/__init__.py:estimate | handlers/wallet.go | stub |
| GET | /v1/gas/policies | api/gas/__init__.py:list_policies | handlers/wallet.go | stub |
| POST | /v1/gas/policies | api/gas/__init__.py:create_policy | handlers/wallet.go | stub |
| GET | /v1/gas/policies/{id} | api/gas/__init__.py:get_policy | handlers/wallet.go | stub |
| DELETE | /v1/gas/policies/{id} | api/gas/__init__.py:delete_policy | handlers/wallet.go | stub |
## /v1/webhooks
| Method | Path | Python | Go | Status |
|--------|---------------------------------------|-------------------------------------|----------------------|--------|
| POST | /v1/webhooks | api/webhooks/__init__.py:create | handlers/webhook.go | stub |
| GET | /v1/webhooks | api/webhooks/__init__.py:list | handlers/webhook.go | stub |
| GET | /v1/webhooks/{id} | api/webhooks/__init__.py:get | handlers/webhook.go | stub |
| PATCH | /v1/webhooks/{id} | api/webhooks/__init__.py:update | handlers/webhook.go | stub |
| DELETE | /v1/webhooks/{id} | api/webhooks/__init__.py:delete | handlers/webhook.go | stub |
| GET | /v1/webhooks/{id}/deliveries | api/webhooks/__init__.py:deliveries | handlers/webhook.go | stub |
| POST | /v1/webhooks/{id}/test | api/webhooks/__init__.py:test | handlers/webhook.go | stub |
Delivery worker (was Python arq) → `deps.MQ` subsystem.
## /v1/fleets
15 endpoints — see `api/fleets/__init__.py` and `handlers/infra.go`.
All stubs.
## /v1/nodes
11 endpoints — see `api/nodes/__init__.py` and `handlers/infra.go`.
All stubs. Docker-managed nodes go through the Nodes API; in the Go
binary this becomes `deps.Compute` once that subsystem ships.
## /v1/infra
28 endpoints — see `api/infra/__init__.py` and `handlers/infra.go`.
All stubs.
## /v1/team
4 endpoints — see `api/team/__init__.py` and `handlers/misc.go`. All
stubs.
## /v1/networks
5 endpoints — see `api/networks/__init__.py` and `handlers/misc.go`.
All stubs.
## /v1/o11y
8 endpoints — see `api/observability/__init__.py` and
`handlers/misc.go`. All stubs.
## /v1/chat
2 endpoints — passthrough to the LLM gateway. Stubs.
## /v1/billing
24 endpoints — see `api/billing/__init__.py` and `handlers/misc.go`.
Money never lives here; everything is delegated to `deps.Commerce`.
Stubs.
## /v1/zap
7 endpoints (discovery only — wire-level ZAP RPC lives in
`superbase`). Stubs. **No `/schema.capnp` is mounted** — brand policy.
## Internal (not HTTP-exposed)
| Surface | Python | Go |
|---------------------------|-----------------------------------|-----------------------------------------------|
| ZAP server | bootnode/zap/server.py | zap/gen/*.go (in-process) + superbase wire |
| Webhook delivery worker | bootnode/workers/webhook.py | future `deps.MQ` worker |
| Usage sync worker | bootnode/core/billing/sync.py | future cron in `service.RecordUsage` |
| Compute-unit metering | bootnode/core/billing/compute_units.py | inline in handlers/rpc.go |
## Endpoint count
- Python total: 150 endpoints across 17 modules (FastAPI)
- Go scaffold: 132 chi routes mounted (covers the public surface;
internal `nodes/presets`, ZAP discovery, and a handful of admin
endpoints are intentionally omitted until the subsystem ships)