input('name'))); if (empty($name)) { return response()->json(['error' => 'Name required'], 422); } // Verify the name exists on chain $alias = $this->rpc->getAliasByName($name); if (! $alias) { return response()->json([ 'error' => 'Name not registered on chain', 'name' => $name, ], 404); } // TODO: verify signature proves ownership of the alias wallet // For now, trust the name exists on chain as proof of identity $this->registry->register($name, [ 'capabilities' => $request->input('capabilities', []), 'region' => $request->input('region', 'unknown'), 'bandwidth_mbps' => (int) $request->input('bandwidth_mbps', 0), 'max_connections' => (int) $request->input('max_connections', 0), 'wireguard_endpoint' => $request->input('wireguard_endpoint', ''), 'proxy_endpoint' => $request->input('proxy_endpoint', ''), ]); return response()->json([ 'status' => 'paired', 'name' => $name . '.lthn', 'heartbeat_interval' => 60, 'ttl' => 300, 'message' => 'Gateway paired. Send heartbeat every 60s to stay live.', ]); } /** * POST /v1/gateway/heartbeat * {"name": "charon", "connections": 12, "load": 45, "bytes_since_last": 104857600} */ public function heartbeat(Request $request): JsonResponse { $name = strtolower(trim((string) $request->input('name'))); $ok = $this->registry->heartbeat($name, [ 'connections' => (int) $request->input('connections', 0), 'load' => (int) $request->input('load', 0), 'bytes_since_last' => (int) $request->input('bytes_since_last', 0), ]); if (! $ok) { return response()->json([ 'error' => 'Gateway not paired. Call /pair first.', 'name' => $name, ], 404); } return response()->json([ 'status' => 'ok', 'name' => $name . '.lthn', 'next_heartbeat' => 60, ]); } /** * GET /v1/gateway/live?cap=proxy * * List all live paired gateways with real-time stats. */ public function live(Request $request): JsonResponse { $capability = strtolower(trim((string) $request->get('cap', ''))); $gateways = $this->registry->liveGateways($capability); return response()->json([ 'gateways' => array_map(fn ($gw) => [ 'name' => $gw['name'] . '.lthn', 'region' => $gw['region'], 'capabilities' => $gw['capabilities'], 'load' => $gw['current_load'] ?? 0, 'connections' => $gw['current_connections'] ?? 0, 'bandwidth_mbps' => $gw['bandwidth_mbps'] ?? 0, 'wireguard' => ! empty($gw['wireguard_endpoint']), 'proxy' => ! empty($gw['proxy_endpoint']), 'status' => $gw['status'], 'last_heartbeat' => $gw['last_heartbeat'], ], $gateways), 'count' => count($gateways), ]); } /** * POST /v1/gateway/dispatch * {"type": "residential", "region": "eu"} * * Select the best live gateway for a customer connection. */ public function dispatch(Request $request): JsonResponse { $type = strtolower(trim((string) $request->input('type', 'proxy'))); $capMap = [ 'mobile' => 'proxy', 'residential' => 'proxy', 'seo' => 'exit', 'vpn' => 'vpn', ]; $capability = $capMap[$type] ?? 'proxy'; $gateway = $this->registry->selectBest($capability); if (! $gateway) { // Fall back to chain-discovered nodes (unpaired but registered) return response()->json([ 'error' => 'No live paired gateways available. Try again later.', 'fallback' => 'chain', ], 503); } return response()->json([ 'gateway' => $gateway['name'] . '.lthn', 'region' => $gateway['region'], 'wireguard_endpoint' => $gateway['wireguard_endpoint'] ?? '', 'proxy_endpoint' => $gateway['proxy_endpoint'] ?? '', 'load' => $gateway['current_load'], 'type' => $type, ]); } }