Skip to content

gRPC API reference

Everything the open API contains: every service, every method, and every field of every message.

This page is generated from the .proto files, so it cannot drift from reality. The files themselves can be downloaded and fed to a client generator:

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.

MethodRequestResponse
Connectstream ClientMessagestream 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.

FieldTypeNo.Description
epochuint641Epoch 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_rootbytes2Merkle 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.
nullifierbytes3Derived 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.
indexuint324Quota slot within the epoch, in [0, quota). Each slot yields a distinct nullifier. Bound into the proof and cannot be restated afterwards.
pointsrepeated bytes5Groth16 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.

FieldTypeNo.Description
token_iduint641NFT token ID to activate
walletstring2Client’s Ethereum wallet address
deadlineint643Unix timestamp — inner-signature expiry
signaturebytes4EIP-712 signature produced by the client’s wallet
gas_deadlineint645Unix timestamp — relayClient meta-tx expiry
gas_sigbytes665-byte EIP-191 sig over relayClient digest
commitmentsrepeated bytes8The 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.

Reserved field numbers: 7, commitment.

ActivationResult is sent back after the activation transaction is mined.

FieldTypeNo.Description
successbool1—
error_messagestring2Set if success = false
tx_hashstring3On-chain tx hash (set if success = true)
token_iduint644Activated token ID
expires_atint645Subscription 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.

FieldTypeNo.Description
walletstring1Client’s Ethereum wallet address
token_iduint642The Orbit to add to
extrauint323Slots to add; the Orbit ends up with at most 5
commitmentsrepeated bytes432-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_coststring5Max USDC agreed to (wei, string)
deadlineint646Unix timestamp — EIP-712 intent expiry
sigbytes765-byte EIP-712 AddSlots signature
permit_deadlineint648Unix timestamp — ERC-20 permit expiry
permit_vuint329—
permit_rbytes10—
permit_sbytes11—
gas_deadlineint6412Unix timestamp — relayClient meta-tx expiry
gas_sigbytes1365-byte EIP-191 sig over relayClient digest
FieldTypeNo.Description
successbool1—
error_messagestring2Set if success = false
tx_hashstring3On-chain tx hash (set if success = true)

AuthResult is sent after ClientAuth.

FieldTypeNo.Description
successbool1—
error_messagestring2Set if success = false
subscriptionSubscriptionInfo3Subscription info

ClientAuth authenticates client using wallet signature.

FieldTypeNo.Description
wallet_addressstring1Ethereum wallet address (0x…)
signaturebytes2EIP-191 personal_sign of the sign-in message
timestampint643Unix timestamp (for replay protection)
noncestring4Random nonce (for replay protection)
public_keybytes5Client’s ephemeral public key for ECDH
client_infoClientInfo6Client info
FieldTypeNo.Description
app_versionstring1App version (e.g., “1.0.0”)
platformstring2Platform (e.g., “ios”, “android”, “windows”)
preferred_countrystring3Preferred country code (ISO 3166-1 alpha-2, e.g., “US”)

oneof message — Exactly one of the fields below.

FieldTypeNo.Description
authClientAuth1—
session_requestSessionRequest2—
traffic_checkpointTrafficCheckpoint3—
session_endSessionEnd4—
pongPong5—
activation_requestActivationRequest6—
get_network_infoGetNetworkInfoRequest7—
mint_requestMintRequest8—
list_countriesListCountriesRequest10—
register_commitmentRegisterCommitmentRequest11—
renew_sessionRenewSessionRequest12—
take_packetTakePacketRequest13—
get_issuer_keyGetIssuerKeyRequest14—
list_nodesListNodesRequest15—
faucet_requestFaucetRequest16—
add_slots_requestAddSlotsRequest17—
governance_requestGovernanceRequest18—

Reserved field numbers: 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.

FieldTypeNo.Description
star_iduint641On-chain Star id. Everything about who runs this node — owner, role — is read from StarRegistry under this id, by the client, from the chain.
countrystring2Country as the node reports it about itself.
latency_msuint323Round 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.
loadfloat4Current 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.
availablebool5—

oneof message — Exactly one of the fields below.

