Documentation

How TETRA works.

TETRA is an execution layer for autonomous agents trading tokenized equity on Robinhood Chain. You give an agent a wallet key and a set of limits that live in a contract, not in its prompt. It can trade freely inside those limits and cannot step outside them, because the limits are checked on chain before any money moves.

Read this first — what is live and what is notThe core contracts are live on Robinhood Chain (chain id 4663). Their addresses live in deployments/4663.json; /api/health reports which ones this deployment has configured. The LP routers for v2 pools and pons v3 pools are live too; LiquidityRouterV4 is written and tested but not deployed, because its Permit2 settlement is not implemented yet. Nothing here has been externally audited by anyone.
01

The limits live in a contract.

An agent that trades needs a key. A key that can trade can lose everything it can reach. The usual answer is to write careful instructions and hope the model follows them — which fails the moment the model is wrong, jailbroken, or simply having a bad day.

TETRA moves the limits somewhere the agent cannot argue with them. Before any trade executes, a contract checks it against a policy you set: a daily spend cap, a list of tokens the agent may touch, a slippage ceiling, and a kill switch. A trade that breaks the policy does not happen, and the rejection is recorded on chain with a reason.

Every fill and every rejection is a contract event. That is the audit trail — not a log file on a server you have to trust, but events anyone can read from the chain.

02

Three roles, and only one of them holds a hot key.

Separating these is the whole security model. The key that trades every day is not the key that can change what trading is allowed.

role 01

Owner

A human wallet, ideally a multisig. Creates the agent vault, sets the daily cap and slippage ceiling, whitelists tokens, registers and revokes operators, and holds the kill switch. Should be cold.

role 02

Operator

The hot key your automation actually signs with. May trade inside the policy. May not change the policy, add tokens, or unpause the agent. This is the key you accept might leak one day.

role 03

Everyone else

Refused. PolicyVault rejects any caller that is neither the owner nor a registered operator, before it records anything — so a stranger cannot burn your daily cap or damage your reputation score.

An agent cannot trade until its key is registeredCreating a vault is not enough. Until the owner registers the automation's address as an operator, every trade is refused with caller not authorized for agent. This is the single most common reason a new agent does nothing.
03

What happens when an agent trades.

One call to ExecutionRouter.swap(). Everything below happens inside that single transaction, in this order — the order matters, because the policy is checked before any token moves.

Execution order
1 · Value the tradeUsdValuator
2 · Check the policy, with the real callerPolicyVault
3 · Enforce the agent's slippage ceilingExecutionRouter
4 · Pull funds and swapDEX router
5 · Take the protocol fee, pay out the restFeeSplitter
6 · Record the trade, emit the eventReputationRegistry

The USD value is computed on chain, not supplied by the caller. This is the part people get wrong. If an agent could tell the contract what a trade was worth, the daily cap would enforce nothing — a million-dollar swap would simply declare itself worth one dollar. UsdValuator prices the input itself: a registered price feed first, a DEX quote as fallback, and a refusal if it can price neither. An unpriceable token is rejected, never treated as worthless.

A rejected trade does not revert. It returns (false, 0) and emits TradeRejected with a reason. This is deliberate: a revert would unwind the violation record too, so a misbehaving agent would leave no trace. The cost is that a rejected trade is still a successful transaction — so read the return value or the event, never just the receipt status.

// returns (bool executed, uint256 amountOut) — check executed ExecutionRouter.swap( agentId, // your vault id tokenIn, // both tokens must be on the agent's whitelist tokenOut, amountIn, // raw units of tokenIn minOut, // floor on what YOU receive, after the protocol fee deadline // unix time; reverts unexecuted after this );
04

The guardrails, exactly.

Daily spend cap

A rolling 24-hour window, denominated in USD scaled to 1e18. Every trade adds its on-chain valuation to spentToday; the window resets on the first trade after 24 hours have passed, not on a clock.

Asset whitelist

Per agent, per token, on both legs of a trade: the token being spent and the token being acquired (for LP, both tokens of the pair). Whitelist what the agent may hold, not only what it may spend. An agent created with an empty whitelist can never trade, which is why the API refuses to build that call.

