The gap
The agent task stack now has three ways to post and settle a task. ERC-8183 escrows a single job and lets an evaluator release it. ERC-8195 defines five procurement modes. ERC-8414 makes the task itself an ERC-721 with a vault inside it. All three leave the same question outside the standard: when several agents want one task, who is awarded, and at what price?
- ERC-8183 runs bidding through an off-chain hook and only receives the result through
setProvider(see its Example 2). - ERC-8195’s Auction mode relays bids off-chain and hardcodes lowest-bid-wins. The thread already notes there is no bid commitment or reveal structure at the interface level, and that point was never picked up.
- ERC-8414 says plainly that bidding is a companion extension, not kernel.
So each market writes its own award logic, the same agent bids under three incompatible rules, and no indexer can say what an award meant or whether the price was fair.
What this proposes
A small companion standard that answers only that question. It fixes three things:
- A sealed-bid procedure: commit, reveal, award, with a bond that is returned on reveal and slashed on silence.
- A stateless mechanism interface,
award(bids, reserve, units) → (winner, price), that a tender contract calls once reveals close. - Three normative pricing rules: first-price, second-price (Vickrey), and uniform-price for identical units. Anything else is a plugin that implements the same interface.
It holds no task funds, judges no deliverable, and writes no reputation. It returns an award that an ERC-8183 hook can pass to setProvider and setBudget, that an ERC-8195 Auction-mode contract can read in place of selectLowestBidder, or that an ERC-8414 eligibility profile can require of the fulfiller of record.
Why this shape
The design follows Myerson’s optimal auction results (Mathematics of Operations Research, 1981), read as a procurement problem where the private type is each agent’s cost.
- Revelation principle. Any feasible auction is equivalent to a direct mechanism: bidders report a type, the mechanism computes an allocation and a payment. So the on-chain interface should be exactly that pair of functions, and commit–reveal is what makes a sealed report credible on a public ledger.
- Revenue equivalence. The requester’s expected payment is fixed once the allocation rule and the reserve are fixed. A standard therefore only needs to pin down those two, and can leave the pricing rule to a plugin without changing what the requester expects to pay.
- Second-price with a reserve is optimal in the symmetric regular case, and its payment rule contains no distributional parameters. That is why Vickrey is the recommended default: it stays incentive compatible even when the requester’s beliefs about agent costs are wrong, and autonomous agents from different operators have no shared prior to bid against under first-price.
- The optimal reserve is strictly tighter than the requester’s outside value, and no-award is a legitimate outcome. Hence
reserveis a field of its own rather than a budget the hook happens to see. - Discriminating rules and correlated-type mechanisms are deliberately out of scope. The first needs per-bidder beliefs; the second needs the requester to pay losing bidders, which the vault standards forbid. Both remain expressible as plugins.
Interfaces
interface IAwardMechanism {
struct Bid { address bidder; uint256 amount; } // amount = price asked
struct Award { address winner; uint256 price; }
function mechanismId() external pure returns (bytes4); // bytes4(keccak256("award.vickrey")) etc.
/// MUST be pure. Bids arrive in commit order. Empty return = no award.
/// No winner may have bid above reserve; no price may exceed reserve;
/// lowering one bid must never remove that bidder from the award; ties go to the earlier commit.
function award(Bid[] calldata bids, uint256 reserve, uint256 units)
external pure returns (Award[] memory);
}
interface ISealedBidTender {
enum Phase { None, Commit, Reveal, Awarded, Void }
struct TenderTerms {
bytes32 taskRef; // the task in whichever escrow standard opened this tender
address mechanism; // IAwardMechanism
uint256 reserve; // > 0
uint256 units; // >= 1
uint64 commitDeadline;
uint64 revealDeadline; // > commitDeadline
uint256 bond; // escrowed per commit, returned on reveal, slashed otherwise
address bondAsset; // address(0) = native
}
event TenderOpened(bytes32 indexed tenderId, bytes32 indexed taskRef, address indexed requester,
address mechanism, uint256 reserve, uint256 units, uint64 commitDeadline, uint64 revealDeadline);
event BidCommitted(bytes32 indexed tenderId, address indexed bidder, bytes32 commitment);
event BidRevealed(bytes32 indexed tenderId, address indexed bidder, uint256 amount);
event BidSlashed(bytes32 indexed tenderId, address indexed bidder, uint256 bond);
event TenderAwarded(bytes32 indexed tenderId, address indexed winner, uint256 price, bytes4 mechanismId);
event TenderVoid(bytes32 indexed tenderId);
function openTender(TenderTerms calldata terms) external returns (bytes32 tenderId);
// commitment = keccak256(abi.encode(tenderId, msg.sender, amount, salt))
function commitBid(bytes32 tenderId, bytes32 commitment) external payable;
function revealBid(bytes32 tenderId, uint256 amount, bytes32 salt) external;
function finalize(bytes32 tenderId) external returns (IAwardMechanism.Award[] memory);
function termsOf(bytes32 tenderId) external view returns (TenderTerms memory);
function requesterOf(bytes32 tenderId) external view returns (address);
function phaseOf(bytes32 tenderId) external view returns (Phase);
function awardOf(bytes32 tenderId) external view returns (IAwardMechanism.Award[] memory);
}
Normative rules, with revealed amounts sorted b(1) ≤ b(2) ≤ … and reserve r:
| id | winners | price | award iff |
|---|---|---|---|
award.first-price |
lowest units bids |
own bid | bid ≤ r |
award.vickrey (units = 1) |
lowest bid | min(b(2), r), or r if alone | b(1) ≤ r |
award.uniform-price |
lowest units bids |
min(b(units+1), r) | bid ≤ r |
Uniform-price is scoped to one unit per bidder; multi-unit demand should use a VCG plugin.
What I would like the forum’s view on
- Reserve as a first-class field. Should the escrow standards expose a reserve separately from budget or
maxPrice, given that the optimal reserve is tighter and non-award must be allowed? - Bidder heterogeneity. ERC-8004 reputation makes agents asymmetric, and asymmetric optimal auctions discriminate. Should scoring rules be a mechanism plugin, or stay out of the standard entirely?
- Bonds. An unrevealed commitment has to cost something or commit–reveal is free to grief. Kernel field, as drafted, or an admission hook?
- Composition. Is a stateless
IAwardMechanismenough for ERC-8183’s hook, ERC-8195’sselectWorker, and ERC-8414’s eligibility profile, or does each need an adapter? In particular, for ERC-8183: is a hook permitted to callsetProviderandsetBudgetback on the job, or must the client sign those?
The full draft text follows the ERC template and will go up as a PR to ethereum/ERCs once this thread has had a first round. Reference implementation and test vectors to follow; the vectors assert the monotonicity condition and, for Vickrey, that truthful bidding is dominant. Spec text is CC0.