FieldTypeNo.Description
auth_resultAuthResult1—
session_credentialsSessionCredentials2—
session_statusSessionStatus3—
errorError4—
pingPing5—
subscription_statusSubscriptionStatus6—
activation_resultActivationResult7—
network_infoNetworkInfo8—
nonce_updateNonceUpdate9—
mint_resultMintResult10—
list_countries_responseListCountriesResponse12—
register_commitment_resultRegisterCommitmentResult13—
renew_session_resultRenewSessionResult14—
take_packet_resultTakePacketResult15—
get_issuer_key_resultGetIssuerKeyResult16—
list_nodes_resultListNodesResult17—
session_end_resultSessionEndResult18—
faucet_resultFaucetResult19—
add_slots_resultAddSlotsResult20—
governance_resultGovernanceResult21—

Reserved field numbers: 11.

CountryEntry describes one country with a positive node count.

FieldTypeNo.Description
countrystring1ISO 3166-1 alpha-2
node_countint322Currently available (heartbeat-fresh, has capacity)

Error reports an error condition.

FieldTypeNo.Description
codeErrorCode1—
messagestring2—
request_idstring3Related 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.

FieldTypeNo.Description
walletstring1Wallet to mint to; must be the authenticated one
amountstring2Token amount (smallest units, decimal string)
gas_deadlineint643Unix timestamp — relayClient meta-tx expiry
gas_sigbytes465-byte EIP-191 sig over the relayClient digest
FieldTypeNo.Description
successbool1—
error_messagestring2Set if success = false
tx_hashstring3On-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.

FieldTypeNo.Description
request_idstring1—

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.

FieldTypeNo.Description
request_idstring1—
successbool2—
error_messagestring3—
issuer_star_iduint644—
windowuint645—
issuer_public_keybytes6DER, SubjectPublicKeyInfo
packet_sizeuint327How 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.

FieldTypeNo.Description
walletstring1The signer; no sign-in is needed
callbytes2ABI-encoded participant call
act_deadlineint643Unix timestamp — the Act signature’s expiry
act_sigbytes465-byte EIP-712 sig over Act
gas_deadlineint645Unix timestamp — relayClient meta-tx expiry
gas_sigbytes665-byte EIP-191 sig over the relayClient digest
FieldTypeNo.Description
successbool1—
error_messagestring2Set if success = false
tx_hashstring3On-chain tx hash (set if success = true)

ListCountriesRequest asks for the countries that currently have at least one available node.

FieldTypeNo.Description
request_idstring1—

ListCountriesResponse returns the aggregated country list. Order is not guaranteed — the client should sort for display.

FieldTypeNo.Description
request_idstring1—
countriesrepeated CountryEntry2—

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.

FieldTypeNo.Description
request_idstring1—

ListNodesResult carries the candidates, in no particular order.

FieldTypeNo.Description
request_idstring1—
nodesrepeated ClientNode2—

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.

FieldTypeNo.Description
pay_with_echobool1true = ECHO (20% off), false = USDC
walletstring2Client’s Ethereum wallet address
max_coststring3Max token amount agreed to (wei, string)
mint_deadlineint644Unix timestamp — EIP-712 mint intent expiry
mint_sigbytes565-byte EIP-712 MintRequest signature
permit_deadlineint646Unix timestamp — ERC-20 permit expiry
permit_vuint327ERC-20 permit signature v
permit_rbytes8ERC-20 permit signature r (32 bytes)
permit_sbytes9ERC-20 permit signature s (32 bytes)
gas_deadlineint6410Unix timestamp — relayClient meta-tx expiry
gas_sigbytes1165-byte EIP-191 sig over relayClient digest
slotsuint3212Concurrent 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.

FieldTypeNo.Description
successbool1—
error_messagestring2Set if success = false
tx_hashstring3On-chain tx hash (set if success = true)
token_iduint644Minted 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.

FieldTypeNo.Description
chain_idint641EVM chain ID (421614 = Arbitrum Sepolia)
rpc_urlstring2A public RPC. A hint; see the note above.
subscription_nft_addrstring3SubscriptionNFT contract address
echo_token_addressstring4EchoToken ERC-20 address
usdc_token_addressstring5USDC ERC-20 address
price_usdc_weistring6Current USDC price in wei (string to avoid overflow)
price_echo_weistring7Current Echo price in wei (string to avoid overflow)
mint_nonceuint648Caller’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.

FieldTypeNo.Description
session_uuidstring1—
next_noncebytes2—

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.

FieldTypeNo.Description
issuer_star_iduint641—
windowuint642—
prepared_messagebytes3—
signaturebytes4—
issuer_public_keybytes5DER; checked against the on-chain commitment

Ping checks connection liveness.

FieldTypeNo.Description
timestampint641—

Pong responds to Ping from CP.