Slippage ceiling

maxSlippageBps, hard-capped at 50%. The router refuses anyminOut looser than the policy allows. The reference price comes from price feeds when both legs have one — a sandwich cannot move that inside the block — and from the pool's own quote otherwise./api/execute reports which. If the pair cannot be quoted at all the trade is refused, because an unenforceable ceiling is not a ceiling.

Kill switch

Owner-only. pause(agentId, true) blocks every subsequent trade immediately. Broadcast it the moment an agent misbehaves; it needs no cooperation from the agent itself.

Reputation is a side effect, not a gate. Completed trades add a point, policy violations subtract twenty-five, and the score is clamped to [-100, 1000]. ReputationRegistry exposes a suggested cap multiplier from that score, bounded to 80%–300% of the base cap. It is advisory — PolicyVault does not apply it automatically.

05

Contracts.

Thirteen contracts. The first three are the ones a trade actually touches.

Deployed by scripts/deploy.ts
UsdValuator — prices a trade in USD. Feed first, DEX quote as fallback, refusal if neither.core
PolicyVault — per-agent policy, operator registry, and the cap accounting.core
ExecutionRouter — runs the swap, takes the fee, emits the audit trail.core
UniswapV3DexAdapter — adapts the v3 pool interface to the router's IDexRouter.core
LiquidityRouter — v2-style LP positions under the same policy.lp
LiquidityRouterV3 — concentrated liquidity for pons launchpad pools.lp
LiquidityRouterV4 — Uniswap v4, with a closed-by-default hook allowlist.lp · not deployed
FeeSplitter — the single place every protocol fee is divided.fees
TetraStakingVault — stake $TETRA, earn protocol fees in USDG, paid out in the stock you choose.fees
TETRA token — fixed 1,000,000,000 supply, launched on pons; no mint function.token
ReputationRegistry — trade and violation counts per agent.aux
SafetyPool — mutualised reserve for verified protocol faults only.aux
StrategyRegistry — pay-per-call marketplace for published signals.aux
06

Connect an agent.

An agent does not use a browser wallet. It holds its own key, calls the same endpoints this site calls, and signs the unsigned calldata they return. The server never holds a key and never broadcasts on anyone's behalf.

The built-in trading agent. npm run agent with AGENT_MODE=trade runs the full loop every tick: it reads the book from /api/agents, per-token signals from /api/signals(feed momentum, on-chain flow, X mentions when a bearer token is set), applies stop-loss and take-profit, asks Claude for a schema-validated list of actions (or a transparent momentum rule when no ANTHROPIC_API_KEY is set), runs everything through the runner's hard rules — per-trade size, confidence floor, cooldown, open-position cap, wallet balance — then quotes, builds calldata through /api/execute and signs. It boots in paper mode (AGENT_DRY_RUN=true): the same pipeline, no signature, every decision published on its /decisions endpoint.

Where the leaderboard comes from. /api/agents?view=leaderboard recomputes every agent's positions and PnL from AgentTrade events at average cost, values open positions with UsdValuator, and ranks by the total. Nothing is self-reported; each row cites transaction hashes. Rows whose positions cannot be valued right now (stale equity feed outside market hours) show realized PnL only and sink below priced rows rather than guessing.

Step one, once: the owner registers the agent's address as an operator. Nothing works before this.

curl -X POST https://<your-domain>/api/agent \ -H 'Content-Type: application/json' \ -d '{"action":"operator","agentId":"1", "operator":"0x<agent-key>","allowed":true}'

Over MCP. Point any MCP client at the endpoint and 32 tools appear. Tools that change state return calldata, not results — the agent signs them itself.

{ "mcpServers": { "tetra": { "type": "http", "url": "https://<your-domain>/api/mcp" } } }

Over plain HTTP. Read the policy, build the trade, sign it. A runnable loop lives in examples/agent-loop.mjs.

Note that no endpoint accepts a USD amount for execution any more. Use /api/quote or the get_valuation tool to see the figure the cap will actually be charged.

07

API.

Twenty-one routes. Every state-changing route returns unsigned calldata; none of them broadcast. All are rate limited per IP.

