Docs

Everything about ORRA in one place: what it is, how each page works, how to build and run an agent, and the reference for the API, SDK and contracts. Everything here is a testnet with test tokens.

Start here

ORRA turns what a wallet or an agent actually did into a record that is read from chain state, and labels every number with how it was produced. It runs on Base Sepolia, with Arbitrum Sepolia as the paired chain for LayerZero reads. Tokens are test tokens priced from real Chainlink feeds, and trades fill on a sandbox venue at the oracle price minus a 0.1% fee.

What you need

  • A browser wallet (MetaMask, Coinbase Wallet, Phantom, Backpack or similar). The Connect wallet button lists every wallet it finds so you can pick one.
  • Base Sepolia selected in the wallet. The site offers to switch for you.
  • A little Base Sepolia ETH for gas when you send a transaction. Signing a message is free.

Core concepts

Agent
A key that can swap inside a vault and do nothing else. It is registered on-chain with a name, a description and a strategy fingerprint.
Bond
Test tokens (tORRA) the agent's operator stakes. If a vault the agent trades breaches its drawdown limit, part of the bond is slashed and paid to the vault owner. Unstaking has a delay.
Vault
A contract that holds an allocator's funds and tracks every deposit and withdrawal, so returns never mistake money added or removed for skill. Only the allocator can deposit or withdraw.
Mandate
The vault's hard limits: largest single trade, most per day, maximum drawdown, expiry and the agent's fee. The owner can tighten a mandate but never loosen it.
Allocator
The person who owns a vault and chose the agent. They can pause the vault, tighten limits and withdraw at any time.
Drawdown
How far the vault's return index has fallen from its peak. Past the mandate's limit the agent can no longer trade and anyone can report the breach.
Performance fee
A share of profit above a high-water mark, paid to the agent. Set in the mandate, capped at 30%, and the owner can only lower it.
Attestation
A record of a vault's or wallet's performance written into the on-chain registry. Anyone can write one. Other apps read it with one call.
Badge
How a number was produced. Verified comes from a vault that tracks every flow. Checkpointed is equity read through LayerZero, which mixes deposits with performance. Indexed is computed from on-chain events by our indexer. Self-reported is typed in by a user and never attested.
Hosted agent
An agent you build on the site. ORRA creates a throwaway key for it, pays its gas, lists it, posts its bond and runs it on a schedule. You stay the allocator, so the key can only trade.

What an agent can and cannot do

An agent can call swap on its vault, inside the mandate, between allowed tokens, while the vault is not paused, breached or expired. It has no function to withdraw, approve or move funds anywhere else. The contract enforces trade size, daily volume and drawdown on every swap.

Using the site

DeskLook up any address or use your own wallet. Shows holdings, trades, realized result and the seal that says how each number was produced. Claim test tokens from the faucet (one claim per token per day) and make sandbox trades.
AgentsSearch and sort every agent by followers, return, newest or bond. Reference agents run by ORRA are labelled.
LeaderboardAgents ranked by vault return next to worst drop and bond. A record needs 24 hours to be ranked.
Agent pageBond, strategy fingerprint, each vault's record, reactions and comments, and the form to open a vault. Hosted agents also show their rules and every decision.
FeedTrades, new agents and vaults, attestations, fees, halts and comments. Everyone, or only what you follow.
BuildPick a strategy, set its limits and sign once. See Build an agent below.
MeYour setup checklist, handle, the agents you built, agents you run and vaults you own. Pause, resume, change or retire hosted agents here.
NetworkRead a wallet across Base Sepolia and Arbitrum Sepolia with LayerZero, and send a vault's reputation to Arbitrum.
JournalNotes on your sandbox trades. Stored in your browser only and never sent to a server. Export any time.
AlertsDiscord webhook alerts and Atom feeds for an agent.
StrategiesThe strategies the builder offers and what testing on past prices showed.
MethodologyEvery formula behind every number.
StatusWhat works, what is verified, what is waiting on access, and a LayerZero message tracker.

Opening a vault

  1. Claim tUSD from the faucet on the Desk.
  2. Open an agent's page and set the limits: largest single trade, most per day, the drawdown that halts it, the fee on profit and how many days the mandate lasts.
  3. Create the vault, approve tUSD and deposit. The agent can now trade inside those limits.
  4. Watch the record build. You can pause the vault, tighten the limits or withdraw whenever you want.

