syntax = "proto3"; package singula.admin.v1; option go_package = "github.com/singula/star/proto/gen/adminv1"; // AdminService exposes the runtime view of the network: whether a Star is // connected, how loaded it is, and where it can be reached. // // Registry data — owner, signer, role, registration epoch — is public on // chain and can be read from there directly. This service adds only what a // Control Plane knows from live connections. // // Access follows one rule: a Star's identity is public and answered to any // caller, while its network address is answered only to the wallet that owns // it on chain. Node addresses appear nowhere on chain and exist only inside a // node's live connection to a Control Plane. // // Authorisation is by wallet signature. There are no API keys. service AdminService { // ListNodes returns the Stars the caller may see, with the runtime overlay. // // Everyone gets identity and liveness. Address, port and REALITY key are // filled in only for Stars owned by the signer in `auth` — see AdminAuth. rpc ListNodes(ListNodesRequest) returns (ListNodesResponse); // GetNode returns a single Star by on-chain starId or runtime UUID, under // the same rule as ListNodes. rpc GetNode(GetNodeRequest) returns (GetNodeResponse); // GetHealth returns the CP health status. Open: it carries no addresses and // is how the network discovers which Control Planes are answering at all. rpc GetHealth(HealthRequest) returns (HealthResponse); // WatchNodes streams a NodeStatusEvent whenever a node connects, // disconnects, or sends a heartbeat. Same address rule as ListNodes. rpc WatchNodes(WatchNodesRequest) returns (stream NodeStatusEvent); // Several methods have been withdrawn; their request and response messages // are retained below so their field numbers are never reused. } // AdminAuth identifies the calling wallet, so that address fields can be // scoped to the Stars that wallet owns on chain. // // It carries the signature the dashboard already took at sign-in; no second // signature is required. // // The signed text names the site rather than a specific Control Plane, so one // signature serves a dashboard that talks to several. It opens the addresses // of the Stars the signer owns, and nothing else. // // Optional on every request. Without it a caller still receives each Star's // identity, role, country and liveness; it only adds addresses. // // The verifier rebuilds the signed message byte for byte: // // Singula Stargate // // Sign in to prove you own this wallet. The dashboard uses it to show the // addresses of nodes you own. Nobody else can read them. // // This authorises no transaction and cannot move your funds. // // Only ever sign this on the official site: // // // Wallet: // Nonce: // Issued at (unix): // Valid for: 24 hours // // Every value is raw: no formatted dates, no locale-dependent rendering. The // prose is fixed. // // `domain` is signed but not checked against an allow-list. It is there so the // wallet shows the user which site they are signing in to; enforcing a single // domain would dictate which dashboards may exist. message AdminAuth { string wallet = 1; // 0x… address that signed string domain = 2; // site the user was signing in to int64 timestamp = 3; // unix seconds; accepted for 24 hours string nonce = 4; // hex, chosen by the dashboard bytes signature = 5; // EIP-191 personal_sign of the message above } // ─── Nodes ──────────────────────────────────────────────────────────────────── message ListNodesRequest { reserved 1; reserved "token"; string owner_filter = 2; // Optional: restrict to Stars owned by this address string status = 3; // Optional: filter by "active" | "suspended" | "offline" int32 limit = 4; // 0 = default (100) int32 offset = 5; AdminAuth auth = 6; } message ListNodesResponse { repeated AdminNode nodes = 1; int32 total = 2; } message GetNodeRequest { reserved 1; reserved "token"; string node_id = 2; // UUID AdminAuth auth = 3; } message GetNodeResponse { AdminNode node = 1; } message NodeActionRequest { string token = 1; string node_id = 2; // UUID } message NodeActionResponse { bool ok = 1; string message = 2; } // AdminNode is the unified projection of a Star: on-chain registry data // plus optional runtime overlay from the CP that holds the node's gRPC // connection. CP-Stars (role==CP) and Node-Stars (role==Node) share the // same shape — the only difference is the `role` field. message AdminNode { string node_id = 1; // runtime UUID (derived from the signer address); empty for CP-only entries reserved 2; reserved "name"; // removed: Stars have no on-chain name string public_ip = 3; int32 port = 4; string country = 5; reserved 6; reserved "city"; // removed: never sourced string status = 7; // "active" | "offline" float current_load = 8; // 0.0 – 1.0 int32 max_sessions = 9; int32 active_sessions = 10; int64 used_bandwidth = 11; // bytes int64 last_heartbeat = 12; // Unix timestamp (0 if never) string owner_address = 13; // Ethereum wallet of the Star owner string on_chain_id = 14; // StarRegistry starId (string for JS safety) int64 created_at = 15; // Unix timestamp reserved 16; reserved "master_public_key"; // removed: the node's Ed25519 master key string vless_public_key = 17; // x25519 Reality public key bool is_online = 18; // true if the Star is connected to any Control Plane in the federation string signer_address = 19; // secp256k1 hot signer address int32 role = 20; // StarRegistry.Role: 0=None, 1=Node, 2=CP, 3=Banned // Control Plane currently holding this Star's connection, when that is a // peer rather than the answering Control Plane. Empty when the Star is // connected here, or is not connected at all. // // Runtime detail — sessions, bandwidth, heartbeat, address — is known only // to the Control Plane holding the connection, so those fields are empty for // a Star reached through a peer. This field says where to ask. string cp_domain = 21; } // ─── Sessions ───────────────────────────────────────────────────────────────── message GetSessionRequest { string token = 1; string session_id = 2; // UUID or xray_uuid string } message GetSessionResponse { AdminSession session = 1; } message TerminateSessionRequest { string token = 1; string session_id = 2; } message AdminSession { string session_id = 1; string node_id = 2; string wallet = 3; string xray_uuid = 4; string status = 5; int64 started_at = 6; int64 expires_at = 7; int64 bytes_up = 8; int64 bytes_down = 9; } // ─── Health ─────────────────────────────────────────────────────────────────── message HealthRequest {} message HealthResponse { string status = 1; // "healthy" | "degraded" int64 time = 2; // Unix timestamp } // ─── Node Events (streaming) ────────────────────────────────────────────────── message WatchNodesRequest { reserved 1; reserved "token"; AdminAuth auth = 2; } message NodeStatusEvent { string node_id = 1; // UUID string status = 2; // "active" | "offline" | "suspended" | "pending" float current_load = 3; // 0.0 – 1.0 int32 active_sessions = 4; int64 timestamp = 5; // Unix timestamp of the event } // ─── Subscription lookup ────────────────────────────────────────────────────────────────────── message WalletRequest { string token = 1; // Admin auth token string wallet_address = 2; // Ethereum wallet to look up } message WalletSubscription { bool is_active = 1; // true if subscription is currently active int64 expires_at = 2; // Unix timestamp (0 if not active) int64 active_token_id = 3; // NFT token ID that is currently active (0 if none) repeated int64 unactivated_token_ids = 4; // Token IDs owned but never activated string echo_balance_wei = 5; // Echo token balance in wei (string, avoids overflow) string usdc_balance_wei = 6; // USDC balance in wei (string) } // ─── Epoch Reward Proofs ──────────────────────────────────────────────────────── message EpochRewardProofRequest { string token = 1; // Admin auth token uint64 epoch = 2; // Epoch number string on_chain_id = 3; // Node on-chain ID (from StarRegistry) } message EpochRewardProofResponse { uint64 epoch = 1; string on_chain_id = 2; string owner = 3; // Node owner address (0x...) string amount_wei = 4; // Reward amount in wei (string) string merkle_root = 5; // Hex-encoded Merkle root repeated string proof = 6; // Hex-encoded Merkle proof elements } // ─── Voucher stats (private tier, aggregate-only) ──────────────────────────── message VoucherStatsRequest { string token = 1; // Auth token (validation delegated to admin-web, as elsewhere) } // One epoch's aggregate voucher counters. No wallet addresses, no session // ids — redemption is anonymous by construction and stays that way here. message VoucherEpochStats { uint64 epoch = 1; uint64 vouchers_issued = 2; // Blind signatures handed out uint64 vouchers_redeemed = 3; // Nullified (spent) vouchers uint64 gb_issued = 4; // vouchers_issued × denomination uint64 gb_redeemed = 5; // vouchers_redeemed × denomination } message VoucherStatsResponse { bool enabled = 1; // False when this CP has no voucher service uint32 denomination_gb = 2; uint32 quota_per_epoch_gb = 3; // Current epoch first, then previous epochs (up to ~7). repeated VoucherEpochStats epochs = 4; }