Architecture
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.
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.
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 says what differs between the two. The status of each part is on What works today.
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 withNotOwnerotherwise. 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.freezetakes no deposit.create_mandatetakes 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'sapproval_abovewaits as a hold.approvepays it andrejectreleases it, and each checks that the caller is on the approver list. Once the hold's time is up,approverefuses, and any account may callexpire_heldto 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, thendecide()from@probarion/core, a signed decision entry in the ledger, the freecheck_spenddry run,spendwith the agent key, andanchorwith 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.
Approver signs
An approver signs approve and reject from their own wallet, with exactly 1 yoctoNEAR attached.
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: theProbarionclass, the vault, factory and intents clients, the NEAR AI Cloud adapter, the ledger stores,snapshot()andverify().packages/cli, theprobarionCLI: it prints owner and approver transactions as near-cli-rs commands and signs none of them. It also makes keys withagent keygen, runs thecheck_spenddry run withcheck, 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_statusandverify, none of which takes an address. It calls@probarion/sdkwith the vault's agent and ledger keys and holds no owner key.
Vaults, roles and keys covers each role and key, and SDK reference and CLI reference list the packages' parts.
On NEAR#
- Factory (
contracts/factory/src/lib.rs):create_vaultmakes 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'snew. It adds no access key. The factory has an owner of its own,probarion-owner.testneton testnet, an account the Probarion team holds. That owner signsstore_code,add_versionandpublish_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 isvaults.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 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 incontracts/vault/src/ext.rs, and no others. - USDC token (
packages/core/src/constants.ts): a NEP-141 token. The vault callsft_transferto pay a NEAR payee and to withdraw to the owner, andstorage_depositto register a NEAR payee on the token when a mandate is created. On testnet it isusdc.probarion.testnet, a test token. On mainnet it is native USDC,17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1. - NEAR Intents (
contracts/vault/src/ext.rs): the vault callsft_withdrawon it to pay a Base or Solana payee, with the memoWITHDRAW_TO:<address>that it builds from the mandate, and to send the owner its Intents balance withwithdraw_intents. On mainnet this isintents.near. Intents has no testnet, sointents-stub.probarion.testnetstands 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. Contract reference lists every method, and Why spend has no payee 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 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 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 (opens in a new tab)). 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 (opens in a new tab), the second (opens in a new tab)).
-
Console and API (
apps/web/src/app): the public site,/docs,/proof,/usage,/status,/canaryand 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 underapps/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/submitand/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/alertsand/api/telegram: the email confirm and stop links, and the Telegram bot's/startand/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 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 thepublic_*functions and the helperpb_current_account(). The tables:- vaults and their members:
tenantsandtenant_members, a member being an owner, an approver or a viewer; - sealed keys:
agent_keys; - Payables:
invoicesandinvoice_confirm_nonces, for the owner's signed Confirm; - the ledger the hosted app writes:
ledger_entries; - what the watcher saw:
vault_events; - alerts:
alert_subscriptionsandalerts_sent; - the Canary:
canary_attempts; - the usage numbers:
metrics_dailyandmetrics_runs; - pilot owners and whether they agreed to be named:
pilots; - the rate limiter:
rate_limits; - the extension:
extension_tokensandextension_pair_codes.
- vaults and their members:
-
Watcher (
apps/web/src/lib/watcher/watch.ts):/api/cron/watchreads 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 intovault_events, then sends the alerts. Vercel runs it once a day, asvercel.jsonsets, and it also runs for one vault right after a Payables upload or a Canary attempt./api/cron/metricscomputes the/usagenumbers each day at 00:10 UTC and prunes the rate limiter's old rows. Both answer only a call that carriesCRON_SECRETas 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 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 overhttps://cloud-api.near.ai/v1.@probarion/corechecks the attestation and the signature. The model only proposes: the code rules and the contract still decide. On the server,NEARAI_API_KEYturns 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 ininvoice_confirm_nonces. Payables then pays it with the sealed agent key, and above the mandate'sapproval_abovethe 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 (opens in a new tab), online output (opens in a new tab)). 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 byQwen/Qwen3.8-27B, and the vault paid 2.00 test USDC as spend 4 and again as spend 5 (evidence (opens in a new tab), the second (opens in a new tab), online output (opens in a new tab)). 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 (opens in a new tab)). - Telegram, email (
apps/web/src/lib/alerts): an owner's or an approver's alerts go out through the Telegram Bot API, withTELEGRAM_BOT_TOKEN, and through Resend, withRESEND_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 (opens in a new tab)); 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.comby default on testnet. - 1Click, for the mainnet top-up only, with
ONECLICK_JWT. - Sentry, for error reports, off until
SENTRY_DSNis 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_URLis 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 notNEARAI_API_KEYis 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: what each key can do, and what a breached server can and cannot do.
- Where the keys live: each key, and who holds it.
- Holds and approvals: what an approver decides.
- Ledger and anchors: what the ledger key signs and anchors.
- Limits and costs: 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 asv0.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 and i trust. A vault already created keeps its code and its own keys: see Known gaps. On testnet the team also holds the full-access keys of the test token accounts,usdc.probarion.testnet,base-usdc.probarion.testnetandsol-usdc.probarion.testnet, and ofintents-stub.probarion.testnet, whichscripts/testnet/deploy.shcreates 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 andintents.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_withdrawonintents.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/extensionconnects 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 (opens in a new tab). - 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.
Built from 4ab41ca · reviewed
README.md(opens in a new tab)contracts/vault/src/lib.rs(opens in a new tab)contracts/vault/src/admin.rs(opens in a new tab)contracts/vault/src/held.rs(opens in a new tab)contracts/vault/src/keys.rs(opens in a new tab)contracts/vault/src/mandates.rs(opens in a new tab)contracts/vault/src/spend.rs(opens in a new tab)contracts/vault/src/anchors.rs(opens in a new tab)contracts/vault/src/ext.rs(opens in a new tab)contracts/factory/src/lib.rs(opens in a new tab)contracts/deployments/testnet.json(opens in a new tab)contracts/deployments/mainnet.json(opens in a new tab)contracts/code-hashes.json(opens in a new tab)packages/core/src/constants.ts(opens in a new tab)packages/core/src/policy.ts(opens in a new tab)packages/sdk/src/probarion.ts(opens in a new tab)packages/sdk/src/vault-client.ts(opens in a new tab)packages/sdk/src/nearai.ts(opens in a new tab)packages/cli/src/owner-tx.ts(opens in a new tab)docs/runbooks/testnet.md(opens in a new tab)docs/runbooks/factory.md(opens in a new tab)scripts/testnet/deploy.sh(opens in a new tab)contracts/test-ft/src/lib.rs(opens in a new tab)contracts/intents-stub/src/lib.rs(opens in a new tab)apps/web/vercel.json(opens in a new tab)apps/web/src/lib/env.ts(opens in a new tab)apps/web/src/lib/docs/architecture.ts(opens in a new tab)apps/web/src/lib/sdk/factory.ts(opens in a new tab)apps/web/src/lib/proof/reader.ts(opens in a new tab)apps/web/src/lib/keys/service.ts(opens in a new tab)apps/web/src/lib/auth/verify.ts(opens in a new tab)apps/web/src/lib/watcher/watch.ts(opens in a new tab)apps/web/src/lib/watcher/server.ts(opens in a new tab)apps/web/src/app/api/cron/watch/route.ts(opens in a new tab)apps/web/src/app/api/cron/metrics/route.ts(opens in a new tab)apps/web/src/app/api/topup/quote/route.ts(opens in a new tab)apps/web/src/app/api/topup/submit/route.ts(opens in a new tab)apps/web/src/app/api/topup/status/route.ts(opens in a new tab)apps/web/src/lib/alerts/server.ts(opens in a new tab)apps/web/src/lib/alerts/telegram.ts(opens in a new tab)apps/web/src/lib/alerts/email.ts(opens in a new tab)apps/web/src/lib/topup/faucet.ts(opens in a new tab)apps/web/src/lib/topup/oneclick.ts(opens in a new tab)apps/web/src/components/wallet/connector.ts(opens in a new tab)apps/web/src/lib/near/send.ts(opens in a new tab)apps/web/src/lib/db/clients.ts(opens in a new tab)apps/web/src/lib/crypto/secrets.ts(opens in a new tab)apps/web/src/lib/auth/session.ts(opens in a new tab)apps/web/src/lib/payables/pipeline.ts(opens in a new tab)apps/web/src/lib/payables/owner-confirms.ts(opens in a new tab)apps/web/src/lib/payables/confirm.ts(opens in a new tab)apps/web/src/app/console/[vault]/payables/actions.ts(opens in a new tab)apps/web/src/app/api/console/[vault]/invoices/route.ts(opens in a new tab)apps/web/src/lib/canary/probarion.ts(opens in a new tab)apps/web/src/lib/sentry-scrub.ts(opens in a new tab)apps/web/src/instrumentation-client.ts(opens in a new tab)apps/web/src/sentry.server.config.ts(opens in a new tab)apps/web/src/app/api/health/route.ts(opens in a new tab)apps/web/src/app/api/health/nearai/route.ts(opens in a new tab)apps/web/src/app/api/telegram/webhook/route.ts(opens in a new tab)apps/web/src/app/console/[vault]/alerts/actions.ts(opens in a new tab)apps/web/src/app/api/canary/attempt/route.ts(opens in a new tab)apps/web/src/app/api/ext(opens in a new tab)apps/extension(opens in a new tab)apps/web/supabase/migrations/20261108000001_core.sql(opens in a new tab)apps/web/supabase/migrations/20261108000007_invoice_confirm.sql(opens in a new tab)apps/web/supabase/migrations/20261108000009_metrics.sql(opens in a new tab)apps/web/supabase/migrations/20261108000013_extension_tokens.sql(opens in a new tab)apps/web/supabase/migrations/20261108000015_extension_queue.sql(opens in a new tab)apps/web/content/docs/start/what-is-probarion.mdx(opens in a new tab)apps/web/content/docs/trust/security-model.mdx(opens in a new tab)apps/web/content/docs/trust/reproducible-builds.mdx(opens in a new tab)packages/mcp/src/server.ts(opens in a new tab)docs/evidence/canary-vault/README.md(opens in a new tab)docs/evidence/canary-vault/verify-online-2026-11-11.txt(opens in a new tab)docs/evidence/trial-vault/README.md(opens in a new tab)docs/evidence/demo-vault/README.md(opens in a new tab)docs/evidence/demo-vault/verify-online-2026-11-12.txt(opens in a new tab)