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).
12 KiB
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)