# Architecture

> How Probarion fits together: the vault and factory on NEAR, who signs, the hosted app and the outside services, each part tied to its code.

The vault contract on NEAR holds the money: only its owner changes its rules and keys, freezes it or withdraws from it, and only its approvers decide holds. Probarion runs the factory and a hosted app beside it, with no owner or approver key of your vault.

Status: Live · Network: Testnet · Reviewed: 2026-11-12 · Built from: 4ab41ca1436589c22073fa842ee46f970415e3d5

Canonical page: https://probarion.xyz/docs/operate/architecture

The money sits in the vault, a NEAR contract each owner has of their own: it holds its NEAR USDC, keeps its Base and Solana USDC in its own account inside NEAR Intents, and pays only under the mandates its owner signed. The owner signs every call that sets the vault's rules or keys, freezes or unfreezes it, or withdraws from it, and an approver signs each `approve` or `reject`, both from their own wallet or with near-cli-rs. The agent signs `spend` and `anchor` with two function-call keys the vault added to itself: one can call only `spend`, the other only `anchor`. Once a hold's time is up, any account may call `expire_held` to release it. Probarion deployed the factory that creates vaults, publishes the packages, and runs the hosted app: the console and its API routes, a Supabase database and a watcher. It holds no owner or approver key of your vault.

**Probarion architecture**

The vault contract on NEAR holds its NEAR USDC, and its Base and Solana USDC sit in its own NEAR Intents account. Owners and approvers sign in their own wallets, the agent's two keys can call only spend and anchor, and Probarion runs the factory and the hosted app with no owner or approver key of your vault.

**Parts**

- **Owner** (People and agents): signs every rule
- **Approver** (People and agents): decides holds
- **Console and API** (Hosted app): Next.js on Vercel
- **Wallet** (Other services): full-access keys
- **Database** (Hosted app): Supabase Postgres
- **Factory** (On NEAR): keyless vaults
- **Vault contract** (On NEAR): holds the USDC
- **Watcher** (Hosted app): /api/cron/watch
- **Agent with SDK** (People and agents): agent, ledger keys
- **USDC token** (On NEAR): NEP-141
- **NEAR Intents** (On NEAR): cross-chain payout
- **Telegram, email** (Other services): Bot API, Resend
- **NEAR AI Cloud** (Other services): model in a TEE

**Arrows**

- Owner → Wallet: owner calls
- Approver → Wallet: approve, reject
- Console and API → Wallet: txs to sign
- Wallet → Factory: create_vault
- Wallet → Vault contract: signed calls
- Factory → Vault contract: deploys, no keys
- Agent with SDK → Vault contract: spend, anchor
- Agent with SDK → NEAR AI Cloud: asks for advice
- Vault contract → USDC token: ft_transfer
- Vault contract → NEAR Intents: ft_withdraw
- Console and API → Vault contract: Payables spend
- Console and API → Database: keys, ledger
- Watcher → Vault contract: reads views
- Watcher → Database: vault_events
- Watcher → Telegram, email: held, refused, paid

Each arrow points from the part that acts to the part it acts on, and its label says what passes between them. The dark boxes are contracts on NEAR, and the dashed boxes are services Probarion does not run. The CLI has no box of its own: it prints each owner and approver transaction, which then reaches the vault through the signer's wallet or near-cli-rs, along the "owner calls" and "approve, reject" arrows. A few calls the hosted app makes have no arrow. The server asks NEAR AI Cloud for a proposal for Payables and the Canary, and for its model list in the health check. The console sends the confirmation email of a new email alert, and the Telegram bot's route answers `/start` and `/stop`. On testnet the faucet signs `mint` on the USDC token.

