Lumen–BrightChain Client Protocol
Overview
The Lumen–BrightChain Client Protocol is the communication layer between the Lumen GUI client (@brightchain/lumen-gui) and a BrightChain node (brightchain-api). It provides:
- REST endpoints for node introspection (health, peers, pools, storage, energy)
- A JWT-authenticated WebSocket channel for real-time event subscriptions
- Pool discovery across connected peer nodes
- Role-based access control tiered by
MemberType(User, Admin, System)
This protocol is purpose-built for BrightChain’s semantics. It does not emulate MongoDB’s wire protocol.
Prerequisites
What Needs to Be Running
For a Lumen client to connect to a BrightChain node, the following must be operational:
- BrightChain API Server (
brightchain-api)- Start via
npx nx serve brightchain-api(development) or run the built binary - Listens on the port configured in
.env(default: 3000) - Serves both REST endpoints and WebSocket connections
- Start via
- Environment Configuration (
brightchain-api/src/.env)JWT_SECRET— shared secret for signing/verifying JWT tokens (required)SERVER_URL— the public URL of the node- See
brightchain-api/src/.env.examplefor all options
- Services Wired at Startup (handled automatically by the
Appclass):WebSocketMessageServer— node-to-node gossip on/ws/node/:nodeIdClientWebSocketServer— client events on/ws/clientGossipService— pool announcements between peersAvailabilityService— node health, statistics, partition detectionPoolACLStore— authorization checks for pool accessPoolEncryptionService— encrypted pool metadata handlingPoolDiscoveryService— cross-node pool discoveryEventNotificationSystem— bridges internal events to WebSocket broadcastsIntrospectionController— REST endpoint handlers
No additional database or external service setup is required beyond what the BrightChain node already needs.
Authentication
User Registration and Login
Lumen authenticates against the existing user API:
POST /api/user/register
Body: { "username": "...", "email": "...", "password": "..." }
Response: { "token": "<jwt>", "memberId": "<guid>", "energyBalance": <number> }
POST /api/user/login
Body: { "username": "...", "password": "..." }
Response: { "token": "<jwt>", "memberId": "<guid>", "energyBalance": <number> }
The returned JWT contains the member’s MemberType (User, Admin, or System) in its payload, which determines access to protocol endpoints.
JWT Usage
All introspection endpoints require a valid JWT bearer token:
Authorization: Bearer <jwt-token>
The server uses the existing requireAuth middleware to validate tokens. Admin endpoints additionally use requireMemberTypes(MemberType.Admin, MemberType.System).
Token Lifecycle
- Tokens have an expiration time set by the server
- When a token nears expiration during an active WebSocket session, the server sends a
TokenExpiringevent - The client should re-authenticate (login again) to obtain a fresh token
- Expired tokens on REST requests return
401 Unauthorized - Expired tokens on WebSocket connections trigger close code
4002
REST API — Node Introspection
All endpoints are mounted at /api/introspection.
Public Endpoints
Accessible to any authenticated member (User, Admin, or System).
GET /api/introspection/status
Returns node health, uptime, software version, and capabilities.
{
"message": "...",
"data": {
"nodeId": "<guid>",
"healthy": true,
"uptime": 86400,
"version": "0.16.0",
"capabilities": ["blocks", "pools", "gossip"],
"partitionMode": false,
"disconnectedPeers": ["<guid>", "..."]
}
}
disconnectedPeersis only populated for Admin/System members whenpartitionModeistrue- User members see
partitionModebutdisconnectedPeersis omitted
GET /api/introspection/pools
Returns pools on the local node that the requesting member has Read permission on.
{
"message": "...",
"data": [
{
"poolId": "<id>",
"blockCount": 1024,
"totalSize": 1073741824,
"memberCount": 5,
"encrypted": true,
"publicRead": false,
"publicWrite": false,
"hostingNodes": ["<guid>"]
}
]
}
- Admin/System members see all pools regardless of ACL membership
GET /api/introspection/pools/:poolId
Returns detailed metadata for a specific pool including ACL summary.
{
"message": "...",
"data": {
"poolId": "<id>",
"blockCount": 1024,
"totalSize": 1073741824,
"memberCount": 5,
"encrypted": true,
"publicRead": false,
"publicWrite": false,
"hostingNodes": ["<guid>"],
"owner": "<guid>",
"aclSummary": {
"memberCount": 5,
"adminCount": 2,
"publicRead": false,
"publicWrite": false,
"currentUserPermissions": ["Read", "Write"]
}
}
}
- Returns
403 Forbiddenif the member lacks Read permission and is not Admin/System
GET /api/introspection/energy
Returns the requesting member’s energy account.
{
"message": "...",
"data": {
"memberId": "<guid>",
"balance": 1000,
"availableBalance": 800,
"earned": 1500,
"spent": 500,
"reserved": 200
}
}
Admin Endpoints
Restricted to members with MemberType Admin or System. User members receive 403 Forbidden.
GET /api/introspection/peers
Returns connected peer nodes with connection status and latency.
{
"message": "...",
"data": {
"localNodeId": "<guid>",
"peers": [
{
"nodeId": "<guid>",
"connected": true,
"lastSeen": "2026-02-17T10:00:00.000Z",
"latencyMs": 42
}
],
"totalConnected": 3
}
}
- Returns an empty peers array with
200 OKif no peers are connected
GET /api/introspection/stats
Returns block store statistics.
{
"message": "...",
"data": {
"totalCapacity": 107374182400,
"currentUsage": 53687091200,
"availableSpace": 53687091200,
"blockCounts": {
"InMemory": 100,
"RawData": 500,
"Encrypted": 200
},
"totalBlocks": 800
}
}
GET /api/introspection/energy/:memberId
Returns any member’s energy account (admin override).
- Same response shape as
GET /energy - User members requesting another member’s account receive
403 Forbidden
POST /api/introspection/discover-pools
Queries connected peers for their pool lists and aggregates results.
{
"message": "...",
"data": {
"pools": [
{
"poolId": "<id>",
"blockCount": 512,
"totalSize": 536870912,
"memberCount": 3,
"encrypted": false,
"publicRead": true,
"publicWrite": false,
"hostingNodes": ["<guid-a>", "<guid-b>"]
}
],
"queriedPeers": ["<guid-a>", "<guid-b>", "<guid-c>"],
"unreachablePeers": ["<guid-c>"],
"timestamp": "2026-02-17T10:00:00.000Z"
}
}
- Pools are deduplicated across peers;
hostingNodeslists all nodes hosting each pool - Only pools the requesting member is authorized to see are included
- Encrypted pools the member cannot decrypt are excluded
- Unreachable peers are reported but do not cause an error (partial results returned)
Error Responses
All errors follow the IApiMessageResponse envelope:
| Status | Meaning | When |
|---|---|---|
| 401 | Unauthorized | Missing, malformed, or expired JWT |
| 403 | Forbidden | User accessing Admin endpoint, or unauthorized pool access |
| 404 | Not Found | Pool ID does not exist |
| 500 | Internal Error | Unexpected server failure |
WebSocket Channel — Real-Time Events
Connection
Connect to ws://<host>:<port>/ws/client?token=<jwt>.
This is a separate path from the node-to-node gossip WebSocket (/ws/node/:nodeId), which uses ECIES authentication. The client channel uses JWT authentication.
If the token is invalid or expired, the connection is rejected with close code 4001.
Subscription
After connecting, subscribe to event types by sending a JSON message:
{
"action": "subscribe",
"eventTypes": [
"pool:changed",
"pool:created",
"pool:deleted",
"energy:updated",
"storage:alert",
"peer:connected",
"peer:disconnected"
]
}
Unsubscribe with:
{
"action": "unsubscribe",
"eventTypes": ["storage:alert"]
}
Event Types
| Event Type | Access Tier | Description |
|---|---|---|
peer:connected | Admin | A peer node connected |
peer:disconnected | Admin | A peer node disconnected |
pool:changed | Pool-Scoped | Pool metadata or membership changed |
pool:created | Pool-Scoped | A new pool was created |
pool:deleted | Pool-Scoped | A pool was deleted |
energy:updated | Member-Scoped | The member’s energy balance changed |
storage:alert | Admin | Block store usage crossed a threshold |
auth:token_expiring | Member-Scoped | JWT token is about to expire |
Access Tier Filtering
Events are only delivered to sessions authorized to receive them:
- Public — delivered to all subscribers
- Admin — delivered only to Admin/System members
- Pool-Scoped — delivered only to members with Read permission on the target pool
- Member-Scoped — delivered only to the specific target member
Event Envelope
Every event delivered over the WebSocket has this shape:
{
"eventType": "pool:changed",
"accessTier": "pool-scoped",
"payload": { ... },
"timestamp": "2026-02-17T10:00:00.000Z",
"correlationId": "abc-123",
"targetPoolId": "<pool-id>",
"targetMemberId": "<member-id>"
}
targetPoolIdis present for pool-scoped eventstargetMemberIdis present for member-scoped eventscorrelationIdis always a non-empty string
Connection Lifecycle
- Ping/Pong: The server sends periodic ping frames. If no pong is received within the timeout, the connection is closed.
- Token Expiration: When the JWT expires, the server sends an
auth:token_expiringevent, then closes the connection with code4002after a grace period. - Reconnection: The client should implement exponential backoff reconnection. On auth-related closes (4001, 4002), re-authenticate before reconnecting.
WebSocket Close Codes
| Code | Meaning |
|---|---|
| 1000 | Normal close |
| 1001 | Server shutting down |
| 4001 | Authentication failed (invalid/expired token on connect) |
| 4002 | Token expired during session |
Shared Type Interfaces
All protocol data structures are defined as generic <TID> interfaces in @brightchain/brightchain-lib under interfaces/clientProtocol/. The frontend uses TID = string, the backend uses TID = GuidV4Buffer.
| Interface | Package | Purpose |
|---|---|---|
INodeStatus<TID> | brightchain-lib | Node health response |
IPeerInfo<TID> | brightchain-lib | Single peer entry |
INetworkTopology<TID> | brightchain-lib | Peers list response |
IPoolInfo<TID> | brightchain-lib | Pool summary |
IPoolDetail<TID> | brightchain-lib | Pool with ACL summary |
IPoolAclSummary<TID> | brightchain-lib | ACL metadata |
IPoolDiscoveryResult<TID> | brightchain-lib | Discovery response |
IBlockStoreStats | brightchain-lib | Storage metrics |
IEnergyAccountStatus<TID> | brightchain-lib | Energy account |
IClientEvent<TID> | brightchain-lib | WebSocket event envelope |
ClientEventType | brightchain-lib | Event type enum |
ClientEventAccessTier | brightchain-lib | Access tier enum |
ISubscriptionMessage | brightchain-lib | Subscribe/unsubscribe message |
API response wrappers (extending IApiMessageResponse) are in @brightchain/brightchain-api-lib under interfaces/introspectionApiResponses.ts.
Pool Discovery via Gossip
Nodes announce their pools to connected peers using the gossip protocol:
POOL_ANNOUNCEMENT— broadcast when a pool is created or updatedPOOL_REMOVAL— broadcast when a pool is deleted
The PoolDiscoveryService maintains a cache of remote pool metadata received via gossip. When a Lumen client triggers pool discovery (POST /discover-pools), the service queries connected peers, filters results by ACL and encryption, deduplicates across nodes, and returns the aggregated result.
Encrypted pool announcements use PoolEncryptionService so that only members with valid pool keys can read the metadata.
Quick Start — Connecting Lumen to a Node
- Start the BrightChain API server:
npx nx serve brightchain-api - Register or log in via the Lumen UI (or directly):
curl -X POST http://localhost:3000/api/user/login \ -H "Content-Type: application/json" \ -d '{"username": "alice", "password": "secret"}' - Use the returned JWT for REST calls:
curl http://localhost:3000/api/introspection/status \ -H "Authorization: Bearer <token>" - Open a WebSocket for real-time events:
ws://localhost:3000/ws/client?token=<token> - Subscribe to events:
{"action": "subscribe", "eventTypes": ["pool:changed", "energy:updated"]}
Architecture Summary
Lumen Client (React/MUI/Capacitor)
│
├── REST + JWT ──► /api/introspection/* ──► IntrospectionController
│ ├── AvailabilityService
│ ├── PoolACLStore
│ ├── PoolDiscoveryService
│ └── HeartbeatMonitor
│
└── WebSocket + JWT ──► /ws/client ──► ClientWebSocketServer
├── EventNotificationSystem
└── Access tier filtering
Peer Nodes
│
└── WebSocket + ECIES ──► /ws/node/:nodeId ──► WebSocketMessageServer
└── GossipService
└── PoolDiscoveryService (cache)