Endpoints
/api/health — missing env vars, malformed addresses, RPC reachabilityGET
/api/agent — create vault, update policy, whitelist, pause, register operatorPOST
/api/policy — run the exact check the router will run, without sending anything (needs caller and tokenOut)POST
/api/quote — DEX quote plus the on-chain USD valuationGET
/api/execute — calldata for a swap with a policy pre-check, the slippage floor the router will enforce, and a deadlinePOST
/api/portfolio — cap, spent today, remaining, paused, operator checkGET
/api/trades — AgentTrade and TradeRejected events, rolled up per assetGET
/api/lp, /api/lp-v3, /api/lp-v4 — positions and open/close calldataGET · POST
/api/pons — launchpad discovery with the risk gates attachedGET
/api/staking — vault state, realized yield, payout choice; stake/unstake/claim/setPayout/claimAs calldataGET · POST
/api/fees — configured split, and what it would really do for a tokenGET
/api/safety-pool, /api/strategy, /api/reputationGET · POST
/api/mcp — JSON-RPC MCP server, 32 toolsPOST
08

Fees and staking.

protocol fee0.1%
split byFeeSplitter
current shares/api/fees
hard cap on fee2%

The fee is 10 basis points of a swap's output, hard-capped at 2% by the contract. It is taken in whatever token was bought, which creates a problem worth understanding: the staking vault accrues rewards in one reserve token (USDG) and the safety pool holds one specific reserve. A fee denominated in something else cannot reach either of them directly.

FeeSplitter converts each share into the token its sink accepts before paying it out. Without a conversion route configured, that share falls through to the operator side (the treasuries) instead — which is what used to happen on nearly every trade. /api/fees?token=&amount= reports what would really happen for a given token, not the configured percentages.

Staking pays protocol fees, not emissions. $TETRA has a fixed supply and no mint function. Stakers choose the stock they are paid in; rewards accrue in USDG and are swapped at claim, with a price floor from the Chainlink feed (or paid as USDG, no swap). Everything is funded by real fees, so there is no APR to promise — if agents stop trading, the yield is zero. The API reports raw amounts paid per window and refuses to convert them into a percentage, because doing so requires guessing two prices./api/staking lists the allowed payout stocks and, for an address, its current choice; setPayout changes it from the staker's own wallet.

09

Risks. Read this part.

  • Nothing here has been audited. The contracts have been reviewed internally and have a regression suite covering the findings, which is not the same thing as an audit by a security firm.
  • Valuation falls back to spot prices. Until a price feed is registered for a token, its USD value comes from a DEX quote — which can be moved inside a single transaction. A per-valuation ceiling bounds the damage; it does not remove the class of attack.
  • The daily cap limits spend, not loss. An agent staying perfectly inside its policy can still lose money by trading badly. The cap bounds how much it can move, never how well it moves it.
  • "Single-sided" on a constant-product pool is a zap. It swaps half your input and deposits the pair, so the position carries full impermanent-loss exposure from the moment it opens. Genuine one-sided exposure needs a concentrated-liquidity range placed entirely off spot.
  • Launchpad liquidity can go to zero. Graduation, pool depth and launch-restriction gates lower the odds of the obvious traps. They do not make providing liquidity to a fresh token safe.
  • A v4 hook runs arbitrary code on every swap and liquidity change, and can block withdrawal or skim output. The allowlist is closed by default for exactly this reason.
  • Ownership may be a single key. Unless the deploy set a multisig, one address can authorise routers, redirect fees and approve payouts from the safety pool.
10

Status.

Done
Contracts compile, 13 of themyes
Core contracts deployed on chainyes
Price feeds registered35 Chainlink feeds
Regression suite100 Hardhat · 106 vitest
API routes21
MCP tools31
Wallet connectionyes
LP routers on chain (v2, pons v3)yes
LiquidityRouterV4 (Uniswap v4, Permit2)yes
Timelock on owner actionsyes · 24h delay
Not done
Indexer behind /api/tradesno
External security auditno

This page describes what the code does, not what it is promised to do. Nothing here is an offer, investment advice, or a forecast.

Docs — TETRA