FieldTypeNo.Description
timestampint641—

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.

FieldTypeNo.Description
walletstring1Ethereum wallet holding the subscription
token_iduint642Subscription NFT being registered
deadlineint644EIP-712 Register intent expiry
register_sigbytes565-byte EIP-712 Register signature
commitmentsrepeated bytes832-byte Semaphore identity commitments, one per slot at most counting those already registered for this Orbit.

Reserved field numbers: 3, 6, 7, commitment, gas_deadline, gas_sig.

RegisterCommitmentResult is sent back once the transaction is mined.

FieldTypeNo.Description
successbool1—
error_messagestring2—
tx_hashstring3—

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.

FieldTypeNo.Description
request_idstring1—
session_uuidstring2—
permissionPermission4A 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.
signaturebytes5Proof 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.
timestampint646Unix seconds at signing. Refused when more than five minutes away from the Control Plane’s clock.

Reserved field numbers: 3, access.

RenewSessionResult answers RenewSessionRequest.

FieldTypeNo.Description
request_idstring1—
session_uuidstring2—
successbool3—
expires_atint644new expiry, Unix seconds, when success is true
error_messagestring5—

SessionCredentials contains encrypted VLESS connection info. Only the client can decrypt using ECDH shared secret.

FieldTypeNo.Description
request_idstring1Matches SessionRequest.request_id
session_uuidstring2Session identifier
node_idstring3Node identity (for signature verification)
session_public_keybytes5ECDH key exchange
encrypted_credentialsbytes7Encrypted credentials
noncebytes8AES-GCM nonce
expires_atint649Session limits
challenge_noncebytes10Traffic checkpoint challenge nonce (client must include in first TrafficCheckpoint)
egress_modeEgressMode11How 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_iduint6412Which 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_signaturebytes13The 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.

Reserved field numbers: 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.

FieldTypeNo.Description
session_uuidstring1—
final_bytes_uploadedint642—
final_bytes_downloadedint643—
timestampint644—
signaturebytes5—

SessionEndResult answers a signed SessionEnd.

FieldTypeNo.Description
session_uuidstring1—
successbool2—
error_messagestring3—

SessionRequest requests a new VPN session.

FieldTypeNo.Description
request_idstring1Unique request ID
preferred_countrystring2Preferred country code (ISO 3166-1 alpha-2, empty = any)
session_public_keybytes3Fresh ephemeral key for this session (can be different from auth key)
region_hintstring4Cover 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_keybytes7Ed25519 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.
chainedbool8Set 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.
accessAccessProof9Zero-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.
permissionPermission10One 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_iduint6411Serve 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.

Reserved field numbers: 5, exclude_node_ids, 6, voucher.

SessionStatus provides real-time session updates.

FieldTypeNo.Description
session_uuidstring1—
stateSessionState2—
messagestring3Human-readable status message
FieldTypeNo.Description
is_activebool1—
expires_atint642Unix timestamp
tierstring3Subscription tier (e.g., “basic”, “pro”)
bandwidth_limitint644Monthly bandwidth limit in bytes (0 = unlimited)
bandwidth_usedint645Bandwidth used this period
token_idint646Active NFT token ID (0 if not active)
unactivated_countint327Number of owned but not yet activated NFTs

SubscriptionStatus provides subscription updates.

FieldTypeNo.Description
subscriptionSubscriptionInfo1—
messagestring2e.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.

FieldTypeNo.Description
request_idstring1—
accessAccessProof2Membership proof, built against the permission scope for this window and index rather than a CP scope.
blindedrepeated bytes3Exactly one blinded permission message.
indexuint324Which 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.

FieldTypeNo.Description
request_idstring1—
successbool2—
error_messagestring3—
blind_signaturesrepeated bytes4—
issuer_star_iduint645The 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_keybytes6DER, SubjectPublicKeyInfo
windowuint647—
index_takenbool8The 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.

FieldTypeNo.Description
session_uuidstring1—
bytes_uploadedint642—
bytes_downloadedint643—
timestampint644Unix timestamp
signaturebytes5secp256k1 or Ed25519, per the scheme above
noncebytes6Current 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.

FieldTypeNo.Description
issuer_star_iduint641Must be the redeeming Control Plane’s Star id
epochuint642Epoch the voucher was issued for
tokenbytes3The 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.
signaturebytes4RFC 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.

MethodRequestResponse
ListNodesListNodesRequestListNodesResponse
GetNodeGetNodeRequestGetNodeResponse
GetHealthHealthRequestHealthResponse
WatchNodesWatchNodesRequeststream 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.

