尚未收集儲存庫資訊。
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗The AI proposes, the chain caps, the human signs, and the vault stays private.
Monolith gives a company a private vault where its AI agent prepares confidential payments that the owner signs, and a small public petty cash the agent spends on its own under a cap enforced by the Stellar network.
Built on Stellar testnet for HackMeridian 2026 (tracks: Genesis · Stellar Composed).
Live demo: https://monolith-nine-rosy.vercel.app (testnet, public, no account needed: visitors get simulations; real agent payments need the owner's Freighter sign-in)
Cast (the same in the app, the docs and the video):
GCIF…RS7B).A crypto account is all or nothing: everyone sees everything, and whoever holds the key can spend everything. That is already a problem for a company (salaries, suppliers and treasury are public), and it gets worse when an AI agent gets the key: the more you automate, the more you expose and the more you risk.
Sarah (owner) ── Freighter: signs, approves
│
┌────────────────┴─────────────────┐
│ PRIVATE VAULT │ PUBLIC PETTY CASH
│ Confidential Token (OZ) │ smart account (OZ) + spending_limit
│ Bob, Carol │ Alice
│ AI proposes → Sarah signs │ AI pays alone ≤ 10 USDC / ~24 h
│ (proof in her browser) │ over the cap → #3221 → Sarah approves
│ amounts hidden, Marc reads them │ amounts public, cap enforced on-chain
└──────────────────────────────────┘
We hide how much, not who.
stellar-confidential protocol 0.8.0). Balances and transfer amounts are Pedersen commitments, hidden on-chain. Each operation carries a zero-knowledge proof (UltraHonk) checked by an on-chain verifier. Each account designates its auditor when it opens the vault (here Marc, auditor #1): only that auditor's key can reveal transfer amounts, and nothing else. Our token is bound to our own auditor registry, so the reference demo's public auditor key opens nothing. For a vault recipient the AI never pays: it prepares the payments (e.g. "run payroll") and Sarah signs each one in her browser.spending_limit policy. Under the cap the agent pays alone; above it the Stellar network itself rejects the transaction. Only the owner can approve more. The server-side pre-check is for display only; the real barrier is the on-chain policy.The agent's cap is 10 USDC over a rolling window of 17,280 ledgers (~24 h), enforced by OpenZeppelin's spending_limit policy on the agent's rule of the smart account. The agent can only call transfer on the USDC contract; anything else is rejected by the network. Above the cap, the agent's transaction fails on-chain with Error(Contract, #3221) (SpendingLimitExceeded), and the app prepares the same payment for the owner, who signs it once in Freighter.
Submission texts, video script and on-chain proofs: docs/submission.md.
| Visible on-chain | Hidden on-chain |
|---|---|
| All addresses (sender, recipient) | Confidential vault balances |
| Deposits into the vault and withdrawals out of it (amounts included) | Amounts of confidential transfers |
| Agent payments (public, capped budget) | |
| The fact that an address has a confidential account |
Confidential, not anonymous.
| Page | What it shows |
|---|---|
/vault | Connect Freighter (testnet), get test XLM/USDC, then register → deposit → merge → confidential transfer → withdraw. Proofs are generated in your browser (bb.js, WebAssembly). |
/auditor | "Lire la chaîne" lists every operation on our token as anyone sees it (transfers: chiffré). Pasting the designated auditor's key reveals the amounts; the key is checked on-chain against our registry and is never shipped with the app. "Essayer la clé publique de la démo de référence" shows that the shared public auditor key opens nothing. No wallet needed. |
/agent | Chat with the agent ("paie 1 USDC à Alice", "quel est mon solde ?"). Shows what the planner understood, what the gatekeeper decided and why, and the on-chain result. Above the cap: link to the agent's transaction rejected by the network (#3221), then an approval card the owner signs with Freighter. The budget bar reads the cap and the spending straight from the on-chain policy. |
Fastest check for a judge (no wallet): open /auditor and click Lire la chaîne. The confidential transfer 1cfe3715… shows as chiffré (encrypted) on-chain; check on stellar.expert that the amount is not readable there either. Click Essayer la clé publique de la démo de référence: the key is refused and the amount stays unreadable. The video shows the designated auditor pasting the real key and seeing 7.50 USDC with the sender's balance after (12.50).
Demo video (2 min): link in the HackMeridian submission.
| Scenario | Tx |
|---|---|
| Our auditor registry created (OZ registry wasm already on testnet) / our auditor key registered | ee207713… / 78c52ed8… |
| Confidential USDC deployed, bound to our registry | 8694ce7a… |
Confidential transfer from a test account to Bob, 7.5 USDC — amount hidden (npm run ct:e2e); our auditor reads 7.5, the shared public key can't | 1cfe3715… |
| Deposit 20 USDC into the vault (amount public) | 3f2c290d… |
| Withdraw 7.5 USDC from the vault (amount public) | ec88598c… |
| From the live app with Freighter (Firefox), proofs in the browser: confidential transfer 2.5 USDC — amount hidden; our auditor reads 2.50 | 2e6d3957… |
| ↳ deposit 10 USDC (public) | aa356955… |
| Agent pays 5 USDC alone (under the cap, agent's Ed25519 key, rule B) |
Every step (register, merge, …) and the v1 runs are in docs/architecture.md.
Product rule: inside the vault, the AI proposes and the owner signs. Outside, the AI acts alone, but capped. Cast of the demo: Sarah (owner, GCIF…RS7B), Bob and Carol (employees with a vault account), Alice (outside the vault, paid from the agent's public petty cash), Marc (accountant = our auditor id 1).
What POST /api/intent does, all of it covered by npm test in contracts/ (123 tests, 14 of them on testnet as signed simulations, no transaction sent):
ChainClient.isRegistered, a simulation of confidential_balance, cached 60 s). Registered → the agent pays nothing and returns needs_vault_signature with the list of payments; Sarah signs and proves each confidential transfer in her own browser. The agent never holds the vault keys and never proves anything. Not registered → the capped petty cash (rule B, on-chain cap, #3221, Freighter approval), unchanged. Fail-closed: if the vault check fails, nothing is paid in public (that would reveal the amount); the API answers with an explicit error.verse les salaires, paie l'équipe, run payroll, pagar salários): app/lib/agent/payroll.ts fixes people and amounts (Carol 3 USDC, Bob 2 USDC), never the LLM → needs_vault_signature with 2 payments. An employee outside the vault, pressure wording, an amount or an address in the message → BLOCKED.BLOCKED.signMessage; the server checks the SEP-53 signature (ed25519(sha256("Stellar Signed Message:\n" + message)), format read in Freighter's extension source signBlob.ts / encodeSep53Message) against the owner's public key and sets an HttpOnly; Secure; SameSite=Strict cookie signed with SESSION_SECRET, valid 8 h. The owner's secret key never reaches the server. Everyone else is in demo mode: planner + gatekeeper + a signed simulation of the agent's transaction, nothing sent; the answer says "Simulation : ce paiement aurait été exécuté / refusé (#3221)". Without SESSION_SECRET, nobody is owner. /api/intent is limited to 20 messages / 10 min per IP for visitors.Every sentence the server writes (clarifications, missing-detail questions, vault and payroll texts, first-payment reason, demo-mode simulation texts, budget and address answers, pause, errors) and the fallback planner's keywords live in one file, app/lib/agent/i18n.ts, in English, French and Portuguese. Language = the user's message (simple fr/en/pt detection), else the language the client sends (lang in the /api/intent body, ?lang= or the x-monolith-lang header), else English, the site's default. Every response of /api/intent carries the lang it used. The LLM writes its own questions in the user's language.
npm run eval in contracts/)72 sentences in eval/parsing.jsonl (typos, missing details, answers to the agent's own question, payroll, the agent's mandate, the liquidity planner, private/public routing, injections, reads, unsupported requests; French, English, Portuguese), run through the real /api/intent pipeline in demo mode (simulation only). Each line also checks that the server answered in the message's language. Result on 2026-10-26:
| Category | Score |
|---|---|
| typo | 10/10 |
| slots | 11/11 |
| memory (answer to the agent's question) | 5/5 |
| payroll | 5/5 |
| private | 5/5 |
| public | 5/5 |
| injection | 7/7 |
| reads | 9/9 |
| unsupported | 3/3 |
| mandate | 6/6 |
| liquidity | 6/6 |
| Total | 72/72 (fr 30/30, en 24/24, pt 18/18) |
Honest caveats: Groq rate-limited us (HTTP 429) during the run, so the LLM planner answered 16 of the 72 sentences (none of the 6 liquidity ones); the others went through the regex fallback planner or a code check that runs before the LLM. Both paths go through the same server-side checks. The paused state (B4) is not in the eval: it would need a real on-chain transaction to remove the agent's rule; the PAUSED text is covered by unit tests in the three languages.
| Command | What it checks |
|---|---|
cd contracts && npm run test:unit | 130 unit tests: gatekeeper, private/public routing, payroll, owner session, names and typos, missing-detail questions, i18n (fr/en/pt), mandate, liquidity planner |
cd contracts && npm test | the same, plus on-chain tests against Stellar testnet (receipts, rules, policy reads) |
cd contracts && npm run eval | 72-sentence parsing eval through the real /api/intent pipeline, in demo mode (see above) |
cd app && npm run typecheck · npm run build | TypeScript and the production Next.js build |
cd contracts && npm run status · npm run mandate -- --status | live state of the smart account, the agent's rule, its budget and its mandate |
app/ # Next.js app (frontend + API routes), one Vercel deployment
├── app/
│ ├── page.tsx # home: the cinematic journey
│ ├── vault/ # confidential vault: register, deposit, merge, transfer, withdraw
│ ├── auditor/ # read the chain, apply the audit key, see the amounts
│ ├── agent/ # chat with the agent, budget, mandate, activity
│ └── api/
│ ├── intent/route.ts # message → plan → gatekeeper → executor
│ └── agent/[...path] # session, approvals, activity, receipts, mandate
├── components/agent/ # ApprovalCard, BudgetBar, MandateBadge, LiquidityCard, PaymentLenses…
└── lib/
├── agent/ # planner (LLM, zod), gatekeeper (pure code), executor, payroll,
│ # liquidity, mandate, pause, sweep, session (SEP-53), activity
├── ct/ # confidential token: key derivation, Freighter, auditor, bb.js loader
└── i18n/ # en · fr · pt
packages/ct-sdk/ # vendored stellar-confidential-token-sdk (brozorec, MIT)
contracts/
├── scripts/ # deploy, gonogo, status, reset-agent, mandate, refund, vault-withdraw, eval
└── tests/ # unit + on-chain tests
scripts/ # confidential token: deploy, auditor registry, e2e run, register a member
eval/parsing.jsonl # the 72 parsing-eval sentences
docs/ # architecture (every tx), submission, feedback for OpenZeppelin, design
deployments/ # contract ids (machine-readable)
┌──────────────────────── Next.js (Vercel) ────────────────────────┐
│ VAULT tab (client) │ AGENT tab (chat) │
│ - register / deposit / merge │ - message → /api/intent │
│ - confidential transfer/withdraw │ - owner approval card │
│ - auditor view (disclosure) │ - reasoning + history │
│ Proofs generated in the browser (bb.js / UltraHonk) │
├────────────────────────────────────┴─────────────────────────────┤
│ Next.js API routes (server): /api/intent, /api/agent/* │
│ LLM → { action, amount, to } → executor (agent key, server-only)│
└───────────────┬──────────────────────────────────┬────────────────┘
Confidential Token (OZ 0.8.0) Smart Account (OZ)
+ UltraHonk verifier ├─ rule A: owner (Freighter)
+ OUR auditor registry └─ rule B: CallContract(USDC SAC)
signer = agent (Ed25519)
policy = spending_limit
└──────────── Stellar testnet ────────────┘
One Next.js app: frontend + API routes, one Vercel deployment. The agent key lives only in server-side environment variables.
| Contract | ID |
|---|---|
| Monolith confidential USDC (vanilla: no admin, no freeze, no clawback) | CAYYT5UWB22VLV4Z7DU7DN7V6XFPGIDP5ZD3HV4S6HVQMCGLI5CFLERJ |
Monolith auditor registry (ours; our auditor is auditor_id = 1) | CB5YIDSUCNESNOJK2UFFJA6E3O5BGAPYEQ6F353WGNM4UBGGX3H45IGQ |
| USDC SAC (Circle testnet) | CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA |
| UltraHonk verifier (shared) | CAN45PFA5CVUCU3JB77IE2JNWNTRCDWMRNG55U452L3N2IOCAGOLTUVQ |
| Confidential token factory (shared) | CAWPCXMW47UET5Z4N5T44PRDGAY6CZVP2O2JDEBL7YWEUHJGQ2BR4L6R |
Smart account (OZ multisig-smart-account example, v0.8.0) | CC5D2NUS2GXAWMTK2DMSNJHVRGRH5GBEZNUMEPIOPBPQQJN6GDLO43KA |
| Ed25519 verifier (OZ example) | CBBDHCHRKXP2EROB5JN3EMCMZ4VOOIXSOKDNVPJKY4KIJXZ53ZDC3ZVB |
| Spending-limit policy (OZ example) | CAOT4RM7SYJPLT5XQVPYDEX6HKQOZFODZE6KQVUQJZUSVJY6HBWUVGHQ |
Smart-account rules (read back on-chain with get_context_rule):
| Rule | Context | Signer | Policy |
|---|---|---|---|
A (owner, id 0) | any call (Default) | owner GCIF…RS7B, Delegated (signs with Freighter) | none: full power |
B (agent-usdc-10-24h) | CallContract(USDC SAC) | agent, External Ed25519 via the verifier (key server-only) | spending_limit: 10 USDC / 17,280 ledgers |
P1 deployment details (OZ commit ddc327eb, WASM hashes, every tx): docs/architecture.md and contracts/deployments.testnet.json.
Details, deploy tx and every demo hash: docs/architecture.md. Machine-readable: deployments/testnet.json.
stellar-confidential 0.8.0): our token wraps the testnet USDC SAC and was deployed through the shared factory with deploy_vanilla_token. We use register, deposit, merge, confidential_transfer and withdraw. confidential_transfer and withdraw carry an UltraHonk proof, verified on-chain (deposit is public and needs none).@aztec/bb.js inside the page. This needs SharedArrayBuffer, so the app is served with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: credentialless.K_aud = k·H), and bound our token to it through the factory. Each account picks its auditor at register. The auditor view checks a pasted key against the registry, then decrypts transfer amounts and sender balances from on-chain events, with no wallet and no proving. See scripts/deploy-auditor.ts.spending_limit policy (OpenZeppelin stellar-contracts v0.8.0): the treasury the agent spends from is a contract account. Its __check_auth checks the context rule the client selects (context_rule_ids in the AuthPayload; there is no fallback to another rule). The agent signs sha256(XDR(AuthDigestPreimage)) with its Ed25519 key, checked by the Ed25519 verifier contract. The policy keeps a rolling spending history per (account, rule) and only accepts transfer on the USDC SAC (#3223 NotAllowed otherwise, #3221 SpendingLimitExceeded above the cap). The owner is a signer: the app builds a transaction whose source is the owner, so one Freighter covers both the smart account's auth and the owner's nested auth. See .Requirements: Node ≥ 20, npm. For the vault: the Freighter extension set to Testnet.
git clone https://github.com/Rushyard/Monolith && cd Monolith
npm install # npm workspaces; postinstall builds the vendored SDK
cp .env.example app/.env.local # optional for the vault/auditor (defaults point to our testnet token)
npm run dev # http://localhost:3000
npm run build makes a production build. The prebuild step compiles packages/ct-sdk and copies bb.js's browser build into app/public/vendor/bb (git-ignored).
| Variable | Used by | Notes |
|---|---|---|
NEXT_PUBLIC_STELLAR_NETWORK | all | testnet |
NEXT_PUBLIC_RPC_URL | all | default https://soroban-testnet.stellar.org |
NEXT_PUBLIC_USDC_SAC | vault, agent | default: Circle testnet USDC SAC |
NEXT_PUBLIC_CT_CONTRACT_ID | vault, auditor | default: our confidential USDC |
NEXT_PUBLIC_SMART_ACCOUNT_ID | agent | default: our smart account (app/lib/agent/deployment.json) |
AGENT_SECRET_KEY | agent API | server-only, never NEXT_PUBLIC_. stellar keys show agent |
LLM_API_KEY | agent API | server-only, Groq key. Without it, a deterministic fallback planner (regex) answers |
GROQ_MODEL | agent API | qwen/qwen3.8-27b (checked against Groq's /models) |
SESSION_SECRET | agent API | server-only, ≥ 32 random characters (openssl rand -hex 32). Signs the owner session cookie and challenges. Missing → nobody is owner, everyone is in simulation |
HORIZON_URL | agent API | optional, default https://horizon-testnet.stellar.org (activity feed, first-payment check) |
AGENT_PUBLIC_KEY | agent API | optional, the agent's G address; only used to find its rule when AGENT_SECRET_KEY is absent |
RULE_AGENT_ID | agent API | optional override. By default the API finds the agent rule on-chain (signer = agent key + spending-limit policy, cached 60 s), so a reset needs no redeploy |
Scripts (Node, testnet):
npm run ct:auditor # our auditor registry + key + a confidential USDC bound to it; writes deployments/{testnet,auditor}.json
npm run ct:e2e # full confidential run with fresh friendbot accounts, checks our auditor reads it and the shared public key can't
npm run ct:deploy # (v1) confidential USDC on the shared registry
Smart account and agent (in contracts/, Node + Stellar CLI keystore with identities owner, agent, alice; run npm install in app/ first, the scripts reuse app/lib/agent/core.ts):
cd contracts && npm install
npm run deploy # Ed25519 verifier + spending-limit policy + smart account (rule A = owner), then adds rule B (agent, 10 USDC / ~24 h)
npm run gonogo # agent 5 USDC ✅ · agent 50 USDC ❌ #3221 (on-chain) · owner 50 USDC ✅ · alice refunds
npm run status # balances, cap, spent in the rolling window, remaining budget
npm test # 123 tests: 14 on testnet (signed simulations, rule discovery, vault membership, demo-mode simulation, pause and mandate XDRs; no tx sent) + 109 unit tests (incl. every server sentence in en/fr/pt, mandate)
npm run eval # parsing eval: eval/parsing.jsonl through the real /api/intent, demo mode (no tx)
npm run reset-agent # fresh agent rule (remove old rule B, add a new one): restores the budget; --days N adds a mandate; --dry-run to preview
npm run vault-withdraw -- --check # liquidity planner: is Sarah's vault enough for 10 USDC? (yes/no, balance never printed); --amount N --yes sends the withdraw to the petty cash
npm run mandate -- --days 7 # agent mandate: update_context_rule_valid_until on the agent's rule (--ledgers N, --none, --status, --dry-run)
npm run refund # alice sends her USDC back to the smart account
The WASMs are OpenZeppelin's examples/multisig-smart-account (account, ed25519-verifier, spending-limit-policy) at tag v0.8.0 / commit ddc327eb, unmodified, compiled with stellar contract build; committed in contracts/wasm/ with their sha256 in docs/architecture.md.
auditor_id = 1 in our registry) is held by the Monolith team for the demo; it is not in the repo or the app. In a real deployment the account's accountant or compliance officer generates it. The registry admin can rotate it (rotate_key). Judges can't reveal amounts themselves without that key; the video shows it.brozorec/stellar-confidential-token-demo, not by us. The verifier's verification keys are byte-identical to OpenZeppelin's audited ones (stellar-contracts → packages/confidential/circuits/vks). Our auditor registry runs OpenZeppelin's registry wasm as installed on testnet by that demo.CB4T3M…) used the shared registry's auditor 0, whose secret is public: anyone can decrypt it. It stays on testnet as history; the app no longer uses it.GET /api/agent/activity) read events from the Soroban RPC getEvents, which only keeps a rolling window: on testnet getHealth reports ledgerRetentionWindow = 120960 ledgers, about 7 days (checked 2026-10-25). Older private payments drop out of the feed; public payments and refusals come from Horizon account history instead. After that window the auditor table is empty until new operations happen (npm run ct:e2e).COEP: credentialless: Chrome/Edge/Brave and Firefox ≥ 119. Safari does not support it. Tested end to end with Freighter in Firefox. On Firefox, Freighter may need "Always allow" for the site before the page can see it.Our feedback to OpenZeppelin, Stellar and Freighter (what worked, friction, suggestions), each point checked against the code, the docs or the chain: docs/feedback.md.
Everything in this repo was written during HackMeridian (25–26 Oct 2026), except the vendored SDK below. See the commit history.
Not our code:
packages/ct-sdk is a vendored copy of brozorec/stellar-confidential-token-sdk at commit aad1706 (MIT), unmodified under src/ and circuits/. See VENDORED.md.
The verifier and token factory contracts, and the auditor-registry wasm we instantiated, are shared deployments from brozorec/stellar-confidential-token-demo (MIT), built on OpenZeppelin/stellar-contracts packages/confidential.
Adapted with credit (MIT, noted in file headers): the bb.js vendoring script (scripts/vendor-bb.mjs) and the Freighter key-derivation flow (app/lib/ct/derive-key.ts), both from that demo.
The smart account, Ed25519 verifier and spending-limit policy are OpenZeppelin's stellar-contracts examples (examples/multisig-smart-account, tag v0.8.0, MIT), deployed unmodified.
Built during the event: our auditor registry and key, deployment of our confidential USDC, the testnet end-to-end run, the vault and auditor UI with in-browser proving, the Next.js app and its "2001: A Space Odyssey" design, the Vercel deployment; the smart account's deployment and configuration (rules, policy), the Soroban auth signing for its External and Delegated signers, the on-chain spending-limit integration, the agent's planner/gatekeeper/executor, the API routes, the approval card and the tests.
Prior work. The agent pipeline pattern (LLM planner with schema validation, deterministic gatekeeper, executor) and parts of the planner prompt were adapted from one of our earlier projects. Everything else was built during HackMeridian: the Stellar smart account and on-chain spending cap integration, the confidential vault and auditor flows, payroll, the liquidity planner, the agent mandate, the owner session and the frontend. See the commit history.
Inspirations, credited: OpenZeppelin examples, Agent Card, Who Ate Gerald.
4ba72789… |
Agent tries 50 USDC → rejected by the network: included in a ledger, FAILED, Error(Contract, #3221) SpendingLimitExceeded, no USDC moved | cfda69da… |
| Owner approves 50 USDC (rule A, owner as delegated signer) | a394bd32… |
| From the app with Freighter: agent's attempt rejected on-chain (#3221) | eb70b4a2… |
| ↳ owner approval signed in Freighter, submitted | 39a0634f… |
| Liquidity planner: withdraw 10 USDC from Sarah's vault to the petty cash (amount public, vault balance hidden) | 5227d970… |
budget (cap, spent, remaining, read from the policy) and address (contact book or smart account): the server writes the answer, the LLM never states a number or an address.remove_context_rule, rule found with findAgentRule) and add it back with the same parameters as reset-agent.ts: the server prepares unsigned XDRs (owner as source) with simulated fees, Sarah signs in Freighter. No agent rule on-chain → verdict PAUSED. It only cuts the public petty cash; in the vault Sarah already signs everything. The two transactions were only simulated (pause ~0.10 XLM, re-add ~0.02 XLM in resource fees on 2026-10-25), not sent.plan_liquidity, 1 to 5; every amount must appear in what the user wrote, otherwise the agent asks). Fixed code (app/lib/agent/liquidity.ts, BigInt, 7 decimals) computes need = expenses + 2 USDC reserve, then withdraw = need − petty cash (public USDC balance of the smart account, read on-chain), rounded up to the next 10 USDC and capped at 50, or "no withdrawal needed". Sarah's browser checks her vault's available balance (never shown, never sent to the server) and she signs one withdraw to the petty cash (LiquidityCard); the receipt is checked on-chain. Visitors get the same plan as a simulation. Proven on testnet: 5227d970… (10 USDC out of Sarah's vault).valid_until ledger (OpenZeppelin context rules). Sarah gives or renews it with one update_context_rule_valid_until signed in Freighter ("give the agent a 7-day mandate", 1 hour to 30 days; or npm run mandate); renewing keeps the same rule, so the budget already spent is not reset. Once it expires, the network refuses the agent's signature (#3002, proven on testnet: 29fff98a…), the API answers expired with no transaction and offers to put the petty cash back in the vault, and the budget answer shows the time left. The code refuses to put an expiry on any rule other than the agent's: an expiry on the owner's rule could lock the account permanently (OpenZeppelin smart_account/mod.rs).GET /api/agent/activity is rebuilt from the chain on each call (cached 30 s): public payments and refusals from the Horizon history of the agent and owner accounts (decoded USDC SAC transfer calls, refusal code read with getTransaction), private payments from the confidential token's transfer events whose sender is the owner, addresses and hash only, never an amount. POST /api/agent/vault-receipt { to, hash } checks on-chain that the hash is a successful confidential_transfer from the owner to to, then shows it immediately.DelegatedsignTransaction__check_auth/api/intent, server-only): a Groq LLM planner turns the message into a zod-validated plan; a deterministic gatekeeper reads the cap and the spending from the policy on-chain and decides (auto / owner approval / blocked); a safety net reads the raw message, not the LLM output (pressure wording, "all my money", amounts or addresses that differ from the plan). This pre-check is for the UI only: if it were wrong, the policy would still reject the agent's transaction.transfer), with no trustline needed for a contract address.localStorage so you don't re-sign on every visit. Anyone with access to that browser profile can read your confidential balances (not move funds: that still needs Freighter).npm run reset-agent installs a fresh rule (two owner transactions); the app picks up the new rule id from the chain within 60 s, no redeploy.1dfb0e87…: 9.864 XLM charged, 9.863 XLM of it rent_fee_charged, 0.0012 XLM for compute. Each payment appends an entry to the spending-limit policy's persistent history, which the policy keeps alive for 30 days (OZ SPENDING_LIMIT_EXTEND_AMOUNT = 30 * DAY_IN_LEDGERS). On the afternoon of 2026-10-25 testnet's live Soroban state (~4.14 GB) was above the protocol's 4.0 GB target and our payments went from 3.2 to 11.1 XLM in three hours; that evening the network's target read 7.0 GB and the same payment cost 0.0048 XLM (6d344bf8…). We have not measured mainnet.needs_vault_signature; the vault screen that lets Sarah sign each confidential transfer is P2's part of the app.transfer (#3223 for anything else, #3002 on any other contract or rule, both tested), but it lives in Vercel's environment.GDY5…BXH6, outside the vault), Bob (GBCQ…32GV, the bob of our vault e2e run, registered with auditor 1; its key was not kept, so Bob can receive but not merge or spend) and Carol (GDTM…XPH3, registered with auditor 1). Any other address requires the owner's approval.spending_limit_enforced event, @x402/stellar 2.28.0).