Documentation
Holder access
An installation has one owner, who signs in with a password and administers everything. When the operator configures an access token, it also accepts holder accounts: any wallet that holds at least the required amount of that token can sign in with a signature and use the console with its own model key. This page describes exactly what a holder gets, what stays apart, and where the edges are.
Holder access uses VCMD. The default minimum balance is 2,000,000 tokens. The official contract address is published here when available: Contract address to be announced.
Signing in
- The sign-in page asks the browser's wallet extension (MetaMask, Rabby and the like) for an address.
- The API issues a one-time message in the shape of EIP-4361: it names the console's domain, the address, the chain id, a random nonce and an expiry (10 minutes). The message says what it is for.
- The wallet signs it with
personal_sign. This is a signature, not a transaction: nothing is sent to the chain and no gas is paid. - The API recovers the signer from the signature and checks that it is the address from step 1. The nonce is deleted whether or not the signature verifies, so it cannot be replayed.
- The API reads the token balance of that address on the chain, over plain JSON-RPC (
eth_callofdecimals()once andbalanceOf(address)per check). The threshold is the configured whole-token amount scaled by the contract's decimals. Below it, the sign-in is refused with the balance that was read; at or above it, a session is created and the account exists from then on.
No wallet library runs in the browser and none on the server: signatures are recovered with @noble/curves, and the chain is read with fetch.
What a holder gets
Everything a holder does happens inside their own account:
| Owner | Holder | |
|---|---|---|
| Tasks, approvals, events, transcripts | own | own |
| Workspace files | the configured workspace folder | <data>/users/<account id>/workspace |
| Artifacts | the configured artifact store | <data>/users/<account id>/artifacts |
| Memory entries and retrieval | own | own |
| Schedules | own | own |
| Console defaults (Settings → Defaults) | own | own |
| Provider connections | own; may use keys from the server's environment | own; must store their own key (encrypted) |
| Audit log | own events plus setup and failed sign-ins | own events |
| Live change stream | own tasks | own tasks |
| Administration of accounts | yes | no |
Every table that holds something a person can see carries the account id, and every query in the API filters on it. A holder who asks for an id that belongs to another account gets 404, the same answer as for an id that does not exist. The worker resolves each task's folders from the task's account, so a task's file tools can only list, read and search that account's workspace, and its artifacts are written to that account's store.
Keys named in the server's environment (OPENAI_API_KEY, ANTHROPIC_API_KEY, VCMD_PROVIDER_KEY_*) belong to the owner. A holder's connection cannot reference them: the API refuses the request, and the worker refuses to resolve such a row even if it were written to the database directly. A holder's own key is stored encrypted with VCMD_ENCRYPTION_KEY, exactly like the owner's stored keys, and is used only for that account's tasks. If the server has no encryption key, holders cannot connect a model at all; the console says so.
Checked again while signed in
The balance is read again after VCMD_GATE_RECHECK_MINUTES (30 by default), on the next request the account makes, and by the worker before it starts one of the account's tasks. Only one check runs at a time per account.
- Below the threshold: the account is signed out with a
gate_failedanswer, and tasks that were queued are parked as paused with the reason in their event log. Nothing is deleted. Once the wallet holds enough again, signing in works and paused tasks can be resumed. - Chain unreachable: the previous verdict stands for a grace period (six recheck intervals, at least an hour) and the error is shown in the account's settings. If the chain stays unreachable beyond that, access pauses until it can be read again. A definite answer from the chain (for example, no contract at the configured address) is never treated as a temporary error.
Limits on holder accounts
The owner's account has no ceilings. Holder accounts start with these and the owner can change them per account in Settings → Holders:
| Limit | Default |
|---|---|
| Open tasks at a time | 1 |
| Tasks per rolling 24 hours | 30 |
| Model turns per task, at most | 60 |
| Runtime per task, at most | 1,800 s |
| Schedules | 3 |
| Provider connections | 3 |
| Memory entries | 200 |
| Workspace files | 50 MiB |
| Artifacts | 100 MiB |
fetch_url | not allowed |
fetch_url is off because its requests leave from the server's own address; the owner can allow it for accounts they trust with that. A task that asks for more than the account allows is refused before it is queued, with the reason. Scheduled runs count against the same ceilings; a run that does not fit is recorded on the schedule as an error.
Blocking and deleting
- Block: the account is signed out everywhere at once, its open tasks are cancelled and its schedules are switched off. Sign-in is refused with
blockeduntil the owner unblocks it. Its data is kept. - Delete: the account and everything it holds are removed: rows by cascade, files by removing its folder. The wallet can sign in again afterwards and starts empty.
- Re-check: reads the balance from the chain now, outside the interval.
Blocking, re-checking, deleting and limit changes are recorded in the owner's audit log.
Configuration
Set on the server, for the API and the worker alike:
| Variable | Default | Meaning |
|---|---|---|
VCMD_GATE_TOKEN | empty (wallet sign-in off) | ERC-20 contract address of the access token |
VCMD_GATE_MIN_BALANCE | 2000000 | Whole tokens a wallet must hold |
VCMD_GATE_CHAIN_ID | 4663 | Chain id the RPC endpoint must report |
VCMD_GATE_RPC_URL | https://rpc.mainnet.chain.robinhood.com | JSON-RPC endpoint the server reads balances from. Robinhood's own host is DNS-blocked by Indonesian resolvers (it resolves to a government block page), so a server there needs another public endpoint for chain 4663; the hosted console uses https://robinhood-rpc.publicnode.com, and https://robinhood.drpc.org answered too |
VCMD_GATE_CHAIN_NAME | Robinhood Chain | Shown on the sign-in page and in the signed message |
VCMD_GATE_EXPLORER_URL | the Robinhood Chain explorer | Links from the console to the token and to addresses |
VCMD_GATE_TOKEN_SYMBOL | VCMD | Shown next to amounts |
VCMD_GATE_RECHECK_MINUTES | 30 | How often a signed-in wallet is checked again |
VCMD_USERS_DIR | <data dir>/users | Where holder accounts' folders live |
The console reads the same facts from GET /api/auth/state (gate), so the sign-in page and the Holders tab need no configuration of their own. On the site, the token block reads vcmdContractAddress from the connected Vercel Global Config store. Changes appear throughout the site without rebuilding. Keep the access service configured with the same official contract.
What this does not protect against
- The operator. Whoever administers the server can read its disk and its database, including every account's files, transcripts and encrypted keys (with the encryption key from the same server). The separation described here is enforced by the API and the worker; it is not encryption per account, and it is not a substitute for trusting the operator.
- A stolen wallet. The signature is the only credential. Whoever controls the wallet controls the account; there is no second factor and no password.
- Shared capacity. Holders share the server's CPU, disk and network with the owner. The limits above bound each account; they do not make the server larger.
- Chain data. The balance is whatever the configured RPC endpoint reports. If that endpoint lies, the gate believes it. Use an endpoint you trust.
Verification
Verify wallet signatures, expired nonces, insufficient balances, and unavailable chain connections before enabling holder access.