Loops HouseVeto: on-chain spending firewall for AI agents on NEAR

NEAR HACKATHON (Test)
Team
1 member- BOOwner
Boris Tarantin
Overview
Veto is a spending firewall for AI agent wallets on NEAR. An agent with a key is either unbounded - one prompt injection drains the balance - or chained to a human on every payment, which is no longer an agent. Veto is the third option: the owner writes a policy (per-transaction, daily and 30-day caps, counterparty allow/deny lists, an approval threshold, velocity, allowed action kinds, swap venue/slippage/destination pins, expiry, pause) and every action is judged before money moves.
Three layers enforce it. A pure engine runs ~20 checks per decision with exact bigint arithmetic, deny-by-default and reserved budget for pending approvals, and reports every failed check with a reason code. A Rust contract holds the funds and recomputes the limits from its own storage; the agent's key is a function-call key scoped to spend(), so a stolen key cannot add keys, withdraw or pause. The approval queue re-evaluates the policy at execution time, so an approval granted under yesterday's limits cannot be used today.
Every decision, including refusals, lands in a hash-chained audit log whose head is anchored on-chain; a refused spend reverts and still emits a NEP-297 deny event with the reason code.
NEAR specifics: live NEAR Intents (1Click) quotes are validated before a swap - venue, slippage, expiry and a destination pinned to the agent's own MPC-derived chain-signature address from v1.signer.
Verified on testnet: deploy, policy install, an allowed spend, a per-transaction revert, the rolling daily cap filled and then refused, a NEP-141 (wNEAR) spend under its own limit, the owner kill switch, and 28 conformance checks whose deny reasons are read from the on-chain events. 87 tests, 29 adversarial escape attempts with asserted verdicts. The chain also found a real bug: a low vault let spend() write counters and emit a spend event before the transfer failed; the contract now pre-checks the balance and refuses with INSUFFICIENT_BALANCE.