Docs
The model
Your wallet deploys an AgentVault: a small immutable contract that holds funds and that only your wallet owns. For each agent, PrivateOps generates a session key. Your wallet then writes a policy for that key into the vault. From that point the vault will execute an action only if it is signed by the session key and fits the policy.
The agent never holds your wallet's key, and neither does PrivateOps. PrivateOps holds the session key, encrypted, so it can sign on the agent's behalf. If that key leaked, the holder could do exactly what the policy allows and nothing more, until you pause or revoke it.
What “private” means here
Private means separate: an agent's operating authority is kept apart from your main wallet. The agent works with a session key that can only do what its policy allows, and your wallet's key is never shared with it or with PrivateOps.
It does not mean hidden. Robinhood Chain is a public blockchain. PrivateOps does not hide transactions, mix funds, or make an address anonymous. Every transfer a vault or an operational account makes is visible to anyone, forever.
- The vault, the recipient and the amount of every action are public once it lands, and so is the account that paid its gas.
- A vault's owner is your wallet, onchain, by design.
- The RPC endpoint sees which addresses your browser asks about, and your IP address.
Agent policy
Every rule below is checked by the vault contract on each action. None of them lives only in this app.
- Assets and limits
- Per asset: a maximum per transaction and a maximum per UTC day, in the asset's own base units. No USD conversion, no price oracle.
- Recipients
- An allowlist of addresses. Optional any-recipient mode must be switched on explicitly.
- Contract calls
- An allowlist of (contract, function selector) pairs. Token transfer and approval selectors can never be listed.
- Native value
- ETH attached to an allowed call counts against the ETH limits.
- Validity
- A start time and a mandatory expiry. Keys cannot be made permanent.
- Replay protection
- Each action carries a unique nonce and a deadline; a signature works once.
- Pause
- Per agent, or the whole vault at once. Owner withdrawals keep working while paused.
- Revoke
- Permanent. A revoked key can never be authorised again; PrivateOps then erases its copy.
Agents cannot swap, bridge or approve. Protocol integrations are added as audited action adapters that an owner allowlists per agent; none are configured unless this deployment lists them, and the app shows no controls for adapters that do not exist.
Who pays gas
Every action is checked the same way, signed by the agent's session key, and executed by the vault. What differs is who sends the transaction and pays for it. It is always stated, in the request and in the record, and never chosen for you.
- Caller-paid
- The default. PrivateOps returns a signed, ready transaction: a target, calldata for
AgentVault.execute(action, signature), and an expiry. Any account that can pay gas may send it. The signature covers that one action, nonce and deadline; changing any field makes the vault refuse it. If it expires unsent, it is not re-signed: request a new action. - PrivateOps managed relay
- PrivateOps' executor account sends the transaction and pays. Off by default. It works only when the operator has switched it on and put your owner wallet on its allowlist; otherwise the request is refused with
MANAGED_RELAY_UNAVAILABLE.
On this deployment the managed relay is switched off.
Agent API
Authenticate with the agent's API key. Every write needs an Idempotency-Key; repeating a request with the same key returns the original action instead of executing twice.
POST https://useprivateops.xyz/api/v1/agents/{agentId}/actions?execution=caller_paid
Authorization: Bearer po_xxxxxxxx_…
Idempotency-Key: invoice-2041
Content-Type: application/jsonAction bodies
Amounts are strings of integer base units (for a 6-decimal token, 20 tokens is "20000000").
{ "type": "erc20_transfer", "token": "0x…", "to": "0x…", "amount": "20000000" }
{ "type": "native_transfer", "to": "0x…", "amount": "10000000000000000" }
{ "type": "contract_call", "target": "0x…", "data": "0x…", "value": "0" }Responses
execution is caller_paid (the default) or managed_relay. A caller-paid request answers 200 with the action already prepared: send prepared.data to prepared.to with zero value, from any funded account, before prepared.expiresAt.
{
"action": {
"id": "5b0c…",
"status": "prepared",
"execution": "caller_paid",
"prepared": {
"chainId": 4663,
"to": "0x… (the vault)",
"data": "0x… (execute(action, signature))",
"value": "0",
"signature": "0x…",
"action": { "kind": 1, "asset": "0x…", "target": "0x…", "amount": "20000000", "data": "0x", "nonce": "…", "deadline": 1790000000 },
"expiresAt": "2026-…Z"
},
"txHash": null,
"error": null,
"request": { "type": "erc20_transfer", … }
},
"replayed": false
}Poll GET /api/v1/agents/{agentId}/actions/{actionId} to see it become confirmed (with the transaction hash) once your relayer's transaction is mined, or expired. A managed request answers 202 with the queued action; add ?wait=30 to hold the connection until it settles (up to 30 seconds).
- queued
- Managed relay only. Accepted and waiting for the worker.
- validating
- Being checked against the live onchain policy and the vault balance.
- simulating
- Signed by the session key and dry-run against the vault contract.
- prepared
- Caller-paid only. Checked and signed; the response carries the transaction to send. Nothing is onchain yet.
- submitted
- Managed relay only. Broadcast by the executor. The response carries the transaction hash.
- confirmed
- Mined and confirmed. Only now has the action happened.
- expired
- Caller-paid only. The signature's deadline passed before anyone relayed it. Nothing was spent; request a new action.
- rejected
- Refused before anything was signed for broadcast: policy, balance or simulation said no. Nothing was spent.
- failed
- Could not be completed (for example the RPC stayed unreachable).
Other endpoints
GET https://useprivateops.xyz/api/v1/agents/{agentId}/policy live onchain limits and remaining daily allowance
GET https://useprivateops.xyz/api/v1/agents/{agentId}/actions recent actions (limit, before)Why an action was rejected
action.error.code is stable and machine-readable; action.error.message explains it with the numbers involved.
- VAULT_PAUSED
- The owner paused the vault.
- AGENT_DISABLED
- The owner paused this agent.
- AGENT_REVOKED
- The session key was permanently revoked.
- AGENT_EXPIRED / AGENT_NOT_YET_VALID
- Outside the session key's validity window.
- ASSET_NOT_ALLOWED
- No allowance exists for that asset.
- PER_TX_LIMIT_EXCEEDED
- Above the per-transaction limit.
- DAILY_LIMIT_EXCEEDED
- Would exceed what remains of today's limit (UTC day).
- RECIPIENT_NOT_ALLOWED
- Recipient is not on the allowlist.
- CALL_NOT_ALLOWED
- Contract or function selector is not on the allowlist.
- ADAPTER_NOT_ALLOWED
- Adapter is not allowed for this agent.
- INSUFFICIENT_VAULT_BALANCE
- The vault holds less than the amount requested.
- TARGET_REVERTED
- The call was allowed, but the target contract reverted in simulation.
HTTP errors use the same shape, { "error": { "code", "message" }, "requestId" }: 400 validation, 401 bad key, 409 idempotency key reused or agent revoked, 403 managed relay not available to this owner, 429 rate limited (with Retry-After).
Operational accounts
A side tool, separate from agent vaults: ordinary accounts whose keys stay in this browser. Keys are generated with the platform's secure random source, encrypted with a password you choose (PBKDF2-SHA-256, 600,000 iterations, then AES-256-GCM) and stored in this browser's IndexedDB. The plaintext key exists only in the tab's memory while the account is unlocked; it locks after 15 minutes without use.
PrivateOps' servers are never sent these keys, and are not told which operational accounts you have. Your operation history for them is kept in this browser only. The price of that is that there is no recovery: export the encrypted backup, and remember the password.
An operational account pays its own gas in ETH, so it must be funded before it can send. That funding, like every transfer it makes, is public.
Who you trust
- The vault contract. It is immutable and has no admin other than your wallet. Its limits hold even if PrivateOps' servers are compromised.
- PrivateOps' server, to keep session keys encrypted and to act only on authenticated requests. A breach exposes at most what each agent's policy allows.
- The RPC endpoint and whoever relays, to submit what was signed. They can delay or drop a transaction and observe it, but cannot alter it.
- Your browser and device, with operational-account keys while they are unlocked.
The emergency path never depends on PrivateOps: pause, revoke and withdraw are functions on your vault that your wallet can call from any tool, including the block explorer.