Skip to content
On this page

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.

LiveTestnetExplanationFor Builders, Partners16 min read.md (this page as Markdown)

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 architectureThe 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.Ownersigns every ruleApproverdecides holdsConsole and APINext.js on VercelWalletfull-access keysDatabaseSupabase PostgresFactorykeyless vaultsVault contractholds the USDCWatcher/api/cron/watchAgent with SDKagent, ledger keysUSDC tokenNEP-141NEAR Intentscross-chain payoutTelegram, emailBot API, ResendNEAR AI Cloudmodel in a TEEowner callsapprove, rejecttxs to signcreate_vaultsigned callsdeploys, no keysspend, anchorasks for adviceft_transferft_withdrawPayables spendkeys, ledgerreads viewsvault_eventsheld, 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 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 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 covers each role and key, and SDK reference and CLI reference 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 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. 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, /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 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 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 (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 by Qwen/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, 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 (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.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#

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 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.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 (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

Show

Loading the search index