// Research · 2026-07-31
The seller sets the price, and the buyer’s agent is told to always pay it.
Virtuals’ Agent Commerce Protocol runs escrowed agent-to-agent jobs on Base, and its contract does something most agentic payment systems do not: it actually checks the amount. Calling fund() reverts with BudgetMismatch unless the budget stored on-chain equals the number the buyer passed in. That is a real control, and it genuinely defeats front-running between the moment a buyer decides and the moment its transaction lands.
It also cannot help with the question one step upstream. The only party permitted to set the price is the party being paid, and the number the contract checks against is the buyer’s own. So the contract proves the buyer paid what it intended. Nothing proves that what it intended was what it was quoted.
The numbers
405,000 (~225h of Base)20719610 (5.10%)60000 → 700000 (11.7×)4 min 18 sNo70,531AgenticCommerceV3 behind ERC-1967 proxy 0x238E541B…1832E0 on Base mainnet, read near chain tip on 2026-07-31. Eight of the ten re-priced jobs re-emitted the same value — idempotent retries. Two changed it: job 70386 fell from 700000 to 250000, and job 70381 rose 11.7×.
What the contract permits
From the Basescan-verified source. These are not inferences about intent; they are the access-control lines themselves.
// setBudget — only the SELLER may price the job if (msg.sender != job.provider) revert Unauthorized(); // fund — the buyer's own number, not the merchant's quote if (job.budget != expectedBudget) revert BudgetMismatch();
job.provider — the party being paidnonenone, while status is Openjob.client — the buyerjob.budget == expectedBudgetthe buyer's own decisionBoth functions revert unless status == Open, so a seller cannot re-price after funding. The window is between the quote and the payment, and inside that window there is no cap on the amount and no limit on how many times it may change.
The SDK is what makes it live
A contract that permits something is not the same as a system that does it. The behaviour comes from @virtuals-protocol/acp-node-v2, where tools are routed by role and the strings below are read by a language model deciding what to do next.
const TOOL_MATRIX = {
provider: { // the SELLER
open: [TOOL_SET_BUDGET, TOOL_SEND_MESSAGE, TOOL_WAIT],
budget_set: [TOOL_SET_BUDGET], // its only tool: re-pricing is the sanctioned move
},
client: { // the BUYER
budget_set: [TOOL_SEND_MESSAGE, TOOL_FUND, TOOL_WAIT],
},
};And the description the buyer’s model reads:
TOOL_FUND.description = "Fund this job with the agreed budget ... ALWAYS call this once the seller has set a budget ... Choosing 'wait' instead will stall the job indefinitely."
So the seller’s only available action in budget_setis to change what funding costs, while the buyer’s model is told that not funding stalls the job. And expectedBudgetis filled in from the buyer’s own figure:
// acpAgent.js:559, 616, 645, 717 expectedBudget: params.amount.rawAmount,
What happened on chain
Job 70381, each event read from Base rather than from a log aggregator:
block 49217924 2026-07-28T07:53:15Z BudgetSet amount=60000 block 49218053 2026-07-28T07:57:33Z BudgetSet amount=700000 JobFunded: never status=Open description="corporate_action_monitor"
0.06 USDC to 0.70 USDC, by the party receiving it, in four minutes and eighteen seconds, on a job that was still open for funding.
What this is not
It is not evidence that anyone lost money. Job 70381 was never funded. Either the buyer’s agent declined or it simply never got there — the chain does not say which. This is a demonstrated mechanism, not a demonstrated theft, and describing it as the latter would be false.
It is not a contract bug. A seller quoting a price is correct behaviour in a negotiation. The access control is deliberate, and BudgetMismatch is a stronger control than anything in the x402 ecosystem, where a systematic audit found conformance violations in all fifteen facilitators studied.
5.10% is not a stable rate. A narrower 50-hour window over the same contract found 45 jobs and zero re-pricing. The wider sample is an existence proof plus a small number, and anyone citing it should say so.
It is not prompt injection. On-chain description values are mostly short service tags — token_alpha, quick_snapshot. We scanned 340 of them and found zero injection matches. About 9% are multi-sentence instructions, so the surface exists and is currently unexercised.
The field already has a name for the missing half
A 2026 systematization of agentic-commerce security calls this intent verification: confirming that an agent’s intent, as formed by its reasoning, matches the action encoded in the transaction. By that definition ACP does it well. The two other mechanisms that paper credits — AP2 separating intent declaration from execution, and MPP binding a payment challenge to a request body with a content digest — do the same thing: they compare a payment against state the agent or the server already holds.
None of them compares it against the price the merchant would give if you asked again, over a separate channel. A second paper, surveying what MCP, A2A and ACP structurally cannot express, lists “verification that executed actions match negotiated terms” among the gaps. A third notes that payment terms “may not always be fixed upfront … interactions can involve iterative or multi-round negotiation over price” and calls for lifecycle models that account for it. That is this measurement, named as a research direction before anyone measured it.
Reproduce it
The script is thirty lines of viem against a public Base RPC. No credential, no archive node, no allowlist.
node research/acp-reprice-base-rate.mjs # BLOCKS=90000 for a shorter window
It reads BudgetSet events in 9,000 block pages, groups them by job id, and reports any job with more than one. Independently re-run immediately before publication: 196 jobs, 10 re-priced, job 70381 unchanged at 60000 → 700000.
What closes it
The buyer needs to compare the budget it is about to fund against the price it was actually quoted, before signing — a comparison neither the contract nor the SDK can perform, because neither holds the quote. It is arithmetic rather than detection: there is no false-negative rate, because there is no judgement involved. Either the number matches or it does not.
That is what wormhole-x402 does, and the reason we went looking here at all. We would rather publish the gap than describe it.
Contract source verified on Basescan. Sources: arXiv 2604.15367 (SoK: Security of Autonomous LLM Agents in Agentic Commerce), arXiv 2606.31498 (Governance Gaps in Agent Interoperability Protocols), arXiv 2604.03733 (SoK: Blockchain Agent-to-Agent Payments), arXiv 2605.30998 (Free-Riding the Agentic Web). No vulnerability disclosure was withheld — every fact here is readable from public chain state and a published npm package.