Build an agent

On the site (no code)

  1. Open Build, connect a wallet and choose a strategy. Set its parameters and how often it decides (hourly, every 4 hours or daily).
  2. Name it and sign one free message. The message contains the exact rules, so what you sign is what runs.
  3. ORRA creates a throwaway testnet key for the agent, sends it gas, lists it, posts its bond and starts it. This takes about a minute.
  4. Open a vault for it from your own wallet. Until a vault hires it, the agent waits and checks again every couple of minutes.
  5. Change the rules, pause, resume or retire from the Me page. Changing the rules publishes a new on-chain fingerprint.

Each wallet can have 3 hosted agents at a time. The agent's key is encrypted on the server, can only call swap on vaults that hire it, and never leaves the server. Every decision, including holds and skipped trades, is logged on the agent's page.

With your own code

Write a strategy as one function and run it on your own machine with the agent kit. The agent is a throwaway key that holds nothing but gas.

export AGENT_PRIVATE_KEY=0x…   # a throwaway testnet key, never a real wallet
# send it a little Base Sepolia ETH from a faucet, then:
pnpm --filter @orra/agent-kit agent setup \
  --deployment packages/contracts/deployments/baseSepolia.json \
  --rpc https://sepolia.base.org \
  --name "My Agent" --description "What it does, in one line" --deposit 5000

pnpm --filter @orra/agent-kit agent run \
  --deployment packages/contracts/deployments/baseSepolia.json \
  --rpc https://sepolia.base.org \
  --strategy ./my-strategy.ts --every 300

setup claims the bond from the faucet, lists the agent, posts the bond and, with --deposit, opens and funds a vault owned by the same address. run loops: read prices and balances, call your strategy, send the swaps. Add --once for a single pass.

import type { StrategyFn } from "@orra/agent-kit";

const strategy: StrategyFn = (s) => {
  // s.prices   token symbol to USD price (the oracle that fills your trades)
  // s.balances units the vault holds
  // s.vault    { navUsd, paused, mandate: { maxSwapUsd, maxDailyUsd, maxDrawdownBps, expiry } }
  // s.memory   your own state, kept between runs
  // s.now      unix seconds
  const cash = s.balances.tUSD * s.prices.tUSD;
  if (cash > 0.3 * s.vault.navUsd) return { action: "swap", from: "tUSD", to: "tWETH", amountUsd: 100, note: "adding tWETH" };
  return { action: "hold", note: "nothing to do" };
};
export default strategy;

A strategy returns one decision or a list. The runner clamps every swap to 90% of the mandate's per-trade and daily limits and to what the vault holds, and sets a minimum-output floor. A swap the contract would reject is simulated first and skipped with a reason, so a bad decision costs no gas. The same building blocks the builder uses are exported: LIBRARY (trend, breakout, mix and the rest), seedHistory and fetchDailyHistory.

Getting on the board

An agent ranks once a vault that hired it has 24 hours of record. Records come from the vault's own accounting, so there is nothing to submit. Anyone can write the record on-chain by calling attestVault, which gives it a Verified attestation other apps can read.

Strategies

The builder offers six strategies, all long-only between tUSD, tWETH and tWBTC with slow signals, because fast trading loses most of its gains to the 0.1% fee. The strategies page has what each one does and the backtest figures. The method, tables and limits are in STRATEGY_RESEARCH.md, and research/ reproduces every number.

Trend RiderHolds a token while price is above its long moving average, with a dead zone to avoid flip-flopping and a risk dial that shrinks the position when markets are wild.
BreakoutBuys a new N-day high and sells a break of the M-day low.
Core and TrendA steady, rebalanced core plus a trend sleeve.
Rebalanced MixFixed shares of ETH, BTC and cash, rebalanced when they drift.
Easy InBuilds a target position gradually over N days.
Hold a target shareKeeps a fixed share in one token. The baseline an active agent should beat.

Strategies sample one price per UTC day and remember up to 300 days. New hosted agents start with a year of public daily closes, so they act on day one. Backtests use past prices, assume no slippage and are not a promise.

REST API

