Здесь перечислено всё, что есть в открытом API: каждый сервис, каждый метод и каждое поле каждого сообщения.
Страница генерируется прямо из .proto, поэтому разойтись с реальностью не может. Сами файлы можно скачать и скормить генератору клиента:
ClientService — ClientService is the interface between client applications and a Control Plane: authentication, session establishment, traffic reporting, and gasless relaying of on-chain transactions. Authentication is by wallet signature. There are no API keys and no registration step. Three operations require no signature, because a caller needs them before a wallet exists: • GetNetworkInfo (this file) — contract addresses and prices. • AdminService.GetHealth — which Control Planes are reachable. • AdminService.ListNodes — Star identity and liveness. Node network addresses are not included; see admin.proto. Every other operation on this stream is authorised individually, against the entitlement that operation requires.
| Метод | Запрос | Ответ |
|---|
Connect | поток ClientMessage | поток ControlPlaneMessage |
AccessProof is a Semaphore membership proof over the on-chain SubscriberRegistry set, together with the nullifier that makes it single-use. Field elements are 32-byte big-endian values, not decimal strings.
| Поле | Тип | № | Описание |
|---|
epoch | uint64 | 1 | Epoch the proof was built for. Advisory only: the Control Plane rebuilds the scope from its own Star id and its own current epoch, and the proof must match that. |
merkle_root | bytes | 2 | Merkle root of the subscriber set the proof was built against. Accepted while the Control Plane recognises it as current or recently superseded: any registration moves the root, so a short grace window applies. |
nullifier | bytes | 3 | Derived from (secret, epoch, Control Plane Star id, index), and spendable once at that Control Plane. The Star id is part of the derivation, so one subscriber produces a different nullifier at each Control Plane. |
index | uint32 | 4 | Quota slot within the epoch, in [0, quota). Each slot yields a distinct nullifier. Bound into the proof and cannot be restated afterwards. |
points | repeated bytes | 5 | Groth16 proof: exactly 8 field elements in the packed order Semaphore emits — Ethereum calldata order, in which B’s Fq2 coefficients are swapped relative to snarkjs. |
ActivationRequest asks the CP to call activateWithSignature on behalf of the client (gasless activation). The client pre-signs: 1. inner EIP-712 ActivationRequest intent (signature) 2. outer EIP-191 GasStation.relayClient meta-tx (gas_sig + gas_deadline) CP is a pure forwarder — does not sign anything.
| Поле | Тип | № | Описание |
|---|
token_id | uint64 | 1 | NFT token ID to activate |
wallet | string | 2 | Client’s Ethereum wallet address |
deadline | int64 | 3 | Unix timestamp — inner-signature expiry |
signature | bytes | 4 | EIP-712 signature produced by the client’s wallet |
gas_deadline | int64 | 5 | Unix timestamp — relayClient meta-tx expiry |
gas_sig | bytes | 6 | 65-byte EIP-191 sig over relayClient digest |
commitments | repeated bytes | 8 | The holder’s Semaphore identity commitments, 32 bytes big-endian each, at most one per slot of the Orbit, joined to the anonymity set in the same transaction as the activation. They are part of what the holder signs, so none can be swapped in transit. Empty to activate without joining; the holder can register separately later. |
Зарезервированные номера полей: 7, commitment.
ActivationResult is sent back after the activation transaction is mined.
| Поле | Тип | № | Описание |
|---|
success | bool | 1 | — |
error_message | string | 2 | Set if success = false |
tx_hash | string | 3 | On-chain tx hash (set if success = true) |
token_id | uint64 | 4 | Activated token ID |
expires_at | int64 | 5 | Subscription expiry (Unix timestamp) |
AddSlotsRequest adds slots to an Orbit the wallet holds, paid for the part of its term left, and joins the anonymity set with the new slots’ identities. Relayed like a mint: the client pre-signs the EIP-712 AddSlots intent, an ERC-20 permit, and the GasStation.relayClient meta-tx.
| Поле | Тип | № | Описание |
|---|
wallet | string | 1 | Client’s Ethereum wallet address |
token_id | uint64 | 2 | The Orbit to add to |
extra | uint32 | 3 | Slots to add; the Orbit ends up with at most 5 |
commitments | repeated bytes | 4 | 32-byte Semaphore identity commitments for the new slots, at most one each; empty for an Orbit not activated yet, which names them at activation. |
max_cost | string | 5 | Max USDC agreed to (wei, string) |
deadline | int64 | 6 | Unix timestamp — EIP-712 intent expiry |
sig | bytes | 7 | 65-byte EIP-712 AddSlots signature |
permit_deadline | int64 | 8 | Unix timestamp — ERC-20 permit expiry |
permit_v | uint32 | 9 | — |
permit_r | bytes | 10 | — |
permit_s | bytes | 11 | — |
gas_deadline | int64 | 12 | Unix timestamp — relayClient meta-tx expiry |
gas_sig | bytes | 13 | 65-byte EIP-191 sig over relayClient digest |
| Поле | Тип | № | Описание |
|---|
success | bool | 1 | — |
error_message | string | 2 | Set if success = false |
tx_hash | string | 3 | On-chain tx hash (set if success = true) |
AuthResult is sent after ClientAuth.
| Поле | Тип | № | Описание |
|---|
success | bool | 1 | — |
error_message | string | 2 | Set if success = false |
subscription | SubscriptionInfo | 3 | Subscription info |
ClientAuth authenticates client using wallet signature.
| Поле | Тип | № | Описание |
|---|
wallet_address | string | 1 | Ethereum wallet address (0x…) |
signature | bytes | 2 | EIP-191 personal_sign of the sign-in message |
timestamp | int64 | 3 | Unix timestamp (for replay protection) |
nonce | string | 4 | Random nonce (for replay protection) |
public_key | bytes | 5 | Client’s ephemeral public key for ECDH |
client_info | ClientInfo | 6 | Client info |
| Поле | Тип | № | Описание |
|---|
app_version | string | 1 | App version (e.g., “1.0.0”) |
platform | string | 2 | Platform (e.g., “ios”, “android”, “windows”) |
preferred_country | string | 3 | Preferred country code (ISO 3166-1 alpha-2, e.g., “US”) |
oneof message — Ровно одно из полей ниже.
| Поле | Тип | № | Описание |
|---|
auth | ClientAuth | 1 | — |
session_request | SessionRequest | 2 | — |
traffic_checkpoint | TrafficCheckpoint | 3 | — |
session_end | SessionEnd | 4 | — |
pong | Pong | 5 | — |
activation_request | ActivationRequest | 6 | — |
get_network_info | GetNetworkInfoRequest | 7 | — |
mint_request | MintRequest | 8 | — |
list_countries | ListCountriesRequest | 10 | — |
register_commitment | RegisterCommitmentRequest | 11 | — |
renew_session | RenewSessionRequest | 12 | — |
take_packet | TakePacketRequest | 13 | — |
get_issuer_key | GetIssuerKeyRequest | 14 | — |
list_nodes | ListNodesRequest | 15 | — |
faucet_request | FaucetRequest | 16 | — |
add_slots_request | AddSlotsRequest | 17 | — |
governance_request | GovernanceRequest | 18 | — |
Зарезервированные номера полей: 9.
ClientNode is one candidate, described by what a client needs to choose. Deliberately without an address, a port or a REALITY key. Those reach a client with its session credentials and exist in no public place; listing them here would put a map of the network one call away.
| Поле | Тип | № | Описание |
|---|
star_id | uint64 | 1 | On-chain Star id. Everything about who runs this node — owner, role — is read from StarRegistry under this id, by the client, from the chain. |
country | string | 2 | Country as the node reports it about itself. |
latency_ms | uint32 | 3 | Round trip from this Control Plane to the node, in milliseconds. Zero means it has not been measured yet. Not the client’s latency to the node, and it cannot be: a Control Plane does not know where a client is and never asks. Added to the client’s own round trip to this Control Plane, it is the closest estimate of the whole path available from what either side legitimately knows. |
load | float | 4 | Current load, 0.0 to 1.0, and whether the node is accepting sessions at all. A client that picks a full node is refused, so both are worth having before choosing rather than after. |
available | bool | 5 | — |
oneof message — Ровно одно из полей ниже.
| Поле | Тип | № | Описание |
|---|
auth_result | AuthResult | 1 | — |
session_credentials | SessionCredentials | 2 | — |
session_status | SessionStatus | 3 | — |
error | Error | 4 | — |
ping | Ping | 5 | — |
subscription_status | SubscriptionStatus | 6 | — |
activation_result | ActivationResult | 7 | — |
network_info | NetworkInfo | 8 | — |
nonce_update | NonceUpdate | 9 | — |
mint_result | MintResult | 10 | — |
list_countries_response | ListCountriesResponse | 12 | — |
register_commitment_result | RegisterCommitmentResult | 13 | — |
renew_session_result | RenewSessionResult | 14 | — |
take_packet_result | TakePacketResult | 15 | — |
get_issuer_key_result | GetIssuerKeyResult | 16 | — |
list_nodes_result | ListNodesResult | 17 | — |
session_end_result | SessionEndResult | 18 | — |
faucet_result | FaucetResult | 19 | — |
add_slots_result | AddSlotsResult | 20 | — |
governance_result | GovernanceResult | 21 | — |
Зарезервированные номера полей: 11.
CountryEntry describes one country with a positive node count.
| Поле | Тип | № | Описание |
|---|
country | string | 1 | ISO 3166-1 alpha-2 |
node_count | int32 | 2 | Currently available (heartbeat-fresh, has capacity) |
Error reports an error condition.
| Поле | Тип | № | Описание |
|---|
code | ErrorCode | 1 | — |
message | string | 2 | — |
request_id | string | 3 | Related request ID (if applicable) |
FaucetRequest asks for test USDC, on a test network only. The wallet signs a GasStation.relayClient meta-transaction calling the test token’s mint(wallet, amount), so the gas comes out of the wallet’s own per-epoch budget and nobody can ask on another wallet’s behalf. A Control Plane on a production network answers with an error: there, USDC is real and has no public mint. amount is capped: a wallet that already holds the cap gets nothing more.
| Поле | Тип | № | Описание |
|---|
wallet | string | 1 | Wallet to mint to; must be the authenticated one |
amount | string | 2 | Token amount (smallest units, decimal string) |
gas_deadline | int64 | 3 | Unix timestamp — relayClient meta-tx expiry |
gas_sig | bytes | 4 | 65-byte EIP-191 sig over the relayClient digest |
| Поле | Тип | № | Описание |
|---|
success | bool | 1 | — |
error_message | string | 2 | Set if success = false |
tx_hash | string | 3 | On-chain tx hash (set if success = true) |
GetIssuerKeyRequest asks which key this Control Plane is blind-signing with. Needed before a permission can be asked for at all: blinding is done under the issuer’s public key, so a client that does not have it cannot produce the request. The key is not on chain — only a commitment to it is — so there is nowhere else to read it from. Open to an unauthenticated stream, like the permission request it precedes. The answer is the same public value for every caller and reveals nothing about who asked.
| Поле | Тип | № | Описание |
|---|
request_id | string | 1 | — |
GetIssuerKeyResult carries the key and what a client needs to check it. The key is checked, not trusted: VoucherKeyRegistry holds keccak256(DER ‖ packet_size) for each (issuer, window), committed a window ahead, and a client that reads that commitment knows it was given the key every other client was given. Both values needed for the check are here.
| Поле | Тип | № | Описание |
|---|
request_id | string | 1 | — |
success | bool | 2 | — |
error_message | string | 3 | — |
issuer_star_id | uint64 | 4 | — |
window | uint64 | 5 | — |
issuer_public_key | bytes | 6 | DER, SubjectPublicKeyInfo |
packet_size | uint32 | 7 | How many permissions a subscription has per window under this key, and therefore how many sessions it may hold at once. Part of the on-chain commitment, so a client checking the key checks this with it. |
GetNetworkInfoRequest asks for current contract addresses and prices. Requires no authentication: the values are public on chain, and a client needs them before it has a wallet. They are also what a client needs to construct and sign activation transactions offline.
GovernanceRequest takes part in the network’s governance without ETH: opening a proposal, voting on one, or replying in its discussion, as a subscriber or as a Star’s owner. call is the ABI-encoded call to one of ProtocolGovernor’s participant functions (proposeAsStar, proposeAsSubscriber, voteAsStar, voteAsSubscriber, argueAsStar, argueAsSubscriber). The wallet signs it twice: EIP-712 typed data Act(wallet, data, nonce, deadline) in the governor’s domain, which the governor checks before running the call as the wallet; and a GasStation.relayClient meta-transaction over act(wallet, call, act_deadline, act_sig), which GasStation pays for. The stream need not be signed in: the two signatures already bind the action to the wallet. The Control Plane checks the call names a participant function, simulates it, and refuses what would revert. Whether the wallet may do it — weight, ownership of a Star, the proposal’s state — is the governor’s to decide.
| Поле | Тип | № | Описание |
|---|
wallet | string | 1 | The signer; no sign-in is needed |
call | bytes | 2 | ABI-encoded participant call |
act_deadline | int64 | 3 | Unix timestamp — the Act signature’s expiry |
act_sig | bytes | 4 | 65-byte EIP-712 sig over Act |
gas_deadline | int64 | 5 | Unix timestamp — relayClient meta-tx expiry |
gas_sig | bytes | 6 | 65-byte EIP-191 sig over the relayClient digest |
| Поле | Тип | № | Описание |
|---|
success | bool | 1 | — |
error_message | string | 2 | Set if success = false |
tx_hash | string | 3 | On-chain tx hash (set if success = true) |
ListCountriesRequest asks for the countries that currently have at least one available node.
| Поле | Тип | № | Описание |
|---|
request_id | string | 1 | — |
ListCountriesResponse returns the aggregated country list. Order is not guaranteed — the client should sort for display.
| Поле | Тип | № | Описание |
|---|
request_id | string | 1 | — |
countries | repeated CountryEntry | 2 | — |
ListNodesRequest asks which nodes this Control Plane can serve right now. Open to any stream. Node identity is on chain already, and what is added here — how busy a node is and how far it is from this Control Plane — says nothing about any client.
| Поле | Тип | № | Описание |
|---|
request_id | string | 1 | — |
ListNodesResult carries the candidates, in no particular order.
| Поле | Тип | № | Описание |
|---|
request_id | string | 1 | — |
nodes | repeated ClientNode | 2 | — |
MintRequest asks the CP to call mintWithPermit on behalf of the client. Client pre-signs: 1. EIP-712 MintRequest intent (mint_sig) 2. ERC-20 permit for token approval (permit_v/r/s) 3. outer EIP-191 GasStation.relayClient meta-tx (gas_sig + gas_deadline) CP is a pure forwarder — does not sign anything; GasStation pays gas and accounts it against the client’s per-epoch EOA budget.
| Поле | Тип | № | Описание |
|---|
pay_with_echo | bool | 1 | true = ECHO (20% off), false = USDC |
wallet | string | 2 | Client’s Ethereum wallet address |
max_cost | string | 3 | Max token amount agreed to (wei, string) |
mint_deadline | int64 | 4 | Unix timestamp — EIP-712 mint intent expiry |
mint_sig | bytes | 5 | 65-byte EIP-712 MintRequest signature |
permit_deadline | int64 | 6 | Unix timestamp — ERC-20 permit expiry |
permit_v | uint32 | 7 | ERC-20 permit signature v |
permit_r | bytes | 8 | ERC-20 permit signature r (32 bytes) |
permit_s | bytes | 9 | ERC-20 permit signature s (32 bytes) |
gas_deadline | int64 | 10 | Unix timestamp — relayClient meta-tx expiry |
gas_sig | bytes | 11 | 65-byte EIP-191 sig over relayClient digest |
slots | uint32 | 12 | Concurrent sessions the Orbit pays for, 1 to 5. It costs that many times the price, and is part of what mint_sig signs. |
MintResult is sent back after the mint transaction is mined.
| Поле | Тип | № | Описание |
|---|
success | bool | 1 | — |
error_message | string | 2 | Set if success = false |
tx_hash | string | 3 | On-chain tx hash (set if success = true) |
token_id | uint64 | 4 | Minted token ID |
NetworkInfo carries chain parameters and current prices. Convenience, not a trust anchor: every value here comes from the Control Plane the client is talking to, and the client checks Control Planes against the chain, not the other way round. Anything a decision rests on comes from SingulaProtocol instead: one address pinned in the client’s own build, reached over an RPC the client chose, with every other address read from it. These fields exist so a wallet-tier client can show prices and build a mint transaction without a second round trip, and for nothing else.
| Поле | Тип | № | Описание |
|---|
chain_id | int64 | 1 | EVM chain ID (421614 = Arbitrum Sepolia) |
rpc_url | string | 2 | A public RPC. A hint; see the note above. |
subscription_nft_addr | string | 3 | SubscriptionNFT contract address |
echo_token_address | string | 4 | EchoToken ERC-20 address |
usdc_token_address | string | 5 | USDC ERC-20 address |
price_usdc_wei | string | 6 | Current USDC price in wei (string to avoid overflow) |
price_echo_wei | string | 7 | Current Echo price in wei (string to avoid overflow) |
mint_nonce | uint64 | 8 | Caller’s current on-chain mint nonce (from mintNonces[wallet]) |
NonceUpdate is pushed to the client after each accepted TrafficCheckpoint. The client MUST use next_nonce in its following checkpoint.
| Поле | Тип | № | Описание |
|---|
session_uuid | string | 1 | — |
next_nonce | bytes | 2 | — |
Permission is one blind-signed entitlement, for one window. Carries the prepared message rather than the secret behind it: the randomized RFC 9474 variant signs the prepared form, so that is what a verifier checks and what identifies the permission in the spent set. The issuer and window are named because the signing key is registered per (issuer, window) — which is what pins a permission to its window, since the issuer signs blind and cannot police what is inside.
| Поле | Тип | № | Описание |
|---|
issuer_star_id | uint64 | 1 | — |
window | uint64 | 2 | — |
prepared_message | bytes | 3 | — |
signature | bytes | 4 | — |
issuer_public_key | bytes | 5 | DER; checked against the on-chain commitment |
Ping checks connection liveness.
| Поле | Тип | № | Описание |
|---|
timestamp | int64 | 1 | — |
Pong responds to Ping from CP.
| Поле | Тип | № | Описание |
|---|
timestamp | int64 | 1 | — |
RegisterCommitmentRequest asks the Control Plane to relay a subscriber’s registration into the on-chain SubscriberRegistry, so joining the anonymity set costs the subscriber no gas. The registry verifies register_sig itself: a Control Plane can decline to relay, but cannot register a commitment the wallet did not sign for. The wallet-to-commitment pair is public on chain once the transaction lands. A membership proof never reveals which commitment it used.
| Поле | Тип | № | Описание |
|---|
wallet | string | 1 | Ethereum wallet holding the subscription |
token_id | uint64 | 2 | Subscription NFT being registered |
deadline | int64 | 4 | EIP-712 Register intent expiry |
register_sig | bytes | 5 | 65-byte EIP-712 Register signature |
commitments | repeated bytes | 8 | 32-byte Semaphore identity commitments, one per slot at most counting those already registered for this Orbit. |
Зарезервированные номера полей: 3, 6, 7, commitment, gas_deadline, gas_sig.
RegisterCommitmentResult is sent back once the transaction is mined.
| Поле | Тип | № | Описание |
|---|
success | bool | 1 | — |
error_message | string | 2 | — |
tx_hash | string | 3 | — |
RenewSessionRequest asks to extend a session that is about to expire. A session is issued for a fixed stretch and then ends. Renewal is how a client that is still entitled carries on without the tunnel dropping — and, more to the point, how one that has stopped being entitled is noticed. The subscription is checked again here, so a lapsed one stops working within a renewal period rather than whenever its last session happened to run out. Send it before expiry, not after: once the session is gone the node has already removed the credentials and there is nothing left to extend.
| Поле | Тип | № | Описание |
|---|
request_id | string | 1 | — |
session_uuid | string | 2 | — |
permission | Permission | 4 | A permission from the window being renewed INTO. This is how an anonymous session carries on without the tunnel dropping, and how a lapsed subscription stops carrying on. A permission for the next window needs a fresh membership proof, so a subscriber whose commitment was removed cannot obtain one — and their session ends at the boundary without anything having to hunt it down. A permission rather than a proof because the permission is what counts sessions: renewing on a bare proof would extend a session without spending anything, and a permission would stop meaning one session. |
signature | bytes | 5 | Proof that the sender holds the session, so renewal works over any stream. An Ed25519 signature by the session’s checkpoint key — the key named in SessionRequest.checkpoint_public_key, the same one that signs TrafficCheckpoint — over keccak256(“singula-renew-v1” ‖ session_uuid ‖ 0x00 ‖ request_id ‖ timestamp) with session_uuid and request_id as UTF-8 bytes, a single zero byte between them, and timestamp as 8 bytes big-endian. The prefix keeps a checkpoint signature from being accepted as a renewal and the other way round. Without it, the renewal is accepted only on the stream that opened the session. |
timestamp | int64 | 6 | Unix seconds at signing. Refused when more than five minutes away from the Control Plane’s clock. |
Зарезервированные номера полей: 3, access.
RenewSessionResult answers RenewSessionRequest.
| Поле | Тип | № | Описание |
|---|
request_id | string | 1 | — |
session_uuid | string | 2 | — |
success | bool | 3 | — |
expires_at | int64 | 4 | new expiry, Unix seconds, when success is true |
error_message | string | 5 | — |
SessionCredentials contains encrypted VLESS connection info. Only the client can decrypt using ECDH shared secret.
| Поле | Тип | № | Описание |
|---|
request_id | string | 1 | Matches SessionRequest.request_id |
session_uuid | string | 2 | Session identifier |
node_id | string | 3 | Node identity (for signature verification) |
session_public_key | bytes | 5 | ECDH key exchange |
encrypted_credentials | bytes | 7 | Encrypted credentials |
nonce | bytes | 8 | AES-GCM nonce |
expires_at | int64 | 9 | Session limits |
challenge_nonce | bytes | 10 | Traffic checkpoint challenge nonce (client must include in first TrafficCheckpoint) |
egress_mode | EgressMode | 11 | How this session’s node reaches the internet. Reported from what the node observed rather than from its configuration, so a failed WARP registration is reported as direct. Self-reported. |
report_to_star_id | uint64 | 12 | Which Control Plane this session’s checkpoints must go to, as a Star id. Zero, and the answer is “the one you are talking to” — the ordinary case. It is set when the node serving you belongs to another operator: that Control Plane generated your challenge nonce and holds the node’s stream, so it is the only party that can turn your reports into payment for the node. Reporting to anyone else means the node works for nothing. Resolve the address yourself from CPEndpointRegistry rather than asking for it — a Star id names a party the chain can vouch for, an address does not. |
node_signer_signature | bytes | 13 | The node’s on-chain signer key over this session’s key exchange; see SessionReady.signer_signature in node.proto for the exact digest. Recover the address and compare it with StarRegistry.getSigner for the node: it is the one check here that a relaying Control Plane cannot satisfy for a node it does not run. |
Зарезервированные номера полей: 4, 6, node_master_public_key, master_key_signature.
SessionEnd notifies CP that client is ending session. A wallet session is ended by its wallet’s stream, as before. An anonymous session is ended by its own key: signature is the session’s Ed25519 checkpoint key over keccak256(“singula-end-v1” ‖ session_uuid (36 ASCII bytes) ‖ final_bytes_uploaded (u64 BE) ‖ final_bytes_downloaded (u64 BE) ‖ timestamp (i64 BE, Unix seconds)) over any stream, signed in or not. The Control Plane answers a signed end with SessionEndResult. Ending the session stops its permission’s lease, so the permission is free again across the network within one lease.
| Поле | Тип | № | Описание |
|---|
session_uuid | string | 1 | — |
final_bytes_uploaded | int64 | 2 | — |
final_bytes_downloaded | int64 | 3 | — |
timestamp | int64 | 4 | — |
signature | bytes | 5 | — |
SessionEndResult answers a signed SessionEnd.
| Поле | Тип | № | Описание |
|---|
session_uuid | string | 1 | — |
success | bool | 2 | — |
error_message | string | 3 | — |
SessionRequest requests a new VPN session.
| Поле | Тип | № | Описание |
|---|
request_id | string | 1 | Unique request ID |
preferred_country | string | 2 | Preferred country code (ISO 3166-1 alpha-2, empty = any) |
session_public_key | bytes | 3 | Fresh ephemeral key for this session (can be different from auth key) |
region_hint | string | 4 | Cover preset hint forwarded to the chosen node. Values: "" — same as “auto” “auto” — the “default” preset; nothing looks up where the client is “RU”|“CN”|“IR”|“default” — request a specific cover preset |
checkpoint_public_key | bytes | 7 | Ed25519 public key that signs this session’s TrafficCheckpoints in place of the wallet’s secp256k1 key. Required when access is set, ignored otherwise. Must be freshly generated per session. |
chained | bool | 8 | Set when the client reaches this node through another proxy rather than dialling it directly — the exit leg of a multi-hop chain. Such a leg cannot use XTLS Vision, which requires a direct dialer, so the client presents an empty flow and the node must register the account without Vision. The node cannot detect the extra hop itself, so the client declares it here and the Control Plane forwards it in PrepareSession. Set on the exit leg only. The entry leg is dialled directly and keeps Vision. |
access | AccessProof | 9 | Zero-knowledge proof of subscription, used instead of a wallet signature. When set, the request is valid without a prior ClientAuth on the stream: the proof is the credential. There is no issuance step — the client builds the proof itself. |
permission | Permission | 10 | One of the subscription’s permissions for this window. The proof and the permission answer different questions and neither replaces the other. The proof says the holder is still a subscriber right now — a commitment removed when a subscription lapsed cannot produce one. The permission says how many sessions that subscription may hold, and it says so across the whole federation, because it was signed blind and is spent from one shared set. Required on the anonymous path; a request without one is refused. |
node_star_id | uint64 | 11 | Serve this exact node, named by its on-chain Star id. Zero leaves the choice to the Control Plane, which is the ordinary case. Set, it is honoured or refused — never quietly substituted. A client that named a node did so for a reason the Control Plane cannot see, and giving it a different one would answer a question it did not ask. Candidates come from ListNodes. The Star id is what ties a node to its owner in StarRegistry, so a client that cares who runs its nodes can check that itself rather than take anyone’s word for it. Choosing also keeps the path out of the Control Plane’s hands: one that picked every node could steer its clients onto nodes it works with. |
Зарезервированные номера полей: 5, exclude_node_ids, 6, voucher.
SessionStatus provides real-time session updates.
| Поле | Тип | № | Описание |
|---|
session_uuid | string | 1 | — |
state | SessionState | 2 | — |
message | string | 3 | Human-readable status message |
| Поле | Тип | № | Описание |
|---|
is_active | bool | 1 | — |
expires_at | int64 | 2 | Unix timestamp |
tier | string | 3 | Subscription tier (e.g., “basic”, “pro”) |
bandwidth_limit | int64 | 4 | Monthly bandwidth limit in bytes (0 = unlimited) |
bandwidth_used | int64 | 5 | Bandwidth used this period |
token_id | int64 | 6 | Active NFT token ID (0 if not active) |
unactivated_count | int32 | 7 | Number of owned but not yet activated NFTs |
SubscriptionStatus provides subscription updates.
| Поле | Тип | № | Описание |
|---|
subscription | SubscriptionInfo | 1 | — |
message | string | 2 | e.g., “Subscription renewed”, “Expires soon” |
TakePacketRequest asks for one of the subscription’s permissions for this window, by its index. Each index is issued once per subscription per window, across the whole federation rather than per Control Plane: the proof is scoped to the window and the index, with no CP in it, so the nullifier it spends means the same thing everywhere. The message is blinded, so the issuer signs a value it never sees and cannot link the permission to the request it came from. Validity is pinned by which key signs: keys are registered per window in VoucherKeyRegistry, and a verifier accepts a permission only under the key for the window it is in. Retrying with the same blinded message returns the same signature and costs nothing: a client that lost the reply keeps its permission. A different message for an index already issued is refused with index_taken.
| Поле | Тип | № | Описание |
|---|
request_id | string | 1 | — |
access | AccessProof | 2 | Membership proof, built against the permission scope for this window and index rather than a CP scope. |
blinded | repeated bytes | 3 | Exactly one blinded permission message. |
index | uint32 | 4 | Which of the subscription’s permissions this is, below the published count. Each index is taken once per window, network-wide; a subscription’s devices each take their own. |
TakePacketResult carries the blind signature. The client unblinds it against the state it kept. What it holds afterwards is a pair — the prepared message and the signature — because the randomized variant signs the prepared form, not the secret behind it. A client that kept only its secret would hold a signature it cannot present.
| Поле | Тип | № | Описание |
|---|
request_id | string | 1 | — |
success | bool | 2 | — |
error_message | string | 3 | — |
blind_signatures | repeated bytes | 4 | — |
issuer_star_id | uint64 | 5 | The issuer, so the client knows whose key a verifier must look up, and the key itself so it need not fetch it from chain to present the permission. |
issuer_public_key | bytes | 6 | DER, SubjectPublicKeyInfo |
window | uint64 | 7 | — |
index_taken | bool | 8 | The index was taken already, by another of the subscription’s devices or an earlier request: try another. |
TrafficCheckpoint reports cumulative session traffic, signed by the client. Nodes collect these for reward claims. The signing scheme follows how the session was opened: • wallet-authenticated — 65-byte secp256k1 ECDSA over keccak256(payload), using the wallet key; • anonymous — 64-byte Ed25519 over the same payload, verified against SessionRequest.checkpoint_public_key. A wallet key must not sign checkpoints for an anonymous session.
| Поле | Тип | № | Описание |
|---|
session_uuid | string | 1 | — |
bytes_uploaded | int64 | 2 | — |
bytes_downloaded | int64 | 3 | — |
timestamp | int64 | 4 | Unix timestamp |
signature | bytes | 5 | secp256k1 or Ed25519, per the scheme above |
nonce | bytes | 6 | Current session nonce (rotated per checkpoint) |
VoucherSpend presents one unblinded blind-RSA voucher for redemption. Deprecated: superseded by AccessProof, and retained only for clients that have not yet migrated. The Control Plane verifies the signature against its own epoch key, checks the token against its local nullifier store, and marks it spent atomically with session creation. A voucher is redeemable only at the Control Plane that issued it.
| Поле | Тип | № | Описание |
|---|
issuer_star_id | uint64 | 1 | Must be the redeeming Control Plane’s Star id |
epoch | uint64 | 2 | Epoch the voucher was issued for |
token | bytes | 3 | The RFC 9474 prepared message: randomPrefix(32) ‖ clientToken(32) for the SHA384-PSS-Randomized variant — 64 bytes total. This is the value the blind signature verifies over. |
signature | bytes | 4 | RFC 9474 blind-RSA signature over token |
SessionState — SESSION_STATE_UNSPECIFIED = 0, SESSION_STATE_PREPARING = 1, SESSION_STATE_READY = 2, SESSION_STATE_ACTIVE = 3, SESSION_STATE_TERMINATED = 4, SESSION_STATE_ERROR = 5
ErrorCode — ERROR_CODE_UNSPECIFIED = 0, ERROR_CODE_AUTH_FAILED = 1, ERROR_CODE_SUBSCRIPTION_EXPIRED = 2, ERROR_CODE_SUBSCRIPTION_NOT_FOUND = 3, ERROR_CODE_NO_NODES_AVAILABLE = 4, ERROR_CODE_NODE_ERROR = 5, ERROR_CODE_SESSION_NOT_FOUND = 6, ERROR_CODE_RATE_LIMITED = 7, ERROR_CODE_INTERNAL = 8, ERROR_CODE_PERMISSION_SPENT = 9, ERROR_CODE_ACCESS_CLAIM_USED = 10
EgressMode — EGRESS_MODE_UNSPECIFIED = 0, EGRESS_MODE_DIRECT = 1, EGRESS_MODE_WARP = 2, EGRESS_MODE_CUSTOM = 3
AdminService — 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.
| Метод | Запрос | Ответ |
|---|
ListNodes | ListNodesRequest | ListNodesResponse |
GetNode | GetNodeRequest | GetNodeResponse |
GetHealth | HealthRequest | HealthResponse |
WatchNodes | WatchNodesRequest | поток NodeStatusEvent |
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: <domain> Wallet: <lowercase 0x address> Nonce: <hex> Issued at (unix): <timestamp> 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.
| Поле | Тип | № | Описание |
|---|
wallet | string | 1 | 0x… address that signed |
domain | string | 2 | site the user was signing in to |
timestamp | int64 | 3 | unix seconds; accepted for 24 hours |
nonce | string | 4 | hex, chosen by the dashboard |
signature | bytes | 5 | EIP-191 personal_sign of the message above |
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.
| Поле | Тип | № | Описание |
|---|
node_id | string | 1 | runtime UUID (derived from the signer address); empty for CP-only entries |
public_ip | string | 3 | — |
port | int32 | 4 | — |
country | string | 5 | — |
status | string | 7 | “active” | “offline” |
current_load | float | 8 | 0.0 – 1.0 |
max_sessions | int32 | 9 | — |
active_sessions | int32 | 10 | — |
used_bandwidth | int64 | 11 | bytes |
last_heartbeat | int64 | 12 | Unix timestamp (0 if never) |
owner_address | string | 13 | Ethereum wallet of the Star owner |
on_chain_id | string | 14 | StarRegistry starId (string for JS safety) |
created_at | int64 | 15 | Unix timestamp |
vless_public_key | string | 17 | x25519 Reality public key |
is_online | bool | 18 | true if the Star is connected to any Control Plane in the federation |
signer_address | string | 19 | secp256k1 hot signer address |
role | int32 | 20 | StarRegistry.Role: 0=None, 1=Node, 2=CP, 3=Banned |
cp_domain | string | 21 | 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. |
Зарезервированные номера полей: 2, name, 6, city, 16, master_public_key.
| Поле | Тип | № | Описание |
|---|
session_id | string | 1 | — |
node_id | string | 2 | — |
wallet | string | 3 | — |
xray_uuid | string | 4 | — |
status | string | 5 | — |
started_at | int64 | 6 | — |
expires_at | int64 | 7 | — |
bytes_up | int64 | 8 | — |
bytes_down | int64 | 9 | — |
| Поле | Тип | № | Описание |
|---|
token | string | 1 | Admin auth token |
epoch | uint64 | 2 | Epoch number |
on_chain_id | string | 3 | Node on-chain ID (from StarRegistry) |
| Поле | Тип | № | Описание |
|---|
epoch | uint64 | 1 | — |
on_chain_id | string | 2 | — |
owner | string | 3 | Node owner address (0x…) |
amount_wei | string | 4 | Reward amount in wei (string) |
merkle_root | string | 5 | Hex-encoded Merkle root |
proof | repeated string | 6 | Hex-encoded Merkle proof elements |
| Поле | Тип | № | Описание |
|---|
node_id | string | 2 | UUID |
auth | AdminAuth | 3 | — |
Зарезервированные номера полей: 1, token.
| Поле | Тип | № | Описание |
|---|
node | AdminNode | 1 | — |
| Поле | Тип | № | Описание |
|---|
token | string | 1 | — |
session_id | string | 2 | UUID or xray_uuid string |
| Поле | Тип | № | Описание |
|---|
session | AdminSession | 1 | — |
| Поле | Тип | № | Описание |
|---|
status | string | 1 | “healthy” | “degraded” |
time | int64 | 2 | Unix timestamp |
| Поле | Тип | № | Описание |
|---|
owner_filter | string | 2 | Optional: restrict to Stars owned by this address |
status | string | 3 | Optional: filter by “active” | “suspended” | “offline” |
limit | int32 | 4 | 0 = default (100) |
offset | int32 | 5 | — |
auth | AdminAuth | 6 | — |
Зарезервированные номера полей: 1, token.
| Поле | Тип | № | Описание |
|---|
nodes | repeated AdminNode | 1 | — |
total | int32 | 2 | — |
| Поле | Тип | № | Описание |
|---|
token | string | 1 | — |
node_id | string | 2 | UUID |
| Поле | Тип | № | Описание |
|---|
ok | bool | 1 | — |
message | string | 2 | — |
| Поле | Тип | № | Описание |
|---|
node_id | string | 1 | UUID |
status | string | 2 | “active” | “offline” | “suspended” | “pending” |
current_load | float | 3 | 0.0 – 1.0 |
active_sessions | int32 | 4 | — |
timestamp | int64 | 5 | Unix timestamp of the event |
| Поле | Тип | № | Описание |
|---|
token | string | 1 | — |
session_id | string | 2 | — |
One epoch’s aggregate voucher counters. No wallet addresses, no session ids — redemption is anonymous by construction and stays that way here.
| Поле | Тип | № | Описание |
|---|
epoch | uint64 | 1 | — |
vouchers_issued | uint64 | 2 | Blind signatures handed out |
vouchers_redeemed | uint64 | 3 | Nullified (spent) vouchers |
gb_issued | uint64 | 4 | vouchers_issued × denomination |
gb_redeemed | uint64 | 5 | vouchers_redeemed × denomination |
| Поле | Тип | № | Описание |
|---|
token | string | 1 | Auth token (validation delegated to admin-web, as elsewhere) |
| Поле | Тип | № | Описание |
|---|
enabled | bool | 1 | False when this CP has no voucher service |
denomination_gb | uint32 | 2 | — |
quota_per_epoch_gb | uint32 | 3 | — |
epochs | repeated VoucherEpochStats | 4 | Current epoch first, then previous epochs (up to ~7). |
| Поле | Тип | № | Описание |
|---|
token | string | 1 | Admin auth token |
wallet_address | string | 2 | Ethereum wallet to look up |
| Поле | Тип | № | Описание |
|---|
is_active | bool | 1 | true if subscription is currently active |
expires_at | int64 | 2 | Unix timestamp (0 if not active) |
active_token_id | int64 | 3 | NFT token ID that is currently active (0 if none) |
unactivated_token_ids | repeated int64 | 4 | Token IDs owned but never activated |
echo_balance_wei | string | 5 | Echo token balance in wei (string, avoids overflow) |
usdc_balance_wei | string | 6 | USDC balance in wei (string) |
| Поле | Тип | № | Описание |
|---|
auth | AdminAuth | 2 | — |
Зарезервированные номера полей: 1, token.