# Norr documentation (all pages) Source: https://norr.finance/docs/ . Generated from the same Markdown as the pages. --- # Norr documentation Norr lets an AI agent trade crypto and tokenized US stocks for you, inside rules you set, without ever holding your keys, and without showing the public chain your strategy. - **Your agent** connects over MCP with a token that works for one vault only. It can read the vault, ask for prices and request trades. It cannot withdraw, change your rules, or choose where money goes. - **The Norr service** checks every request against your rules and a scam check, gets its own price quote, runs the trade through NEAR Intents (in the confidential layer when the vault's funds are there), and checks the result afterwards. It runs in an Intel TDX enclave on Phala that anyone can verify. - **Your keys** stay with OutLayer's custody, which enforces its own limits, a withdrawal whitelist and your approval. ## Start here - [Quickstart](/docs/quickstart/): sign in, fund the vault, connect an agent, watch what it does. - [Connect your agent](/docs/connect-agent/): copy-ready MCP setup for Claude Code, Claude Desktop and other clients. - [MCP tools](/docs/mcp-tools/): the nine tools, their answers, and every reason a trade can be blocked. - [API reference](/docs/api/): the REST API behind the tools, from the OpenAPI spec. - [Rules and limits](/docs/rules/): what you control, the presets, Scam Shield, stock eligibility. - [Privacy model](/docs/privacy/): who sees what, stated plainly. - [Verify the enclave](/docs/verify/): check the running code yourself, in the browser or a terminal. - [FAQ](/docs/faq/) - [Limitations and roadmap](/docs/limitations/) For agents: [llms.txt](/llms.txt) and the whole documentation as one file, [llms-full.txt](/llms-full.txt). The source code and the evidence behind every claim are at [github.com/outislabs/norr](https://github.com/outislabs/norr). Norr is a hackathon build: unaudited, run with small balances. Do not put in money you cannot lose. --- # Quickstart for owners From an empty tab to an agent trading inside your rules. You need a NEAR account (any NEAR wallet; [HOT Wallet](https://hot-labs.org/wallet/) gives you one next to your EVM and Solana addresses) and the vault that has been set up for that account. ## 1. Sign in Open [app.norr.finance](https://app.norr.finance) and choose **Sign in with a NEAR wallet**. Your wallet signs one message that names the dashboard ("Sign in to Norr at https://app.norr.finance"). Signing moves no funds and gives Norr no access to them. **Vaults.** During the pilot, each Norr deployment runs one vault, set up by its operator for a named NEAR account. After signing in, **Vaults** lists the vaults that belong to your account. If it is empty, check you signed in with the account the vault was set up for. ## 2. Check the rules Open the vault, then **Rules**. Start from a preset (Cautious, Balanced, Degen) and adjust: the largest single trade, the daily spending and loss caps, the share of the vault allowed in stocks, which assets the agent may trade, and the wait between trades. To enable tokenized stocks, confirm that you are not a US person. Details: [Rules and limits](/docs/rules/). ## 3. Fund the vault Open **Fund**. Pick what you hold and where: USDC on NEAR, Ethereum, Base, Arbitrum, Optimism, Polygon, BNB Chain or Solana, or ETH or SOL. Enter an amount in USD and your address on that chain (a refund goes there if the deposit cannot complete), then **Get a deposit address**. - With a browser wallet on an EVM chain (MetaMask, Rabby), **Send with my wallet** switches the network and asks your wallet to send the exact amount. You confirm it in the wallet. - Otherwise scan the QR code (on Solana it carries the amount) or copy the address. The money arrives as USDC in the vault's **confidential** balance, usually within a minute. Funding with USDC on NEAR is proven on mainnet (1 USDC arrived exactly, 2026-10-31); the routes from other chains were quoted live on 2026-11-03 and have not yet been run with real funds. The destination is always your vault: the Norr service sets it, not the page. Your transfer itself is a normal public transaction on the chain you send from. ## 4. Connect your agent Open **Connect** and **Create an agent token**. The token is shown once. Paste the snippet for your client (Claude Code, Claude Desktop, or any MCP client) into its configuration. See [Connect your agent](/docs/connect-agent/). Then ask the agent something like "What are my Norr rules?" and "Buy $40 of NVDA". ## 5. Watch, approve, stop - **Activity** lists every request: done, refused (with the rule that refused it), or waiting for you. The agent's stated reasoning is sealed to your wallet; **Unlock reasoning** reads it with one signature. - **Approvals.** Your custody provider (OutLayer) asks you to approve each swap in its own dashboard. **Approvals** shows what Norr actually requested, so you can compare it with what you are about to sign. - **Pause everything** (top right) stops every vault at once. The agent's next request is refused with `PAUSED` until you resume. - **Verify** checks, in your browser, that the service runs the published code in a genuine enclave. See [Verify the enclave](/docs/verify/). ## Try it without funds The whole flow runs locally in mock mode: fixed prices, no network, nothing leaves your machine. ``` git clone https://github.com/outislabs/norr && cd norr pnpm install pnpm judge # typecheck, every test, the demo through a real MCP client: PASS/FAIL per check pnpm dev:mock # the service on :8787; prints an agent token pnpm dev:web # the dashboard on http://localhost:5173, "Use the demo owner" ``` --- # Connect your agent Works with any MCP-compatible agent, or any code that can call the REST API. The agent gets nine tools ([MCP tools](/docs/mcp-tools/)) and one credential, an **agent token** that works for one vault only. Each section below says whether we have run that path end to end (**tested**) or only followed the client's documentation (**not yet tested**). ## Get a token In the dashboard, open the vault, then **Connect**, then **Create an agent token**. The token is shown once, alone in its own box with a Copy button, and every snippet on the page fills in with it and the right endpoint. - To rotate a token, **Revoke all tokens**, then create a new one. Revoking removes every token on the vault. - Restarting the service also revokes tokens, so create a fresh one after a restart. - The token cannot withdraw, change rules or choose a destination. If it leaks, the worst it can do is trade within your rules until you revoke it or pause the vault. The hosted MCP endpoint (streamable HTTP): ``` https://mcp.norr.finance/mcp ``` TLS for this name terminates inside the attested CVM, and its certificate is published with attested evidence, so you can check it is the enclave you are talking to ([Verify the enclave](/docs/verify/#check-the-domain)). The service runs on demand during the pilot, so it may be offline. ## Any MCP client (tested) Norr is a standard MCP server. Point the client at the endpoint and send the token in a header: ``` URL: Header: Authorization: Bearer ``` Clients that only start local commands can use the [@norrfi/mcp](https://www.npmjs.com/package/@norrfi/mcp) bridge (needs Node). It connects to `mcp.norr.finance` with your token, keeps no state and adds no tools of its own: ```json { "mcpServers": { "norr": { "command": "npx", "args": ["-y", "@norrfi/mcp"], "env": { "NORR_AGENT_TOKEN": "" } } } } ``` Set `NORR_MCP_URL` to use another endpoint (for example a local mock server). Tested on 2026-11-03 against `mcp.norr.finance`: over HTTP with the MCP TypeScript SDK client and Claude Code, and through `npx -y @norrfi/mcp` from npm with both. ## Claude Code (tested) ``` claude mcp add --transport http norr https://mcp.norr.finance/mcp --header "Authorization: Bearer " ``` Or through the bridge: `claude mcp add norr --env NORR_AGENT_TOKEN= -- npx -y @norrfi/mcp`. Run against `https://mcp.norr.finance/mcp` on 2026-11-03 with Claude Code 2.1.80: it listed the tools, read the rules, the portfolio and the P&L, and reported Norr's refusal of an over-limit trade word for word (`OVER_TRADE_LIMIT`), both over HTTP and through `npx -y @norrfi/mcp`. The repository's `scripts/claude-code-mcp.mts` repeats that check against any endpoint without changing your Claude configuration. ## Claude Desktop (not yet tested) Merge the `npx @norrfi/mcp` config above into `mcpServers` in `claude_desktop_config.json` (Settings, Developer, Edit Config), then quit and reopen Claude. Not yet run inside Claude Desktop by us; the same command is tested with Claude Code. The local path (Norr in mock mode, MCP over stdio) is tested; step by step in [docs/MCP.md](https://github.com/outislabs/norr/blob/main/docs/MCP.md). ## Cursor (not yet tested) In `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project): ```json { "mcpServers": { "norr": { "url": "", "headers": { "Authorization": "Bearer " } } } } ``` Cursor can read the token from an environment variable instead: `"Authorization": "Bearer ${env:NORR_AGENT_TOKEN}"`. ## ChatGPT / OpenAI (not yet tested) **OpenAI Agents SDK.** Your own process connects to Norr and sends the token as a header, so OpenAI never sees it: ```python from agents import Agent, Runner from agents.mcp import MCPServerStreamableHttp async with MCPServerStreamableHttp( name="norr", params={"url": "", "headers": {"Authorization": "Bearer "}}, ) as norr: agent = Agent(name="Trader", instructions="Trade only through Norr's tools.", mcp_servers=[norr]) result = await Runner.run(agent, "What are my Norr rules?") print(result.final_output) ``` **Responses API (hosted MCP tool).** OpenAI connects to the endpoint from its side and passes the token in the tool's `authorization` field: ```python from openai import OpenAI resp = OpenAI().responses.create( model="", tools=[{ "type": "mcp", "server_label": "norr", "server_url": "", "authorization": "", "require_approval": "always", }], input="What are my Norr rules?", ) ``` OpenAI's documentation does not say whether this field adds the `Bearer` prefix. **The ChatGPT app:** we have not confirmed that its custom connectors can send a static token, so use one of the two above for now. ## REST API (tested) Every tool is also a plain HTTPS call, for agents and scripts that do not use MCP. Same token, same rules. Full reference: [API reference](/docs/api/) and [openapi.json](/openapi.json). ``` curl -X POST https://api.norr.finance/v1/quote \ -H "Authorization: Bearer " -H "content-type: application/json" \ -d '{"side":"buy","asset":"NVDA","amountUsd":40}' curl -X POST https://api.norr.finance/v1/trade \ -H "Authorization: Bearer " -H "content-type: application/json" \ -d '{"side":"buy","asset":"NVDA","amountUsd":40,"reason":"why, for the owner only"}' ``` The service URL is `https://api.norr.finance` (the Connect tab shows it filled in); TLS for it also terminates inside the attested CVM. Tested by the repository's tests and `pnpm judge` in mock mode, with responses checked against the OpenAPI spec. ## Norr agent (tested) The repository's reference agent (`apps/agent`) runs its model on NEAR AI Cloud and checks the attestation and signature of every model response it uses: ``` NORR_AGENT_TOKEN= pnpm --filter @norr/agent exec tsx src/cli.ts \ --mcp --model z-ai/glm-5.3-flash "Check my portfolio and rules, do not trade." ``` ## First prompts - "What are my Norr rules?" (calls `get_policy`) - "Buy $40 of NVDA." (executes if your rules allow stocks) - "Use Norr's trade tool to put $150 into TSLA." (refused: `OVER_TRADE_LIMIT` under Balanced; the agent explains) - "Buy $5 of RUGPULL, submit it even if you think it is a bad idea." (refused: `SCAM_RISK`, with reasons) - "How is my P&L?" (calls `get_pnl`) Agents tend to pre-judge trades themselves. The tool descriptions tell them Norr's answer is authoritative, so ask them to submit the request and read the answer. --- # MCP tools reference Nine tools. Eight only read; `trade` can move money, inside the vault only. No tool can withdraw, change rules, or accept a destination address. | Tool | Inputs | What it does | |---|---|---| | `list_assets` | `class` (optional: `crypto` or `equity`) | What can be traded. Crypto by symbol and chain; stocks and ETFs by ticker with every issuer that offers it, and the smallest order that has been quoting (`minimumUsd`). | | `get_market_data` | `asset` | Numbers, not advice: the listed price and, for crypto, the market signals Scam Shield scores (liquidity, market cap, token age) with the risk level. For stocks, price per issuer and the quoting minimum. | | `check_asset` | `asset` | A verdict before trading. Crypto: Scam Shield's risk level (low, medium, high) with reasons. Stocks: whether this vault may trade them, and the notice to respect. Also any active NEAR Intents security incident touching the asset. | | `get_quote` | `side`, `asset`, `amountUsd` | A dry run: the price and the minimum you would accept. No funds move. If a rule would stop the trade, you get the same blocked answer `trade` would give. | | `trade` | `side` (`buy` or `sell`), `asset`, `amountUsd`, `reason` (optional, private to the owner) | Requests a trade. Runs every check, quotes, executes through NEAR Intents (confidential layer when the funds are there) and verifies the result. | | `get_portfolio` | none | Holdings in USD, split into crypto and tokenized stocks. | | `get_pnl` | none | Profit and loss since the vault's first trade, per asset and in total: realized (sells at average cost) and unrealized (what is still held from Norr's buys, at today's price). Deposits have no known cost and are not counted. | | `get_policy` | none | The rules: largest trade, daily spending cap and what is left today, daily loss cap, stock share, allowlists, cooldown, whether stocks are enabled, whether the vault is paused. | | `get_activity` | none | Recent requests and outcomes, newest first. Use it to follow up on a trade waiting for approval. | `asset` is a symbol (`SOL`, `USDC`), a ticker (`NVDA`, which Norr routes to the best-priced issuer), or a NEAR Intents asset id. ## What `trade` answers | Status | Meaning | What the agent should do | |---|---|---| | `executed` | It happened. Includes the fill (asset, amount, price), `venue` (`confidential` or `public`) and `verification` (`independent`, or `custody-reported` for confidential swaps). | Nothing; check `get_portfolio` if needed. | | `blocked` | A rule or safety check stopped it. Includes a `code` (below) and a plain-language `message`. | Adjust the request; do not resend it unchanged. | | `pending_approval` | The owner must approve it in the custody provider first. | Do not resend. Follow up with `get_activity`. | | `failed` or `refunded` | It did not complete; nothing was lost. | Read the message. | ## Block codes | Code | Why | |---|---| | `PAUSED` | The owner paused this vault (or everything). | | `OVER_TRADE_LIMIT` | Larger than the vault's largest single trade. | | `DAILY_SPEND_CAP` | Buys today would pass the daily spending cap (stops one big trade being split into many small ones). | | `DAILY_LOSS_CAP` | Realized losses today reached the cap; buying stops, selling still works. | | `COOLDOWN` | Too soon after the last trade. | | `ASSET_NOT_ALLOWED` | Not on the vault's allowlist. | | `EQUITY_LIMIT` | Would push stocks above the vault's allowed share. | | `INELIGIBLE` | Stocks are off until the owner confirms non-US status. | | `SCAM_RISK` | Scam Shield rates the token high risk (or medium, when the vault does not allow medium). | | `SHIELD_INCIDENT` | An active NEAR Intents security incident touches the asset, or the incident feed cannot be read (Norr fails closed). | | `PRICE_SANITY` | The quoted stock price is more than 2% from the reference price, or there is no reliable reference. | | `UNKNOWN_ASSET` | Not in the asset list. | | `NO_LIQUIDITY` | No route quoted for that size. | | `PRIVACY_UNAVAILABLE` | The vault requires the confidential layer and the trade cannot run there. | | `PRICING_UNAVAILABLE` | The service has not loaded market data yet (it keeps retrying). | Tokenized markets trade around the clock, so there is no market-closed code; stock minimums are higher outside US market hours. --- # API reference The REST API behind Norr's MCP tools. Every /v1 route needs the vault's agent token as `Authorization: Bearer `; a token is scoped to one vault and no route can withdraw, change rules or choose a destination. The MCP endpoint (`/mcp` on the MCP host) exposes the same operations as tools. /health, /attestation and /info are public. The machine-readable spec is [openapi.json](/openapi.json) (OpenAPI 3.1.0); this page is generated from it. ## Servers - `https://api.norr.finance`: Hosted service. TLS terminates inside the attested CVM (check the certificate with scripts/verify-domain.mts). Runs on demand during the pilot. - `https://85e1d6a557dae81985c9e30e28270f91bee5c0e9-8787.dstack-pha-prod5.phala.network`: The same CVM through Phala's gateway. - `http://localhost:8787`: Local mock mode (pnpm dev:mock): fixed prices, no funds. ## Authentication Send the vault's agent token on every `/v1` request: `Authorization: Bearer `. Create one in the dashboard (Connect). A token works for one vault only. ## Assets What can be traded, and what is known about it. ### GET /v1/assets **List tradable assets.** Crypto by symbol and chain; tokenized stocks and ETFs by ticker, with every issuer that offers it and the smallest order that has been quoting. MCP tool: list_assets. | Query parameter | Type | | |---|---|---| | `class` | `crypto`, `equity` | Only crypto, or only tokenized stocks and ETFs. | Response `200` (`AssetList`): | Field | Type | | |---|---|---| | `crypto` | object[] | | | `equities` | object[] | | | `equityNotice` | string | | | `equityMinimumNote` | string | | | `notice` | string | | Other responses: `400`, `401`. ### GET /v1/assets/check **Risk or eligibility verdict for one asset.** Crypto: Scam Shield's risk level with reasons. Stocks: whether this vault may trade them, and the notice. Also any active NEAR Intents security incident touching the asset. MCP tool: check_asset. | Query parameter | Type | | |---|---|---| | `asset` | string | required. A symbol (SOL), a ticker (NVDA: routed to the best-priced issuer) or a NEAR Intents asset id. | Response `200` (`AssetCheck`): | Field | Type | | |---|---|---| | `found` | boolean | required | | `message` | string | | | `class` | `crypto`, `equity` | | | `ticker` | string | | | `versions` | object[] | | | `eligible` | boolean | | | `notice` | string | | Other responses: `400`, `401`. ### GET /v1/market **Market data for one asset.** Listed price and, for crypto, the read-only signals Scam Shield scores (liquidity, market cap, age) with its risk level. For stocks: price per issuer and the quoting minimum. Facts, not advice. MCP tool: get_market_data. | Query parameter | Type | | |---|---|---| | `asset` | string | required. A symbol (SOL), a ticker (NVDA: routed to the best-priced issuer) or a NEAR Intents asset id. | Response `200` (`MarketData`): | Field | Type | | |---|---|---| | `found` | boolean | required | | `message` | string | | | `class` | `crypto`, `equity` | | | `ticker` | string | | | `source` | string | | | `versions` | object[] | | Other responses: `400`, `401`. ## Trading Price a trade (dry run) or request one. ### POST /v1/quote **Price a trade without trading.** Dry run: no funds move. Runs the same checks as a trade, so a trade that would be blocked returns the same blocked decision. MCP tool: get_quote. Request body (`TradeInput`): | Field | Type | | |---|---|---| | `side` | `buy`, `sell` | required | | `asset` | string | required | | `amountUsd` | number | required | | `reason` | string | The agent's reasoning, sealed to the owner's key; the service cannot read it back. | Response `200` (`Quote or Blocked`): One of: Quote One of: Blocked Other responses: `400`, `401`. ### POST /v1/trade **Request a trade.** Checks the rules and the asset's risk, quotes, executes through NEAR Intents (in the confidential layer when the vault's funds are there), and verifies the result. Funds always stay in the vault. A blocked answer names the rule; adjust the request instead of resending it. MCP tool: trade. Request body (`TradeInput`): | Field | Type | | |---|---|---| | `side` | `buy`, `sell` | required | | `asset` | string | required | | `amountUsd` | number | required | | `reason` | string | The agent's reasoning, sealed to the owner's key; the service cannot read it back. | Response `200` (`Decision`): One of: Executed One of: Blocked One of: PendingApproval One of: Failed Other responses: `400`, `401`. ## Vault Holdings, rules, results and history. ### GET /v1/portfolio **Holdings in USD.** Every holding with its layer (public or confidential), valued in USD, and the split between crypto and tokenized stocks. MCP tool: get_portfolio. Response `200` (`Portfolio`): | Field | Type | | |---|---|---| | `holdings` | object[] | required | | `totalUsd` | number | required | | `byClass` | object | required | Other responses: `401`, `503`. ### GET /v1/policy **The rules the agent must stay within.** Planning view of the vault's rules, including what is left of today's spending cap. MCP tool: get_policy. Response `200` (`PolicyView`): | Field | Type | | |---|---|---| | `maxTradeUsd` | number | required | | `dailySpendCapUsd` | number | required | | `spentTodayUsd` | number | required | | `spendRemainingTodayUsd` | number | required | | `dailyLossCapUsd` | number | required | | `maxEquityPct` | number | required | | `allowedCrypto` | "any_safe" or string[] | required | | `allowedEquities` | string[] | required | | `cooldownSec` | number | required | | `allowMediumRisk` | boolean | required | | `equitiesEnabled` | boolean | required | | `privacy` | `public`, `prefer`, `require` | required | | `paused` | boolean | required | Other responses: `401`. ### GET /v1/pnl **Profit and loss since the first trade.** Per asset and in total: realized (sells at average cost) and unrealized (what is still held from Norr's buys, at today's listed price). Deposits have no known cost and are not counted. MCP tool: get_pnl. Response `200` (`Pnl`): | Field | Type | | |---|---|---| | `since` | string or null | required | | `sinceMs` | number or null | | | `scope` | string | required | | `assets` | object[] | required | | `totals` | object | required | | `unpriced` | string[] | required | Other responses: `401`. ### GET /v1/activity **Recent requests and outcomes.** The last 20 trade requests, newest first. Swaps waiting for the owner's approval are settled here once approved. MCP tool: get_activity. Response `200` (`Activity`): | Field | Type | | |---|---|---| | `activity` | object[] | required | Other responses: `401`. ## Public Health and attestation; no token. ### GET /health **Is the service up.** No token needed. Response `200` (`object`): | Field | Type | | |---|---|---| | `ok` | boolean | required | | `mock` | boolean | required | | `pricing` | `ready`, `retrying` | required | | `mockOwner` | string | Mock mode only. | ### GET /attestation **A fresh TDX quote bound to your nonce.** The quote's report data is your nonce, padded with zeros. Check it with the dashboard's Verify screen or scripts/verify-attestation.mts. No token needed. | Query parameter | Type | | |---|---|---| | `nonce` | string | required. 1 to 64 bytes of hex. | Response `200` (`object`): | Field | Type | | |---|---|---| | `attested` | true | required | | `nonce` | string | required | | `quote` | string | required. Hex TDX quote. | | `eventLog` | | | | `commit` | string or null | | Other responses: `400`, `429`, `503`. ### GET /info **The measured app configuration.** No token needed. Response `200` (`object`): | Field | Type | | |---|---|---| | `attested` | true | required | | `appId` | string | | | `composeHash` | string | | | `appCompose` | string | | | `commit` | string or null | | Other responses: `503`. ## Decisions What `POST /v1/trade` answers (`Decision`), by `status`: **Executed** | Field | Type | | |---|---|---| | `status` | "executed" | required | | `txRef` | string | required | | `filled` | object | required | | `venue` | `public`, `confidential` | | | `verification` | `independent`, `custody-reported` | | | `warnings` | string[] | | **Blocked** | Field | Type | | |---|---|---| | `status` | "blocked" | required | | `code` | BlockCode | required | | `message` | string | required | | `suggestedUsd` | number | | **PendingApproval** | Field | Type | | |---|---|---| | `status` | "pending_approval" | required | | `requestId` | string | required | | `message` | string | required | **Failed** | Field | Type | | |---|---|---| | `status` | `failed`, `refunded` | required | | `message` | string | required | Block codes and what they mean: [MCP tools](/docs/mcp-tools/#block-codes). --- # Rules and limits Two layers of limits protect a vault. Norr's rules are checked before every trade; the custody provider's own limits sit underneath and neither the agent nor Norr can change them. ## Norr's rules (you set them in Rules) | Rule | What it does | |---|---| | Largest single trade | Any trade above it is refused (`OVER_TRADE_LIMIT`). | | Daily spending cap | Total buys per UTC day. Counted when the swap is sent and given back if it did not run. | | Daily loss cap | Once realized losses today reach it, buying stops; selling still works. | | Stock share | The most of the vault's value that may sit in tokenized stocks. | | Crypto allowed | Either "anything that passes the risk check", or a list of symbols. | | Stocks allowed | A list of tickers. | | Allow medium-risk tokens | Off by default. High-risk tokens are always refused. | | Wait between trades | A cooldown in seconds. | | Privacy | Confidential when possible (default), confidential only, or public only. | | Non-US confirmation | Required before any stock trade (`INELIGIBLE` without it). | | Paused | The kill switch; refuses every request. | Trades for one vault run one at a time, so parallel requests cannot slip past a cap. ## Presets | | Cautious | Balanced | Degen | |---|---|---|---| | Largest trade | $25 | $50 | $200 | | Daily spending cap | $50 | $150 | $600 | | Daily loss cap | $10 | $25 | $100 | | Stock share | 20% | 60% | 100% | | Crypto | BTC, ETH, SOL, NEAR, USDC, USDT | anything that passes the risk check | anything that passes the risk check | | Stocks | SPY, QQQ | NVDA, TSLA, AAPL, MSFT, GOOGL, AMZN, META, SPY, QQQ | Balanced plus CRCL, HOOD | | Medium-risk tokens | no | no | yes | | Wait between trades | 5 minutes | 10 seconds | none | No preset confirms non-US status; you do that yourself. ## Safety checks on every trade In this order: Scam Shield (crypto) or eligibility (stocks), then your rules, then the incident feed. A trade that breaks one of your rules is refused with that rule's reason; only trades that pass every rule reach the feed. - **Scam Shield (crypto).** Scores liquidity, market cap and token age from read-only market data; known stablecoins and majors are allowlisted, and anything not routable through NEAR Intents is high risk. 65 or more is high (refused), 35 to 64 medium (refused unless you allow it). Unknown data counts as risk, so it fails closed. It is a set of heuristics, not a guarantee. - **Shield incident feed.** An active NEAR Intents security incident touching the asset blocks the trade. If the feed cannot be read, trades are blocked (fail closed). - **Price sanity (stocks).** A stock quote more than 2% from the reference price is refused. - **Minimum output.** Every swap carries a positive minimum taken from Norr's own quote, and the result is checked afterwards against that quote. A mismatch, or a result that cannot be checked, pauses the vault; the dashboard shows it paused until you resume. ## Tokenized stocks Stocks and ETFs are Ondo's (and other issuers') tokens: structured notes that track the underlying security, with no shareholder rights, offered to non-US persons only. Norr asks for the ticker and routes to the best-priced issuer. The smallest order that quotes moves with the market: about $5 on the most liquid tickers at US market open, $26 for others, higher outside market hours. `list_assets` shows the current floor. ## The custody provider's limits (set in OutLayer) Your vault is an OutLayer custody wallet. In OutLayer's own dashboard you set per-token limits, an address whitelist for withdrawals, an approval threshold, and a freeze. Withdrawals go only to your whitelisted address and need your approval, so the agent can never choose a destination. On OutLayer, swaps and withdrawals share one transaction type, so each swap also needs your approval there. --- # Privacy model Private from the public is not private from everyone. This is who sees what. | Data | Public chain observers | OutLayer (custody) | NEAR Intents operator and solvers | Norr service | You | |---|---|---|---|---|---| | A confidential swap: pair, size, price, balances | **Hidden** (confirmed on mainnet: no public balance change, no public transaction) | Sees all | Sees the intents | Sees all | Yes | | Funding and withdrawing the vault | **Visible**: a public transfer from your address, on the chain you send from | Sees all | Sees all | Sees all | Yes | | The agent's stated reasoning | No | No | No | Writes it, **cannot read it back** | Only you | | Decisions, counters, rules | No | No | No | Yes (it must, to enforce caps); encrypted on disk | Yes | | The agent's model and prompts | No | No | No | No | Private if the model runs on NEAR AI Cloud; otherwise your agent host's own privacy | | The code that enforces all this | Anyone can verify it | | | | | | That the traffic comes from Norr | Not private: Norr's partner key identifies it to 1Click and OutLayer | | | | | ## How each part works - **Confidential trades.** When the vault's funds are in NEAR's Confidential Intents layer, Norr quotes and trades there (`CONFIDENTIAL_INTENTS` deposit, recipient and refund types). Observers see no swap and no balance change. Under "confidential when possible", a trade falls back to the public layer only when the funds are not in the confidential layer; under "confidential only" it is refused instead (`PRIVACY_UNAVAILABLE`). - **Funding lands confidential.** A deposit from any chain is routed into the vault's confidential balance. The transfer you send is public on its own chain. - **Sealed reasoning.** The `reason` an agent gives is encrypted to a key derived from your wallet's signature (X25519 and AES-GCM). The service stores it but holds only the public half, so it cannot read it back. The dashboard derives the private half in your browser when you unlock. - **The engine is attested.** The Norr service runs in an Intel TDX confidential VM. Anyone can check which code and images it booted ([Verify the enclave](/docs/verify/)). Attestation proves the code, not the secrets the VM was given; Phala's key service, which releases the VM's encrypted settings, is a trust assumption. - **The dashboard.** Strict Content-Security-Policy: no third-party scripts, no inline code, no fonts from Google. The verifier and your session stay in the browser. ## What Norr does not claim - Not invisible to the custody provider, the NEAR Intents operator or the Norr service: they see confidential trades in order to execute and check them. - A funding transfer and the next confidential hop can be correlated by timing and amount. - Custody is not trustless. For swaps, OutLayer's gateway builds the route and deposit address, and its enclave does not re-check token or amount. Limits and approvals bind an honest gateway; Norr detects tampering afterwards. Funds can still be lost to a dishonest or compromised gateway, up to what your limits allow. The observations behind this table, with dates and request ids: [docs/PRIVACY.md](https://github.com/outislabs/norr/blob/main/docs/PRIVACY.md). --- # Verify the enclave The Norr service runs in an Intel TDX confidential VM on Phala. It answers two public endpoints, `/attestation?nonce=…` and `/info`, so anyone can check which code is running without trusting Norr's word. No account is needed. ## In the browser Open [app.norr.finance/#/verify](https://app.norr.finance/#/verify) and choose **Check attestation**. Your browser picks a random nonce, asks the service for a fresh hardware quote, and runs seven checks on it locally: 1. **The quote is a TDX quote** (version 4). 2. **It answers your nonce**: the quote was made for this request, not replayed. 3. **The configuration matches**: SHA-256 of the app configuration equals the measured compose-hash event. 4. **The event log replays**: replaying the boot events reproduces the quote's RTMR3 register. 5. **Production OS**: the VM runs a production dstack image, not a debug one. 6. **Exact images**: every container image is pinned by digest. 7. **Genuine Intel hardware**: the quote's signature chain verifies up to Intel's root certificate. This runs in your browser with Phala's `dcap-qvl` verifier compiled to WebAssembly. It fetches Intel-signed certificate data from Phala's certificate cache (which can withhold it but cannot forge it) and sends the quote nowhere. If the browser check cannot run, the page offers the quote to copy into [Phala's TEE Attestation Explorer](https://proof.t16z.com/). ## From a terminal ``` git clone https://github.com/outislabs/norr && cd norr && pnpm install pnpm exec tsx scripts/verify-attestation.mts https://api.norr.finance ``` Same checks; the seventh asks Phala's verification API. The service runs on demand during the pilot, so it may be offline; the dashboard says so. ## Check the domain `api.norr.finance` (the service) and `mcp.norr.finance` (the MCP endpoint) are served by Phala's dstack-ingress inside the CVM: their TLS certificates are issued and held there, and published at `https:///evidences/` with a TDX quote over them. This checks that the certificate your client sees belongs to the attested app: ``` pnpm exec tsx scripts/verify-domain.mts api.norr.finance https://api.norr.finance pnpm exec tsx scripts/verify-domain.mts mcp.norr.finance https://api.norr.finance ``` It verifies the evidence quote's signature chain, that the quote covers the published certificate, that the certificate served over TLS is that one, and that the quote comes from the same VM and configuration as a fresh attestation of the service. It also reports whether CAA pins certificate issuance for the name to the CVM's own ACME account. ## Match the code The checks report the git commit, the compose hash and the image digests. Compare them with the repository: - The images in `deploy/docker-compose.yml` are pinned by digest. - Images are built in CI with signed build provenance. Check one: ``` gh attestation verify oci://ghcr.io/outislabs/norr@sha256: --repo outislabs/norr ``` - The values observed for the current deployment are listed in the README, "Verify it yourself". ## What this proves, and what it does not It proves which code and images booted, on genuine Intel TDX hardware, just now. It does not prove what secrets the VM was given (Phala's key service is a trust assumption), and it says nothing about the custody provider, whose limits you set separately. --- # FAQ ## Can my agent steal the money? It holds one token, scoped to one vault, that can read the vault, get prices and request trades. It cannot withdraw, change rules, or choose where money goes: trades stay in the vault, and withdrawals go only to your whitelisted address with your approval. The most a misbehaving agent can do is make trades within your rules until you pause the vault or revoke its token. ## Does Norr hold my keys? No. The vault's keys live in OutLayer's custody (in its enclave). The Norr service holds an API key for the vault's custody wallet, which is bounded by the limits you set in OutLayer. Custody is not trustless; see [Privacy model](/docs/privacy/). ## What if the Norr service is offline? Nothing trades. Your funds stay in the custody wallet, and you can still withdraw through OutLayer's own dashboard to your whitelisted address. During the pilot the hosted service runs on demand. ## Which chains can I fund from? USDC on NEAR, Ethereum, Base, Arbitrum, Optimism, Polygon, BNB Chain or Solana, or ETH or SOL. It arrives as USDC in the vault's confidential balance. Funding with USDC on NEAR is proven on mainnet; the other chains were quoted live (USDC at about 0.01%, 20 to 45 seconds) but not yet funded with real money. ## Do I need a NEAR wallet? To own a vault, yes: sign-in is a NEAR signed message, and swap approvals happen on your NEAR account in OutLayer. [HOT Wallet](https://hot-labs.org/wallet/) gives you a NEAR account next to your EVM and Solana ones. Funding can come from any listed chain. ## Why do I approve every swap? On OutLayer, swaps and withdrawals share one transaction type, so approvals cannot be turned off for swaps without also turning them off for withdrawals. Keeping them is what guarantees the agent can never move money out. The Approvals tab shows what Norr asked for, so you can compare before you sign. ## Which agents work? Any MCP client: Claude Code, Claude Desktop (through `mcp-remote`), or your own. The repository includes a reference agent whose model runs on NEAR AI Cloud. See [Connect your agent](/docs/connect-agent/). ## What is the smallest trade? Crypto: a few dollars. Tokenized stocks: the smallest order that quotes, about $5 on the most liquid tickers at US market open and $26 for others, higher outside market hours. ## Can US persons trade stocks? No. Ondo's tokenized stocks are offered to non-US persons only. You confirm non-US status before stocks are enabled. This is not legal advice. ## What does it cost? Norr charges nothing in the pilot. Routes have their own costs: a $1.5 USDC to SOL round trip cost about 0.15%, and a $6 stock round trip about 1.2%. ## Is it audited? No. It is a hackathon build, run with small balances. The code, tests and evidence are public: [github.com/outislabs/norr](https://github.com/outislabs/norr). --- # Limitations and roadmap ## Known limitations - **Custody trust.** For swaps, OutLayer's gateway builds the route and the deposit address, and its enclave does not re-check token or amount; its swap endpoint also ignores the minimum output. Norr checks every result afterwards and pauses the vault on a mismatch. Funds can still be lost to a dishonest or compromised gateway, up to what your limits allow. - **Phala's key service** releases the VM's encrypted settings and is a second trust assumption. - **Every swap needs your approval** in OutLayer, because swaps and withdrawals share one transaction type there. - **Confidential swaps have no independent audit trail.** They are checked from balances and the provider's own report, and labelled `custody-reported`. - **Funding and withdrawals are public**, and the custody provider and NEAR Intents operator see confidential trades. A funding transfer and the next hop can be correlated by timing and amount. - **One vault per deployment, set up by the operator.** There is no self-serve vault creation yet. - **Funding from other chains is quoted, not yet proven.** USDC on NEAR is proven on mainnet; the other chains were quoted live but not run with real funds. - **Owning a vault needs a NEAR account.** EVM and Solana users can fund a vault but not sign in or approve swaps. - **The Shield incident feed needs a key.** Without it the hosted service blocks every trade (it fails closed). - **Limit orders are out of v1**: two test limit orders never filled. - **Stock minimums move** with the market and the time of day. - **Stock eligibility** follows third-party sources; this is not legal advice and has not been reviewed by counsel. - **Scam Shield is heuristics**, not a guarantee; it blocks some legitimate tokens it cannot score. - **Not audited**; small balances; the hosted service runs on demand. ## Roadmap Planned, not built: - **Self-serve hosted vaults.** Create a vault, its custody wallet and its first agent token from the dashboard, without an operator. - **Multi-vault and client management.** Several vaults per owner (one per agent or strategy), and an operator view for people who run vaults for clients, with each client's data still sealed to that client. - **TypeScript SDK.** A typed client for the [agent API](/docs/api/), generated from the OpenAPI spec, for agents that do not use MCP. - **Verified P&L and Follow.** P&L that others can check, each trade backed by evidence, so an owner can publish a track record without revealing positions in real time, and others can follow it. Researched, not built: - **Owning a vault from an EVM or Solana wallet.** Needs an owner identity that is not a NEAR account and a way to approve custody requests. - **Norr as a NEAR Intents solver** for orders under $5; gated by KYC/KYB of an operating entity. - **Order pooling** for $1 to $5 stock orders; changes the custody model and needs legal review. - **Smaller stock minimums** if NEAR Intents or the issuers make the floor configurable. Details and evidence for each item are in the repository's `docs/` folder.