Read-only endpoints need no key. Responses are JSON unless noted. The API caches answers briefly, so a new agent or trade can take up to about 30 seconds to appear. Add ?fresh=1 to bypass the cache, for example right after you send a transaction. The base URL is wherever the API runs; for a local stack that is http://localhost:4000.

Records and status

GET/v1/healthService status, network and chain idIncludes indexer, alert and hosted-agent counters when those are on.
GET/v1/deploymentContract addresses for this network
GET/v1/chainsWhich chains are configured and what is missing for each
GET/v1/methodologyEvery formula and badge definition, as used by the app
GET/v1/agentsAll registered agents with bond, status and vault addresses
GET/v1/agents/:idOne agent and its vaults with their records404 if the id does not exist.
GET/v1/vaults/:addressMandate, verified performance, checkpoint series, fee state
GET/v1/vaults/:address/benchmarkThe vault against simply holding ETH over the same windowUses Chainlink history. Null if there is not enough data.
GET/v1/leaderboardAgents ranked by vault return, with worst drop, bond and record lengthNeeds 24 hours of record to rank. Halted vaults are removed from the ranking.
GET/v1/attestations/:subjectEvery attestation for a vault or wallet, plus the latest
GET/v1/wallets/:address/summarySandbox holdings, concentration, trade stats and the dataset fingerprintBadge is Indexed, or Checkpointed when a cross-chain read exists.
GET/v1/users/:address/overviewEverything the Me page shows for a wallet: checklist, agents run, vaults owned
GET/v1/profile/:addressPassport: best badge, weekly review and points
GET/v1/cards/:address.svgShare card image (also .png)Caches for 60 seconds. States testnet and no value on the card.
GET/v1/badge/:address.svgSmall embeddable badge with the date it was readCaches for 5 minutes.
GET/v1/crosschain/configLayerZero setup for the Base and Arbitrum pair
GET/v1/crosschain/:addressValue, checkpoints and reputation for a wallet across the paired chains
GET/v1/messages/tx/:hashWhere a LayerZero message is, from LayerZero ScanReturns found: false until Scan has indexed the transaction.
GET/v1/feedActivity feed: trades, new agents and vaults, attestations, fees, halts, commentsQuery: scope=all|following, follower=<address> (required for following), limit=1..100 (default 40).
GET/v1/events.xmlAtom feed of protocol events
GET/v1/agents/:id/feed.xmlAtom feed for one agent
GET/v1/openapi.jsonMachine-readable route list
GET/v1/admin/preflightDeployment self-check: wiring, tokens, oracle, bonds, LayerZero, ownershipRead-only and safe to expose.
GET/v1/admin/statsProtocol totals

Social (signed writes)

GET/v1/social/agentsFollower counts for every agent
GET/v1/social/agents/:idReactions and comments for one agentQuery: viewer=<address> adds that wallet's own reaction and follow state.
GET/v1/social/profile/:addressHandle, bio, followers and following for a wallet
GET/v1/social/following/:addressAgents and wallets this wallet follows
POST/v1/social/profileSet your handle and bioSigned. Handles are 3 to 20 lowercase letters, numbers or underscores. A few names are reserved.
POST/v1/social/followFollow or unfollow an agent or walletSigned.
POST/v1/social/reactReact to an agent with signal, solid or risky, or clear your reactionSigned. One reaction per wallet per agent.
POST/v1/social/commentComment on an agentSigned. 10 per minute.
POST/v1/social/comment/deleteDelete your own commentSigned.

Hosted agents

GET/v1/hosted/configWhether the builder is on, the templates, schedules, tokens and per-wallet limit
GET/v1/hosted/agents/:idOne hosted agent: rules, status, decision logNo keys are ever returned.
GET/v1/hosted/owner/:addressHosted agents built by a wallet
GET/v1/hosted/by-agent/:agentIdThe hosted record behind an on-chain agent id
POST/v1/hosted/agentsCreate a hosted agentSigned by the owner. 6 per hour per address.
POST/v1/hosted/agents/:id/pausePauseSigned by the owner.
POST/v1/hosted/agents/:id/resumeResumeSigned by the owner.
POST/v1/hosted/agents/:id/updateChange the rulesSigned by the owner. Publishes a new on-chain strategy fingerprint.
POST/v1/hosted/agents/:id/retireRetire permanentlySigned by the owner. The agent stops trading.

Alerts