FieldTypeNo.Description
walletstring10x… address that signed
domainstring2site the user was signing in to
timestampint643unix seconds; accepted for 24 hours
noncestring4hex, chosen by the dashboard
signaturebytes5EIP-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.

FieldTypeNo.Description
node_idstring1runtime UUID (derived from the signer address); empty for CP-only entries
public_ipstring3—
portint324—
countrystring5—
statusstring7“active” | “offline”
current_loadfloat80.0 – 1.0
max_sessionsint329—
active_sessionsint3210—
used_bandwidthint6411bytes
last_heartbeatint6412Unix timestamp (0 if never)
owner_addressstring13Ethereum wallet of the Star owner
on_chain_idstring14StarRegistry starId (string for JS safety)
created_atint6415Unix timestamp
vless_public_keystring17x25519 Reality public key
is_onlinebool18true if the Star is connected to any Control Plane in the federation
signer_addressstring19secp256k1 hot signer address
roleint3220StarRegistry.Role: 0=None, 1=Node, 2=CP, 3=Banned
cp_domainstring21Control 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.

Reserved field numbers: 2, name, 6, city, 16, master_public_key.

FieldTypeNo.Description
session_idstring1—
node_idstring2—
walletstring3—
xray_uuidstring4—
statusstring5—
started_atint646—
expires_atint647—
bytes_upint648—
bytes_downint649—
FieldTypeNo.Description
tokenstring1Admin auth token
epochuint642Epoch number
on_chain_idstring3Node on-chain ID (from StarRegistry)
FieldTypeNo.Description
epochuint641—
on_chain_idstring2—
ownerstring3Node owner address (0x…)
amount_weistring4Reward amount in wei (string)
merkle_rootstring5Hex-encoded Merkle root
proofrepeated string6Hex-encoded Merkle proof elements
FieldTypeNo.Description
node_idstring2UUID
authAdminAuth3—

Reserved field numbers: 1, token.

FieldTypeNo.Description
nodeAdminNode1—
FieldTypeNo.Description
tokenstring1—
session_idstring2UUID or xray_uuid string
FieldTypeNo.Description
sessionAdminSession1—
FieldTypeNo.Description
statusstring1“healthy” | “degraded”
timeint642Unix timestamp
FieldTypeNo.Description
owner_filterstring2Optional: restrict to Stars owned by this address
statusstring3Optional: filter by “active” | “suspended” | “offline”
limitint3240 = default (100)
offsetint325—
authAdminAuth6—

Reserved field numbers: 1, token.

FieldTypeNo.Description
nodesrepeated AdminNode1—
totalint322—
FieldTypeNo.Description
tokenstring1—
node_idstring2UUID
FieldTypeNo.Description
okbool1—
messagestring2—
FieldTypeNo.Description
node_idstring1UUID
statusstring2“active” | “offline” | “suspended” | “pending”
current_loadfloat30.0 – 1.0
active_sessionsint324—
timestampint645Unix timestamp of the event
FieldTypeNo.Description
tokenstring1—
session_idstring2—

One epoch’s aggregate voucher counters. No wallet addresses, no session ids — redemption is anonymous by construction and stays that way here.

FieldTypeNo.Description
epochuint641—
vouchers_issueduint642Blind signatures handed out
vouchers_redeemeduint643Nullified (spent) vouchers
gb_issueduint644vouchers_issued × denomination
gb_redeemeduint645vouchers_redeemed × denomination
FieldTypeNo.Description
tokenstring1Auth token (validation delegated to admin-web, as elsewhere)
FieldTypeNo.Description
enabledbool1False when this CP has no voucher service
denomination_gbuint322—
quota_per_epoch_gbuint323—
epochsrepeated VoucherEpochStats4Current epoch first, then previous epochs (up to ~7).
FieldTypeNo.Description
tokenstring1Admin auth token
wallet_addressstring2Ethereum wallet to look up
FieldTypeNo.Description
is_activebool1true if subscription is currently active
expires_atint642Unix timestamp (0 if not active)
active_token_idint643NFT token ID that is currently active (0 if none)
unactivated_token_idsrepeated int644Token IDs owned but never activated
echo_balance_weistring5Echo token balance in wei (string, avoids overflow)
usdc_balance_weistring6USDC balance in wei (string)
FieldTypeNo.Description
authAdminAuth2—

Reserved field numbers: 1, token.