Today the contracts run on NEAR testnet. On mainnet one small vault, `v0.probarion.near`, runs with no factory, and the mainnet factory is not deployed. [Testnet and mainnet](https://probarion.xyz/docs/start/testnet-and-mainnet.md) says what differs between the two. The status of each part is on [What works today](https://probarion.xyz/docs/start/what-works-today.md).

## People and agents

- **Owner** (`contracts/vault/src/admin.rs`): the account named in the vault's config. Every owner method checks that the call comes from that account, and fails with `NotOwner` otherwise. The owner creates the vault through the factory, funds it, signs its mandates, adds and removes agent keys, sets the ledger key, freezes, unfreezes and withdraws. `freeze` takes no deposit. `create_mandate` takes a storage deposit and refunds the excess. Most other owner methods need exactly 1 yoctoNEAR, which only a full-access key can attach.
- **Approver** (`contracts/vault/src/held.rs`): one of the accounts named when the vault was created. A spend above its mandate's `approval_above` waits as a hold. `approve` pays it and `reject` releases it, and each checks that the caller is on the approver list. Once the hold's time is up, `approve` refuses, and any account may call `expire_held` to release the hold: it checks no caller, only the time. No method changes the approver list.
- **Agent with SDK** (`packages/sdk/src/probarion.ts`): your own process running `@probarion/sdk`. `process()` runs one payment: an optional proposal from NEAR AI Cloud, then `decide()` from `@probarion/core`, a signed decision entry in the ledger, the free `check_spend` dry run, `spend` with the agent key, and `anchor` with the ledger key. The SDK reads the chain through its own JSON-RPC client, which fails over between RPC providers, and signs with near-api-js. It keeps the ledger in a store you pick, such as a file.

> **Owner signs**
>
> The owner signs `create_vault`, `create_mandate`, `revoke_mandate`, `add_agent`, `remove_agent`, `set_ledger_key`, `freeze`, `unfreeze`, `withdraw` and `withdraw_intents`, from their own wallet or with near-cli-rs.
>
> _The owner signs this transaction. Probarion never signs it._

> **Approver signs**
>
> An approver signs `approve` and `reject` from their own wallet, with exactly 1 yoctoNEAR attached.
>
> _An approver signs this transaction. Probarion never signs it._

The code for these parts is in four packages, all on npm at 0.1.1:

- `packages/core`, `@probarion/core`: `decide()`, the ledger, the attestation checks and the verifier's checks a to i. No network, no file system.
- `packages/sdk`, `@probarion/sdk`: the `Probarion` class, the vault, factory and intents clients, the NEAR AI Cloud adapter, the ledger stores, `snapshot()` and `verify()`.
- `packages/cli`, the `probarion` CLI: it prints owner and approver transactions as near-cli-rs commands and signs none of them. It also makes keys with `agent keygen`, runs the `check_spend` dry run with `check`, exports a ledger and runs the verifier.
- `packages/mcp`, `@probarion/mcp` (Live): an MCP server over stdio with five tools, `list_mandates`, `check_spend`, `request_spend`, `get_spend_status` and `verify`, none of which takes an address. It calls `@probarion/sdk` with the vault's agent and ledger keys and holds no owner key.

[Vaults, roles and keys](https://probarion.xyz/docs/concepts/vaults-roles-and-keys.md) covers each role and key, and [SDK reference](https://probarion.xyz/docs/build/sdk-reference.md) and [CLI reference](https://probarion.xyz/docs/build/cli-reference.md) list the packages' parts.

## On NEAR

- **Factory** (`contracts/factory/src/lib.rs`): `create_vault` makes the account `<name>.<factory>` for its caller, who becomes the vault's owner. In one batch it creates the account, sends it the deposit, deploys the vault code, either a copy of stored wasm or a global contract referenced by hash, and calls the vault's `new`. It adds no access key. The factory has an owner of its own, `probarion-owner.testnet` on testnet, an account the Probarion team holds. That owner signs `store_code`, `add_version` and `publish_global`, each with exactly 1 yoctoNEAR, to publish a new vault version, which only vaults created after it get. No factory method changes, upgrades or takes funds from a vault already created. On testnet the factory is `vaults.probarion.testnet`, deployed on 2026-11-05 with one version, an embedded copy of the vault code. The mainnet factory, `vaults.probarion.near`, is not deployed.
- **Vault contract** (`contracts/vault/src/lib.rs`): holds the NEAR USDC, keeps the mandates, spends, refs and anchors, and runs the [14 spend checks](https://probarion.xyz/docs/concepts/payment-checks.md) on every spend. It adds the agent and ledger keys to itself when the owner says so. It calls two other contracts, through the interfaces in `contracts/vault/src/ext.rs`, and no others.
- **USDC token** (`packages/core/src/constants.ts`): a NEP-141 token. The vault calls `ft_transfer` to pay a NEAR payee and to withdraw to the owner, and `storage_deposit` to register a NEAR payee on the token when a mandate is created. On testnet it is `usdc.probarion.testnet`, a test token. On mainnet it is native USDC, `17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1`.
- **NEAR Intents** (`contracts/vault/src/ext.rs`): the vault calls `ft_withdraw` on it to pay a Base or Solana payee, with the memo `WITHDRAW_TO:<address>` that it builds from the mandate, and to send the owner its Intents balance with `withdraw_intents`. On mainnet this is `intents.near`. Intents has no testnet, so `intents-stub.probarion.testnet` stands in there: it records the burn, and nothing leaves NEAR.

The vault logs an event for each mandate, key, spend, hold, freeze, anchor and withdrawal, listed on [Events](https://probarion.xyz/docs/build/events.md). [Contract reference](https://probarion.xyz/docs/build/contract-reference.md) lists every method, and [Why spend has no payee](https://probarion.xyz/docs/concepts/why-spend-has-no-payee.md) explains why the payee comes from the mandate.

The contract code lives in `contracts/vault` and `contracts/factory`. Beside them, `contracts/test-ft` is the test token behind the testnet USDC and the testnet Base and Solana tokens, `contracts/intents-stub` is the testnet stand-in for Intents, and `contracts/integration-tests` runs both contracts on a near-workspaces sandbox. The testnet accounts are recorded in `contracts/deployments/testnet.json`, and [Reproducible builds](https://probarion.xyz/docs/trust/reproducible-builds.md) shows how to rebuild the code the vault and factory accounts run. The builds of the test token and the Intents stub are not recorded.

## Hosted app

The console, its API routes and the watcher are one Next.js app, `apps/web`, deployed on Vercel. The database is a Supabase Postgres project, whose migrations live in `apps/web/supabase/migrations`. One deployment is built to serve both networks and takes the network from the address it was opened at, as [How the site picks a network](https://probarion.xyz/docs/start/testnet-and-mainnet.md#how-the-site-picks-a-network) explains. Today it serves testnet only. Apart from Payables, the console and the parts behind it are Preview: built and tested, and tried on testnet with a real wallet (the demo vault on 2026-11-09, and the trial vault on 2026-11-11 and 2026-11-12: [its record](https://github.com/probarion/probarion/blob/main/docs/evidence/trial-vault/README.md)). Payables is Live on testnet: on 2026-11-12 it paid two invoices on its own with an attested model, with test tokens ([the record](https://github.com/probarion/probarion/blob/main/docs/evidence/demo-vault/README.md#2026-11-12-payables-pays-on-its-own-with-an-attested-model), [the second](https://github.com/probarion/probarion/blob/main/docs/evidence/demo-vault/README.md#2026-11-12-filmed-a-second-invoice-paid-on-its-own-freeze-and-unfreeze)).

- **Console and API** (`apps/web/src/app`): the public site, `/docs`, `/proof`, `/usage`, `/status`, `/canary` and the console at `/console`. Sign-in is a NEP-413 message signed by a full-access key of your account and checked on the server, then a session cookie that lasts 12 hours. The console's pages read the chain and the database, and hand each owner or approver transaction to your wallet to sign. Its API routes, by folder under `apps/web/src/app/api`:

  - `/api/auth`: the sign-in challenge, its check and sign-out.
  - `/api/console`: the Payables invoice upload.
  - `/api/topup`: on testnet, the faucet. On mainnet, the 1Click top-up of the vault's Base USDC inside NEAR Intents, in three routes, `/api/topup/quote`, `/api/topup/submit` and `/api/topup/status`, which has not been tried with a live quote yet.
  - `/api/ext`: the server half of the browser extension. A connection reads what waits for you and queues an invoice for the owner's Confirm; it cannot pay, approve or confirm.
  - `/api/alerts` and `/api/telegram`: the email confirm and stop links, and the Telegram bot's `/start` and `/stop`.
  - `/api/canary`: one attempt at the Canary, the public challenge, which is Live on testnet.
  - `/api/tee`: the proof page's live TEE check, which checks NEAR AI's attestation for a nonce the visitor's browser draws.
  - `/api/cron`: the watcher and the daily usage numbers.
  - `/api/health`: the deployment's settings, one RPC provider, and NEAR AI Cloud's model list.

  [HTTP API](https://probarion.xyz/docs/build/http-api.md) lists every route and who may call it.
- **Database** (`apps/web/supabase/migrations`): Supabase Postgres, with row-level security on every table. The server reads and writes with the service role key, after its own check that a signed-in caller is a member of the vault. The publishable key reads no table, and may call only the `public_*` functions and the helper `pb_current_account()`. The tables:
  - vaults and their members: `tenants` and `tenant_members`, a member being an owner, an approver or a viewer;
  - sealed keys: `agent_keys`;
  - Payables: `invoices` and `invoice_confirm_nonces`, for the owner's signed Confirm;
  - the ledger the hosted app writes: `ledger_entries`;
  - what the watcher saw: `vault_events`;
  - alerts: `alert_subscriptions` and `alerts_sent`;
  - the Canary: `canary_attempts`;
  - the usage numbers: `metrics_daily` and `metrics_runs`;
  - pilot owners and whether they agreed to be named: `pilots`;
  - the rate limiter: `rate_limits`;
  - the extension: `extension_tokens` and `extension_pair_codes`.
- **Watcher** (`apps/web/src/lib/watcher/watch.ts`): `/api/cron/watch` reads each registered vault from where it last stopped: its spends, mandates and anchors through RPC views, its keys' gas allowances and its free NEAR, and the ledger entries in the database, where a refusal by the contract is recorded. It writes each new event once into `vault_events`, then sends the alerts. Vercel runs it once a day, as `vercel.json` sets, and it also runs for one vault right after a Payables upload or a Canary attempt. `/api/cron/metrics` computes the `/usage` numbers each day at 00:10 UTC and prunes the rate limiter's old rows. Both answer only a call that carries `CRON_SECRET` as its bearer token.

Payables, the console's reference agent, and the Canary run the same SDK on the server. Payables pays an invoice by itself only when an attested NEAR AI Cloud model states the same amount the code read. Otherwise it sends nothing on its own, as NEAR AI Cloud under Other services explains. For a vault whose keys the console made, Payables signs `spend` with the sealed agent key its mandate names and anchors with the sealed ledger key: that is the "Payables spend" arrow. The console's Keys tab generates those keys on the server and seals each secret with libsodium's secretbox, under a key derived from `KEY_ENCRYPTION_SECRET`, and the owner adds each one to the vault with its public key only. Payables and the Canary keep their ledgers in `ledger_entries`. A vault has one ledger writer, so a vault that Payables pays for cannot also take your own SDK agent, as [One writer per vault](https://probarion.xyz/docs/concepts/ledger-and-anchors.md#one-writer-per-vault) explains.

One more key lives on the server, for testnet only. The Top-up tab's faucet mints 25 test USDC into a vault, signed as `probarion.testnet` with `TESTNET_FAUCET_KEY`, a function-call key that may call only `mint` on the test token.

## Other services

- **Wallet** (`apps/web/src/components/wallet/connector.ts`): the console connects wallets through NEAR Connect (`@hot-labs/near-connect`), from a wallet list the code pins. The owner's and the approvers' full-access keys stay in their wallets, which sign each transaction the console hands them and the NEP-413 sign-in message. near-cli-rs plays the same part for the commands the CLI prints.
- **NEAR AI Cloud** (`packages/sdk/src/nearai.ts`): the SDK's optional model adapter. For one invoice it fetches the model's attestation report for a nonce tied to the next ledger position, asks the private model in a TEE for one proposal, pay, hold or refuse, and fetches the provider's signature of the answer, all over `https://cloud-api.near.ai/v1`. `@probarion/core` checks the attestation and the signature. The model only proposes: the code rules and the contract still decide. On the server, `NEARAI_API_KEY` turns it on, and the Canary does not open without it. When it is off, or the model gives no attested answer or no amount, Payables sends nothing on its own: the invoice waits for the owner, who confirms the amount in the console with a NEP-413 signature from their wallet, against a single-use nonce kept in `invoice_confirm_nonces`. Payables then pays it with the sealed agent key, and above the mandate's `approval_above` the vault still holds it for the approvers. An attested model that reads another amount makes the code rules refuse the invoice. The adapter is tested against a fake NEAR AI Cloud. On testnet, the Canary's ledger of 2026-11-10, published in the repository, holds two real attested decisions, which pass A1 to A7 offline and, on 2026-11-11, O1 and O2 online ([evidence](https://github.com/probarion/probarion/blob/main/docs/evidence/canary-vault/README.md#the-signed-ledger-published-2026-11-11), [online output](https://github.com/probarion/probarion/blob/main/docs/evidence/canary-vault/verify-online-2026-11-11.txt)). The testnet demo vault's ledger holds five more, made by Payables, which pass A1 to A7 offline; four of them, entries 10 to 13, also passed O1 and O2 online on 2026-11-12. The code allowed the fourth and the fifth, both by `Qwen/Qwen3.8-27B`, and the vault paid 2.00 test USDC as spend 4 and again as spend 5 ([evidence](https://github.com/probarion/probarion/blob/main/docs/evidence/demo-vault/README.md#2026-11-12-payables-pays-on-its-own-with-an-attested-model), [the second](https://github.com/probarion/probarion/blob/main/docs/evidence/demo-vault/README.md#2026-11-12-filmed-a-second-invoice-paid-on-its-own-freeze-and-unfreeze), [online output](https://github.com/probarion/probarion/blob/main/docs/evidence/demo-vault/verify-online-2026-11-12.txt)). The TEE measurement is not pinned, so this does not show which model image ran, and A8 was not run on the Canary's two; on the demo vault's five it passed on 2026-11-12, with the invoices committed in the repository ([output](https://github.com/probarion/probarion/blob/main/docs/evidence/demo-vault/verify-a8-2026-11-12.txt)).
- **Telegram, email** (`apps/web/src/lib/alerts`): an owner's or an approver's alerts go out through the Telegram Bot API, with `TELEGRAM_BOT_TOKEN`, and through Resend, with `RESEND_API_KEY`. Each switches on once its setting is set. Telegram is set on probarion.xyz, and on 2026-11-11 its bot sent real messages to a chat linked to a testnet vault ([the record](https://github.com/probarion/probarion/blob/main/docs/evidence/trial-vault/README.md)); no real email has been sent yet.

The hosted app talks to a few more services that the diagram leaves out:

- **NEAR RPC providers.** The console, the watcher and the SDK read the chain through each network's RPC list, `https://rpc.testnet.fastnear.com` by default on testnet.
- **1Click**, for the mainnet top-up only, with `ONECLICK_JWT`.
- **Sentry**, for error reports, off until `SENTRY_DSN` is set. Before an event leaves the server or the browser, the shared scrubber removes the user, cookies, headers, bodies and query strings.
- **Better Stack**, which gets a heartbeat after each healthy scheduled watcher run, once `BETTERSTACK_HEARTBEAT_URL` is set.
- **Intel PCS and NVIDIA NRAS**, for the proof page's live TEE check (`/api/tee`). The server asks NEAR AI Cloud's public attestation report with no API key, whether or not `NEARAI_API_KEY` is set, then asks Intel's PCS (`api.trustedservices.intel.com`) for the collateral that checks the report's TDX quote, and sends the report's GPU evidence to NVIDIA NRAS (`nras.attestation.nvidia.com`).
- **Vercel**, which hosts the app and runs its two daily jobs.

## Where to go next

- [Security model](https://probarion.xyz/docs/trust/security-model.md): what each key can do, and what a breached server can and cannot do.
- [Where the keys live](https://probarion.xyz/docs/concepts/vaults-roles-and-keys.md#where-the-keys-live): each key, and who holds it.
- [Holds and approvals](https://probarion.xyz/docs/concepts/holds-and-approvals.md): what an approver decides.
- [Ledger and anchors](https://probarion.xyz/docs/concepts/ledger-and-anchors.md): what the ledger key signs and anchors.
- [Limits and costs](https://probarion.xyz/docs/build/limits-and-costs.md): the contracts' limits and the gas they attach.

## What this does not do

- **Probarion holds no key that can withdraw from your vault or change its rules.** The owner's and the approvers' full-access keys stay in their wallets. Every owner method checks the calling account, and most need 1 yoctoNEAR, which a function-call key cannot attach. A vault the factory creates starts with no access key, and the vault has no method to change its owner or its approvers or to upgrade its code. The keys Probarion does hold, for vaults whose keys the console made, are sealed agent keys, which can spend only inside the mandates that name them and only to their payees, and sealed ledger keys, which can only anchor. On testnet it also holds the faucet key, which can only mint the test token. The team also holds the factory owner's key, for `probarion-owner.testnet`, which can publish a new vault version for vaults created later and cannot change, upgrade or take funds from a vault already created. That account also owns the team's own testnet vaults, such as `v0.probarion.testnet`. The team also holds the full-access key of the factory account itself, `vaults.probarion.testnet`, which no runbook step deletes. That key can deploy new factory code, which could change how later vaults are created and which versions verifier checks [f](https://probarion.xyz/docs/trust/verifier-checks.md#check-f) and [i](https://probarion.xyz/docs/trust/verifier-checks.md#check-i) trust. A vault already created keeps its code and its own keys: see [Known gaps](https://probarion.xyz/docs/trust/security-model.md#known-gaps). On testnet the team also holds the full-access keys of the test token accounts, `usdc.probarion.testnet`, `base-usdc.probarion.testnet` and `sol-usdc.probarion.testnet`, and of `intents-stub.probarion.testnet`, which `scripts/testnet/deploy.sh` creates and does not lock. Those keys can redeploy the tokens and the stub, and so move a vault's test balances, but cannot change a vault's code or keys. The test tokens have no value. On mainnet the vault uses native USDC and `intents.near`, which Probarion does not run.
- **The console and the CLI sign no owner or approver transaction.** The console hands each one to your wallet, and the CLI prints it for you to sign.
- **The watcher writes nothing to the chain.** It reads, records and sends alerts, and holds no key.
- **It does not run NEAR Intents or the bridge.** On mainnet a Base or Solana payout leaves the vault as an `ft_withdraw` on `intents.near`, and the POA bridge releases it on the other chain. Probarion trusts both to do so.
- **It has no public agent HTTP API and no outbound webhook.** An agent pays from its own process through the SDK, through the MCP server `@probarion/mcp` (Live), which runs beside the agent over stdio and calls the same SDK, or through Payables in the console, which needs a signed-in owner or approver. The one webhook route takes Telegram's updates in, and alerts go out only by Telegram and email.
- **The browser extension cannot pay.** `apps/extension` connects with a revocable token, shows what waits for you and queues an invoice through `/api/ext`; only the owner's wallet-signed Confirm in the console pays it. Install it from the [Chrome Web Store](https://chromewebstore.google.com/detail/probarion/hhadnmnjdlamihjjoejphkimnbcfidmn).
- **It does not serve mainnet yet.** Only the testnet address is served. The one mainnet vault, `v0.probarion.near`, was set up from the owner's own terminal, and no mainnet factory is deployed.