POST/v1/alertsSubscribe a Discord webhookBody: webhook (Discord URL), agent (id or *), events (optional list). 10 per hour. The delete token is shown once.
GET/v1/alerts/:idDescribe a subscription without exposing the webhook
DELETE/v1/alerts/:id?token=…Unsubscribe with the token you were given

Examples

curl http://localhost:4000/v1/leaderboard
curl http://localhost:4000/v1/agents/1
curl "http://localhost:4000/v1/feed?scope=following&follower=0xYourAddress&limit=20"
curl http://localhost:4000/v1/attestations/0xVaultAddress

Errors are { "error": "message" } with a 400 for bad input, 403 for a bad signature or wrong owner, 404 for something that does not exist, 409 for a conflict such as a full agent quota, 429 when you are rate limited and 503 when a feature is off.

TypeScript SDK

The SDK reads everything the API serves, straight from the chain, so you can run it without the API.

import { createOrra } from "@orra/sdk";

const orra = createOrra({ deployment, rpcUrl: "https://sepolia.base.org" });

const agents = await orra.getAgents();                 // bond, status, vaults
const board = await orra.getLeaderboard();             // ranked and unranked
const vault = await orra.getVault(vaultAddress);       // mandate, return, drawdown, checkpoints
const [latest] = (await orra.getAttestations(vaultAddress)).slice(-1);
if (latest?.badge === "VERIFIED" && latest.returnIndex > 1.05) {
  // the vault's own accounting shows more than a 5% gain
}
const wallet = await orra.getWalletSummary(address);   // holdings, trades, stats, badge
const check = await orra.preflight();                  // deployment self-check

Other reads: getBenchmark, getProfile, getUserOverview, getCrossChain, getProtocolStats and tokenInfo. Pass a comma-separated list of RPC URLs, or set RPC_FALLBACKS, and the client falls back across providers automatically.

Contracts

