Checking wallet

Docs

How PrivateOps works, what it protects, what it does not, and how agents call it.

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/json

Action 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.