Addresses for this network are in packages/contracts/deployments/*.json and at /v1/deployment.

AgentRegistryregister, update, setActive. One entry per agent with its operator, description and strategy fingerprint.
BondManagerstake, requestUnstake, withdraw. Holds bonds and lets vaults created by the factory slash them.
VaultFactorycreateVault(agentId, mandate). Checks the agent's bond, tracks vaults by owner and by agent, and holds the allowed-token list.
OrraVaultdeposit, withdrawToken, setPaused, tightenMandate (owner only). swap (agent only). mark, crystallizeFee, reportBreach (anyone).
AttestationRegistryattestVault, attestWallet, recordCheckpoint. Read with latestOf, getAttestation, idsOf, checkpointAt.
SandboxVenue and ChainlinkPriceOracleSwaps at the Chainlink price minus 0.1%, minting and burning test tokens. Reads revert if a feed is stale.
OrraRead, OrraEquityReader, ReputationSyncLayerZero lzRead of a wallet's value on the other chain, and delivery of a vault's attestation to Arbitrum.

Mandate

struct Mandate {
  uint128 maxSwapUsd;      // largest single trade, USD with 18 decimals
  uint128 maxDailyUsd;     // most per UTC day, must be >= maxSwapUsd
  uint16  maxDrawdownBps;  // 1 to 10000; past it the agent is halted
  uint64  expiry;          // unix seconds; no swaps after this
  uint16  perfFeeBps;      // agent's share of profit above the high-water mark, at most 3000
}

Read an attestation from a contract

(bool found, , AttestationRegistry.Attestation memory a) = registry.latestOf(vault);
require(found && a.badge == AttestationRegistry.Badge.VERIFIED && a.flowsProven, "no proven record");
require(a.maxDrawdownBps <= 2000, "too risky");

Check flowsProven before trusting a return. A checkpointed record always has it set to false, because a wallet's equity includes deposits and withdrawals.

Breaches

If a vault's drawdown passes its limit, the agent can no longer trade it. Anyone can call reportBreach(), which marks the vault breached and slashes part of the agent's bond to the vault owner.

Signed requests

Anything that changes your handle, follows, reactions, comments or hosted agents is proven with a wallet signature, which is free and moves no funds. Nothing is stored for an address that did not sign, a signature is valid for 5 minutes, and a signature cannot be used twice. The text you sign names the action, the target and the time:

ORRA social v1          (or: ORRA hosted v1)
action: comment
agent: 3
body: Nice drawdown control
issued: 1794435692
Testnet only. This signature moves no funds.

Sign it with personal_sign (EIP-191) and send the address, time and signature with the request body. Your wallet shows this exact text before you sign. A hosted agent's rules are part of the signed text, so what you approve is what runs.

Alerts and feeds

Subscribe a Discord webhook on the Alerts page, or use Atom feeds in any reader: /v1/events.xml for everything and /v1/agents/:id/feed.xml for one agent.

Event types: listed, opened, attestation, halted, paused, resumed, drawdown (past 50% or 80% of the limit), bond, fee and weekly. Only Discord webhook URLs are accepted. The webhook is stored on the server and never shown again, and one that fails ten deliveries is dropped. Keep the delete token you get when you subscribe.

Limits

API reads120 requests per minute per address.
Signed writes30 per minute per address. Comments 10 per minute.
Create a hosted agent6 per hour per address, and 3 live agents per wallet, 50 in total.
Alert subscriptions10 per hour per address.
Hosted agent scheduleThe builder offers hourly, every 4 hours or daily. The API accepts every 10 minutes at the fastest.
FaucetOne claim per token per address per day.
Performance feeAt most 30% of profit above the high-water mark.
LeaderboardA record needs 24 hours of vault history. Halted vaults are not ranked.
Signature window5 minutes.

Troubleshooting

The page says Loading from the chain for a long timeThe API is waiting on the chain provider. The page shows the last answer it has when there is one, and a Try again button when there is not. If you run the stack yourself, open /status, then check the API log. Slow or rate-limited RPC providers are the usual cause; add more in RPC_FALLBACKS.
Connect wallet opens the wrong walletUse the chooser that opens when you click Connect wallet. Disconnecting asks your wallet to forget the site. If a wallet ignores that, remove the site from its connected sites.
I have two wallet extensions and the console shows provider errorsExtensions fight over window.ethereum. The site uses the standard wallet discovery, so it can still see each of them. Disable the one you do not use if connecting misbehaves.
My agent is Live but not tradingIt has no vault yet, or the strategy has nothing to do. Open its page and read the decision log: hold, below trend and waiting for a breakout are normal. It checks again every couple of minutes until a vault hires it.
A new agent or trade is missingThe API caches for a short time. Wait up to 30 seconds, or reload the page.
Wrong network bannerSwitch your wallet to Base Sepolia (chain id 84532). The banner has a button for it.
A transaction failsCheck you have test ETH for gas and the tokens you are spending. Vault errors name the cause: TokenNotAllowed, SwapTooLarge, DailyLimit, OverDrawdown, Expired, Paused or Breached.
Faucet says waitEach token allows one claim per day per address.
Signature rejectedYour clock may be off by more than 5 minutes, or the request was signed once already. Try again.
Hosted builder is switched offThe server needs HOSTED_SECRET and HOSTED_FUNDER_KEY. See LAUNCH.md.

Glossary

bps
Basis points. 100 bps is 1%.
Calmar
Return divided by worst drop. Used in the strategy research.
CAGR
Compound yearly growth rate.
Checkpoint
A recorded snapshot of a vault's index or a wallet's equity at a time.
Dead zone
A band around a moving average inside which a trend strategy does not change its mind.
High-water mark
The highest value the fee has been paid on. Fees are only paid on profit above it.
lzRead
A LayerZero request that reads another chain's state and delivers the answer back.
Mandate
A vault's hard limits for its agent.
NAV
Net asset value: what the vault holds, at oracle prices.
Operator
The address that controls an agent.
Return index
1.00 at the start, moved by each trade's result, never by deposits or withdrawals.
Sharpe
Return per unit of volatility.
Slashing
Taking part of an agent's bond when a vault breaches its drawdown limit.
tUSD, tWETH, tWBTC, tORRA
Test tokens with no value. tORRA is used for bonds.

What this is not

  • It is a testnet. Tokens have no value and nothing here promises a future token, airdrop or return.
  • Listing an agent is not an endorsement. Reactions, comments and backtests are opinions and history, not proof.
  • Short records swing widely and one window is not a verdict.
  • ORRA is independent and is not affiliated with or endorsed by LayerZero or any network it reads from.
  • Nothing here is financial advice.